Compare commits

..
Author SHA1 Message Date
Konrad Piascik 22ba8875c5 Merge branch 'main' into kpiascik/shadowtestassert 2025-07-04 10:12:24 -04:00
Konrad Piascik 843e1a43c4 Fix shadowtest assert
A zero color target fragment stage implies a depth pass by Filament.
We can create a pipeline without a fragment stage to make it valid.

BUGS=[421933134]
2025-07-04 09:55:02 -04:00
Powei Feng 98eea205f3 renderdiff: viewer can pull artifacts given PR number (#8919)
This will allow for a faster flow then downloading manually.
Though it will require user to supply a github token.
2025-07-03 14:17:31 -07:00
Andy Hovingh 1c22b48893 webgpu: does not support depth/stencil resolve at this time 2025-07-03 10:48:43 -05:00
Mathias Agopian 249dd9752d Fix SSR when postfx and msaa are disabled.
The problem was that we need to know before rendering if we're doing so
directly in the swapchain or an intermediate buffer. Unfortunately 
this is determined by the framegraph (after culling and such). But at
the point we need it the framefraph is not even constructed.

So we need to "guess" this from the parameters of the View. That logic
missed the case where SSR (reflection or refraction) was used.

An extra level of complexity is that we need to generate the high level
commands before we can determine if refraction will happen.

Fix #8910
2025-07-02 16:58:11 -07:00
Anish GoyalandSerge Metral bb4cd43835 Convert staging images to resources (#8834)
* Convert staging images to resources

This will reduce the amount of computation required in gc() calls to
determine if a staging image is still in use or not. This follows the
change made to staging buffers.

* Address PR comment: formatting

Split up >100 char lines, runs clang-format on changed lines

---------

Co-authored-by: Serge Metral <sergemetral@google.com>
2025-07-02 12:52:26 -07:00
Sungun Park 954d1021c7 Add #include <cstdlib> to avoid compilation error (#8922) 2025-07-02 18:54:03 +00:00
Sungun Park bf598071a3 gl: move shader code instead of copy (#8918)
In Debug mode, remove unnecessary memory reallocation & copy by moving
shader code blob.
2025-07-02 17:41:43 +00:00
Powei Feng 5cbc2321c7 Add feature flag for attribute stride check (#8913)
We also add a define to log (instead of assert) failtures of
certain correctness checks.  This allows clients to gradually
fix their code.
2025-07-02 17:06:37 +00:00
Mathias Agopian 6bee5fb56f fix PixelBufferDescriptor overflow calculation
We simply compute the coordinate of the last pixel that need to be 
accessed and deduce its offset, which must be smaller than the 
buffer size.
2025-07-01 16:08:58 -07:00
Mathias Agopian eb0fcc065b workaround gcc permissive warning. Fixes #8883. 2025-07-01 16:08:26 -07:00
Andy Hovingh 117535364b webgpu: support auto-resolve render passes (render target samples != attached texture samples)
The solution resembles that of the other backends, to implement msaa sidecar textures.

Note: this solution does not resolve depth/stencil textures
2025-07-01 15:59:09 -05:00
Powei Feng b15ae15f39 No need for blending when upscaling (#8900)
The dst buffer is allocated and should not be blended against.
The blending code is a relic of a previous code path that's
no longer present [link].

[link]: https://github.com/google/filament/blame/c049a1bfff0851098bf640159fb6c5daab88d5d4/filament/src/PostProcessManager.cpp

FIXES=422593171
2025-07-01 05:43:23 +00:00
Powei Feng 74445904f2 Release Filament 1.62.0 2025-06-30 16:39:15 -07:00
Powei Feng 678ad4ff98 Fix assert in resolve for stencil buffer (#8908)
The assert in resolve() will trigger even if the pass is
ultimately not needed (culled out). So if a user has
 - disabled postprocessing
 - uses stencil buffer

They will still hit an assert in debug mode. Here we just delay
the assert until when we're actuallly setting up the pass.
2025-06-30 21:18:36 +00:00
Powei Feng b6d7ed2b35 Bump Material Version to 62 2025-06-29 22:34:35 -07:00
Jeremy Nelson 725e33763a remove 256 padding in WriteTexture 2025-06-27 13:30:51 -07:00
Juan Caldas f24146c169 webgpu: Solve black issue screen (#8907)
Solve black issue screen
2025-06-27 15:15:43 -04:00
Ben Doherty 67288d483f materials: Add VARIANT_DEPTH define for depth shader variants (#8905) 2025-06-27 14:23:37 -04:00
Anish GoyalandSerge Metral c80c504a2a Fix adjustedMemcpy for compressed formats (#8902)
Right now, the way we calculate 'bytes per pixel' returns 0 in the case
of compressed texture formats. The easy solution in these cases is to
simply fall back to the full buffer size.

Co-authored-by: Serge Metral <sergemetral@google.com>
2025-06-27 08:28:55 -07:00
Ben Doherty 1f8cacdd70 matinfo: Report if a material has custom depth (#8904) 2025-06-26 17:30:01 -04:00
Andy Hovingh a3ae9431f1 webgpu: update pipeline cache key to include render target 2025-06-26 14:56:45 -05:00
Powei Feng 3d0653604f android: add static, default model for gltf-viewer (#8898)
The default gltf for android's gltf-viewer is the buster drone,
which uses skinning.

We add an additional model (damaged helmet) as an alternative
default. This will be useful for debugging WebGPU, where skinnning is
not yet supported. This alternate path can only be invoke by starting
the app through adb:

  `adb shell am start -n com.google.android.filament.gltf/.MainActivity --ez "use-static-model" true`
2025-06-26 19:15:54 +00:00
Powei Feng 42a0a41be0 webgpu: remove float filterable requirement (#8895)
This will enable webgpu for devices such as pixel 4, which does
not support this feature.
2025-06-26 18:15:41 +00:00
Powei Feng 468d9cccb7 Add Engine config for gpu priority (#8893) 2025-06-26 17:47:57 +00:00
Powei Feng 6cfd058e3d docs: fix commit hash parsing bug (#8882)
- Re-enable docs presbumit check (checks if there has been direct
    edits of /docs).
 - Fix script to download and install mdbook
 - Update Materials.md.html and re-run the build script
    (build/run.py).
2025-06-26 17:31:03 +00:00
Andy Hovingh 8234549cb9 webgpu: implement render pass based mipmap generation (supports more texture formats than compute pass based mipmap generation) 2025-06-26 08:25:23 -05:00
52 changed files with 2127 additions and 468 deletions
+4 -6
View File
@@ -54,7 +54,6 @@ jobs:
build-android:
name: build-android
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -73,7 +72,6 @@ jobs:
build-ios:
name: build-iOS
runs-on: macos-14-xlarge
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -89,7 +87,6 @@ jobs:
build-web:
name: build-web
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -107,11 +104,12 @@ jobs:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- id: get_commit_msg
uses: ./.github/actions/get-commit-msg
- name: Check for manual edits to /docs
run: |
echo "${{ github.event.pull_request.head.sha }} -- ${{ github.event.pull_request.head.sha }}"
# disable for now
# bash docs_src/build/presubmit_check.sh ${{ github.event.pull_request.head.sha }}
COMMIT_ID=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | head -n 1 | sed "s/commit //g")
bash docs_src/build/presubmit_check.sh ${COMMIT_ID}
test-renderdiff:
name: test-renderdiff
+5
View File
@@ -7,6 +7,11 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.62.1
- Add new shader define `VARIANT_DEPTH`, defined when a material is compiled for depth variants
(e.g., shadows) [**Requires recompiling materials**]
## v1.62.0
- Add new `unfilterable` field to Filament Material's `sampler` [⚠️ **New Material Version**]
@@ -556,7 +556,8 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge,
jboolean disableHandleUseAfterFreeCheck,
jint preferredShaderLanguage,
jboolean forceGLES2Context, jboolean assertNativeWindowIsValid) {
jboolean forceGLES2Context, jboolean assertNativeWindowIsValid,
jint gpuContextPriority) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
Engine::Config config = {
.commandBufferSizeMB = (uint32_t) commandBufferSizeMB,
@@ -574,6 +575,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
.preferredShaderLanguage = (Engine::Config::ShaderLanguage) preferredShaderLanguage,
.forceGLES2Context = (bool) forceGLES2Context,
.assertNativeWindowIsValid = (bool) assertNativeWindowIsValid,
.gpuContextPriority = (Engine::GpuContextPriority) gpuContextPriority,
};
builder->config(&config);
}
@@ -175,6 +175,36 @@ public class Engine {
MULTIVIEW,
};
/**
* This controls the priority level for GPU work scheduling, which helps prioritize the
* submitted GPU work and enables preemption.
*/
public enum GpuContextPriority {
/**
* Backend default GPU context priority (typically MEDIUM)
*/
DEFAULT,
/**
* For non-interactive, deferrable workloads. This should not interfere with standard
* applications.
*/
LOW,
/**
* The default priority level for standard applications.
*/
MEDIUM,
/**
* For high-priority, latency-sensitive workloads that are more important than standard
* applications.
*/
HIGH,
/**
* The highest priority, intended for system-critical, real-time applications where missing
* deadlines is unacceptable (e.g., VR/AR compositors or other system-critical tasks).
*/
REALTIME,
};
/**
* Constructs <code>Engine</code> objects using a builder pattern.
*/
@@ -233,7 +263,8 @@ public class Engine {
config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge,
config.disableHandleUseAfterFreeCheck,
config.preferredShaderLanguage.ordinal(),
config.forceGLES2Context, config.assertNativeWindowIsValid);
config.forceGLES2Context, config.assertNativeWindowIsValid,
config.gpuContextPriority.ordinal());
return this;
}
@@ -489,6 +520,11 @@ public class Engine {
* @Deprecated use "backend.opengl.assert_native_window_is_valid" feature flag instead
*/
public boolean assertNativeWindowIsValid = false;
/**
* GPU context priority level. Controls GPU work scheduling and preemption.
*/
public GpuContextPriority gpuContextPriority = GpuContextPriority.DEFAULT;
}
private Engine(long nativeEngine, Config config) {
@@ -1492,7 +1528,8 @@ public class Engine {
long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge,
boolean disableHandleUseAfterFreeCheck,
int preferredShaderLanguage,
boolean forceGLES2Context, boolean assertNativeWindowIsValid);
boolean forceGLES2Context, boolean assertNativeWindowIsValid,
int gpuContextPriority);
private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal);
private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext);
private static native void nSetBuilderPaused(long nativeBuilder, boolean paused);
@@ -18,6 +18,7 @@ package com.google.android.filament.gltf
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.*
@@ -50,6 +51,7 @@ class MainActivity : Activity() {
// Load the library for the utility layer, which in turn loads gltfio and the Filament core.
init { Utils.init() }
private const val TAG = "gltf-viewer"
private const val STATIC_MODEL_TAG = "use-static-model"
}
private lateinit var surfaceView: SurfaceView
@@ -69,6 +71,7 @@ class MainActivity : Activity() {
private var loadStartTime = 0L
private var loadStartFence: Fence? = null
private val viewerContent = AutomationEngine.ViewerContent()
private var useStaticModel = false
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
@@ -83,6 +86,12 @@ class MainActivity : Activity() {
doubleTapDetector = GestureDetector(applicationContext, doubleTapListener)
singleTapDetector = GestureDetector(applicationContext, singleTapListener)
val intent: Intent = intent
val bundle: Bundle? = intent.extras
bundle?.let {
useStaticModel = it.getBoolean(STATIC_MODEL_TAG, false)
}
modelViewer = ModelViewer(surfaceView)
viewerContent.view = modelViewer.view
viewerContent.sunlight = modelViewer.light
@@ -141,7 +150,16 @@ class MainActivity : Activity() {
}
private fun createDefaultRenderables() {
val buffer = assets.open("models/scene.gltf").use { input ->
// Sometimes it's useful to set to the default model to something static. You can enable
// the static model by launching the app from adb, as in
// `adb shell am start -n com.google.android.filament.gltf/.MainActivity --ez "use-static-model" true`
val modelPath = if (useStaticModel) {
"models/helmet.glb"
} else {
"models/scene.gltf"
}
val buffer = assets.open(modelPath).use { input ->
val bytes = ByteArray(input.available())
input.read(bytes)
ByteBuffer.wrap(bytes)
-5
View File
@@ -30,11 +30,6 @@ from utils import execute, ArgParseImpl
CUR_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT_DIR = os.path.join(CUR_DIR, '../../')
def get_last_commit_hash():
res, ret = execute('git rev-parse HEAD', cwd=ROOT_DIR)
assert res == 0, 'Failed to get the last commit hash'
return ret.strip()
def get_edited_files(commit_hash):
INSERT = '#####?????'
res, ret = execute(f'git show --name-only --pretty=%b{INSERT} {commit_hash}')
+6 -5
View File
@@ -39,13 +39,13 @@ function download_mdbook() {
fi
local CHECK_UNAME="
import sys;
parts=[a.lower() for a in sys.stdin.read().strip().split(' ')];
import sys
parts=[a.lower() for a in sys.stdin.read().strip().split(' ')]
def get_type():
if 'darwin' in parts:
if 'x86_64' in parts:
return ${DARWIN_X86}
elif 'aarch' in parts:
elif 'aarch' in parts or 'arm64' in parts:
return ${DARWIN_ARM}
elif 'linux' in parts:
if 'x86_64' in parts:
@@ -63,11 +63,12 @@ print(get_type())
echo "*** Need to install Rust ***"
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
fi
source "${HOME}/.cargo/env"
if ! (command -v cargo >/dev/null 2>&1); then
echo "*** Still cannot find `cargo` ***"
echo "*** Still cannot find cargo ***"
exit 1
fi
cargo install mdbook
cargo install --force mdbook
else
# Download prebuilts from github
mkdir -p ${HOME}/.cargo/bin
+30 -23
View File
@@ -13,7 +13,7 @@ This document is part of the [Filament project](https://github.com/google/filame
## Authors
- [Romain Guy](https://github.com/romainguy), [@romainguy](https://twitter.com/romainguy)
- [Mathias Agopian](https://github.com/pixelflinger), [@darthmoosious](https://twitter.com/darthmoosious)
- [Mathias Agopian](https://github.com/pixelflinger), [@pixelflinger](https://bsky.app/profile/pixelflinger.bsky.social)
# Overview
@@ -78,26 +78,27 @@ in table [standardProperties].
Property | Definition
-----------------------:|:---------------------
**baseColor** | Diffuse albedo for non-metallic surfaces, and specular color for metallic surfaces
**metallic** | Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1)
**roughness** | Perceived smoothness (1.0) or roughness (0.0) of a surface. Smooth surfaces exhibit sharp reflections
**metallic** | Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1)
**reflectance** | Fresnel reflectance at normal incidence for dielectric surfaces. This directly controls the strength of the reflections
**sheenColor** | Strength of the sheen layer
**sheenRoughness** | Perceived smoothness or roughness of the sheen layer
**ambientOcclusion** | Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0
**clearCoat** | Strength of the clear coat layer
**clearCoatRoughness** | Perceived smoothness or roughness of the clear coat layer
**clearCoatNormal** | A detail normal used to perturb the clear coat layer using _bump mapping_ (_normal mapping_)
**anisotropy** | Amount of anisotropy in either the tangent or bitangent direction
**anisotropyDirection** | Local surface direction in tangent space
**ambientOcclusion** | Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0
**normal** | A detail normal used to perturb the surface using _bump mapping_ (_normal mapping_)
**bentNormal** | A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality
**clearCoatNormal** | A detail normal used to perturb the clear coat layer using _bump mapping_ (_normal mapping_)
**emissive** | Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass
**postLightingColor** | Additional color that can be blended with the result of the lighting computations. See `postLightingBlending`
**ior** | Index of refraction, either for refractive objects or as an alternative to reflectance
**transmission** | Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is
**absorption** | Absorption factor for refractive objects
**microThickness** | Thickness of the thin layer of refractive objects
**thickness** | Thickness of the solid volume of refractive objects
**sheenColor** | Strength of the sheen layer
**sheenRoughness** | Perceived smoothness or roughness of the sheen layer
**emissive** | Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass
**normal** | A detail normal used to perturb the surface using _bump mapping_ (_normal mapping_)
**postLightingColor** | Additional color that can be blended with the result of the lighting computations. See `postLightingBlending`
**absorption** | Absorption factor for refractive objects
**transmission** | Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is
**ior** | Index of refraction, either for refractive objects or as an alternative to reflectance
**microThickness** | Thickness of the thin layer of refractive objects
**bentNormal** | A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality
**shadowStrength** | Strength factor between 0 and 1 for all shadows received by this material
[Table [standardProperties]: Properties of the standard model]
The type and range of each property is described in table [standardPropertiesTypes].
@@ -991,10 +992,13 @@ Type
Value
: Each entry is an object with the properties `name` and `type`, both of `string` type. The
name must be a valid GLSL identifier. Entries also have an optional `precision`, which can be
name must be a valid GLSL identifier. Entries have an optional `precision`, which can be
one of `default` (best precision for the platform, typically `high` on desktop, `medium` on
mobile), `low`, `medium`, `high`. The type must be one of the types described in
table [materialParamsTypes].
table [materialParamsTypes]. For Android external textures, entries also have an optional
transformName parameter to specify the name of the material parameter that will be
used to expose the transform matrix associated with the external sampler. In iOS and Vulkan,
this will always be identity.
Type | Description
:----------------------|:---------------------------------
@@ -1023,12 +1027,8 @@ samplerCubemap | Cubemap texture
[Table [materialParamsTypes]: Material parameter types]
Samplers
: Sampler types can specify additional options:
- `format`: either `int` or `float` (defaults to `float`).
- `stages`: array of strings containing the list of shader stages this
sampler can be accessed from. Each entry must be either `vertex` or
`fragment` (defaults to both).
: Sampler types can also specify a `format` which can be either `int` or `float` (defaults to
`float`).
Arrays
: A parameter can define an array of values by appending `[size]` after the type name, where
@@ -1273,6 +1273,9 @@ Description
when selecting any shading model that is not `unlit`. See the shader sections of this document
for more information on how to access these attributes from the shaders.
!!! Note: Interaction with custom variables
When the `color` attribute is specified, only four custom variables are available instead of five.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
material {
parameters : [
@@ -1303,7 +1306,7 @@ Type
: array of `string`
Value
: Up to 4 strings, each must be a valid GLSL identifier.
: Up to 5 strings, each must be a valid GLSL identifier.
Description
: Defines custom interpolants (or variables) that are output by the material's vertex shader.
@@ -1319,6 +1322,10 @@ Description
particular if `default` is specified the default precision is used is the fragment shader
(`mediump`) and in the vertex shader (`highp`).
!!! Warning: Interaction with required attributes
If the `color` attribute is specified in the `required` list, then only four variables can be used
instead of five.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
material {
name : Skybox,
+30 -19
View File
@@ -146,7 +146,7 @@ This document is part of the <a href="https://github.com/google/filament">Filame
<ul>
<li class="minus"><a href="https://github.com/romainguy">Romain Guy</a>, <a href="https://twitter.com/romainguy">@romainguy</a>
</li>
<li class="minus"><a href="https://github.com/pixelflinger">Mathias Agopian</a>, <a href="https://twitter.com/darthmoosious">@darthmoosious</a></li></ul>
<li class="minus"><a href="https://github.com/pixelflinger">Mathias Agopian</a>, <a href="https://bsky.app/profile/pixelflinger.bsky.social">@pixelflinger</a></li></ul>
</p>
<a class="target" name="overview">&nbsp;</a><a class="target" name="overview">&nbsp;</a><a class="target" name="toc2">&nbsp;</a><h1>Overview</h1>
<p>
@@ -217,26 +217,27 @@ in <a href="#table_standardproperties" target="_self">table&nbsp;1</a>.
<div class='table'>
<a class="target" name="table_standardproperties">&nbsp;</a><table class="table longtable"><tr><th style="text-align:right"> Property </th><th style="text-align:left"> Definition </th></tr>
<tr><td style="text-align:right"> <strong class="asterisk">baseColor</strong> </td><td style="text-align:left"> Diffuse albedo for non-metallic surfaces, and specular color for metallic surfaces </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">metallic</strong> </td><td style="text-align:left"> Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1) </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">roughness</strong> </td><td style="text-align:left"> Perceived smoothness (1.0) or roughness (0.0) of a surface. Smooth surfaces exhibit sharp reflections </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">metallic</strong> </td><td style="text-align:left"> Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1) </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">reflectance</strong> </td><td style="text-align:left"> Fresnel reflectance at normal incidence for dielectric surfaces. This directly controls the strength of the reflections </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">sheenColor</strong> </td><td style="text-align:left"> Strength of the sheen layer </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">sheenRoughness</strong> </td><td style="text-align:left"> Perceived smoothness or roughness of the sheen layer </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">ambientOcclusion</strong> </td><td style="text-align:left"> Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0 </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">clearCoat</strong> </td><td style="text-align:left"> Strength of the clear coat layer </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">clearCoatRoughness</strong> </td><td style="text-align:left"> Perceived smoothness or roughness of the clear coat layer </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">clearCoatNormal</strong> </td><td style="text-align:left"> A detail normal used to perturb the clear coat layer using <em class="underscore">bump mapping</em> (<em class="underscore">normal mapping</em>) </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">anisotropy</strong> </td><td style="text-align:left"> Amount of anisotropy in either the tangent or bitangent direction </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">anisotropyDirection</strong> </td><td style="text-align:left"> Local surface direction in tangent space </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">ambientOcclusion</strong> </td><td style="text-align:left"> Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0 </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">normal</strong> </td><td style="text-align:left"> A detail normal used to perturb the surface using <em class="underscore">bump mapping</em> (<em class="underscore">normal mapping</em>) </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">bentNormal</strong> </td><td style="text-align:left"> A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">clearCoatNormal</strong> </td><td style="text-align:left"> A detail normal used to perturb the clear coat layer using <em class="underscore">bump mapping</em> (<em class="underscore">normal mapping</em>) </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">emissive</strong> </td><td style="text-align:left"> Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">postLightingColor</strong> </td><td style="text-align:left"> Additional color that can be blended with the result of the lighting computations. See <code>postLightingBlending</code> </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">ior</strong> </td><td style="text-align:left"> Index of refraction, either for refractive objects or as an alternative to reflectance </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">transmission</strong> </td><td style="text-align:left"> Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">absorption</strong> </td><td style="text-align:left"> Absorption factor for refractive objects </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">microThickness</strong> </td><td style="text-align:left"> Thickness of the thin layer of refractive objects </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">thickness</strong> </td><td style="text-align:left"> Thickness of the solid volume of refractive objects </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">sheenColor</strong> </td><td style="text-align:left"> Strength of the sheen layer </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">sheenRoughness</strong> </td><td style="text-align:left"> Perceived smoothness or roughness of the sheen layer </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">emissive</strong> </td><td style="text-align:left"> Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">normal</strong> </td><td style="text-align:left"> A detail normal used to perturb the surface using <em class="underscore">bump mapping</em> (<em class="underscore">normal mapping</em>) </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">postLightingColor</strong> </td><td style="text-align:left"> Additional color that can be blended with the result of the lighting computations. See <code>postLightingBlending</code> </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">absorption</strong> </td><td style="text-align:left"> Absorption factor for refractive objects </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">transmission</strong> </td><td style="text-align:left"> Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">ior</strong> </td><td style="text-align:left"> Index of refraction, either for refractive objects or as an alternative to reflectance </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">microThickness</strong> </td><td style="text-align:left"> Thickness of the thin layer of refractive objects </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">bentNormal</strong> </td><td style="text-align:left"> A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">shadowStrength</strong> </td><td style="text-align:left"> Strength factor between 0 and 1 for all shadows received by this material </td></tr>
</table><center><div class="tablecaption"><b style="font-style:normal;">Table&nbsp;1:</b> Properties of the standard model</div></center></div>
</p><p>
The type and range of each property is described in <a href="#table_standardpropertiestypes" target="_self">table&nbsp;2</a>.
@@ -1097,10 +1098,13 @@ non-shader data.
<dl><dt>Type</dt><dd><p> array of parameter objects
</p></dd><dt>Value</dt><dd><p> Each entry is an object with the properties <code>name</code> and <code>type</code>, both of <code>string</code> type. The
name must be a valid GLSL identifier. Entries also have an optional <code>precision</code>, which can be
name must be a valid GLSL identifier. Entries have an optional <code>precision</code>, which can be
one of <code>default</code> (best precision for the platform, typically <code>high</code> on desktop, <code>medium</code> on
mobile), <code>low</code>, <code>medium</code>, <code>high</code>. The type must be one of the types described in
<a href="#table_materialparamstypes" target="_self">table&nbsp;14</a>.
<a href="#table_materialparamstypes" target="_self">table&nbsp;14</a>. For Android external textures, entries also have an optional
transformName parameter to specify the name of the material parameter that will be
used to expose the transform matrix associated with the external sampler. In iOS and Vulkan,
this will always be identity.
</p></dd></dl><div class='table'>
<a class="target" name="table_materialparamstypes">&nbsp;</a><table class="table longtable"><tr><th style="text-align:left"> Type </th><th style="text-align:left"> Description </th></tr>
<tr><td style="text-align:left"> bool </td><td style="text-align:left"> Single boolean </td></tr>
@@ -1322,7 +1326,10 @@ non-shader data.
required and does not need to be specified. The <code>tangents</code> attribute is automatically required
when selecting any shading model that is not <code>unlit</code>. See the shader sections of this document
for more information on how to access these attributes from the shaders.
</p></dd></dl></p><pre class="listing tilde"><code><span class="line"></span>material <span class="hljs-punctuation">{</span>
</p></dd></dl><div class="admonition note"><div class="admonition-title"> Interaction with custom variables</div>
</p><p>
When the <code>color</code> attribute is specified, only four custom variables are available instead of five.</div>
</p><pre class="listing tilde"><code><span class="line"></span>material <span class="hljs-punctuation">{</span>
<span class="line"></span> parameters <span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
<span class="line"></span> <span class="hljs-punctuation">{</span>
<span class="line"></span> type <span class="hljs-punctuation">:</span> sampler2d<span class="hljs-punctuation">,</span>
@@ -1347,7 +1354,7 @@ non-shader data.
<p>
<dl><dt>Type</dt><dd><p> array of <code>string</code>
</p></dd><dt>Value</dt><dd><p> Up to 4 strings, each must be a valid GLSL identifier.
</p></dd><dt>Value</dt><dd><p> Up to 5 strings, each must be a valid GLSL identifier.
</p></dd><dt>Description</dt><dd><p> Defines custom interpolants (or variables) that are output by the material's vertex shader.
Each entry of the array defines the name of an interpolant. The full name in the fragment
shader is the name of the interpolant with the <code>variable_</code> prefix. For instance, if you
@@ -1360,7 +1367,11 @@ non-shader data.
In this case the specified precision is used as-is in both fragment and vertex stages, in
particular if <code>default</code> is specified the default precision is used is the fragment shader
(<code>mediump</code>) and in the vertex shader (<code>highp</code>).
</p></dd></dl></p><pre class="listing tilde"><code><span class="line"></span>material <span class="hljs-punctuation">{</span>
</p></dd></dl><div class="admonition warning"><div class="admonition-title"> Interaction with required attributes</div>
</p><p>
If the <code>color</code> attribute is specified in the <code>required</code> list, then only four variables can be used
instead of five.</div>
</p><pre class="listing tilde"><code><span class="line"></span>material <span class="hljs-punctuation">{</span>
<span class="line"></span> name <span class="hljs-punctuation">:</span> Skybox<span class="hljs-punctuation">,</span>
<span class="line"></span> parameters <span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
<span class="line"></span> <span class="hljs-punctuation">{</span>
+4
View File
@@ -273,10 +273,14 @@ if (FILAMENT_SUPPORTS_WEBGPU)
src/webgpu/WebGPUFence.h
src/webgpu/WebGPUIndexBuffer.cpp
src/webgpu/WebGPUIndexBuffer.h
src/webgpu/WebGPUMsaaTextureResolver.cpp
src/webgpu/WebGPUMsaaTextureResolver.h
src/webgpu/WebGPUPipelineCreation.cpp
src/webgpu/WebGPUPipelineCreation.h
src/webgpu/WebGPUProgram.cpp
src/webgpu/WebGPUProgram.h
src/webgpu/WebGPURenderPassMipmapGenerator.cpp
src/webgpu/WebGPURenderPassMipmapGenerator.h
src/webgpu/WebGPURenderPrimitive.h
src/webgpu/WebGPURenderTarget.cpp
src/webgpu/WebGPURenderTarget.h
@@ -66,7 +66,7 @@ static std::string to_string(int const i) { return std::to_string(i); }
static std::string to_string(float const f) { return "float(" + std::to_string(f) + ")"; }
static void logCompilationError(ShaderStage shaderType, const char* name, GLuint shaderId,
CString const& sourceCode) noexcept;
Program::ShaderBlob const& sourceCode) noexcept;
static void logProgramLinkError(char const* name, GLuint program) noexcept;
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext const& context, char* source,
@@ -88,7 +88,7 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
ShaderCompilerService& compiler;
CString const& name;
FixedCapacityVector<std::pair<CString, uint8_t>> attributes;
shaders_source_t shaderSourceCode;
Program::ShaderSource shaderSourceCode;
void* user = nullptr;
struct {
shaders_t shaders{};
@@ -612,7 +612,7 @@ void ShaderCompilerService::executeTickOps() noexcept {
#ifndef NDEBUG
// for debugging we return the original shader source (without the modifications we
// made here), otherwise the line numbers wouldn't match.
token->shaderSourceCode[i] = { shader_src, shader_len };
token->shaderSourceCode[i] = std::move(shader);
#endif
token->gl.shaders[i] = shaderId;
}
@@ -758,7 +758,8 @@ void ShaderCompilerService::executeTickOps() noexcept {
UTILS_NOINLINE
/* static */ void logCompilationError(ShaderStage shaderType, const char* name,
GLuint const shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
GLuint const shaderId,
UTILS_UNUSED_IN_RELEASE Program::ShaderBlob const& sourceCode) noexcept {
{ // scope for the temporary string storage
auto to_string = [](ShaderStage type) -> const char* {
@@ -785,7 +786,8 @@ UTILS_NOINLINE
}
#ifndef NDEBUG
std::string_view const shader{ sourceCode.data(), sourceCode.size() };
std::string_view const shader{ reinterpret_cast<const char*>(sourceCode.data()),
sourceCode.size() };
size_t lc = 1;
size_t start = 0;
std::string line;
@@ -58,7 +58,6 @@ class ShaderCompilerService {
public:
using program_token_t = std::shared_ptr<OpenGLProgramToken>;
using shaders_t = std::array<GLuint, Program::SHADER_TYPE_COUNT>;
using shaders_source_t = std::array<utils::CString, Program::SHADER_TYPE_COUNT>;
explicit ShaderCompilerService(OpenGLDriver& driver);
+29 -40
View File
@@ -156,27 +156,27 @@ void VulkanStagePool::destroyStage(VulkanStage const*&& stage) {
delete stage;
}
VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, PixelDataType type,
uint32_t width, uint32_t height) {
fvkmemory::resource_ptr<VulkanStageImage::Resource> VulkanStagePool::acquireImage(
PixelDataFormat format, PixelDataType type, uint32_t width, uint32_t height) {
// Helper lambda so we can return stage images wrapped as resources that can
// be held by command buffers until no longer needed.
auto wrapAsResource = [this](VulkanStageImage* image) {
auto recycleFn = [this](VulkanStageImage* image) {
this->mFreeImages.insert(image);
};
return fvkmemory::resource_ptr<VulkanStageImage::Resource>::construct(
this->mResManager, image, recycleFn);
};
const VkFormat vkformat = fvkutils::getVkFormat(format, type);
for (auto image : mFreeImages) {
if (image->format == vkformat && image->width == width && image->height == height) {
mFreeImages.erase(image);
image->lastAccessed = mCurrentFrame;
mUsedImages.push_back(image);
return image;
for (auto stageImage : mFreeImages) {
if (stageImage->format() == vkformat && stageImage->width() == width && stageImage->height() == height) {
mFreeImages.erase(stageImage);
stageImage->mLastAccessed = mCurrentFrame;
return wrapAsResource(stageImage);
}
}
VulkanStageImage* image = new VulkanStageImage({
.format = vkformat,
.width = width,
.height = height,
.lastAccessed = mCurrentFrame,
});
mUsedImages.push_back(image);
const VkImageCreateInfo imageInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
@@ -194,14 +194,19 @@ VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, Pi
.usage = VMA_MEMORY_USAGE_CPU_TO_GPU
};
VkImage image;
VmaAllocation memory;
const UTILS_UNUSED VkResult result = vmaCreateImage(mAllocator, &imageInfo, &allocInfo,
&image->image, &image->memory, nullptr);
&image, &memory, nullptr);
assert_invariant(result == VK_SUCCESS);
VkImageAspectFlags const aspectFlags = fvkutils::getImageAspect(vkformat);
VkCommandBuffer const cmdbuffer = mCommands->get().buffer();
VulkanStageImage* stageImage = new VulkanStageImage(
vkformat, width, height, memory, image, mCurrentFrame);
// We use VK_IMAGE_LAYOUT_GENERAL here because the spec says:
// "Host access to image memory is only well-defined for linear images and for image
// subresources of those images which are currently in either the
@@ -209,12 +214,13 @@ VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, Pi
// vkGetImageSubresourceLayout for a linear image returns a subresource layout mapping that is
// valid for either of those image layouts."
fvkutils::transitionLayout(cmdbuffer, {
.image = image->image,
.image = stageImage->image(),
.oldLayout = VulkanLayout::UNDEFINED,
.newLayout = VulkanLayout::STAGING, // (= VK_IMAGE_LAYOUT_GENERAL)
.subresources = { aspectFlags, 0, 1, 0, 1 },
});
return image;
return wrapAsResource(stageImage);
}
void VulkanStagePool::gc() noexcept {
@@ -262,25 +268,14 @@ void VulkanStagePool::gc() noexcept {
decltype(mFreeImages) freeImages;
freeImages.swap(mFreeImages);
for (auto image : freeImages) {
if (image->lastAccessed < evictionTime) {
vmaDestroyImage(mAllocator, image->image, image->memory);
if (image->mLastAccessed < evictionTime) {
vmaDestroyImage(mAllocator, image->image(), image->memory());
delete image;
} else {
mFreeImages.insert(image);
}
}
// Reclaim images that are no longer being used by any command buffer.
decltype(mUsedImages) usedImages;
usedImages.swap(mUsedImages);
for (auto image : usedImages) {
if (image->lastAccessed < evictionTime) {
image->lastAccessed = mCurrentFrame;
mFreeImages.insert(image);
} else {
mUsedImages.push_back(image);
}
}
FVK_SYSTRACE_END();
}
@@ -290,14 +285,8 @@ void VulkanStagePool::terminate() noexcept {
}
mStages.clear();
for (auto image : mUsedImages) {
vmaDestroyImage(mAllocator, image->image, image->memory);
delete image;
}
mUsedImages.clear();
for (auto image : mFreeImages) {
vmaDestroyImage(mAllocator, image->image, image->memory);
vmaDestroyImage(mAllocator, image->image(), image->memory());
delete image;
}
mFreeImages.clear();
+66 -10
View File
@@ -126,13 +126,70 @@ private:
std::unordered_map<uint32_t, Segment*> mSegments;
};
struct VulkanStageImage {
VkFormat format;
uint32_t width;
uint32_t height;
mutable uint64_t lastAccessed;
VmaAllocation memory;
VkImage image;
class VulkanStageImage {
public:
class Resource : public fvkmemory::Resource {
public:
using RecycleFn = std::function<void(VulkanStageImage*)>;
Resource(VulkanStageImage* image, RecycleFn&& onRecycleFn)
: mImage(image),
mOnRecycleFn(onRecycleFn)
{}
~Resource() {
if (mOnRecycleFn) {
mOnRecycleFn(mImage);
}
}
inline VkFormat format() const { return mImage->format(); }
inline uint32_t width() const { return mImage->width(); }
inline uint32_t height() const { return mImage->height(); }
inline VmaAllocation memory() const { return mImage->memory(); }
inline VkImage image() const { return mImage->image(); }
private:
Resource() = delete;
Resource(const Resource& other) = delete;
Resource(Resource&& other) = delete;
Resource& operator=(const Resource& other) = delete;
Resource& operator=(Resource&& other) = delete;
VulkanStageImage* const mImage;
RecycleFn mOnRecycleFn;
};
VulkanStageImage(VkFormat format, uint32_t width, uint32_t height, VmaAllocation memory,
VkImage image, uint64_t lastAccessed)
: mFormat(format),
mWidth(width),
mHeight(height),
mMemory(memory),
mImage(image),
mLastAccessed(lastAccessed) {}
VulkanStageImage(const VulkanStageImage& other) = delete;
VulkanStageImage(VulkanStageImage&& other) = delete;
VulkanStageImage& operator=(const VulkanStageImage& other) = delete;
VulkanStageImage& operator=(VulkanStageImage&& other) = delete;
inline VkFormat format() const { return mFormat; }
inline uint32_t width() const { return mWidth; }
inline uint32_t height() const { return mHeight; }
inline VmaAllocation memory() const { return mMemory; }
inline VkImage image() const { return mImage; }
private:
const VkFormat mFormat;
const uint32_t mWidth;
const uint32_t mHeight;
const VmaAllocation mMemory;
const VkImage mImage;
uint64_t mLastAccessed;
// Denote as a friend so that it can update mLastAccessed.
friend class VulkanStagePool;
};
// Manages a pool of stages, periodically releasing stages that have been unused for a while.
@@ -153,7 +210,7 @@ public:
uint32_t alignment = 0);
// Images have VK_IMAGE_LAYOUT_GENERAL and must not be transitioned to any other layout
VulkanStageImage const* acquireImage(PixelDataFormat format, PixelDataType type,
fvkmemory::resource_ptr<VulkanStageImage::Resource> acquireImage(PixelDataFormat format, PixelDataType type,
uint32_t width, uint32_t height);
// Evicts old unused stages and bumps the current frame number.
@@ -193,8 +250,7 @@ private:
// Use an ordered multimap for quick (capacity => stage) lookups using lower_bound().
std::multimap<uint32_t, VulkanStage*> mStages;
std::unordered_set<VulkanStageImage const*> mFreeImages;
std::vector<VulkanStageImage const*> mUsedImages;
std::unordered_set<VulkanStageImage*> mFreeImages;
// Store the current "time" (really just a frame count) and LRU eviction parameters.
uint64_t mCurrentFrame = 0;
+16 -9
View File
@@ -227,7 +227,7 @@ void adjustedMemcpy(void* mapped, PixelBufferDescriptor const& p, size_t width,
// Slow path of copying row by row
assert_invariant(pbdStride >= width);
if (UTILS_UNLIKELY(p.left > 0 || p.top > 0 || pbdStride > width)) {
if (UTILS_UNLIKELY(pixelSize > 0 && (p.left > 0 || p.top > 0 || pbdStride > width))) {
size_t const pbdRowSize =
PixelBufferDescriptor::computeDataSize(p.format, p.type, pbdStride, 1, p.alignment);
size_t const pbdHeight = p.size / pixelSize / pbdStride / depth;
@@ -248,7 +248,9 @@ void adjustedMemcpy(void* mapped, PixelBufferDescriptor const& p, size_t width,
}
}
} else {
size_t const writeSize = pixelSize * (width * height * depth);
// Note: if pixelSize = 0, we're dealing with a compressed format; we
// should fall back to the buffer's size.
size_t const writeSize = pixelSize > 0 ? pixelSize * (width * height * depth) : p.size;
memcpy(mapped, buf, writeSize);
}
}
@@ -517,7 +519,9 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
// Otherwise, use vkCmdCopyBufferToImage.
size_t const bpp =
PixelBufferDescriptor::computeDataSize(hostData->format, hostData->type, 1, 1, 1);
size_t const writeSize = width * height * depth * bpp;
// Note: if bpp = 0, we're dealing with a compressed format; we should fall
// back to the buffer's size.
size_t const writeSize = bpp > 0 ? width * height * depth * bpp : hostData->size;
// Note: the following stageSegment must be stored within the command buffer
// before going out of scope, to ensure proper bookkeeping within the
@@ -593,18 +597,21 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& data, uint3
uint32_t height, uint32_t depth, uint32_t miplevel) {
// Otherwise, use vkCmdCopyBufferToImage.
size_t const bpp = PixelBufferDescriptor::computeDataSize(data.format, data.type, 1, 1, 1);
size_t const writeSize = width * height * depth * bpp;
// Note: if bpp = 0, we're dealing with a compressed format; we should fall
// back to the buffer's size.
size_t const writeSize = bpp > 0 ? width * height * depth * bpp : data.size;
void* mapped = nullptr;
VulkanStageImage const* stage
fvkmemory::resource_ptr<VulkanStageImage::Resource> stage
= mState->mStagePool.acquireImage(data.format, data.type, width, height);
vmaMapMemory(mState->mAllocator, stage->memory, &mapped);
vmaMapMemory(mState->mAllocator, stage->memory(), &mapped);
adjustedMemcpy(mapped, data, width, height, depth);
vmaUnmapMemory(mState->mAllocator, stage->memory);
vmaFlushAllocation(mState->mAllocator, stage->memory, 0, writeSize);
vmaUnmapMemory(mState->mAllocator, stage->memory());
vmaFlushAllocation(mState->mAllocator, stage->memory(), 0, writeSize);
VulkanCommandBuffer& commands = mState->mCommands->get();
VkCommandBuffer const cmdbuf = commands.buffer();
commands.acquire(stage);
commands.acquire(fvkmemory::resource_ptr<VulkanTexture>::cast(this));
// TODO: support blit-based format conversion for 3D images and cubemaps.
@@ -627,7 +634,7 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& data, uint3
VulkanLayout const oldLayout = getLayout(layer, miplevel);
transitionLayout(&commands, range, newLayout);
vkCmdBlitImage(cmdbuf, stage->image, fvkutils::getVkLayout(VulkanLayout::TRANSFER_SRC),
vkCmdBlitImage(cmdbuf, stage->image(), fvkutils::getVkLayout(VulkanLayout::TRANSFER_SRC),
mState->mTextureImage, fvkutils::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST);
transitionLayout(&commands, range, oldLayout);
@@ -27,6 +27,7 @@ template ResourceType getTypeEnum<VulkanProgram>() noexcept;
template ResourceType getTypeEnum<VulkanRenderTarget>() noexcept;
template ResourceType getTypeEnum<VulkanSwapChain>() noexcept;
template ResourceType getTypeEnum<VulkanStage::Segment>() noexcept;
template ResourceType getTypeEnum<VulkanStageImage::Resource>() noexcept;
template ResourceType getTypeEnum<VulkanRenderPrimitive>() noexcept;
template ResourceType getTypeEnum<VulkanTexture>() noexcept;
template ResourceType getTypeEnum<VulkanTextureState>() noexcept;
@@ -58,6 +59,9 @@ ResourceType getTypeEnum() noexcept {
if constexpr (std::is_same_v<D, VulkanStage::Segment>) {
return ResourceType::STAGE_SEGMENT;
}
if constexpr (std::is_same_v<D, VulkanStageImage::Resource>) {
return ResourceType::STAGE_IMAGE;
}
if constexpr (std::is_same_v<D, VulkanRenderPrimitive>) {
return ResourceType::RENDER_PRIMITIVE;
}
@@ -105,6 +109,8 @@ std::string getTypeStr(ResourceType type) {
return "SwapChain";
case ResourceType::STAGE_SEGMENT:
return "Stage::Segment";
case ResourceType::STAGE_IMAGE:
return "Stage::Image";
case ResourceType::RENDER_PRIMITIVE:
return "RenderPrimitive";
case ResourceType::TEXTURE:
@@ -51,7 +51,8 @@ enum class ResourceType : uint8_t {
FENCE = 13,
VULKAN_BUFFER = 14,
STAGE_SEGMENT = 15,
UNDEFINED_TYPE = 16, // Must be the last enum because we use it for iterating over the enums.
STAGE_IMAGE = 16,
UNDEFINED_TYPE = 17, // Must be the last enum because we use it for iterating over the enums.
};
template<typename D>
@@ -81,6 +81,9 @@ void ResourceManager::destroyWithType(ResourceType type, HandleId id) {
case ResourceType::STAGE_SEGMENT:
destruct<VulkanStage::Segment>(Handle<VulkanStage::Segment>(id));
break;
case ResourceType::STAGE_IMAGE:
destruct<VulkanStageImage::Resource>(Handle<VulkanStageImage::Resource>(id));
break;
case ResourceType::RENDER_PRIMITIVE:
destruct<VulkanRenderPrimitive>(Handle<VulkanRenderPrimitive>(id));
break;
+148 -101
View File
@@ -24,6 +24,7 @@
#include "WebGPUProgram.h"
#include "WebGPURenderPrimitive.h"
#include "WebGPURenderTarget.h"
#include "WebGPUStrings.h"
#include "WebGPUSwapChain.h"
#include "WebGPUTexture.h"
#include "WebGPUVertexBuffer.h"
@@ -42,7 +43,7 @@
#include <utils/CString.h>
#include <utils/Hash.h>
#include <utils/Panic.h>
#include <utils/ostream.h>
#include <utils/compiler.h>
#include <webgpu/webgpu_cpp.h>
@@ -71,7 +72,8 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
mAdapter{ mPlatform.requestAdapter(nullptr) },
mDevice{ mPlatform.requestDevice(mAdapter) },
mQueue{ mDevice.GetQueue() },
mMipMapGenerator{ mDevice },
mRenderPassMipmapGenerator{ mDevice },
mSpdComputePassMipmapGenerator{ mDevice },
mHandleAllocator{ "Handles", driverConfig.handleArenaSize,
driverConfig.disableHandleUseAfterFreeCheck, driverConfig.disableHeapHandleTags } {
mDevice.GetLimits(&mDeviceLimits);
@@ -455,8 +457,13 @@ void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> renderTargetHandle
const TargetBufferFlags targetFlags, const uint32_t width, const uint32_t height,
const uint8_t samples, const uint8_t layerCount, const MRT color,
const TargetBufferInfo depth, const TargetBufferInfo stencil) {
constructHandle<WebGPURenderTarget>(renderTargetHandle, width, height, samples, layerCount,
color, depth, stencil, targetFlags);
constructHandle<WebGPURenderTarget>(
renderTargetHandle, width, height, samples, layerCount, color, depth, stencil,
targetFlags,
[&](const Handle<HwTexture> textureHandle) {
return handleCast<WebGPUTexture>(textureHandle);
},
mDevice);
}
void WebGPUDriver::createFenceR(Handle<HwFence> fenceHandle, const int /* dummy */) {
@@ -584,7 +591,7 @@ bool WebGPUDriver::isParallelShaderCompileSupported() {
}
bool WebGPUDriver::isDepthStencilResolveSupported() {
return true;
return false;
}
bool WebGPUDriver::isDepthStencilBlitSupported(const TextureFormat format) {
@@ -717,27 +724,6 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
const uint8_t* dataBuff = static_cast<const uint8_t*>(data->buffer);
size_t dataSize = data->size;
std::unique_ptr<uint8_t[]> paddedBuffer;
if (bytesPerRow % 256 != 0) {
uint32_t padding = 256 - (bytesPerRow % 256);
uint32_t paddedBytesPerRow = bytesPerRow + padding;
size_t paddedBufferSize = static_cast<size_t>(paddedBytesPerRow) * height * depth;
paddedBuffer = std::make_unique<uint8_t[]>(paddedBufferSize);
uint8_t* dest = paddedBuffer.get();
for (uint32_t z = 0; z < depth; ++z) {
for (uint32_t y = 0; y < height; ++y) {
std::memcpy(dest, dataBuff, bytesPerRow);
dest += paddedBytesPerRow;
dataBuff += bytesPerRow;
}
}
dataBuff = paddedBuffer.get();
dataSize = paddedBufferSize;
bytesPerRow = paddedBytesPerRow;
}
auto layout = wgpu::TexelCopyBufferLayout{ .bytesPerRow = bytesPerRow, .rowsPerImage = height };
@@ -768,41 +754,58 @@ void WebGPUDriver::generateMipmaps(Handle<HwTexture> textureHandle) {
wgpu::Texture wgpuTexture = texture->getTexture();
assert_invariant(wgpuTexture);
const auto usage = wgpuTexture.GetUsage();
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::TextureBinding)
<< "Texture for mipmap generation must have TextureBinding usage. "
"supportsMultipleMipLevels for texture format "
<< static_cast<uint32_t>(wgpuTexture.GetFormat()) << " is "
<< WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(wgpuTexture.GetFormat());
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::StorageBinding)
<< "Texture for mipmap generation must have StorageBinding usage. "
"supportsMultipleMipLevels for texture format "
<< static_cast<uint32_t>(wgpuTexture.GetFormat()) << " is "
<< WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(wgpuTexture.GetFormat());
const uint32_t totalMipLevels = wgpuTexture.GetMipLevelCount();
if (totalMipLevels <= 1) {
return;
return; // nothing to do
}
// We will record all passes into a single command encoder.
wgpu::CommandEncoderDescriptor encoderDesc = {};
encoderDesc.label = "Mipmap Command Encoder";
wgpu::CommandEncoder encoder = mDevice.CreateCommandEncoder(&encoderDesc);
const auto usage = wgpuTexture.GetUsage();
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::TextureBinding)
<< "Texture for mipmap generation must have TextureBinding usage.";
spd::SPDPassConfig spdConfig = { .filter = spd::SPDFilter::Average,
.targetTexture = wgpuTexture,
.numMips = totalMipLevels,
.halfPrecision = false,
.sourceMipLevel = 0 };
switch (texture->getMipmapGenerationStrategy()) {
case WebGPUTexture::MipmapGenerationStrategy::RENDER_PASS:
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::RenderAttachment)
<< "Texture for mipmap generation via render pass blit must have "
"RenderAttachment usage.";
mRenderPassMipmapGenerator.generateMipmaps(mQueue, texture->getTexture());
break;
case WebGPUTexture::MipmapGenerationStrategy::NONE:
FWGPU_LOGW << "Trying to generate mipmaps for a texture we already know we cannot do "
"such generation. Texture sample type "
<< to_string(texture->target) << " WebGPU texture format "
<< webGPUTextureFormatToString(texture->getViewFormat()) << " samples "
<< static_cast<uint32_t>(texture->samples)
<< ". Going to try using the SPD_COMPUTE_PASS approach.";
UTILS_FALLTHROUGH;
case WebGPUTexture::MipmapGenerationStrategy::SPD_COMPUTE_PASS:
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::StorageBinding)
<< "Texture for mipmap generation must have StorageBinding usage "
"(as a compute pass strategy was selected for use). "
"supportsMultipleMipLevels for WebGPU texture format "
<< webGPUTextureFormatToString(wgpuTexture.GetFormat()) << " is "
<< WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(
wgpuTexture.GetFormat());
// We will record all passes into a single command encoder.
wgpu::CommandEncoderDescriptor encoderDesc = {};
encoderDesc.label = "Mipmap Command Encoder";
wgpu::CommandEncoder encoder = mDevice.CreateCommandEncoder(&encoderDesc);
mMipMapGenerator.Generate(encoder, wgpuTexture, spdConfig);
spd::SPDPassConfig spdConfig = { .filter = spd::SPDFilter::Average,
.targetTexture = wgpuTexture,
.numMips = totalMipLevels,
.halfPrecision = false,
.sourceMipLevel = 0 };
// Finish the encoder and submit all the passes at once.
wgpu::CommandBufferDescriptor cmdBufferDesc = {};
cmdBufferDesc.label = "Mipmap Command Buffer";
wgpu::CommandBuffer commandBuffer = encoder.Finish(&cmdBufferDesc);
mQueue.Submit(1, &commandBuffer);
mSpdComputePassMipmapGenerator.Generate(encoder, wgpuTexture, spdConfig);
// Finish the encoder and submit all the passes at once.
wgpu::CommandBufferDescriptor cmdBufferDesc = {};
cmdBufferDesc.label = "Mipmap Command Buffer";
wgpu::CommandBuffer commandBuffer = encoder.Finish(&cmdBufferDesc);
mQueue.Submit(1, &commandBuffer);
break;
}
}
void WebGPUDriver::compilePrograms(CompilerPriorityQueue priority,
@@ -821,10 +824,15 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
wgpu::TextureView defaultDepthStencilView = nullptr;
wgpu::TextureFormat defaultDepthStencilFormat = wgpu::TextureFormat::Undefined;
const bool msaaSidecarsRequired{ renderTarget->getSamples() > 1 &&
renderTarget->getSampleCountPerAttachment() <= 1 };
std::array<wgpu::TextureView, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> customColorViews{};
std::array<wgpu::TextureView, customColorViews.size()> customColorMsaaSidecarViews{};
uint32_t customColorViewCount = 0;
wgpu::TextureView customDepthStencilView = nullptr;
wgpu::TextureView customDepthStencilMsaaSidecarTextureView = nullptr;
wgpu::TextureFormat customDepthStencilFormat = wgpu::TextureFormat::Undefined;
mCurrentRenderTarget = renderTarget;
@@ -835,21 +843,17 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
defaultDepthStencilFormat = mSwapChain->getDepthFormat();
if (any(renderTarget->getTargetFlags() & TargetBufferFlags::STENCIL) &&
!(defaultDepthStencilFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
defaultDepthStencilFormat == wgpu::TextureFormat::Depth32FloatStencil8 ||
defaultDepthStencilFormat == wgpu::TextureFormat::Stencil8)) {
FILAMENT_CHECK_POSTCONDITION(false) << "Default render target requested stencil, but swap chain's depth format "
<< (uint32_t)defaultDepthStencilFormat << " does not have a stencil aspect.";
!(hasStencil(defaultDepthStencilFormat))) {
FILAMENT_CHECK_POSTCONDITION(false)
<< "Default render target requested stencil, but swap chain's depth format "
<< (uint32_t) defaultDepthStencilFormat << " does not have a stencil aspect.";
}
if (any(renderTarget->getTargetFlags() & TargetBufferFlags::DEPTH) &&
!(defaultDepthStencilFormat == wgpu::TextureFormat::Depth16Unorm ||
defaultDepthStencilFormat == wgpu::TextureFormat::Depth32Float ||
defaultDepthStencilFormat == wgpu::TextureFormat::Depth24Plus ||
defaultDepthStencilFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
defaultDepthStencilFormat == wgpu::TextureFormat::Depth32FloatStencil8)) {
FILAMENT_CHECK_POSTCONDITION(false) << "Default render target requested depth, but swap chain's depth format "
<< (uint32_t)defaultDepthStencilFormat << " does not have a depth aspect.";
!(hasDepth(defaultDepthStencilFormat))) {
FILAMENT_CHECK_POSTCONDITION(false)
<< "Default render target requested depth, but swap chain's depth format "
<< (uint32_t) defaultDepthStencilFormat << " does not have a depth aspect.";
}
} else {
// Resolve views for custom render target
@@ -865,8 +869,19 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
<< ".";
const uint8_t mipLevel = colorInfos[i].level;
const uint32_t arrayLayer = colorInfos[i].layer;
customColorViews[customColorViewCount++] =
customColorViews[customColorViewCount] =
colorTexture->getOrMakeTextureView(mipLevel, arrayLayer);
if (msaaSidecarsRequired) {
const wgpu::TextureView msaaSidecarView{
colorTexture->makeMsaaSidecarTextureViewIfTextureSidecarExists(
renderTarget->getSamples(), mipLevel, arrayLayer)
};
FILAMENT_CHECK_POSTCONDITION(msaaSidecarView)
<< "Could not get a required MSAA sidecar texture view for color "
<< customColorViewCount << "?";
customColorMsaaSidecarViews[customColorViewCount] = msaaSidecarView;
}
customColorViewCount++;
}
}
}
@@ -898,15 +913,21 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
if (depthStencilSourceHandle) {
auto dsTexture = handleCast<WebGPUTexture>(depthStencilSourceHandle);
if (dsTexture) {
customDepthStencilView = dsTexture->getOrMakeTextureView(depthStencilMipLevel, depthStencilArrayLayer);
customDepthStencilView = dsTexture->getOrMakeTextureView(depthStencilMipLevel,
depthStencilArrayLayer);
if (msaaSidecarsRequired) {
customDepthStencilMsaaSidecarTextureView =
dsTexture->makeMsaaSidecarTextureViewIfTextureSidecarExists(
renderTarget->getSamples(), depthStencilMipLevel,
depthStencilArrayLayer);
FILAMENT_CHECK_POSTCONDITION(customDepthStencilMsaaSidecarTextureView)
<< "Could not get a required MSAA sidecar texture view for "
"depth/stencil?";
}
customDepthStencilFormat = dsTexture->getViewFormat();
if (any(renderTarget->getTargetFlags() & TargetBufferFlags::STENCIL) &&
!(customDepthStencilFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
customDepthStencilFormat ==
wgpu::TextureFormat::Depth32FloatStencil8 ||
customDepthStencilFormat ==
wgpu::TextureFormat::Stencil8)) {
!(hasStencil(customDepthStencilFormat))) {
FILAMENT_CHECK_POSTCONDITION(false)
<< "Custom render target requested stencil, but the provided texture"
"format number"
@@ -914,14 +935,7 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
<< " does not have a stencil aspect.";
}
if (any(renderTarget->getTargetFlags() & TargetBufferFlags::DEPTH) &&
!(customDepthStencilFormat == wgpu::TextureFormat::Depth16Unorm ||
customDepthStencilFormat == wgpu::TextureFormat::Depth32Float ||
customDepthStencilFormat ==
wgpu::TextureFormat::Depth24Plus ||
customDepthStencilFormat ==
wgpu::TextureFormat::Depth24PlusStencil8 ||
customDepthStencilFormat ==
wgpu::TextureFormat::Depth32FloatStencil8)) {
!(hasDepth(customDepthStencilFormat))) {
FILAMENT_CHECK_POSTCONDITION(false) << "Custom render target requested depth, "
"but the provided texture format number"
<< (uint32_t) customDepthStencilFormat
@@ -936,8 +950,10 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
defaultColorView,
defaultDepthStencilView,
customColorViews.data(),
customColorMsaaSidecarViews.data(),
customColorViewCount,
customDepthStencilView);
customDepthStencilView,
customDepthStencilMsaaSidecarTextureView);
mRenderPassEncoder = mCommandEncoder.BeginRenderPass(&renderPassDescriptor);
@@ -978,13 +994,13 @@ void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
wgpu::TextureFormat depthFormat = mSwapChain->getDepthFormat();
TargetBufferFlags newTargetFlags = filament::backend::TargetBufferFlags::NONE;
//Assuming Color and Depth are always present.
//Assuming Color always present in default render target.
newTargetFlags |= filament::backend::TargetBufferFlags::COLOR;
if (depthFormat != wgpu::TextureFormat::Undefined) {
newTargetFlags |= filament::backend::TargetBufferFlags::DEPTH;
if (depthFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
depthFormat == wgpu::TextureFormat::Depth32FloatStencil8) {
if (hasDepth(depthFormat)) {
newTargetFlags |= filament::backend::TargetBufferFlags::DEPTH;
}
if (hasStencil(depthFormat)) {
newTargetFlags |= filament::backend::TargetBufferFlags::STENCIL;
}
}
@@ -1071,7 +1087,33 @@ void WebGPUDriver::blitDEPRECATED(TargetBufferFlags buffers,
void WebGPUDriver::resolve(Handle<HwTexture> destinationTextureHandle, const uint8_t sourceLevel,
const uint8_t sourceLayer, Handle<HwTexture> sourceTextureHandle,
const uint8_t destinationLevel, const uint8_t destinationLayer) {
// todo
FILAMENT_CHECK_PRECONDITION(mCommandEncoder)
<< "Resolve assumes there is a valid command encoder to piggyback on.";
FILAMENT_CHECK_PRECONDITION(mRenderPassEncoder == nullptr)
<< "Resolve cannot be called during an existing render pass";
const auto sourceTexture{ handleCast<WebGPUTexture>(sourceTextureHandle) };
const auto destinationTexture{ handleCast<WebGPUTexture>(destinationTextureHandle) };
assert_invariant(sourceTexture);
assert_invariant(destinationTexture);
const WebGPUMsaaTextureResolver::ResolveRequest request{
.commandEncoder = mCommandEncoder,
.viewFormat = sourceTexture->getViewFormat(),
.source = {
.texture = sourceTexture->getTexture(),
.viewDimension = sourceTexture->getViewDimension(),
.mipLevel = sourceLevel,
.layer = sourceLayer,
.aspect = sourceTexture->getAspect(),
},
.destination = {
.texture = destinationTexture->getTexture(),
.viewDimension = destinationTexture->getViewDimension(),
.mipLevel = destinationLevel,
.layer = destinationLayer,
.aspect = destinationTexture->getAspect(),
},
};
mMsaaTextureResolver.resolve(request);
}
void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_t sourceLevel,
@@ -1081,13 +1123,24 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
// todo
}
void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
size_t WebGPUDriver::computePipelineKey(PipelineState const& pipelineState,
WebGPURenderTarget const* const renderTarget) const {
// TODO Investigate implications of this hash more closely. Vulkan has a whole class
// VulkanPipelineCache to handle this, may be missing nuance
static auto pipleineStateHasher = utils::hash::MurmurHashFn<filament::backend::PipelineState>();
auto hash = pipleineStateHasher(pipelineState);
if (mPipelineMap.find(hash) != mPipelineMap.end()) {
mRenderPassEncoder.SetPipeline(mPipelineMap[hash]);
// VulkanPipelineCache to handle this, may be missing nuance
static const auto pipelineStateHasher{
utils::hash::MurmurHashFn<filament::backend::PipelineState>()
};
const std::hash<uint32_t> intHasher{};
const std::hash<WebGPURenderTarget const*> addressHasher{};
const size_t pipelineStateHash{ intHasher(pipelineStateHasher(pipelineState)) };
const size_t renderTargetHash{ addressHasher(renderTarget) };
return utils::hash::combine(pipelineStateHash, renderTargetHash);
}
void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
auto pipelineKey{ computePipelineKey(pipelineState, mCurrentRenderTarget) };
if (mPipelineMap.find(pipelineKey) != mPipelineMap.end()) {
mRenderPassEncoder.SetPipeline(mPipelineMap[pipelineKey]);
return;
}
const auto program = handleCast<WebGPUProgram>(pipelineState.program);
@@ -1162,18 +1215,12 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
}
}
// TODO: We expected this to be a sane check, however it complains when running shadowtest.
//if (program->fragmentShaderModule != nullptr) {
// FILAMENT_CHECK_POSTCONDITION(!pipelineColorFormats.empty())
// << "Render pipeline with fragment shader must have at least one color target "
// "format.";
//}
wgpu::RenderPipeline pipeline = createWebGPURenderPipeline(mDevice, *program, *vertexBufferInfo,
layout, pipelineState.rasterState, pipelineState.stencilState,
pipelineState.polygonOffset, pipelineState.primitiveType, pipelineColorFormats,
pipelineDepthStencilFormat, pipelineSamples, requestedDepth, requestedStencil);
assert_invariant(pipeline);
mPipelineMap[hash] = pipeline;
mPipelineMap[pipelineKey] = pipeline;
mRenderPassEncoder.SetPipeline(pipeline);
}
+10 -3
View File
@@ -19,6 +19,8 @@
#include "WebGPURenderTarget.h"
#include "webgpu/WebGPUConstants.h"
#include "webgpu/WebGPUMsaaTextureResolver.h"
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
#include <backend/platforms/WebGPUPlatform.h>
#include "DriverBase.h"
@@ -55,7 +57,7 @@ public:
[[nodiscard]] static Driver* create(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept;
private:
explicit WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept;
WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept;
[[nodiscard]] ShaderModel getShaderModel() const noexcept final;
[[nodiscard]] ShaderLanguage getShaderLanguage() const noexcept final;
[[nodiscard]] wgpu::Sampler makeSampler(SamplerParams const& params);
@@ -77,9 +79,11 @@ private:
wgpu::CommandBuffer mCommandBuffer = nullptr;
WebGPURenderTarget* mDefaultRenderTarget = nullptr;
WebGPURenderTarget* mCurrentRenderTarget = nullptr;
spd::MipmapGenerator mMipMapGenerator;
WebGPURenderPassMipmapGenerator mRenderPassMipmapGenerator;
spd::MipmapGenerator mSpdComputePassMipmapGenerator;
WebGPUMsaaTextureResolver mMsaaTextureResolver{};
tsl::robin_map<uint32_t, wgpu::RenderPipeline> mPipelineMap;
tsl::robin_map<size_t, wgpu::RenderPipeline> mPipelineMap;
struct DescriptorSetBindingInfo{
wgpu::BindGroup bindGroup;
@@ -87,6 +91,9 @@ private:
backend::DescriptorSetOffsetArray offsets;
};
std::array<DescriptorSetBindingInfo,MAX_DESCRIPTOR_SET_COUNT> mCurrentDescriptorSets;
[[nodiscard]] size_t computePipelineKey(PipelineState const&, WebGPURenderTarget const*) const;
/*
* Driver interface
*/
@@ -0,0 +1,125 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "WebGPUMsaaTextureResolver.h"
#include "WebGPUTexture.h"
#include <utils/Panic.h>
#include <webgpu/webgpu_cpp.h>
namespace filament::backend {
namespace {
void resolveColorTextures(wgpu::CommandEncoder const& commandEncoder,
wgpu::TextureView const& sourceTextureView,
wgpu::TextureView const& destinationTextureView) {
const wgpu::RenderPassColorAttachment colorAttachment{
.view = sourceTextureView,
.depthSlice = wgpu::kDepthSliceUndefined, // being explicit for consistent behavior
.resolveTarget = destinationTextureView,
.loadOp = wgpu::LoadOp::Load,
.storeOp = wgpu::StoreOp::Store,
.clearValue = {}, // being explicit for consistent behavior
};
const wgpu::RenderPassDescriptor renderPassDescriptor{
.label = "resolve_render_pass",
.colorAttachmentCount = 1,
.colorAttachments = &colorAttachment,
.depthStencilAttachment = nullptr, // being explicit for consistent behavior
.occlusionQuerySet = nullptr, // being explicit for consistent behavior
.timestampWrites = nullptr, // being explicit for consistent behavior
};
const wgpu::RenderPassEncoder renderPassEncoder{ commandEncoder.BeginRenderPass(
&renderPassDescriptor) };
FILAMENT_CHECK_POSTCONDITION(renderPassEncoder)
<< "Failed to create wgpu::RenderPassEncoder for WebGPUDriver::resolve";
renderPassEncoder.End(); // only the implicit resolve is happening in the pass
}
} // namespace
void WebGPUMsaaTextureResolver::resolve(ResolveRequest const& request) {
ResolveRequest::TextureInfo const& source{ request.source };
ResolveRequest::TextureInfo const& destination{ request.destination };
FILAMENT_CHECK_PRECONDITION(destination.texture.GetWidth() == source.texture.GetWidth() &&
destination.texture.GetHeight() == source.texture.GetHeight())
<< "invalid resolve: source and destination sizes don't match";
FILAMENT_CHECK_PRECONDITION(
source.texture.GetSampleCount() > 1 && destination.texture.GetSampleCount() == 1)
<< "invalid resolve: source.samples=" << source.texture.GetSampleCount()
<< ", destination.samples=" << destination.texture.GetSampleCount();
FILAMENT_CHECK_PRECONDITION(source.texture.GetFormat() == destination.texture.GetFormat())
<< "source and destination texture format don't match";
const wgpu::TextureFormat format{ source.texture.GetFormat() };
FILAMENT_CHECK_PRECONDITION(!hasDepth(format)) << "can't resolve depth formats";
FILAMENT_CHECK_PRECONDITION(!hasStencil(format)) << "can't resolve stencil formats";
FILAMENT_CHECK_PRECONDITION(source.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
<< "source texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
FILAMENT_CHECK_PRECONDITION(
destination.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
<< "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
FILAMENT_CHECK_PRECONDITION(destination.texture.GetUsage() & wgpu::TextureUsage::TextureBinding)
<< "destination texture usage doesn't have wgpu::TextureUsage::TextureBinding";
const wgpu::TextureViewDescriptor sourceTextureViewDescriptor{
.label = "resolve_source_texture_view",
.format = request.viewFormat,
.dimension = source.viewDimension,
.baseMipLevel = source.mipLevel,
.mipLevelCount = 1,
.baseArrayLayer = source.layer,
.arrayLayerCount = 1,
.aspect = source.aspect,
.usage = source.texture.GetUsage(),
};
const wgpu::TextureView sourceTextureView{ source.texture.CreateView(
&sourceTextureViewDescriptor) };
FILAMENT_CHECK_POSTCONDITION(sourceTextureView)
<< "Failed to create wgpu::TextureView sourceTextureView";
const wgpu::TextureViewDescriptor destinationTextureViewDescriptor{
.label = "resolve_destination_texture_view",
.format = request.viewFormat,
.dimension = destination.viewDimension,
.baseMipLevel = destination.mipLevel,
.mipLevelCount = 1,
.baseArrayLayer = destination.layer,
.arrayLayerCount = 1,
.aspect = destination.aspect,
.usage = destination.texture.GetUsage(),
};
const wgpu::TextureView destinationTextureView{ destination.texture.CreateView(
&destinationTextureViewDescriptor) };
FILAMENT_CHECK_POSTCONDITION(destinationTextureView)
<< "Failed to create wgpu::TextureView destinationTextureView.";
if (hasDepth(format)) {
PANIC_PRECONDITION("DEPTH RESOLVE NOT IMPLEMENTED YET");
} else {
resolveColorTextures(request.commandEncoder, sourceTextureView, destinationTextureView);
}
}
} // namespace filament::backend
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_WEBGPUMSAATEXTURERESOLVER_H
#define TNT_FILAMENT_BACKEND_WEBGPUMSAATEXTURERESOLVER_H
#include <cstdint>
namespace wgpu {
class CommandEncoder;
class Texture;
enum class TextureAspect : uint32_t;
enum class TextureFormat : uint32_t;
enum class TextureViewDimension : uint32_t;
} // namespace wgpu
namespace filament::backend {
class WebGPUTexture;
class WebGPUMsaaTextureResolver final {
public:
struct ResolveRequest final {
struct TextureInfo final {
wgpu::Texture const& texture;
wgpu::TextureViewDimension viewDimension;
uint8_t mipLevel;
uint8_t layer;
wgpu::TextureAspect aspect;
};
wgpu::CommandEncoder const& commandEncoder;
wgpu::TextureFormat viewFormat;
TextureInfo source;
TextureInfo destination;
};
void resolve(ResolveRequest const&);
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUMSAATEXTURERESOLVER_H
@@ -281,27 +281,32 @@ wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device,
}
};
if (program.fragmentShaderModule != nullptr) {
fragmentState.module = program.fragmentShaderModule;
fragmentState.entryPoint = "main";
// see the comment about constants for the vertex state, as the same reasoning applies
// here
fragmentState.constantCount = 0,
fragmentState.constants = nullptr,
fragmentState.targetCount = colorFormats.size();
fragmentState.targets = colorTargets.data();
assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
// We expect a fragment shader implies at least one color target if it outputs color.
// This should be guaranteed by the caller ensuring colorFormats is not empty.
// However, this fails on shadowtest.cpp, TODO investigate why
// assert_invariant(fragmentState.targetCount > 0);
for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) {
auto& colorTarget = colorTargets[targetIndex];
colorTarget.format = colorFormats[targetIndex];
colorTarget.blend = rasterState.hasBlending() ? &blendState : nullptr;
colorTarget.writeMask =
rasterState.colorWrite ? wgpu::ColorWriteMask::All : wgpu::ColorWriteMask::None;
// According to the WebGPU spec, a pipeline cannot have a fragment stage with zero color
// targets. This situation can arise in Filament during depth-only passes (like shadow map
// generation) if the material variant still includes a fragment shader.
//
// To handle this, we check if any color targets are configured for this pipeline. If not, we
// create a pipeline *without* a fragment stage. This makes the pipeline valid for a
// depth-only pass, allowing depth writes to proceed correctly.
if (!colorFormats.empty()) {
fragmentState.module = program.fragmentShaderModule;
fragmentState.entryPoint = "main";
// see the comment about constants for the vertex state, as the same reasoning applies
// here
fragmentState.constantCount = 0,
fragmentState.constants = nullptr,
fragmentState.targetCount = colorFormats.size();
fragmentState.targets = colorTargets.data();
assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) {
auto& colorTarget = colorTargets[targetIndex];
colorTarget.format = colorFormats[targetIndex];
colorTarget.blend = rasterState.hasBlending() ? &blendState : nullptr;
colorTarget.writeMask =
rasterState.colorWrite ? wgpu::ColorWriteMask::All : wgpu::ColorWriteMask::None;
}
pipelineDescriptor.fragment = &fragmentState;
}
pipelineDescriptor.fragment = &fragmentState;
}
const wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor);
FILAMENT_CHECK_POSTCONDITION(pipeline)
@@ -0,0 +1,552 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "WebGPURenderPassMipmapGenerator.h"
#include <utils/Panic.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
#include <string_view>
namespace filament::backend {
namespace {
// The shaders expect a single-sampled 2D texture
// with a format compatible with scalar sample format f32
// (this is checked with the getCompatibilityFor function)
constexpr uint32_t TEXTURE_BIND_GROUP_INDEX{ 0 };
constexpr size_t TEXTURE_BIND_GROUP_ENTRY_SIZE{ 2 }; // sampler and texture
constexpr uint32_t SAMPLER_BINDING_INDEX{ 0 };
constexpr uint32_t TEXTURE_BINDING_INDEX{ 1 };
constexpr std::string_view VERTEX_SHADER_ENTRY_POINT{ "vertexShaderMain" };
constexpr std::string_view FRAGMENT_SHADER_ENTRY_POINT{ "fragmentShaderMain" };
// Note: would be nice to generate this constexpr string from the constexpr variables above
// to avoid them getting out of sync while still having the compile/build time optimization
constexpr std::string_view SHADER_SOURCE{ R"(
@group(0) @binding(0) var previousMipLevelSampler: sampler;
@group(0) @binding(1) var previousMipLevelTexture: texture_2d<f32>;
struct VertexShaderOutput {
@builtin(position) position: vec4<f32>,
@location(0) textureCoordinate: vec2<f32>,
};
@vertex
fn vertexShaderMain(@builtin(vertex_index) vertexIndex: u32) -> VertexShaderOutput {
let fullScreenTriangleVertices = array<vec2<f32>, 3>(
vec2<f32>(-1.0, -1.0),
vec2<f32>(3.0, -1.0),
vec2<f32>(-1.0, 3.0)
);
var out: VertexShaderOutput;
let vertex = fullScreenTriangleVertices[vertexIndex];
out.position = vec4<f32>(vertex * 2.0 - 1.0, 0.0, 1.0);
out.textureCoordinate = vec2<f32>(vertex.x, 1.0 - vertex.y);
return out;
}
@fragment
fn fragmentShaderMain(in: VertexShaderOutput) -> @location(0) vec4<f32> {
return textureSample(previousMipLevelTexture,
previousMipLevelSampler,
in.textureCoordinate);
}
)" };
[[nodiscard]] wgpu::Sampler createPreviousMipLevelSampler(wgpu::Device const& device) {
const wgpu::SamplerDescriptor descriptor{
.label = "previous_mip_level_sampler",
.addressModeU = wgpu::AddressMode::ClampToEdge,
.addressModeV = wgpu::AddressMode::ClampToEdge,
.addressModeW = wgpu::AddressMode::Undefined, // only 2D textures supported for now
.magFilter = wgpu::FilterMode::Linear,
.minFilter = wgpu::FilterMode::Linear,
.mipmapFilter = wgpu::MipmapFilterMode::Nearest, // unused for this purpose
.lodMinClamp = 0.0f, // should not matter, just being consistently defined
.lodMaxClamp = 32.0f, // should not matter, just being consistently defined
.compare = wgpu::CompareFunction::Undefined, // should not matter, just being consistently
// defined
.maxAnisotropy = 1, // should not matter, just being consistently defined
};
const wgpu::Sampler sampler{ device.CreateSampler(&descriptor) };
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create previous mip level sampler?";
return sampler;
}
[[nodiscard]] wgpu::ShaderModule createShaderModule(wgpu::Device const& device) {
wgpu::ShaderModuleWGSLDescriptor wgslDescriptor{};
wgslDescriptor.code = SHADER_SOURCE.data();
const wgpu::ShaderModuleDescriptor shaderModuleDescriptor{
.nextInChain = &wgslDescriptor,
.label = "render_pass_mipmap_generation_shaders",
};
const wgpu::ShaderModule shaderModule{ device.CreateShaderModule(&shaderModuleDescriptor) };
FILAMENT_CHECK_POSTCONDITION(shaderModule)
<< "Failed to create shader module for render pass mipmap generation?";
return shaderModule;
}
[[nodiscard]] wgpu::BindGroupLayout createTextureBindGroupLayout(wgpu::Device const& device) {
const wgpu::BindGroupLayoutEntry bindGroupLayoutEntries[TEXTURE_BIND_GROUP_ENTRY_SIZE] {
{
.binding = SAMPLER_BINDING_INDEX,
.visibility = wgpu::ShaderStage::Fragment,
.sampler = {
.type = wgpu::SamplerBindingType::Filtering, // only hardware accelerated filtering
// supported
},
},
{
.binding = TEXTURE_BINDING_INDEX,
.visibility = wgpu::ShaderStage::Fragment,
.texture = {
.sampleType = wgpu::TextureSampleType::Float, // only F32 scalar sample
// type supported for now
.viewDimension = wgpu::TextureViewDimension::e2D, // only 2D supported for now
.multisampled = false, // MSAA textures not supported for now
},
},
};
const wgpu::BindGroupLayoutDescriptor textureBindGroupLayoutDescriptor{
.label = "render_pass_mipmap_generation_texture_bind_group_layout",
.entryCount = TEXTURE_BIND_GROUP_ENTRY_SIZE,
.entries = bindGroupLayoutEntries,
};
const wgpu::BindGroupLayout textureBindGroupLayout{ device.CreateBindGroupLayout(
&textureBindGroupLayoutDescriptor) };
FILAMENT_CHECK_POSTCONDITION(textureBindGroupLayout)
<< "Failed to create texture bind group layout for render pass mipmap generation?";
return textureBindGroupLayout;
}
[[nodiscard]] wgpu::PipelineLayout createPipelineLayout(wgpu::Device const& device,
wgpu::BindGroupLayout const& textureBindGroupLayout) {
const wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor{
.label = "render_pass_mipmap_generation_pipeline_layout",
.bindGroupLayoutCount = 1,
.bindGroupLayouts = &textureBindGroupLayout,
};
const wgpu::PipelineLayout pipelineLayout{ device.CreatePipelineLayout(
&pipelineLayoutDescriptor) };
FILAMENT_CHECK_POSTCONDITION(pipelineLayout)
<< "Failed to create pipeline layout for render pass mipmap generation?";
return pipelineLayout;
}
[[nodiscard]] wgpu::RenderPipeline createPipeline(wgpu::Device const& device,
wgpu::ShaderModule const& shaderModule, wgpu::PipelineLayout const& pipelineLayout,
const wgpu::TextureFormat textureFormat) {
const wgpu::ColorTargetState colorTargetState{ .format = textureFormat };
const wgpu::FragmentState fragmentState{
.module = shaderModule,
.entryPoint = FRAGMENT_SHADER_ENTRY_POINT,
.constantCount = 0, // should not matter, just being consistently defined
.constants = nullptr, // should not matter, just being consistently defined
.targetCount = 1,
.targets = &colorTargetState,
};
const wgpu::RenderPipelineDescriptor pipelineDescriptor{
.label = "render_pass_mipmap_generation_pipeline",
.layout = pipelineLayout,
.vertex = {
.module = shaderModule,
.entryPoint = VERTEX_SHADER_ENTRY_POINT,
.constantCount = 0, // should not matter, just being consistently defined
.constants = nullptr, // should not matter, just being consistently defined
.bufferCount = 0, // using hardcoded vertices in the shader for
// performance and simplicity
.buffers = nullptr,
},
.primitive = {
.topology = wgpu::PrimitiveTopology::TriangleList,
.stripIndexFormat = wgpu::IndexFormat::Undefined, // should not matter
// (not using a strip topology),
// just being consistently defined
.frontFace = wgpu::FrontFace::Undefined, // should not matter (no cull mode),
// just being consistently defined
.cullMode = wgpu::CullMode::None,
.unclippedDepth = false, // should not matter, just being consistently defined
},
.depthStencil = nullptr, // not applicable, but explicitly set here for
// consistent definition
.multisample = {
.count = 1, // no multi-sampling
.mask = 0xFFFFFFFF,
.alphaToCoverageEnabled = false,
},
.fragment = &fragmentState,
};
const wgpu::RenderPipeline pipeline{ device.CreateRenderPipeline(&pipelineDescriptor) };
FILAMENT_CHECK_POSTCONDITION(pipeline)
<< "Failed to create pipeline for render pass mipmap generation?";
return pipeline;
}
} // namespace
WebGPURenderPassMipmapGenerator::WebGPURenderPassMipmapGenerator(wgpu::Device const& device)
: mDevice{ device },
mPreviousMipLevelSampler{ createPreviousMipLevelSampler(mDevice) },
mShaderModule{ createShaderModule(mDevice) },
mTextureBindGroupLayout{ createTextureBindGroupLayout(mDevice) },
mPipelineLayout{ createPipelineLayout(mDevice, mTextureBindGroupLayout) } {}
WebGPURenderPassMipmapGenerator::FormatCompatibility
WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat format,
const wgpu::TextureDimension dimension, const uint32_t sampleCount) {
// check that the format is compatible (currently expects that the scalar sample type is
// f32)...
switch (format) {
case wgpu::TextureFormat::Depth16Unorm:
case wgpu::TextureFormat::Depth24Plus:
case wgpu::TextureFormat::Depth24PlusStencil8:
case wgpu::TextureFormat::Depth32Float:
case wgpu::TextureFormat::Depth32FloatStencil8:
return {
.compatible = false,
.reason = "A depth texture format requires special sampler treatment and does "
"not "
"generally support linear filtering needed for render pass based "
"mipmap "
"generation, thus this texture is not supported, as it is a kind of "
"depth texture.",
};
case wgpu::TextureFormat::Undefined:
case wgpu::TextureFormat::External:
return {
.compatible = false,
.reason = "Undefined or External textures are not supported for render pass "
"based "
"mipmap generation",
};
default:
const ScalarSampleType scalarSampleType{ getScalarSampleTypeFrom(format) };
switch (scalarSampleType) {
case ScalarSampleType::U32:
return {
.compatible = false,
.reason = "The provided texture format requires an unsigned integer "
"sampler, which does not natively support linear filtering, "
"which is needed for render pass based mipmap generation.",
};
case ScalarSampleType::I32:
return {
.compatible = false,
.reason = "The provided texture format requires a signed integer "
"sampler, which does not natively support linear filtering, "
"which is needed for render pass based mipmap generation.",
};
case ScalarSampleType::F32:
break;
}
}
// check that the dimensionality is compatible (currently expects 2D textures)...
switch (dimension) {
case wgpu::TextureDimension::Undefined:
return {
.compatible = false,
.reason = "Texture has an undefined texture dimension (expected 2D), thus it is "
"not "
"supported by the render pass based mipmap generation implementation.",
};
case wgpu::TextureDimension::e1D:
return {
.compatible = false,
.reason = "Texture has a 1D texture dimension (expected 2D), thus it is not "
"current supported by the render pass based mipmap generation "
"implementation (but could be extended to later if needed).",
};
case wgpu::TextureDimension::e3D:
return {
.compatible = false,
.reason = "Texture has a 3D texture dimension (expected 2D), thus it is not "
"current supported by the render pass based mipmap generation "
"implementation (but could be extended to later if REALLY needed).",
};
case wgpu::TextureDimension::e2D:
break;
}
// check that the texture is single-sampled (for now)...
if (sampleCount > 1) {
return {
.compatible = false,
.reason = "Multi-sampled textures are not currently supported with the render pass "
"based mipmap generation implementation (but could be extended to later if "
"REALLY needed. The idea would be to resolve the texture to a single "
"sampled "
"texture before generating single-sample mipmaps, but that may still not "
"necessarily be what is desired.).",
};
}
return { .compatible = true, .reason = "" };
}
constexpr WebGPURenderPassMipmapGenerator::ScalarSampleType
WebGPURenderPassMipmapGenerator::getScalarSampleTypeFrom(const wgpu::TextureFormat format) {
switch (format) {
case wgpu::TextureFormat::R8Uint:
case wgpu::TextureFormat::R16Uint:
case wgpu::TextureFormat::R32Uint:
case wgpu::TextureFormat::Stencil8:
case wgpu::TextureFormat::RG8Uint:
case wgpu::TextureFormat::RG16Uint:
case wgpu::TextureFormat::RGBA8Uint:
case wgpu::TextureFormat::RGB10A2Uint:
case wgpu::TextureFormat::RG32Uint:
case wgpu::TextureFormat::RGBA16Uint:
case wgpu::TextureFormat::RGBA32Uint:
return ScalarSampleType::U32;
case wgpu::TextureFormat::R8Sint:
case wgpu::TextureFormat::R16Sint:
case wgpu::TextureFormat::R32Sint:
case wgpu::TextureFormat::RG8Sint:
case wgpu::TextureFormat::RG16Sint:
case wgpu::TextureFormat::RGBA8Sint:
case wgpu::TextureFormat::RG32Sint:
case wgpu::TextureFormat::RGBA16Sint:
case wgpu::TextureFormat::RGBA32Sint:
return ScalarSampleType::I32;
case wgpu::TextureFormat::R8Snorm:
case wgpu::TextureFormat::R8Unorm:
case wgpu::TextureFormat::R16Float:
case wgpu::TextureFormat::RG16Float:
case wgpu::TextureFormat::RG8Unorm:
case wgpu::TextureFormat::RG8Snorm:
case wgpu::TextureFormat::R32Float:
case wgpu::TextureFormat::RGBA8Unorm:
case wgpu::TextureFormat::RGBA8UnormSrgb:
case wgpu::TextureFormat::RGBA8Snorm:
case wgpu::TextureFormat::BGRA8Unorm:
case wgpu::TextureFormat::BGRA8UnormSrgb:
case wgpu::TextureFormat::RGB10A2Unorm:
case wgpu::TextureFormat::RG11B10Ufloat:
case wgpu::TextureFormat::RGB9E5Ufloat:
case wgpu::TextureFormat::RG32Float:
case wgpu::TextureFormat::RGBA16Float:
case wgpu::TextureFormat::RGBA32Float:
case wgpu::TextureFormat::BC1RGBAUnorm:
case wgpu::TextureFormat::BC1RGBAUnormSrgb:
case wgpu::TextureFormat::BC2RGBAUnorm:
case wgpu::TextureFormat::BC2RGBAUnormSrgb:
case wgpu::TextureFormat::BC3RGBAUnorm:
case wgpu::TextureFormat::BC3RGBAUnormSrgb:
case wgpu::TextureFormat::BC4RUnorm:
case wgpu::TextureFormat::BC4RSnorm:
case wgpu::TextureFormat::BC5RGUnorm:
case wgpu::TextureFormat::BC5RGSnorm:
case wgpu::TextureFormat::BC6HRGBUfloat:
case wgpu::TextureFormat::BC6HRGBFloat:
case wgpu::TextureFormat::BC7RGBAUnorm:
case wgpu::TextureFormat::BC7RGBAUnormSrgb:
case wgpu::TextureFormat::ETC2RGB8Unorm:
case wgpu::TextureFormat::ETC2RGB8UnormSrgb:
case wgpu::TextureFormat::ETC2RGB8A1Unorm:
case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb:
case wgpu::TextureFormat::ETC2RGBA8Unorm:
case wgpu::TextureFormat::ETC2RGBA8UnormSrgb:
case wgpu::TextureFormat::EACR11Unorm:
case wgpu::TextureFormat::EACR11Snorm:
case wgpu::TextureFormat::EACRG11Unorm:
case wgpu::TextureFormat::EACRG11Snorm:
case wgpu::TextureFormat::ASTC4x4Unorm:
case wgpu::TextureFormat::ASTC4x4UnormSrgb:
case wgpu::TextureFormat::ASTC5x4Unorm:
case wgpu::TextureFormat::ASTC5x4UnormSrgb:
case wgpu::TextureFormat::ASTC5x5Unorm:
case wgpu::TextureFormat::ASTC5x5UnormSrgb:
case wgpu::TextureFormat::ASTC6x5Unorm:
case wgpu::TextureFormat::ASTC6x5UnormSrgb:
case wgpu::TextureFormat::ASTC6x6Unorm:
case wgpu::TextureFormat::ASTC6x6UnormSrgb:
case wgpu::TextureFormat::ASTC8x5Unorm:
case wgpu::TextureFormat::ASTC8x5UnormSrgb:
case wgpu::TextureFormat::ASTC8x6Unorm:
case wgpu::TextureFormat::ASTC8x6UnormSrgb:
case wgpu::TextureFormat::ASTC8x8Unorm:
case wgpu::TextureFormat::ASTC8x8UnormSrgb:
case wgpu::TextureFormat::ASTC10x5Unorm:
case wgpu::TextureFormat::ASTC10x5UnormSrgb:
case wgpu::TextureFormat::ASTC10x6Unorm:
case wgpu::TextureFormat::ASTC10x6UnormSrgb:
case wgpu::TextureFormat::ASTC10x8Unorm:
case wgpu::TextureFormat::ASTC10x8UnormSrgb:
case wgpu::TextureFormat::ASTC10x10Unorm:
case wgpu::TextureFormat::ASTC10x10UnormSrgb:
case wgpu::TextureFormat::ASTC12x10Unorm:
case wgpu::TextureFormat::ASTC12x10UnormSrgb:
case wgpu::TextureFormat::ASTC12x12Unorm:
case wgpu::TextureFormat::ASTC12x12UnormSrgb:
case wgpu::TextureFormat::R16Unorm:
case wgpu::TextureFormat::RG16Unorm:
case wgpu::TextureFormat::RGBA16Unorm:
case wgpu::TextureFormat::R16Snorm:
case wgpu::TextureFormat::RG16Snorm:
case wgpu::TextureFormat::RGBA16Snorm:
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
case wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm:
case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm:
case wgpu::TextureFormat::R8BG8Biplanar422Unorm:
case wgpu::TextureFormat::R8BG8Biplanar444Unorm:
case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm:
case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm:
return ScalarSampleType::F32;
case wgpu::TextureFormat::Depth16Unorm:
case wgpu::TextureFormat::Depth24Plus:
case wgpu::TextureFormat::Depth24PlusStencil8:
case wgpu::TextureFormat::Depth32Float:
case wgpu::TextureFormat::Depth32FloatStencil8:
PANIC_POSTCONDITION("A depth texture format requires special sampler treatment and "
"does not generally support linear filtering (needed for mipmap "
"generation). Thus no applicable scalar sample type for %d",
format);
break;
case wgpu::TextureFormat::Undefined:
case wgpu::TextureFormat::External:
PANIC_POSTCONDITION("No scalar sample type for texture format %d", format);
break;
}
}
void WebGPURenderPassMipmapGenerator::generateMipmaps(wgpu::Queue const& queue,
wgpu::Texture const& texture) {
const uint32_t mipLevelCount{ texture.GetMipLevelCount() };
if (mipLevelCount < 2) {
return; // nothing to do
}
wgpu::RenderPipeline const& pipeline{ getOrCreatePipelineFor(texture.GetFormat()) };
const wgpu::CommandEncoderDescriptor commandEncoderDescriptor{
.label = "mipmap_generation_render_pass_cmd_encoder",
};
const wgpu::CommandEncoder commandEncoder{ mDevice.CreateCommandEncoder(
&commandEncoderDescriptor) };
FILAMENT_CHECK_POSTCONDITION(commandEncoder)
<< "Failed to create command encoder for layer for render pass mipmap generation?";
const uint32_t layerCount{ texture.GetDepthOrArrayLayers() };
for (uint32_t layer = 0; layer < layerCount; layer++) {
for (uint32_t mipLevel = 1; mipLevel < mipLevelCount; mipLevel++) {
generateMipmap(commandEncoder, texture, pipeline, layer, mipLevel);
}
}
// submit the command buffer...
const wgpu::CommandBufferDescriptor commandBufferDescriptor{
.label = "mipmap_generation_render_pass_cmd_buffer",
};
const wgpu::CommandBuffer commandBuffer{ commandEncoder.Finish(&commandBufferDescriptor) };
FILAMENT_CHECK_POSTCONDITION(commandBuffer)
<< "Failed to create command buffer for layer for render pass mipmap generation?";
queue.Submit(1, &commandBuffer);
}
void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const& commandEncoder,
const wgpu::Texture& texture, const wgpu::RenderPipeline& pipeline, const uint32_t layer,
const uint32_t mipLevel) {
// create texture views for this pass...
const wgpu::TextureFormat format{ texture.GetFormat() };
// only 2D textures supported at this time...
const wgpu::TextureViewDimension dimension{ wgpu::TextureViewDimension::e2D };
const wgpu::TextureViewDescriptor sourceViewDescriptor{
.label = "previous_mip_level_texture_view",
.format = format,
.dimension = dimension,
.baseMipLevel = mipLevel - 1,
.mipLevelCount = 1,
.baseArrayLayer = layer,
.arrayLayerCount = 1,
.aspect = wgpu::TextureAspect::All,
.usage = wgpu::TextureUsage::TextureBinding,
};
const wgpu::TextureView sourceView{ texture.CreateView(&sourceViewDescriptor) };
FILAMENT_CHECK_POSTCONDITION(pipeline)
<< "Failed to create source texture view for layer " << layer << " and mip level "
<< mipLevel << " for render pass mipmap generation?";
const wgpu::TextureViewDescriptor destinationViewDescriptor{
.label = "next_mip_level_texture_view",
.format = format,
.dimension = dimension,
.baseMipLevel = mipLevel,
.mipLevelCount = 1,
.baseArrayLayer = layer,
.arrayLayerCount = 1,
.aspect = wgpu::TextureAspect::All,
.usage = wgpu::TextureUsage::RenderAttachment,
};
const wgpu::TextureView destinationView{ texture.CreateView(&destinationViewDescriptor) };
FILAMENT_CHECK_POSTCONDITION(pipeline)
<< "Failed to create destination texture view for layer " << layer << " and mip level "
<< mipLevel << " for render pass mipmap generation?";
// create the render pass...
const wgpu::BindGroupEntry textureBindGroupEntries[TEXTURE_BIND_GROUP_ENTRY_SIZE]{
{
.binding = SAMPLER_BINDING_INDEX,
.sampler = mPreviousMipLevelSampler,
},
{
.binding = TEXTURE_BINDING_INDEX,
.textureView = sourceView,
},
};
const wgpu::BindGroupDescriptor textureBindGroupDescriptor{
.label = "render_pass_mipmap_generation_texture_bind_group",
.layout = mTextureBindGroupLayout,
.entryCount = TEXTURE_BIND_GROUP_ENTRY_SIZE,
.entries = textureBindGroupEntries,
};
const wgpu::BindGroup textureBindGroup{ mDevice.CreateBindGroup(&textureBindGroupDescriptor) };
FILAMENT_CHECK_POSTCONDITION(pipeline)
<< "Failed to create texture bind group for layer " << layer << " and mip level "
<< mipLevel << " for render pass mipmap generation?";
const wgpu::RenderPassColorAttachment colorAttachment{
.view = destinationView,
.depthSlice = wgpu::kDepthSliceUndefined, // not applicable
.resolveTarget = nullptr, // not applicable
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store,
.clearValue = { .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 },
};
const wgpu::RenderPassDescriptor renderPassDescriptor{
.label = "mipmap_generation_render_pass",
.colorAttachmentCount = 1,
.colorAttachments = &colorAttachment,
.depthStencilAttachment = nullptr, // not applicable
.occlusionQuerySet = nullptr, // not applicable
.timestampWrites = nullptr, // not applicable
};
const wgpu::RenderPassEncoder renderPassEncoder{ commandEncoder.BeginRenderPass(
&renderPassDescriptor) };
FILAMENT_CHECK_POSTCONDITION(renderPassEncoder)
<< "Failed to create render pass encoder for layer " << layer << " and mip level "
<< mipLevel << " for render pass mipmap generation?";
renderPassEncoder.SetPipeline(pipeline);
renderPassEncoder.SetBindGroup(TEXTURE_BIND_GROUP_INDEX, textureBindGroup);
renderPassEncoder.Draw(3); // draw the full-screen triangle
// with hard-coded vertices in the shader
renderPassEncoder.End();
}
wgpu::RenderPipeline& WebGPURenderPassMipmapGenerator::getOrCreatePipelineFor(
const wgpu::TextureFormat textureFormat) {
const uint32_t key{ static_cast<uint32_t>(textureFormat) };
if (mPipelineByTextureFormat.find(key) == mPipelineByTextureFormat.end()) {
mPipelineByTextureFormat[key] =
createPipeline(mDevice, mShaderModule, mPipelineLayout, textureFormat);
}
return mPipelineByTextureFormat[key];
}
} // namespace filament::backend
@@ -0,0 +1,69 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_WEBGPURENDERPASSMIPMAPGENERATOR_H
#define TNT_FILAMENT_BACKEND_WEBGPURENDERPASSMIPMAPGENERATOR_H
#include <utils/Panic.h>
#include <tsl/robin_map.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
#include <string_view>
namespace filament::backend {
class WebGPURenderPassMipmapGenerator final {
public:
struct FormatCompatibility final {
bool compatible{ false };
std::string_view reason;
};
[[nodiscard]] static FormatCompatibility getCompatibilityFor(wgpu::TextureFormat,
wgpu::TextureDimension, uint32_t sampleCount);
explicit WebGPURenderPassMipmapGenerator(wgpu::Device const&);
void generateMipmaps(wgpu::Queue const&, wgpu::Texture const&);
private:
enum class ScalarSampleType : uint8_t {
F32,
I32,
U32,
};
[[nodiscard]] constexpr static ScalarSampleType getScalarSampleTypeFrom(
wgpu::TextureFormat format);
[[nodiscard]] wgpu::RenderPipeline& getOrCreatePipelineFor(wgpu::TextureFormat);
void generateMipmap(wgpu::CommandEncoder const&, wgpu::Texture const&,
wgpu::RenderPipeline const&, uint32_t layer, uint32_t mipLevel);
wgpu::Device const& mDevice;
const wgpu::Sampler mPreviousMipLevelSampler{ nullptr };
const wgpu::ShaderModule mShaderModule{ nullptr };
const wgpu::BindGroupLayout mTextureBindGroupLayout{ nullptr };
const wgpu::PipelineLayout mPipelineLayout{ nullptr };
tsl::robin_map<uint32_t, wgpu::RenderPipeline> mPipelineByTextureFormat;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPURENDERPASSMIPMAPGENERATOR_H
@@ -16,7 +16,11 @@
#include "WebGPURenderTarget.h"
#include "WebGPUTexture.h"
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <backend/TargetBufferInfo.h>
#include <private/backend/BackendUtils.h>
@@ -26,13 +30,97 @@
#include <webgpu/webgpu_cpp.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <functional>
#include <string_view>
namespace filament::backend {
namespace {
/**
* Panics if the number of samples in the original attachment textures do not match.
* @return The number of samples in the original attachment textures (the number/count in each one).
* Returns 0 if there are no attachments.
*/
[[nodiscard]] uint8_t createMsaaSidecarTextures(const uint8_t renderTargetSampleCount,
const TargetBufferFlags targetFlags, MRT const& colorAttachments,
TargetBufferInfo const& depthAttachment, TargetBufferInfo const& stencilAttachment,
std::function<WebGPUTexture*(const Handle<HwTexture>)> const& getWebGPUTexture,
wgpu::Device const& device) {
struct Target final {
std::string_view name;
TargetBufferFlags flag{ TargetBufferFlags::NONE };
size_t colorIndex{ 0 };
};
// assuming 8 colors. hopefully the following static_assert will get tripped if another
// color ever gets added
static_assert(
(TargetBufferFlags::COLOR0 | TargetBufferFlags::COLOR1 | TargetBufferFlags::COLOR2 |
TargetBufferFlags::COLOR3 | TargetBufferFlags::COLOR4 |
TargetBufferFlags::COLOR5 | TargetBufferFlags::COLOR6 |
TargetBufferFlags::COLOR7) == TargetBufferFlags::COLOR_ALL);
const static std::array TARGETS{
Target{ .name = "COLOR0", .flag = TargetBufferFlags::COLOR0, .colorIndex = 0 },
Target{ .name = "COLOR1", .flag = TargetBufferFlags::COLOR1, .colorIndex = 1 },
Target{ .name = "COLOR2", .flag = TargetBufferFlags::COLOR2, .colorIndex = 2 },
Target{ .name = "COLOR3", .flag = TargetBufferFlags::COLOR3, .colorIndex = 3 },
Target{ .name = "COLOR4", .flag = TargetBufferFlags::COLOR4, .colorIndex = 4 },
Target{ .name = "COLOR5", .flag = TargetBufferFlags::COLOR5, .colorIndex = 5 },
Target{ .name = "COLOR6", .flag = TargetBufferFlags::COLOR6, .colorIndex = 6 },
Target{ .name = "COLOR7", .flag = TargetBufferFlags::COLOR7, .colorIndex = 7 },
Target{ .name = "DEPTH", .flag = TargetBufferFlags::DEPTH },
Target{ .name = "STENCIL", .flag = TargetBufferFlags::STENCIL },
};
#ifndef NDEBUG
for (auto const& target: TARGETS) {
assert_invariant(target.colorIndex <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT &&
"color index is >= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT, which should "
"not be possible and could lead to us indexing into the colorAttachments "
"array beyond its bounds.");
}
#endif
bool firstAttachment{ true };
uint8_t sampleCountPerAttachment{ 0 };
for (auto const& target: TARGETS) {
if (any(targetFlags & target.flag)) {
const Handle<HwTexture> textureHandle{
target.flag == TargetBufferFlags::DEPTH
? depthAttachment.handle
: (target.flag == TargetBufferFlags::STENCIL
? stencilAttachment.handle
: colorAttachments[target.colorIndex].handle)
};
WebGPUTexture* const texture{ getWebGPUTexture(textureHandle) };
assert_invariant(texture != nullptr && "target flag indicate the use of an attachment "
"for which we do not have a texture?");
if (firstAttachment) {
sampleCountPerAttachment = texture->samples;
firstAttachment = false;
}
FILAMENT_CHECK_PRECONDITION(texture->samples == sampleCountPerAttachment)
<< target.name << " attachment texture has " << +texture->samples
<< " but the other attachment(s) have " << +sampleCountPerAttachment;
if (renderTargetSampleCount > 1 && sampleCountPerAttachment == 1) {
texture->createMsaaSidecarTextureIfNotAlreadyCreated(renderTargetSampleCount,
device);
}
}
}
assert_invariant(sampleCountPerAttachment);
return sampleCountPerAttachment;
}
} // namespace
WebGPURenderTarget::WebGPURenderTarget(const uint32_t width, const uint32_t height,
const uint8_t samples, const uint8_t layerCount, MRT const& colorAttachmentsMRT,
Attachment const& depthAttachmentInfo, Attachment const& stencilAttachmentInfo, TargetBufferFlags const& targetFlags)
Attachment const& depthAttachmentInfo, Attachment const& stencilAttachmentInfo,
TargetBufferFlags const& targetFlags,
std::function<WebGPUTexture*(const Handle<HwTexture>)> const& getWebGPUTexture,
wgpu::Device const& device)
: HwRenderTarget{ width, height },
mDefaultRenderTarget{ false },
mTargetFlags{ targetFlags },
@@ -40,8 +128,11 @@ WebGPURenderTarget::WebGPURenderTarget(const uint32_t width, const uint32_t heig
mLayerCount{ layerCount },
mColorAttachments{ colorAttachmentsMRT },
mDepthAttachment{ depthAttachmentInfo },
mStencilAttachment{ stencilAttachmentInfo } {
// TODO consider making this an array
mStencilAttachment{ stencilAttachmentInfo },
mSampleCountPerAttachment{ createMsaaSidecarTextures(mSamples, mTargetFlags,
mColorAttachments, mDepthAttachment, mStencilAttachment, getWebGPUTexture, device) } {
// TODO consider possibly making this an array (that would avoid a heap allocation, but the
// concern is the size limitation on the handle itself)
mColorAttachmentDesc.reserve(MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
}
@@ -51,8 +142,8 @@ WebGPURenderTarget::WebGPURenderTarget()
mDefaultRenderTarget{ true },
mTargetFlags{ TargetBufferFlags::NONE },
mSamples{ 1 },
mLayerCount{ 1 }
{}
mLayerCount{ 1 },
mSampleCountPerAttachment{ 1 } {}
wgpu::LoadOp WebGPURenderTarget::getLoadOperation(RenderPassParams const& params,
const TargetBufferFlags bufferToOperateOn) {
@@ -76,15 +167,41 @@ wgpu::StoreOp WebGPURenderTarget::getStoreOperation(RenderPassParams const& para
void WebGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor& outDescriptor,
RenderPassParams const& params, wgpu::TextureView const& defaultColorTextureView,
wgpu::TextureView const& defaultDepthStencilTextureView,
wgpu::TextureView const* customColorTextureViews, uint32_t customColorTextureViewCount,
wgpu::TextureView const& customDepthStencilTextureView){
wgpu::TextureView const* customColorTextureViews,
wgpu::TextureView const* customColorMsaaSidecarTextureViews,
uint32_t customColorTextureViewCount,
wgpu::TextureView const& customDepthStencilTextureView,
wgpu::TextureView const& customDepthStencilMsaaSidecarTextureView) {
const bool hasMsaaSidecars{ (customColorTextureViewCount > 0 &&
customColorMsaaSidecarTextureViews[0]) ||
customDepthStencilMsaaSidecarTextureView };
// either all the textures have MSAA sidecars or none of them do
if (hasMsaaSidecars) {
FILAMENT_CHECK_PRECONDITION(std::all_of(customColorMsaaSidecarTextureViews,
customColorMsaaSidecarTextureViews + customColorTextureViewCount,
[](wgpu::TextureView const& msaaView) { return msaaView != nullptr; }))
<< "A color or depth/stencil attachment texture has a MSAA sidecar but at least "
"one other color attachment texture does not.";
FILAMENT_CHECK_PRECONDITION(customDepthStencilMsaaSidecarTextureView != nullptr)
<< "The color attachment texture(s) have MSAA sidecar(s) but the depth/stencil "
"texture does not.";
} else {
FILAMENT_CHECK_PRECONDITION(std::all_of(customColorMsaaSidecarTextureViews,
customColorMsaaSidecarTextureViews + customColorTextureViewCount,
[](wgpu::TextureView const& msaaView) { return msaaView == nullptr; }))
<< "A color or depth/stencil attachment texture does not have a MSAA sidecar but "
"at least one color attachment texture does.";
FILAMENT_CHECK_PRECONDITION(customDepthStencilMsaaSidecarTextureView == nullptr)
<< "Custom color textures for the render target do not have MSAA sidecar(s) but "
"the depth/stencil texture does.";
}
mColorAttachmentDesc.clear();
const bool hasDepth = any(mTargetFlags & TargetBufferFlags::DEPTH);
const bool hasStencil = any(mTargetFlags & TargetBufferFlags::STENCIL);
mHasDepthStencilAttachment = hasDepth || hasStencil;
bool const depthReadOnly =
(params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0;
bool const depthReadOnly = (params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0;
// Color attachments
if (mDefaultRenderTarget) {
@@ -94,21 +211,31 @@ void WebGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor&
.resolveTarget = nullptr,
.loadOp = WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::COLOR0),
.storeOp = WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::COLOR0),
.clearValue = { params.clearColor.r, params.clearColor.g, params.clearColor.b,
params.clearColor.a } });
.clearValue = {
.r = params.clearColor.r,
.g = params.clearColor.g,
.b = params.clearColor.b,
.a = params.clearColor.a,
},
});
} else {
for (uint32_t i = 0; i < customColorTextureViewCount; ++i) {
if (customColorTextureViews[i]) {
mColorAttachmentDesc.push_back({ .view = customColorTextureViews[i],
// .resolveTarget = nullptr; // TODO: MSAA resolve for custom RT
const wgpu::TextureView msaaSidecar{ customColorMsaaSidecarTextureViews[i] };
mColorAttachmentDesc.push_back({
.view = hasMsaaSidecars ? msaaSidecar : customColorTextureViews[i],
.resolveTarget = hasMsaaSidecars ? customColorTextureViews[i] : nullptr,
.loadOp =
WebGPURenderTarget::getLoadOperation(params, getTargetBufferFlagsAt(i)),
.storeOp = WebGPURenderTarget::getStoreOperation(params,
getTargetBufferFlagsAt(i)),
.clearValue = { .r = params.clearColor.r,
.clearValue = {
.r = params.clearColor.r,
.g = params.clearColor.g,
.b = params.clearColor.b,
.a = params.clearColor.a } });
.a = params.clearColor.a,
},
});
}
}
}
@@ -117,13 +244,14 @@ void WebGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor&
// Depth/Stencil Attachments
if (mHasDepthStencilAttachment) {
wgpu::TextureView depthStencilViewToUse{nullptr};
wgpu::TextureView depthStencilViewToUse{ nullptr };
if (mDefaultRenderTarget) {
depthStencilViewToUse = defaultDepthStencilTextureView;
assert_invariant(depthStencilViewToUse);
} else {
if (customDepthStencilTextureView) {
depthStencilViewToUse = customDepthStencilTextureView;
depthStencilViewToUse = hasMsaaSidecars ? customDepthStencilMsaaSidecarTextureView
: customDepthStencilTextureView;
}
}
@@ -132,8 +260,12 @@ void WebGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor&
mDepthStencilAttachmentDesc.view = depthStencilViewToUse;
if (hasDepth) {
mDepthStencilAttachmentDesc.depthLoadOp = depthReadOnly ? wgpu::LoadOp::Undefined :getLoadOperation(params, TargetBufferFlags::DEPTH);
mDepthStencilAttachmentDesc.depthStoreOp = depthReadOnly ? wgpu::StoreOp::Undefined :getStoreOperation(params, TargetBufferFlags::DEPTH);
mDepthStencilAttachmentDesc.depthLoadOp =
depthReadOnly ? wgpu::LoadOp::Undefined
: getLoadOperation(params, TargetBufferFlags::DEPTH);
mDepthStencilAttachmentDesc.depthStoreOp =
depthReadOnly ? wgpu::StoreOp::Undefined
: getStoreOperation(params, TargetBufferFlags::DEPTH);
mDepthStencilAttachmentDesc.depthClearValue = static_cast<float>(params.clearDepth);
mDepthStencilAttachmentDesc.depthReadOnly = depthReadOnly;
} else {
@@ -143,10 +275,13 @@ void WebGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor&
}
if (hasStencil) {
mDepthStencilAttachmentDesc.stencilLoadOp = getLoadOperation(params, TargetBufferFlags::STENCIL);
mDepthStencilAttachmentDesc.stencilStoreOp = getStoreOperation(params, TargetBufferFlags::STENCIL);
mDepthStencilAttachmentDesc.stencilLoadOp =
getLoadOperation(params, TargetBufferFlags::STENCIL);
mDepthStencilAttachmentDesc.stencilStoreOp =
getStoreOperation(params, TargetBufferFlags::STENCIL);
mDepthStencilAttachmentDesc.stencilClearValue = params.clearStencil;
mDepthStencilAttachmentDesc.stencilReadOnly = (params.readOnlyDepthStencil & RenderPassParams::READONLY_STENCIL) > 0;
mDepthStencilAttachmentDesc.stencilReadOnly =
(params.readOnlyDepthStencil & RenderPassParams::READONLY_STENCIL) > 0;
} else {
mDepthStencilAttachmentDesc.stencilLoadOp = wgpu::LoadOp::Undefined;
mDepthStencilAttachmentDesc.stencilStoreOp = wgpu::StoreOp::Undefined;
@@ -163,10 +298,6 @@ void WebGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor&
outDescriptor.depthStencilAttachment = nullptr;
mDepthStencilAttachmentDesc = {};
}
// descriptor.sampleCount was removed from the core spec. If your webgpu.h still has it,
// and your Dawn version expects it, you might need to set it here based on this->samples.
// e.g., descriptor.sampleCount = this->samples;
}
} // namespace filament::backend
@@ -17,6 +17,8 @@
#ifndef TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
#define TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
#include "WebGPUTexture.h"
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#include <backend/TargetBufferInfo.h>
@@ -24,6 +26,7 @@
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
#include <functional>
#include <vector>
namespace filament::backend {
@@ -34,24 +37,27 @@ public:
WebGPURenderTarget(uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount,
MRT const& colorAttachments, Attachment const& depthAttachment,
Attachment const& stencilAttachment, TargetBufferFlags const& targetFlags);
Attachment const& stencilAttachment, TargetBufferFlags const& targetFlags,
std::function<WebGPUTexture*(const Handle<HwTexture>)> const&, wgpu::Device const&);
// Default constructor for the default render target
WebGPURenderTarget();
void setUpRenderPassAttachments(
wgpu::RenderPassDescriptor& outDescriptor,
void setUpRenderPassAttachments(wgpu::RenderPassDescriptor& outDescriptor,
RenderPassParams const& params,
// For default render target:
wgpu::TextureView const& defaultColorTextureView,
wgpu::TextureView const& defaultDepthStencilTextureView,
// For custom render targets:
wgpu::TextureView const* customColorTextureViews, // Array of views
wgpu::TextureView const* customColorTextureViews, // Array of views
wgpu::TextureView const* customColorMsaaSidecarTextureViews, // nullptrs if N/A
uint32_t customColorTextureViewCount,
wgpu::TextureView const& customDepthStencilTextureView);
wgpu::TextureView const& customDepthStencilTextureView,
wgpu::TextureView const& customDepthStencilMsaaSidecarTextureView /* nullptr if N/A */);
[[nodiscard]] bool isDefaultRenderTarget() const { return mDefaultRenderTarget; }
[[nodiscard]] uint8_t getSamples() const { return mSamples; }
[[nodiscard]] uint8_t getSampleCountPerAttachment() const { return mSampleCountPerAttachment; }
[[nodiscard]] uint8_t getLayerCount() const { return mLayerCount; }
[[nodiscard]] MRT const& getColorAttachmentInfos() const { return mColorAttachments; }
@@ -78,6 +84,8 @@ private:
Attachment mDepthAttachment{};
Attachment mStencilAttachment{};
uint8_t mSampleCountPerAttachment = 0;
// Cached descriptors for the render pass
std::vector<wgpu::RenderPassColorAttachment> mColorAttachmentDesc;
wgpu::RenderPassDepthStencilAttachment mDepthStencilAttachmentDesc{};
+116
View File
@@ -123,6 +123,122 @@ template<typename WebGPUPrintable>
}
}
[[nodiscard]] constexpr std::string_view webGPUTextureFormatToString(
const wgpu::TextureFormat format) {
switch (format) {
case wgpu::TextureFormat::Undefined: return "Undefined";
case wgpu::TextureFormat::R8Unorm: return "R8Unorm";
case wgpu::TextureFormat::R8Snorm: return "R8Snorm";
case wgpu::TextureFormat::R8Uint: return "R8Uint";
case wgpu::TextureFormat::R8Sint: return "R8Sint";
case wgpu::TextureFormat::R16Uint: return "R16Uint";
case wgpu::TextureFormat::R16Sint: return "R16Sint";
case wgpu::TextureFormat::R16Float: return "R16Float";
case wgpu::TextureFormat::RG8Unorm: return "RG8Unorm";
case wgpu::TextureFormat::RG8Snorm: return "RG8Snorm";
case wgpu::TextureFormat::RG8Uint: return "RG8Uint";
case wgpu::TextureFormat::RG8Sint: return "RG8Sint";
case wgpu::TextureFormat::R32Float: return "R32Float";
case wgpu::TextureFormat::R32Uint: return "R32Uint";
case wgpu::TextureFormat::R32Sint: return "R32Sint";
case wgpu::TextureFormat::RG16Uint: return "RG16Uint";
case wgpu::TextureFormat::RG16Sint: return "RG16Sint";
case wgpu::TextureFormat::RG16Float: return "RG16Float";
case wgpu::TextureFormat::RGBA8Unorm: return "RGBA8Unorm";
case wgpu::TextureFormat::RGBA8UnormSrgb: return "RGBA8UnormSrgb";
case wgpu::TextureFormat::RGBA8Snorm: return "RGBA8Snorm";
case wgpu::TextureFormat::RGBA8Uint: return "RGBA8Uint";
case wgpu::TextureFormat::RGBA8Sint: return "RGBA8Sint";
case wgpu::TextureFormat::BGRA8Unorm: return "BGRA8Unorm";
case wgpu::TextureFormat::BGRA8UnormSrgb: return "BGRA8UnormSrgb";
case wgpu::TextureFormat::RGB10A2Uint: return "RGB10A2Uint";
case wgpu::TextureFormat::RGB10A2Unorm: return "RGB10A2Unorm";
case wgpu::TextureFormat::RG11B10Ufloat: return "RG11B10Ufloat";
case wgpu::TextureFormat::RGB9E5Ufloat: return "RGB9E5Ufloat";
case wgpu::TextureFormat::RG32Float: return "RG32Float";
case wgpu::TextureFormat::RG32Uint: return "RG32Uint";
case wgpu::TextureFormat::RG32Sint: return "RG32Sint";
case wgpu::TextureFormat::RGBA16Uint: return "RGBA16Uint";
case wgpu::TextureFormat::RGBA16Sint: return "RGBA16Sint";
case wgpu::TextureFormat::RGBA16Float: return "RGBA16Float";
case wgpu::TextureFormat::RGBA32Float: return "RGBA32Float";
case wgpu::TextureFormat::RGBA32Uint: return "RGBA32Uint";
case wgpu::TextureFormat::RGBA32Sint: return "RGBA32Sint";
case wgpu::TextureFormat::Stencil8: return "Stencil8";
case wgpu::TextureFormat::Depth16Unorm: return "Depth16Unorm";
case wgpu::TextureFormat::Depth24Plus: return "Depth24Plus";
case wgpu::TextureFormat::Depth24PlusStencil8: return "Depth24PlusStencil8";
case wgpu::TextureFormat::Depth32Float: return "Depth32Float";
case wgpu::TextureFormat::Depth32FloatStencil8: return "Depth32FloatStencil8";
case wgpu::TextureFormat::BC1RGBAUnorm: return "BC1RGBAUnorm";
case wgpu::TextureFormat::BC1RGBAUnormSrgb: return "BC1RGBAUnormSrgb";
case wgpu::TextureFormat::BC2RGBAUnorm: return "BC2RGBAUnorm";
case wgpu::TextureFormat::BC2RGBAUnormSrgb: return "BC2RGBAUnormSrgb";
case wgpu::TextureFormat::BC3RGBAUnorm: return "BC3RGBAUnorm";
case wgpu::TextureFormat::BC3RGBAUnormSrgb: return "BC3RGBAUnormSrgb";
case wgpu::TextureFormat::BC4RUnorm: return "BC4RUnorm";
case wgpu::TextureFormat::BC4RSnorm: return "BC4RSnorm";
case wgpu::TextureFormat::BC5RGUnorm: return "BC5RGUnorm";
case wgpu::TextureFormat::BC5RGSnorm: return "BC5RGSnorm";
case wgpu::TextureFormat::BC6HRGBUfloat: return "BC6HRGBUfloat";
case wgpu::TextureFormat::BC6HRGBFloat: return "BC6HRGBFloat";
case wgpu::TextureFormat::BC7RGBAUnorm: return "BC7RGBAUnorm";
case wgpu::TextureFormat::BC7RGBAUnormSrgb: return "BC7RGBAUnormSrgb";
case wgpu::TextureFormat::ETC2RGB8Unorm: return "ETC2RGB8Unorm";
case wgpu::TextureFormat::ETC2RGB8UnormSrgb: return "ETC2RGB8UnormSrgb";
case wgpu::TextureFormat::ETC2RGB8A1Unorm: return "ETC2RGB8A1Unorm";
case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb: return "ETC2RGB8A1UnormSrgb";
case wgpu::TextureFormat::ETC2RGBA8Unorm: return "ETC2RGBA8Unorm";
case wgpu::TextureFormat::ETC2RGBA8UnormSrgb: return "ETC2RGBA8UnormSrgb";
case wgpu::TextureFormat::EACR11Unorm: return "EACR11Unorm";
case wgpu::TextureFormat::EACR11Snorm: return "EACR11Snorm";
case wgpu::TextureFormat::EACRG11Unorm: return "EACRG11Unorm";
case wgpu::TextureFormat::EACRG11Snorm: return "EACRG11Snorm";
case wgpu::TextureFormat::ASTC4x4Unorm: return "ASTC4x4Unorm";
case wgpu::TextureFormat::ASTC4x4UnormSrgb: return "ASTC4x4UnormSrgb";
case wgpu::TextureFormat::ASTC5x4Unorm: return "ASTC5x4Unorm";
case wgpu::TextureFormat::ASTC5x4UnormSrgb: return "ASTC5x4UnormSrgb";
case wgpu::TextureFormat::ASTC5x5Unorm: return "ASTC5x5Unorm";
case wgpu::TextureFormat::ASTC5x5UnormSrgb: return "ASTC5x5UnormSrgb";
case wgpu::TextureFormat::ASTC6x5Unorm: return "ASTC6x5Unorm";
case wgpu::TextureFormat::ASTC6x5UnormSrgb: return "ASTC6x5UnormSrgb";
case wgpu::TextureFormat::ASTC6x6Unorm: return "ASTC6x6Unorm";
case wgpu::TextureFormat::ASTC6x6UnormSrgb: return "ASTC6x6UnormSrgb";
case wgpu::TextureFormat::ASTC8x5Unorm: return "ASTC8x5Unorm";
case wgpu::TextureFormat::ASTC8x5UnormSrgb: return "ASTC8x5UnormSrgb";
case wgpu::TextureFormat::ASTC8x6Unorm: return "ASTC8x6Unorm";
case wgpu::TextureFormat::ASTC8x6UnormSrgb: return "ASTC8x6UnormSrgb";
case wgpu::TextureFormat::ASTC8x8Unorm: return "ASTC8x8Unorm";
case wgpu::TextureFormat::ASTC8x8UnormSrgb: return "ASTC8x8UnormSrgb";
case wgpu::TextureFormat::ASTC10x5Unorm: return "ASTC10x5Unorm";
case wgpu::TextureFormat::ASTC10x5UnormSrgb: return "ASTC10x5UnormSrgb";
case wgpu::TextureFormat::ASTC10x6Unorm: return "ASTC10x6Unorm";
case wgpu::TextureFormat::ASTC10x6UnormSrgb: return "ASTC10x6UnormSrgb";
case wgpu::TextureFormat::ASTC10x8Unorm: return "ASTC10x8Unorm";
case wgpu::TextureFormat::ASTC10x8UnormSrgb: return "ASTC10x8UnormSrgb";
case wgpu::TextureFormat::ASTC10x10Unorm: return "ASTC10x10Unorm";
case wgpu::TextureFormat::ASTC10x10UnormSrgb: return "ASTC10x10UnormSrgb";
case wgpu::TextureFormat::ASTC12x10Unorm: return "ASTC12x10Unorm";
case wgpu::TextureFormat::ASTC12x10UnormSrgb: return "ASTC12x10UnormSrgb";
case wgpu::TextureFormat::ASTC12x12Unorm: return "ASTC12x12Unorm";
case wgpu::TextureFormat::ASTC12x12UnormSrgb: return "ASTC12x12UnormSrgb";
case wgpu::TextureFormat::R16Unorm: return "R16Unorm";
case wgpu::TextureFormat::RG16Unorm: return "RG16Unorm";
case wgpu::TextureFormat::RGBA16Unorm: return "RGBA16Unorm";
case wgpu::TextureFormat::R16Snorm: return "R16Snorm";
case wgpu::TextureFormat::RG16Snorm: return "RG16Snorm";
case wgpu::TextureFormat::RGBA16Snorm: return "RGBA16Snorm";
case wgpu::TextureFormat::R8BG8Biplanar420Unorm: return "R8BG8Biplanar420Unorm";
case wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm: return "R10X6BG10X6Biplanar420Unorm";
case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm: return "R8BG8A8Triplanar420Unorm";
case wgpu::TextureFormat::R8BG8Biplanar422Unorm: return "R8BG8Biplanar422Unorm";
case wgpu::TextureFormat::R8BG8Biplanar444Unorm: return "R8BG8Biplanar444Unorm";
case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm: return "R10X6BG10X6Biplanar422Unorm";
case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm: return "R10X6BG10X6Biplanar444Unorm";
case wgpu::TextureFormat::External: return "External";
}
}
[[nodiscard]] constexpr std::string_view filamentShaderStageToString(const ShaderStage stage) {
switch (stage) {
case ShaderStage::VERTEX: return "vertex";
@@ -42,7 +42,7 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
if (capabilities.formatCount > 0 && capabilities.formats != nullptr) {
std::for_each(capabilities.formats, capabilities.formats + capabilities.formatCount,
[](wgpu::TextureFormat const format) {
FWGPU_LOGI << " " << webGPUPrintableToString(format);
FWGPU_LOGI << " " << webGPUTextureFormatToString(format);
});
}
FWGPU_LOGI << " surface present modes (" << capabilities.presentModeCount << "):";
@@ -66,7 +66,7 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config,
wgpu::TextureFormat depthFormat) {
FWGPU_LOGI << "WebGPU surface configuration:";
FWGPU_LOGI << " surface format: " << webGPUPrintableToString(config.format);
FWGPU_LOGI << " surface format: " << webGPUTextureFormatToString(config.format);
FWGPU_LOGI << " surface usage: " << webGPUPrintableToString(config.usage);
FWGPU_LOGI << " surface view formats (" << config.viewFormatCount << "):";
if (config.viewFormatCount > 0 && config.viewFormats != nullptr) {
@@ -79,7 +79,7 @@ void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config,
FWGPU_LOGI << " surface width: " << config.width;
FWGPU_LOGI << " surface height: " << config.height;
FWGPU_LOGI << " surface present mode: " << webGPUPrintableToString(config.presentMode);
FWGPU_LOGI << "WebGPU selected depth format: " << webGPUPrintableToString(depthFormat);
FWGPU_LOGI << "WebGPU selected depth format: " << webGPUTextureFormatToString(depthFormat);
}
#endif// FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
+122 -15
View File
@@ -17,6 +17,8 @@
#include "WebGPUTexture.h"
#include "WebGPUConstants.h"
#include "WebGPURenderPassMipmapGenerator.h"
#include "WebGPUStrings.h"
#include "DriverBase.h"
#include "private/backend/BackendUtils.h"
@@ -140,10 +142,13 @@ namespace {
* @param samples How many samples to use for MSAA
* @param needsComputeStorageSupport if we need to use this texture as storage binding in something
* like a compute shader
* @param needsRenderAttachmentSupport if we need to use this texture as a render pass attachment
* in something like a render pass blit (e.g. mipmap generation)
* @return The appropriate texture usage flags for the underlying texture
*/
[[nodiscard]] wgpu::TextureUsage fToWGPUTextureUsage(TextureUsage const& fUsage,
const uint8_t samples, const bool needsComputeStorageSupport) {
const uint8_t samples, const bool needsComputeStorageSupport,
const bool needsRenderAttachmentSupport) {
wgpu::TextureUsage retUsage = wgpu::TextureUsage::None;
// if needsComputeStorageSupport we need to read and write to the texture in a shader and thus
@@ -164,6 +169,16 @@ namespace {
if (needsComputeStorageSupport) {
retUsage |= wgpu::TextureUsage::StorageBinding;
}
if (needsRenderAttachmentSupport) {
retUsage |= wgpu::TextureUsage::RenderAttachment;
}
if (any(TextureUsage::BLIT_SRC & fUsage)) {
retUsage |= wgpu::TextureUsage::RenderAttachment;
}
if (any(TextureUsage::BLIT_DST & fUsage)) {
retUsage |= wgpu::TextureUsage::RenderAttachment;
retUsage |= wgpu::TextureUsage::TextureBinding;
}
// WGPU Render attachment covers either color or stencil situation dependant
// NOTE: Depth attachment isn't used this way in Vulkan but logically maps to WGPU docs. If
// issues, investigate here.
@@ -258,6 +273,26 @@ namespace {
}
}
[[nodiscard]] WebGPUTexture::MipmapGenerationStrategy determineMipmapGenerationStrategy(
const wgpu::TextureFormat format, const SamplerType samplerType, const uint8_t sampleCount,
const uint8_t mipmapLevels) {
if (mipmapLevels <= 1) {
return WebGPUTexture::MipmapGenerationStrategy::NONE; // no mipmap generation needed
}
const WebGPURenderPassMipmapGenerator::FormatCompatibility renderPassCompatibility{
WebGPURenderPassMipmapGenerator::getCompatibilityFor(format,
toWebGPUTextureDimension(samplerType), sampleCount)
};
if (renderPassCompatibility.compatible) {
return WebGPUTexture::MipmapGenerationStrategy::RENDER_PASS;
}
if (WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(format)) {
return WebGPUTexture::MipmapGenerationStrategy::SPD_COMPUTE_PASS;
}
// we don't have a way to generate mipmaps for this texture, at least at this time
return WebGPUTexture::MipmapGenerationStrategy::NONE;
}
[[nodiscard]] constexpr wgpu::Extent3D toTextureSize(const SamplerType samplerType,
const uint32_t width, const uint32_t height, const uint32_t depth) {
switch (samplerType) {
@@ -295,14 +330,17 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
wgpu::Device const& device) noexcept
: HwTexture{ samplerType, levels, samples, width, height, depth, format, usage },
mViewFormat{ fToWGPUTextureFormat(format) },
mSupportsMultipleMipLevels{ levels > 1 &&
supportsMultipleMipLevelsViaStorageBinding(mViewFormat) },
mWebGPUFormat{ mSupportsMultipleMipLevels
mMipmapGenerationStrategy{ determineMipmapGenerationStrategy(mViewFormat, samplerType,
samples, levels) },
mWebGPUFormat{ mMipmapGenerationStrategy == MipmapGenerationStrategy::SPD_COMPUTE_PASS
? storageBindingCompatibleFormatForViewFormat(mViewFormat)
: mViewFormat },
mAspect{ fToWGPUTextureViewAspect(usage, format) },
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples, mSupportsMultipleMipLevels) },
mViewUsage{ fToWGPUTextureUsage(usage, samples, false) },
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples,
mMipmapGenerationStrategy == MipmapGenerationStrategy::SPD_COMPUTE_PASS,
mMipmapGenerationStrategy == MipmapGenerationStrategy::RENDER_PASS) },
mViewUsage{ fToWGPUTextureUsage(usage, samples, false, false) },
mDimension{toWebGPUTextureViewDimension(samplerType)},
mBlockWidth{ filament::backend::getBlockWidth(format) },
mBlockHeight{ filament::backend::getBlockHeight(format) },
mDefaultMipLevel{ 0 },
@@ -352,7 +390,7 @@ WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
: HwTexture{ src->target, levelCount, src->samples, src->width, src->height, src->depth,
src->format, src->usage },
mViewFormat{ src->mViewFormat },
mSupportsMultipleMipLevels{ src->mSupportsMultipleMipLevels },
mMipmapGenerationStrategy{ src->mMipmapGenerationStrategy },
mWebGPUFormat{ src->mWebGPUFormat },
mAspect{ src->mAspect },
mWebGPUUsage{ src->mWebGPUUsage },
@@ -364,7 +402,21 @@ WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
mDefaultMipLevel{ baseLevel },
mDefaultBaseArrayLayer{ src->mArrayLayerCount },
mDefaultTextureView{ makeTextureView(mDefaultMipLevel, levelCount, 0, mDefaultBaseArrayLayer,
src->target) } {}
src->target) },
mMsaaSidecarTexture{ src->mMsaaSidecarTexture } {}
wgpu::Texture const& WebGPUTexture::getMsaaSidecarTexture(const uint8_t sampleCount) const {
if (mMsaaSidecarTexture == nullptr) {
return mMsaaSidecarTexture; // nullptr (no such sidecar)
}
FILAMENT_CHECK_PRECONDITION(sampleCount == mMsaaSidecarTexture.GetSampleCount())
<< "The MSAA sidecar texture has a different sample count ("
<< mMsaaSidecarTexture.GetSampleCount() << ") than requested (" << +sampleCount
<< "). Note that this restriction was written when WebGPU only supported msaa "
"textures with 4 samples. If that has changed, this implementation should be "
"updated (e.g. map of sidecar textures by sampleCount or something).";
return mMsaaSidecarTexture;
}
bool WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(const wgpu::TextureFormat format) {
return storageBindingCompatibleFormatForViewFormat(format) != wgpu::TextureFormat::Undefined;
@@ -379,6 +431,68 @@ wgpu::TextureView WebGPUTexture::getOrMakeTextureView(const uint8_t mipLevel,
return makeTextureView(mipLevel, 1, arrayLayer, 1, target);
}
void WebGPUTexture::createMsaaSidecarTextureIfNotAlreadyCreated(const uint8_t samples,
wgpu::Device const& device) {
FILAMENT_CHECK_PRECONDITION(samples > 1) << "Requesting to create a MSAA sidecar texture for "
<< +samples << " samples? Invalid request.";
if (mMsaaSidecarTexture) {
FILAMENT_CHECK_PRECONDITION(mMsaaSidecarTexture.GetSampleCount() == samples)
<< "An MSAA sidecar texture has already been created for this texture, but with a "
"different sample count ("
<< mMsaaSidecarTexture.GetSampleCount() << ") than requested (" << +samples
<< "). Note that this restriction was written when WebGPU only supported msaa "
"textures with 4 samples. If that has changed, this implementation should be "
"updated (e.g. map of sidecar textures by sampleCount or something).";
return; // we already have the sidecar created
}
const wgpu::TextureDescriptor descriptor{
.label = "msaa_sidecar_texture",
.usage = mTexture.GetUsage(),
.dimension = mTexture.GetDimension(),
.size = {
.width = mTexture.GetWidth(),
.height = mTexture.GetHeight(),
.depthOrArrayLayers = mTexture.GetDepthOrArrayLayers(),
},
.format = mTexture.GetFormat(),
.mipLevelCount = mTexture.GetMipLevelCount(),
.sampleCount = samples,
.viewFormatCount = 1,
.viewFormats = &mViewFormat,
};
mMsaaSidecarTexture = device.CreateTexture(&descriptor);
FILAMENT_CHECK_POSTCONDITION(mMsaaSidecarTexture) << "Failed to create MSAA sidecar texture";
}
wgpu::TextureView WebGPUTexture::makeMsaaSidecarTextureViewIfTextureSidecarExists(
const uint8_t samples, const uint8_t mipLevel, const uint32_t arrayLayer) const {
if (mMsaaSidecarTexture == nullptr) {
return nullptr;
}
FILAMENT_CHECK_PRECONDITION(mMsaaSidecarTexture.GetSampleCount() == samples)
<< "An MSAA sidecar texture has already been created for this texture, but with a "
"different sample count ("
<< mMsaaSidecarTexture.GetSampleCount() << ") than requested for view (" << +samples
<< "). Note that this restriction was written when WebGPU only supported msaa "
"textures with 4 samples. If that has changed, this implementation should be "
"updated (e.g. map of sidecar textures by sampleCount or something).";
const wgpu::TextureViewDescriptor descriptor{
.label = "msaa_sidecar_texture_view",
.format = mViewFormat,
.dimension = mDimension,
.baseMipLevel = mipLevel,
.mipLevelCount = 1,
.baseArrayLayer = arrayLayer,
.arrayLayerCount = 1,
.aspect = mAspect,
.usage = mViewUsage,
};
const wgpu::TextureView textureView{ mMsaaSidecarTexture.CreateView(&descriptor) };
FILAMENT_CHECK_POSTCONDITION(mMsaaSidecarTexture)
<< "Failed to create MSAA sidecar texture view (" << +samples << " samples)";
return textureView;
}
wgpu::TextureFormat WebGPUTexture::fToWGPUTextureFormat(TextureFormat const& fFormat) {
switch (fFormat) {
case TextureFormat::R8: return wgpu::TextureFormat::R8Unorm;
@@ -553,13 +667,6 @@ wgpu::TextureFormat WebGPUTexture::fToWGPUTextureFormat(TextureFormat const& fFo
wgpu::TextureView WebGPUTexture::makeTextureView(const uint8_t& baseLevel,
const uint8_t& levelCount, const uint32_t& baseArrayLayer, const uint32_t& arrayLayerCount,
const SamplerType samplerType) const noexcept {
#if FWGPU_ENABLED(FWGPU_DEBUG_VALIDATION)
if (baseLevel > 0 && !mSupportsMultipleMipLevels) {
FWGPU_LOGW << "Trying to make a texture view into a level ("
<< static_cast<uint32_t>(baseLevel)
<< ") for which we cannot generate mip levels.";
}
#endif
const wgpu::TextureViewDescriptor textureViewDescriptor{
.label = getUserTextureViewLabel(target),
.format = mViewFormat,
+60 -3
View File
@@ -26,8 +26,28 @@
namespace filament::backend {
[[nodiscard]] constexpr bool hasStencil(const wgpu::TextureFormat textureFormat) {
return textureFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
textureFormat == wgpu::TextureFormat::Depth32FloatStencil8 ||
textureFormat == wgpu::TextureFormat::Stencil8;
}
[[nodiscard]] constexpr bool hasDepth(const wgpu::TextureFormat textureFormat) {
return textureFormat == wgpu::TextureFormat::Depth16Unorm ||
textureFormat == wgpu::TextureFormat::Depth32Float ||
textureFormat == wgpu::TextureFormat::Depth24Plus ||
textureFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
textureFormat == wgpu::TextureFormat::Depth32FloatStencil8;
}
class WebGPUTexture : public HwTexture {
public:
enum class MipmapGenerationStrategy : uint8_t {
RENDER_PASS,
SPD_COMPUTE_PASS,
NONE,
};
WebGPUTexture(SamplerType, uint8_t levels, TextureFormat, uint8_t samples, uint32_t width,
uint32_t height, uint32_t depth, TextureUsage, wgpu::Device const&) noexcept;
@@ -41,17 +61,49 @@ public:
[[nodiscard]] wgpu::Texture const& getTexture() const { return mTexture; }
[[nodiscard]] wgpu::Texture const& getMsaaSidecarTexture(uint8_t sampleCount) const;
[[nodiscard]] wgpu::TextureView const& getDefaultTextureView() const {
return mDefaultTextureView;
}
[[nodiscard]] wgpu::TextureView getOrMakeTextureView(uint8_t mipLevel, uint32_t arrayLayer);
[[nodiscard]] wgpu::TextureViewDimension getViewDimension() const { return mDimension; }
[[nodiscard]] wgpu::TextureFormat getViewFormat() const { return mViewFormat; }
[[nodiscard]] uint32_t getArrayLayerCount() const { return mArrayLayerCount; }
[[nodiscard]] bool supportsMultipleMipLevels() const { return mSupportsMultipleMipLevels; }
[[nodiscard]] MipmapGenerationStrategy getMipmapGenerationStrategy() const {
return mMipmapGenerationStrategy;
}
/**
* Creates the MSAA sidecar texture if it has not already been created.
* If the sidecar already exists with a different number of samples this will panic
* (at the time of writing this WebGPU only supports MSAA with 4 samples. If this changes, then
* multiple sidecars with different number of samples could be supported if needed, but that
* level of complexity is not warranted at this time).
* Additionally, if samples is <= 1 this will panic as well, as that is not an MSAA texture.
* @param samples The number of samples the texture will have
*/
void createMsaaSidecarTextureIfNotAlreadyCreated(uint8_t samples, wgpu::Device const&);
/**
* @param samples The number of samples the underlying texture supports
* @param mipLevel The mip level into the underyling texture for which this view will reference
* (this view will only have one mip level)
* @param arrayLayer The layer into the underyling texture for which this view will reference
* (this view will only have one layer)
* @return A texture view for the MSAA sidecar texture
*/
wgpu::TextureView makeMsaaSidecarTextureViewIfTextureSidecarExists(uint8_t samples,
uint8_t mipLevel, uint32_t arrayLayer) const;
/**
* @return nullptr if a MSAA sidecar texture is not appliable, otherwise a view to one
*/
[[nodiscard]] wgpu::TextureView makeMsaaSidecarTextureView(wgpu::Texture const&, uint8_t mipLevel, uint32_t arrayLayer) const;
[[nodiscard]] static wgpu::TextureFormat fToWGPUTextureFormat(
filament::backend::TextureFormat const& fFormat);
@@ -71,8 +123,7 @@ private:
// certain formats (e.g. compute shaders), but we need a view to that format elsewhere
// (e.g. another shader or render target)
wgpu::TextureFormat mViewFormat = wgpu::TextureFormat::Undefined;
// this is true IFF levels > 1 AND we can actually generate mipmaps for the texture
bool mSupportsMultipleMipLevels = false;
MipmapGenerationStrategy mMipmapGenerationStrategy = MipmapGenerationStrategy::NONE;
// format is inherited from HwTexture. This naming is to distinguish it from Filament's format
// this is the underlying texture's format, not necessarily the "view" of it... see mViewFormat
wgpu::TextureFormat mWebGPUFormat = wgpu::TextureFormat::Undefined;
@@ -80,6 +131,7 @@ private:
// usage is inherited from HwTexture. This naming is to distinguish it from Filament's usage
wgpu::TextureUsage mWebGPUUsage = wgpu::TextureUsage::None;
wgpu::TextureUsage mViewUsage = wgpu::TextureUsage::None;
wgpu::TextureViewDimension mDimension = wgpu::TextureViewDimension::Undefined;
size_t mBlockWidth = 0;
size_t mBlockHeight = 0;
uint32_t mArrayLayerCount = 1;
@@ -87,6 +139,11 @@ private:
uint32_t mDefaultMipLevel = 0;
uint32_t mDefaultBaseArrayLayer = 0;
wgpu::TextureView mDefaultTextureView = nullptr;
// At the time of writing this, WebGPU only supported 4 samples in a multi-sampled texture.
// If that has changed, then consider updating the implementation to have a map of msaa textures
// by sampleCount or something like that.
// For now that complexity and cost is not warranted due to WebGPU's restrictions.
wgpu::Texture mMsaaSidecarTexture = nullptr;
[[nodiscard]] wgpu::TextureView makeTextureView(const uint8_t& baseLevel,
const uint8_t& levelCount, const uint32_t& baseArrayLayer,
@@ -56,10 +56,8 @@ namespace {
// The SPD Algorithm can make use of up to 12 storage texture attachments
constexpr uint32_t MAX_MIPMAP_STORAGE_TEXTURES_PER_STAGE = 12u;
constexpr std::array REQUIRED_FEATURES = { wgpu::FeatureName::TransientAttachments,
/*To make filtering assumptions like we want while waiting for Filament to provide that info,
float 32 needs to be filterable*/
wgpu::FeatureName::Float32Filterable,
constexpr std::array REQUIRED_FEATURES = {
wgpu::FeatureName::TransientAttachments,
// Qualcomm 500 and 600 GPUs do not support this so it is not part of core webgpu spec. To
// support such devices, we will either need Filament to not attempt this, or find another
// workaround. https://github.com/gpuweb/gpuweb/issues/2648
+6
View File
@@ -190,6 +190,7 @@ public:
using FeatureLevel = backend::FeatureLevel;
using StereoscopicType = backend::StereoscopicType;
using Driver = backend::Driver;
using GpuContextPriority = backend::Platform::GpuContextPriority;
/**
* Config is used to define the memory footprint used by the engine, such as the
@@ -410,6 +411,11 @@ public:
* @deprecated use "backend.opengl.assert_native_window_is_valid" feature flag instead
*/
bool assertNativeWindowIsValid = false;
/**
* GPU context priority level. Controls GPU work scheduling and preemption.
*/
GpuContextPriority gpuContextPriority = GpuContextPriority::DEFAULT;
};
+2 -1
View File
@@ -57,7 +57,8 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI {
public:
using CullingMode = backend::CullingMode;
using TransparencyMode = TransparencyMode;
// ReSharper disable once CppRedundantQualifier
using TransparencyMode = filament::TransparencyMode;
using DepthFunc = backend::SamplerCompareFunc;
using StencilCompareFunc = backend::SamplerCompareFunc;
using StencilOperation = backend::StencilOperation;
+11 -18
View File
@@ -2989,7 +2989,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscale(FrameGraph& fg, bool
}
if (dsrOptions.quality == QualityLevel::LOW) {
return upscaleBilinear(fg, translucent, dsrOptions, input, vp, outDesc, filter);
return upscaleBilinear(fg, dsrOptions, input, vp, outDesc, filter);
}
if (dsrOptions.quality == QualityLevel::MEDIUM) {
return upscaleSGSR1(fg, sourceHasLuminance, dsrOptions, input, vp, outDesc);
@@ -2997,7 +2997,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscale(FrameGraph& fg, bool
return upscaleFSR1(fg, dsrOptions, input, vp, outDesc);
}
FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph& fg, bool translucent,
FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph& fg,
DynamicResolutionOptions dsrOptions, FrameGraphId<FrameGraphTexture> const input,
filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
SamplerMagFilter filter) noexcept {
@@ -3016,18 +3016,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
.attachments = { .color = { data.output } },
.clearFlags = TargetBufferFlags::DEPTH });
},
[this, vp, translucent, filter](FrameGraphResources const& resources,
[this, vp, filter](FrameGraphResources const& resources,
auto const& data, DriverApi& driver) {
bindPostProcessDescriptorSet(driver);
// helper to enable blending
auto enableTranslucentBlending = [](PipelineState& pipeline) {
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_ALPHA;
pipeline.rasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_ALPHA;
};
auto color = resources.getTexture(data.input);
auto const& inputDesc = resources.getDescriptor(data.input);
@@ -3055,9 +3047,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
auto out = resources.getRenderPassInfo();
auto pipeline = getPipelineState(material.getMaterial(mEngine));
if (translucent) {
enableTranslucentBlending(pipeline);
}
renderFullScreenQuad(out, pipeline, driver);
});
@@ -3065,7 +3054,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
// if we had to take the low quality fallback, we still do the "sharpen pass"
if (dsrOptions.sharpness > 0.0f) {
output = rcas(fg, dsrOptions.sharpness, output, outDesc, translucent);
output = rcas(fg, dsrOptions.sharpness, output, outDesc,
false /* translucent=false, because dst buffer is allocated */);
}
// we rely on automatic culling of unused render passes
@@ -3443,9 +3433,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
return input;
}
// we currently don't support stencil resolve
assert_invariant(!isStencilFormat(inDesc.format));
// The Metal / Vulkan backends currently don't support depth/stencil resolve.
if (isDepthFormat(inDesc.format) && (!mEngine.getDriverApi().isDepthStencilResolveSupported())) {
return resolveDepth(fg, outputBufferName, input, outDesc);
@@ -3463,6 +3450,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
auto const& ppResolve = fg.addPass<ResolveData>("resolve",
[&](FrameGraph::Builder& builder, auto& data) {
// we currently don't support stencil resolve.
assert_invariant(!isStencilFormat(inDesc.format));
data.input = builder.read(input, FrameGraphTexture::Usage::BLIT_SRC);
data.output = builder.createTexture(outputBufferName, outDesc);
data.output = builder.write(data.output, FrameGraphTexture::Usage::BLIT_DST);
@@ -3514,6 +3504,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolveDepth(FrameGraph& fg,
auto const& ppResolve = fg.addPass<ResolveData>("resolveDepth",
[&](FrameGraph::Builder& builder, auto& data) {
// we currently don't support stencil resolve
assert_invariant(!isStencilFormat(inDesc.format));
data.input = builder.sample(input);
data.output = builder.createTexture(outputBufferName, outDesc);
data.output = builder.write(data.output, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
+1 -1
View File
@@ -257,7 +257,7 @@ public:
FrameGraphId<FrameGraphTexture> input, Viewport const& vp,
FrameGraphTexture::Descriptor const& outDesc, backend::SamplerMagFilter filter) noexcept;
FrameGraphId<FrameGraphTexture> upscaleBilinear(FrameGraph& fg, bool translucent,
FrameGraphId<FrameGraphTexture> upscaleBilinear(FrameGraph& fg,
DynamicResolutionOptions dsrOptions, FrameGraphId<FrameGraphTexture> input,
Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
backend::SamplerMagFilter filter) noexcept;
+1 -4
View File
@@ -82,8 +82,6 @@ RenderPassBuilder& RenderPassBuilder::customCommand(
RenderPass RenderPassBuilder::build(FEngine const& engine, DriverApi& driver) const {
assert_invariant(mRenderableSoa);
assert_invariant(mScissorViewport.width <= std::numeric_limits<int32_t>::max());
assert_invariant(mScissorViewport.height <= std::numeric_limits<int32_t>::max());
return RenderPass{ engine, driver, *this };
}
@@ -107,8 +105,7 @@ void RenderPass::DescriptorSetHandleDeleter::operator()(
RenderPass::RenderPass(FEngine const& engine, DriverApi& driver,
RenderPassBuilder const& builder) noexcept
: mRenderableSoa(*builder.mRenderableSoa),
mColorPassDescriptorSet(builder.mColorPassDescriptorSet),
mScissorViewport(builder.mScissorViewport) {
mColorPassDescriptorSet(builder.mColorPassDescriptorSet) {
// compute the number of commands we need
updateSummedPrimitiveCounts(
+7 -9
View File
@@ -306,6 +306,12 @@ public:
// allocated commands ARE NOT freed, they're owned by the Arena
~RenderPass() noexcept;
// Specifies the viewport for the scissor rectangle, that is, the final scissor rect is
// offset by the viewport's left-top and clipped to the viewport's width/height.
void setScissorViewport(backend::Viewport const viewport) noexcept {
mScissorViewport = viewport;
}
Command const* begin() const noexcept { return mCommandBegin; }
Command const* end() const noexcept { return mCommandEnd; }
bool empty() const noexcept { return begin() == end(); }
@@ -461,7 +467,7 @@ private:
FScene::RenderableSoa const& mRenderableSoa;
ColorPassDescriptorSet const* const mColorPassDescriptorSet;
backend::Viewport const mScissorViewport{ 0, 0, INT32_MAX, INT32_MAX };
backend::Viewport mScissorViewport{ 0, 0, INT32_MAX, INT32_MAX };
Command const* /* const */ mCommandBegin = nullptr; // Pointer to the first command
Command const* /* const */ mCommandEnd = nullptr; // Pointer to one past the last command
mutable BufferObjectSharedHandle mInstancedUboHandle; // ubo for instanced primitives
@@ -476,7 +482,6 @@ class RenderPassBuilder {
RenderPass::Arena& mArena;
RenderPass::CommandTypeFlags mCommandTypeFlags{};
backend::Viewport mScissorViewport{ 0, 0, INT32_MAX, INT32_MAX };
FScene::RenderableSoa const* mRenderableSoa = nullptr;
utils::Range<uint32_t> mVisibleRenderables{};
math::float3 mCameraPosition{};
@@ -507,13 +512,6 @@ public:
return *this;
}
// Specifies the viewport for the scissor rectangle, that is, the final scissor rect is
// offset by the viewport's left-top and clipped to the viewport's width/height.
RenderPassBuilder& scissorViewport(backend::Viewport const viewport) noexcept {
mScissorViewport = viewport;
return *this;
}
// specifies the geometry to generate commands for
RenderPassBuilder& geometry(
FScene::RenderableSoa const& soa, utils::Range<uint32_t> const vr) noexcept {
+73 -67
View File
@@ -40,7 +40,6 @@
#include <utils/Panic.h>
#include <algorithm>
#include <optional>
#include <utility>
#include <stddef.h>
@@ -270,87 +269,94 @@ RendererUtils::ColorPassOutput RendererUtils::colorPass(
};
}
std::optional<RendererUtils::ColorPassOutput> RendererUtils::refractionPass(
RenderPass::Command const* RendererUtils::getFirstRefractionCommand(
RenderPass const& pass) noexcept {
// find the first refractive object in channel 2
RenderPass::Command const* const refraction = std::partition_point(pass.begin(), pass.end(),
[](auto const& command) {
constexpr uint64_t mask = RenderPass::CHANNEL_MASK | RenderPass::PASS_MASK;
constexpr uint64_t channel = uint64_t(RenderableManager::Builder::DEFAULT_CHANNEL) << RenderPass::CHANNEL_SHIFT;
constexpr uint64_t value = channel | uint64_t(RenderPass::Pass::REFRACT);
return (command.key & mask) < value;
});
const bool hasScreenSpaceRefraction =
(refraction->key & RenderPass::PASS_MASK) == uint64_t(RenderPass::Pass::REFRACT);
return hasScreenSpaceRefraction ? refraction : nullptr;
}
RendererUtils::ColorPassOutput RendererUtils::refractionPass(
FrameGraph& fg, FEngine& engine, FView const& view,
ColorPassInput colorPassInput,
ColorPassConfig config,
PostProcessManager::ScreenSpaceRefConfig const& ssrConfig,
PostProcessManager::ColorGradingConfig const colorGradingConfig,
RenderPass const& pass) noexcept {
RenderPass const& pass, RenderPass::Command const* const firstRefractionCommand) noexcept {
// find the first refractive object in channel 2
RenderPass::Command const* const refraction = std::partition_point(pass.begin(), pass.end(),
[](auto const& command) {
constexpr uint64_t mask = RenderPass::CHANNEL_MASK | RenderPass::PASS_MASK;
constexpr uint64_t channel = uint64_t(RenderableManager::Builder::DEFAULT_CHANNEL) << RenderPass::CHANNEL_SHIFT;
constexpr uint64_t value = channel | uint64_t(RenderPass::Pass::REFRACT);
return (command.key & mask) < value;
});
const bool hasScreenSpaceRefraction =
(refraction->key & RenderPass::PASS_MASK) == uint64_t(RenderPass::Pass::REFRACT);
assert_invariant(firstRefractionCommand);
RenderPass::Command const* const refraction = firstRefractionCommand;
// if there wasn't any refractive object, just skip everything below.
if (UTILS_UNLIKELY(hasScreenSpaceRefraction)) {
assert_invariant(!colorPassInput.linearColor);
assert_invariant(!colorPassInput.depth);
config.hasScreenSpaceReflectionsOrRefractions = true;
assert_invariant(!colorPassInput.linearColor);
assert_invariant(!colorPassInput.depth);
config.hasScreenSpaceReflectionsOrRefractions = true;
PostProcessManager& ppm = engine.getPostProcessManager();
auto const opaquePassOutput = colorPass(fg,
"Color Pass (opaque)", engine, view, colorPassInput, {
// When rendering the opaques, we need to conserve the sample buffer,
// so create a config that specifies the sample count.
.width = config.physicalViewport.width,
.height = config.physicalViewport.height,
.samples = config.msaa,
.format = config.hdrFormat
},
config, { .asSubpass = false, .customResolve = false },
pass.getExecutor(pass.begin(), refraction));
PostProcessManager& ppm = engine.getPostProcessManager();
auto const opaquePassOutput = colorPass(fg,
"Color Pass (opaque)", engine, view, colorPassInput, {
// When rendering the opaques, we need to conserve the sample buffer,
// so create a config that specifies the sample count.
.width = config.physicalViewport.width,
.height = config.physicalViewport.height,
.samples = config.msaa,
.format = config.hdrFormat
},
config, { .asSubpass = false, .customResolve = false },
pass.getExecutor(pass.begin(), refraction));
// Generate the mipmap chain
// Note: we can run some post-processing effects while the "color pass" descriptor set
// in bound because only the descriptor 0 (frame uniforms) matters, and it's
// present in both.
PostProcessManager::generateMipmapSSR(ppm, fg,
opaquePassOutput.linearColor,
ssrConfig.refraction,
true, ssrConfig);
// Generate the mipmap chain
// Note: we can run some post-processing effects while the "color pass" descriptor set
// in bound because only the descriptor 0 (frame uniforms) matters, and it's
// present in both.
PostProcessManager::generateMipmapSSR(ppm, fg,
opaquePassOutput.linearColor,
ssrConfig.refraction,
true, ssrConfig);
// Now we're doing the refraction pass proper.
// This uses the same framebuffer (color and depth) used by the opaque pass.
// For this reason, the `colorBufferDesc` parameter of colorPass() below is only used for
// the width and height.
colorPassInput.linearColor = opaquePassOutput.linearColor;
colorPassInput.depth = opaquePassOutput.depth;
// Now we're doing the refraction pass proper.
// This uses the same framebuffer (color and depth) used by the opaque pass.
// For this reason, the `colorBufferDesc` parameter of colorPass() below is only used for
// the width and height.
colorPassInput.linearColor = opaquePassOutput.linearColor;
colorPassInput.depth = opaquePassOutput.depth;
// Since we're reusing the existing target we don't want to clear any of its buffer.
// Important: if this target ended up being an imported target, then the clearFlags
// specified here wouldn't apply (the clearFlags of the imported target take precedence),
// and we'd end up clearing the opaque pass. This scenario never happens because it is
// prevented in Renderer.cpp's final blit.
config.clearFlags = TargetBufferFlags::NONE;
auto transparentPassOutput = colorPass(fg, "Color Pass (transparent)",
engine, view, colorPassInput, {
.width = config.physicalViewport.width,
.height = config.physicalViewport.height },
config, colorGradingConfig,
pass.getExecutor(refraction, pass.end()));
// Since we're reusing the existing target we don't want to clear any of its buffer.
// Important: if this target ended up being an imported target, then the clearFlags
// specified here wouldn't apply (the clearFlags of the imported target take precedence),
// and we'd end up clearing the opaque pass. This scenario never happens because it is
// prevented in Renderer.cpp's final blit.
config.clearFlags = TargetBufferFlags::NONE;
auto transparentPassOutput = colorPass(fg, "Color Pass (transparent)",
engine, view, colorPassInput, {
.width = config.physicalViewport.width,
.height = config.physicalViewport.height },
config, colorGradingConfig,
pass.getExecutor(refraction, pass.end()));
if (config.msaa > 1 && !colorGradingConfig.asSubpass) {
// We need to do a resolve here because later passes (such as color grading or DoF) will
// need to sample from 'output'. However, because we have MSAA, we know we're not
// sampleable. And this is because in the SSR case, we had to use a renderbuffer to
// conserve the multi-sample buffer.
transparentPassOutput.linearColor = ppm.resolve(fg, "Resolved Color Buffer",
transparentPassOutput.linearColor, { .levels = 1 });
}
return transparentPassOutput;
if (config.msaa > 1 && !colorGradingConfig.asSubpass) {
// We need to do a resolve here because later passes (such as color grading or DoF) will
// need to sample from 'output'. However, because we have MSAA, we know we're not
// sampleable. And this is because in the SSR case, we had to use a renderbuffer to
// conserve the multi-sample buffer.
transparentPassOutput.linearColor = ppm.resolve(fg, "Resolved Color Buffer",
transparentPassOutput.linearColor, { .levels = 1 });
}
return std::nullopt;
return transparentPassOutput;
}
UTILS_NOINLINE
+9 -6
View File
@@ -26,18 +26,19 @@
#include <filament/Viewport.h>
#include <backend/DriverEnums.h>
#include <backend/PixelBufferDescriptor.h>
#include <backend/Handle.h>
#include <math/vec2.h>
#include <math/vec4.h>
#include <stdint.h>
#include <optional>
#include <utility>
namespace filament {
namespace backend {
class PixelBufferDescriptor;
}
class FRenderTarget;
class FrameGraph;
class FrameGraph;
@@ -99,18 +100,20 @@ public:
PostProcessManager::ColorGradingConfig colorGradingConfig,
RenderPass::Executor passExecutor) noexcept;
static std::optional<ColorPassOutput> refractionPass(
static ColorPassOutput refractionPass(
FrameGraph& fg, FEngine& engine, FView const& view,
ColorPassInput colorPassInput,
ColorPassConfig config,
PostProcessManager::ScreenSpaceRefConfig const& ssrConfig,
PostProcessManager::ColorGradingConfig colorGradingConfig,
RenderPass const& pass) noexcept;
RenderPass const& pass, RenderPass::Command const* firstRefractionCommand) noexcept;
static void readPixels(backend::DriverApi& driver,
backend::Handle<backend::HwRenderTarget> renderTargetHandle,
uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
backend::PixelBufferDescriptor&& buffer);
static RenderPass::Command const* getFirstRefractionCommand(RenderPass const& pass) noexcept;
};
} // namespace filament
+4 -2
View File
@@ -141,6 +141,7 @@ Engine* FEngine::create(Builder const& builder) {
.stereoscopicType = instance->getConfig().stereoscopicType,
.assertNativeWindowIsValid = instance->features.backend.opengl.assert_native_window_is_valid,
.metalDisablePanicOnDrawableFailure = instance->getConfig().metalDisablePanicOnDrawableFailure,
.gpuContextPriority = instance->getConfig().gpuContextPriority,
};
instance->mDriver = platform->createDriver(sharedContext, driverConfig);
@@ -761,6 +762,7 @@ int FEngine::loop() {
.stereoscopicType = mConfig.stereoscopicType,
.assertNativeWindowIsValid = features.backend.opengl.assert_native_window_is_valid,
.metalDisablePanicOnDrawableFailure = mConfig.metalDisablePanicOnDrawableFailure,
.gpuContextPriority = mConfig.gpuContextPriority,
};
mDriver = mPlatform->createDriver(mSharedGLContext, driverConfig);
@@ -822,9 +824,9 @@ int FEngine::loop() {
#if FILAMENT_ENABLE_MATDBG
if(debug.server) {
delete debug.server;
}
}
#endif
#if FILAMENT_ENABLE_FGVIEWER
#if FILAMENT_ENABLE_FGVIEWER
if(debug.fgviewerServer) {
delete debug.fgviewerServer;
}
+19 -4
View File
@@ -98,6 +98,14 @@ namespace filament::fgviewer {
} // namespace filament::fgviewer
#endif
// We have added correctness assertions that breaks clients' projects. We add this define to allow
// for the client's to address these assertions at a more gradual pace.
#if defined(FILAMENT_RELAXED_CORRECTNESS_ASSERTIONS)
#define CORRECTNESS_ASSERTION_DEFAULT false
#else
#define CORRECTNESS_ASSERTION_DEFAULT true
#endif
namespace filament {
class Renderer;
@@ -715,10 +723,14 @@ public:
bool use_shadow_atlas = false;
} shadows;
struct {
// TODO: default the following two flags to true.
bool assert_material_instance_in_use = false;
bool assert_destroy_material_before_material_instance = false;
bool assert_vertex_buffer_count_exceeds_8 = false;
// TODO: clean-up the following flags (equivalent to setting them to true) when
// clients have addressed their usages.
bool assert_material_instance_in_use = CORRECTNESS_ASSERTION_DEFAULT;
bool assert_destroy_material_before_material_instance =
CORRECTNESS_ASSERTION_DEFAULT;
bool assert_vertex_buffer_count_exceeds_8 = CORRECTNESS_ASSERTION_DEFAULT;
bool assert_vertex_buffer_attribute_stride_mult_of_4 =
CORRECTNESS_ASSERTION_DEFAULT;
} debug;
} engine;
struct {
@@ -759,6 +771,9 @@ public:
{ "features.engine.debug.assert_vertex_buffer_count_exceeds_8",
"Assert when a client's number of buffers for a VertexBuffer exceeds 8.",
&features.engine.debug.assert_vertex_buffer_count_exceeds_8, false },
{ "features.engine.debug.assert_vertex_buffer_attribute_stride_mult_of_4",
"Assert that the attribute stride of a vertex buffer is a multiple of 4.",
&features.engine.debug.assert_vertex_buffer_attribute_stride_mult_of_4, false },
}};
utils::Slice<const FeatureFlag> getFeatureFlags() const noexcept {
+31 -18
View File
@@ -1125,16 +1125,6 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
// --------------------------------------------------------------------------------------------
// Color passes
// this makes the viewport relative to xvp
// FIXME: we should use 'vp' when rendering directly into the swapchain, but that's hard to
// know at this point. This will usually be the case when post-process is disabled.
// FIXME: we probably should take the dynamic scaling into account too
// if MSAA is enabled, we end-up rendering in an intermediate buffer. This is the only case where
// "!hasPostProcess" doesn't guarantee rendering into the swapchain.
const bool useIntermediateBuffer = hasPostProcess || msaaOptions.enabled ||
(isRenderingMultiview && engine.debug.stereo.combine_multiview_images);
passBuilder.scissorViewport(useIntermediateBuffer ? xvp : vp);
// This one doesn't need to be a FrameGraph pass because it always happens by construction
// (i.e. it won't be culled, unless everything is culled), so no need to complexify things.
passBuilder.variant(variant);
@@ -1174,8 +1164,35 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
passBuilder.renderFlags(renderFlags);
}
// create the pass, which generates all its commands (this is a heavy operation)
RenderPass const pass{ passBuilder.build(engine, driver) };
// now that we have the commands we can figure out if we have refraction commands
auto* const firstRefractionCommand = [&view](RenderPass const& pass) {
RenderPass::Command const* p = nullptr;
if (UTILS_UNLIKELY(view.isScreenSpaceRefractionEnabled() && !pass.empty())) {
p = RendererUtils::getFirstRefractionCommand(pass);
}
return p;
}(pass);
hasScreenSpaceRefraction = firstRefractionCommand != nullptr;
// this makes the viewport relative to xvp
// FIXME: we should use 'vp' when rendering directly into the swapchain, but that's hard to
// know at this point. This will usually be the case when post-process is disabled.
// FIXME: we probably should take the dynamic scaling into account too
// if MSAA is enabled, we end-up rendering in an intermediate buffer. This is the only case where
// "!hasPostProcess" doesn't guarantee rendering into the swapchain.
const bool useIntermediateBuffer = hasPostProcess || msaaOptions.enabled ||
ssReflectionsOptions.enabled || hasScreenSpaceRefraction ||
(isRenderingMultiview && engine.debug.stereo.
combine_multiview_images);
// this is slightly ugly, but conceptually `pass` is const; it's just that we can't set
// the scissor viewport during construction
const_cast<RenderPass&>(pass).setScissorViewport(useIntermediateBuffer ? xvp : vp);
FrameGraphTexture::Descriptor colorBufferDesc = {
.width = config.physicalViewport.width,
.height = config.physicalViewport.height,
@@ -1220,21 +1237,17 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
},
colorBufferDesc, config, colorGradingConfigForColor, pass.getExecutor());
if (view.isScreenSpaceRefractionEnabled() && !pass.empty()) {
if (UTILS_UNLIKELY(hasScreenSpaceRefraction)) {
// This cancels the colorPass() call above if refraction is active.
// The color pass + refraction + color-grading as subpass if needed
auto const output = RendererUtils::refractionPass(fg, mEngine, view, {
colorPassOutput = RendererUtils::refractionPass(fg, mEngine, view, {
.shadows = blackboard.get<FrameGraphTexture>("shadows"),
.ssao = blackboard.get<FrameGraphTexture>("ssao"),
.ssr = ssrConfig.ssr,
.structure = structure
},
config, ssrConfig, colorGradingConfigForColor, pass);
hasScreenSpaceRefraction = output.has_value();
if (hasScreenSpaceRefraction) {
colorPassOutput = output.value();
}
config, ssrConfig, colorGradingConfigForColor,
pass, firstRefractionCommand);
}
if (colorGradingConfig.customResolve) {
+34 -15
View File
@@ -386,8 +386,9 @@ void FTexture::setImage(FEngine& engine, size_t const level,
FILAMENT_CHECK_PRECONDITION(any(mUsage & Texture::Usage::UPLOADABLE))
<< "Texture is not uploadable.";
FILAMENT_CHECK_PRECONDITION(mSampleCount <= 1) << "Operation not supported with multisample ("
<< unsigned(mSampleCount) << ") texture.";
FILAMENT_CHECK_PRECONDITION(mSampleCount <= 1)
<< "Operation not supported with multisample ("
<< unsigned(mSampleCount) << ") texture.";
FILAMENT_CHECK_PRECONDITION(xoffset + width <= valueForLevel(level, mWidth))
<< "xoffset (" << unsigned(xoffset) << ") + width (" << unsigned(width)
@@ -428,28 +429,46 @@ void FTexture::setImage(FEngine& engine, size_t const level,
<< ") > texture depth (" << effectiveTextureDepthOrLayers << ") at level ("
<< unsigned(level) << ")";
if (UTILS_UNLIKELY(!width || !height || !depth)) {
// The operation is a no-op, return immediately. The PixelBufferDescriptor callback
// should be called automatically when the object is destroyed.
// The precondition check below assumes width, height, depth non null.
return;
}
if (p.type != PixelDataType::COMPRESSED) {
using PBD = PixelBufferDescriptor;
size_t const stride = p.stride ? p.stride : width;
size_t const bpp = PBD::computePixelSize(p.format, p.type);
size_t const bpp = PBD::computeDataSize(p.format, p.type, 1, 1, 1);
size_t const bpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment);
size_t const pbdWidth = stride;
// TODO: PBD should have a "layer stride" (using "depth" as a substitute).
size_t const pbdDepth = depth;
size_t const pbdHeight = p.size / bpr / pbdDepth;
assert_invariant(p.size % bpr == 0 && (p.size / bpr) % pbdDepth == 0);
size_t const bpl = bpr * height; // TODO: PBD should have a "layer stride"
// TODO: PBD should have a p.depth (# layers to skip)
FILAMENT_CHECK_PRECONDITION(
bpp * (pbdWidth - p.left) * (pbdHeight - p.top) * (pbdDepth - 0) >=
bpp * width * height * depth)
/* Calculates the byte offset of the last pixel in a 3D sub-region. */
auto const calculateLastPixelOffset = [bpp, bpr, bpl](
size_t xoff, size_t yoff, size_t zoff,
size_t width, size_t height, size_t depth) {
// The 0-indexed coordinates of the last pixel are:
// x = xoff + width - 1
// y = yoff + height - 1
// z = zoff + depth - 1
// The offset is calculated as: (z * bpl) + (y * bpr) + (x * bpp)
return ((zoff + depth - 1) * bpl) +
((yoff + height - 1) * bpr) +
((xoff + width - 1) * bpp);
};
size_t const lastPixelOffset = calculateLastPixelOffset(
p.left, p.top, 0, width, height, depth);
// make sure the whole last pixel is in the buffer
FILAMENT_CHECK_PRECONDITION(lastPixelOffset + bpp <= p.size)
<< "buffer overflow: (size=" << size_t(p.size) << ", stride=" << size_t(p.stride)
<< ", left=" << unsigned(p.left) << ", top=" << unsigned(p.top)
<< ") smaller than specified region "
"{{"
<< unsigned(xoffset) << "," << unsigned(yoffset) << "," << unsigned(zoffset)
<< "},{" << unsigned(width) << "," << unsigned(height) << "," << unsigned(depth)
<< ")}}";
<< unsigned(xoffset) << "," << unsigned(yoffset) << "," << unsigned(zoffset) << "},{"
<< unsigned(width) << "," << unsigned(height) << "," << unsigned(depth) << ")}}";
}
engine.getDriverApi().update3DImage(mHandle, uint8_t(level), xoffset, yoffset, zoffset, width,
+9 -3
View File
@@ -177,9 +177,15 @@ VertexBuffer* VertexBuffer::Builder::build(Engine& engine) {
<< "attribute " << j << " offset=" << attributes[j].offset
<< " is not multiple of 4";
FILAMENT_CHECK_PRECONDITION((attributes[j].stride & 0x3u) == 0)
<< "attribute " << j << " stride=" << attributes[j].stride
<< " is not multiple of 4";
FEngine* fengine = static_cast<FEngine*>(&engine);
if (fengine->features.engine.debug.assert_vertex_buffer_attribute_stride_mult_of_4) {
FILAMENT_CHECK_PRECONDITION((attributes[j].stride & 0x3u) == 0)
<< "attribute " << j << " stride=" << attributes[j].stride
<< " is not multiple of 4";
} else if ((attributes[j].stride & 0x3u) != 0) {
LOG(WARNING) << "attribute " << j << " stride=" << attributes[j].stride
<< " is not multiple of 4";
}
if (engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0) {
FILAMENT_CHECK_PRECONDITION(!(attributes[j].flags & Attribute::FLAG_INTEGER_TARGET))
+1 -1
View File
@@ -96,7 +96,7 @@ static constexpr std::initializer_list<DescriptorSetLayoutBinding> perRenderable
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::OBJECT_UNIFORMS, DescriptorFlags::DYNAMIC_OFFSET },
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::BONES_UNIFORMS, DescriptorFlags::DYNAMIC_OFFSET },
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::MORPHING_UNIFORMS },
{ DescriptorType::SAMPLER_2D_ARRAY_FLOAT, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_POSITIONS },
{ DescriptorType::SAMPLER_2D_ARRAY_FLOAT, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_POSITIONS, DescriptorFlags::UNFILTERABLE },
{ DescriptorType::SAMPLER_2D_ARRAY_INT, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_TANGENTS },
{ DescriptorType::SAMPLER_2D_ARRAY_FLOAT, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::BONES_INDICES_AND_WEIGHTS },
};
@@ -63,6 +63,8 @@ void ShaderGenerator::generateSurfaceMaterialVariantDefines(io::sstream& out,
filament::Variant::isVSMVariant(variant));
CodeGenerator::generateDefine(out, "VARIANT_HAS_STEREO",
hasStereo(variant, featureLevel));
CodeGenerator::generateDefine(out, "VARIANT_DEPTH",
filament::Variant::isValidDepthVariant(variant));
switch (stage) {
case ShaderStage::VERTEX:
+1
View File
@@ -16,6 +16,7 @@
#include "TangentsJob.h"
#include <cstdlib>
#include <memory>
#include <geometry/SurfaceOrientation.h>
+1
View File
@@ -120,6 +120,7 @@ static bool printMaterial(ostream& text, const ChunkContainer& container) {
printFloatChunk(text, container, MaterialSpecularAntiAliasingVariance, " Variance: ");
printFloatChunk(text, container, MaterialSpecularAntiAliasingThreshold, " Threshold: ");
printChunk<bool, bool>(text, container, MaterialClearCoatIorChange, "Clear coat IOR change: ");
printChunk<bool, bool>(text, container, MaterialHasCustomDepthShader, "Has custom depth: ");
text << endl;
+7 -6
View File
@@ -14,15 +14,12 @@ def _compare_goldens(base_dir, comparison_dir, out_dir=None):
for f in all_files)
all_results = []
for test_dir in test_dirs:
results_meta = {}
results = []
output_test_dir = None if not out_dir else os.path.join(out_dir, test_dir)
output_test_dir = None if not out_dir else os.path.abspath(os.path.join(out_dir, test_dir))
if output_test_dir:
mkdir_p(output_test_dir)
base_test_dir = os.path.abspath(os.path.join(base_dir, test_dir))
comp_test_dir = os.path.abspath(os.path.join(comparison_dir, test_dir))
results_meta['base_dir'] = base_test_dir
results_meta['comparison_dir'] = comp_test_dir
for golden_file in \
glob.glob(os.path.join(base_test_dir, "*.tif")):
base_fname = os.path.abspath(golden_file)
@@ -45,8 +42,12 @@ def _compare_goldens(base_dir, comparison_dir, out_dir=None):
result['result'] = RESULT_OK
results.append(result)
if output_test_dir:
results_meta['results'] = results
output_fname = os.path.join(output_test_dir, "compare_results.json")
results_meta = {
'results': results,
'base_dir': os.path.relpath(output_fname, base_test_dir),
'comparison_dir': os.path.relpath(output_fname, comp_test_dir),
}
with open(output_fname, 'w') as f:
f.write(json.dumps(results_meta, indent=2))
important_print(f'Written comparison results for {test_dir} to \n {output_fname}')
@@ -64,7 +65,7 @@ if __name__ == '__main__':
dest = args.dest
if not dest:
print('Assume the default renderdiff output folder')
dest = os.path.join(os.getcwd(), './out/renderdiff_tests')
dest = os.path.join(os.getcwd(), './out/renderdiff')
assert os.path.exists(dest), f"Destination folder={dest} does not exist."
results = _compare_goldens(args.src, dest, out_dir=args.out)
+182 -5
View File
@@ -17,6 +17,9 @@ import sys
import flask
import pathlib
import json
import requests
import io
import zipfile
from utils import ArgParseImpl
@@ -25,14 +28,163 @@ from flask import Flask, request, make_response, send_from_directory
DIR = pathlib.Path(__file__).parent.absolute()
HTML_DIR = os.path.join(DIR, "viewer_html")
# Generated by gemini
def _download_github_artifacts(pr_number, github_token, output_dir= ".") -> None:
"""
Downloads artifacts associated with a specific GitHub Pull Request.
This function performs the following steps:
1. Fetches the details of the Pull Request to get its head commit SHA.
2. Searches for GitHub Actions workflow runs triggered by that specific commit.
3. Iterates through successful workflow runs to find and list all associated artifacts.
4. Downloads each artifact (which comes as a ZIP file).
5. Extracts the contents of each downloaded ZIP file into a unique subdirectory
within the specified output directory.
Args:
owner (str): The GitHub repository owner (e.g., "octocat").
repo (str): The GitHub repository name (e.g., "Spoon-Knife").
pr_number (int): The Pull Request number.
output_dir (str): The local directory where downloaded artifacts will be saved.
Defaults to the current directory.
"""
# Prepare HTTP headers for GitHub API requests
headers = {"Accept": "application/vnd.github.v3+json"}
if github_token:
headers["Authorization"] = f"token {github_token}"
OWNER_REPO = 'google/filament'
# --- Step 1: Get PR details to find the head commit SHA ---
print(f"Fetching details for PR #{pr_number} in {OWNER_REPO}...")
pr_url = f"https://api.github.com/repos/{OWNER_REPO}/pulls/{pr_number}"
try:
response = requests.get(pr_url, headers=headers)
response.raise_for_status() # Raise an exception for HTTP errors (4xx or 5xx)
pr_data = response.json()
commit_sha = pr_data["head"]["sha"]
print(f"PR #{pr_number} is associated with commit SHA: {commit_sha}")
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
print(f"Error: PR #{pr_number} not found in {OWNER_REPO}. Please check the PR number, owner, and repository name.")
elif e.response.status_code == 403:
print(f"Error: Access forbidden to PR #{pr_number}. You might be hitting API rate limits or need a valid GitHub Token.")
else:
print(f"An HTTP error occurred while fetching PR details: {e}")
return # Exit function on error
except requests.exceptions.RequestException as e:
print(f"A network error occurred while fetching PR details: {e}")
return # Exit function on error
# --- Step 2: Find workflow runs associated with the commit SHA ---
print(f"Searching for workflow runs for commit SHA: {commit_sha}...")
workflow_runs_url = f"https://api.github.com/repos/{OWNER_REPO}/actions/runs"
# Filter by head_sha and event='pull_request' for precision
params = {"head_sha": commit_sha, "event": "pull_request"}
try:
response = requests.get(workflow_runs_url, headers=headers, params=params)
response.raise_for_status()
runs_data = response.json()
workflow_runs = runs_data.get("workflow_runs", [])
if not workflow_runs:
print(f"No workflow runs found directly associated with PR #{pr_number} (commit SHA: {commit_sha}).")
print("This might happen if the workflow was triggered by a push after the PR was opened,")
print("or if the PR head branch was updated without triggering a new workflow run with this exact SHA.")
print("Consider checking GitHub Actions runs manually for this PR's branch on GitHub.")
return None
# Filter for runs that completed successfully
successful_runs = [run for run in workflow_runs if run.get("conclusion") == "success" and run.get("status") == "completed"]
if not successful_runs:
print(f"No *successful and completed* workflow runs found for PR #{pr_number} with commit SHA {commit_sha}. Exiting.")
return None
except requests.exceptions.HTTPError as e:
print(f"An HTTP error occurred while searching for workflow runs: {e}")
return None
except requests.exceptions.RequestException as e:
print(f"A network error occurred while searching for workflow runs: {e}")
return None
# Create the main output directory if it doesn't exist
os.makedirs(output_dir, exist_ok=True)
print(f"Ensuring output directory exists: {os.path.abspath(output_dir)}")
downloaded_any_artifact = False # Flag to track if any artifact was downloaded
# --- Step 3 & 4: List and Download Artifacts for each successful run ---
for run in successful_runs:
run_id = run["id"]
run_name = run["name"]
print(f"\nProcessing workflow run '{run_name}' (ID: {run_id})...")
artifacts_url = f"https://api.github.com/repos/{OWNER_REPO}/actions/runs/{run_id}/artifacts"
try:
response = requests.get(artifacts_url, headers=headers)
response.raise_for_status()
artifacts_data = response.json()
artifacts = artifacts_data.get("artifacts", [])
if not artifacts:
print(f" No artifacts found for workflow run ID {run_id}.")
continue # Move to the next workflow run
for artifact in artifacts:
artifact_id = artifact["id"]
artifact_name = artifact["name"]
archive_download_url = artifact["archive_download_url"]
print(f" Found artifact: '{artifact_name}' (ID: {artifact_id})")
# Perform the download request
print(f" Downloading '{artifact_name}'...")
# Use a copy of headers and specific Accept for ZIP download
download_headers = headers.copy()
download_headers["Accept"] = "application/vnd.github.v3+zip"
download_response = requests.get(archive_download_url, headers=download_headers, stream=True)
download_response.raise_for_status() # Check for errors in download
# --- Step 5: Extract the contents ---
# Use BytesIO to handle the zip file content in memory without saving to a temporary file
with io.BytesIO(download_response.content) as zip_buffer:
try:
with zipfile.ZipFile(zip_buffer, 'r') as zip_ref:
# Create a unique subdirectory for each artifact to avoid file name conflicts
extract_path = os.path.join(output_dir, f"{artifact_name}_{artifact_id}")
os.makedirs(extract_path, exist_ok=True)
zip_ref.extractall(extract_path)
print(f" Successfully extracted '{artifact_name}' to '{extract_path}/'")
downloaded_any_artifact = True
except zipfile.BadZipFile:
print(f" Error: Downloaded file for '{artifact_name}' is not a valid zip file. Skipping extraction.")
except Exception as e:
print(f" An error occurred during extraction of '{artifact_name}': {e}")
except requests.exceptions.HTTPError as e:
print(f" An HTTP error occurred while fetching artifacts for run {run_id}: {e}")
if e.response.status_code == 403:
print(" This often means you need a GitHub Personal Access Token with 'repo' scope (even for public repos for artifact downloads).")
continue # Continue processing the next workflow run
except requests.exceptions.RequestException as e:
print(f" A network error occurred while fetching artifacts for run {run_id}: {e}")
continue # Continue processing the next workflow run
if not downloaded_any_artifact:
print("\nNo artifacts were downloaded for the specified PR.")
else:
print("\nAll available artifacts have been processed.")
return 'Done'
def _create_app(config):
app = Flask(__name__)
client_config = config.copy()
base_dir = client_config['base_dir']
comparison_dir = client_config['comparison_dir']
diff_dir = client_config['diff_dir']
base_dir = os.path.join(diff_dir, client_config['base_dir'])
comparison_dir = os.path.join(diff_dir, client_config['comparison_dir'])
del client_config['base_dir']
del client_config['comparison_dir']
del client_config['diff_dir']
@@ -67,12 +219,37 @@ def _create_app(config):
if __name__ == '__main__':
PORT = 8901
parser = ArgParseImpl()
parser.add_argument('--diff', help='Diff directory', required=True)
parser.add_argument('--diff', type=str, help='Diff result directory')
parser.add_argument('--pr_number', type=str, help='Pull request artifacts to examine')
parser.add_argument('--github_token', type=str, help='Necessary for pull PR artifacts')
args, _ = parser.parse_known_args(sys.argv[1:])
with open(os.path.join(args.diff, 'compare_results.json'), 'r') as f:
if not args.diff and not args.pr_number:
print('Need to specify either a diff result directory or a Pull Request number')
exit(1)
if args.diff and args.pr_number:
print('Cannot specify both a diff result directory and a Pull Request number')
exit(1)
fdir = args.diff
if args.pr_number:
if not args.github_token:
print('Must provide --github_token to be able to download artifacts')
exit(1)
output_dir = f'/tmp/filament-pr{args.pr_number}-rdiff-result'
res = _download_github_artifacts(args.pr_number, args.github_token, output_dir)
if not res:
print('Failed to retrieve PR artifacts')
exit(1)
# TODO: Clean up the following so that we're not so specific on the paths diffs/presubmit
directory_name = list(os.listdir(output_dir))[0]
fdir = os.path.join(os.path.join(output_dir, directory_name), 'diffs/presubmit')
with open(os.path.join(fdir, 'compare_results.json'), 'r') as f:
config = json.loads(f.read())
config['diff_dir'] = os.path.abspath(args.diff)
config['diff_dir'] = os.path.abspath(fdir)
app = _create_app(config)
from waitress import serve