Compare commits
27
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22ba8875c5 | ||
|
|
843e1a43c4 | ||
|
|
98eea205f3 | ||
|
|
1c22b48893 | ||
|
|
249dd9752d | ||
|
|
bb4cd43835 | ||
|
|
954d1021c7 | ||
|
|
bf598071a3 | ||
|
|
5cbc2321c7 | ||
|
|
6bee5fb56f | ||
|
|
eb0fcc065b | ||
|
|
117535364b | ||
|
|
b15ae15f39 | ||
|
|
74445904f2 | ||
|
|
678ad4ff98 | ||
|
|
b6d7ed2b35 | ||
|
|
725e33763a | ||
|
|
f24146c169 | ||
|
|
67288d483f | ||
|
|
c80c504a2a | ||
|
|
1f8cacdd70 | ||
|
|
a3ae9431f1 | ||
|
|
3d0653604f | ||
|
|
42a0a41be0 | ||
|
|
468d9cccb7 | ||
|
|
6cfd058e3d | ||
|
|
8234549cb9 |
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Binary file not shown.
+19
-1
@@ -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)
|
||||
|
||||
@@ -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}')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"> </a><a class="target" name="overview"> </a><a class="target" name="toc2"> </a><h1>Overview</h1>
|
||||
<p>
|
||||
@@ -217,26 +217,27 @@ in <a href="#table_standardproperties" target="_self">table 1</a>.
|
||||
<div class='table'>
|
||||
<a class="target" name="table_standardproperties"> </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 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 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 14</a>.
|
||||
<a href="#table_materialparamstypes" target="_self">table 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"> </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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "TangentsJob.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
#include <geometry/SurfaceOrientation.h>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user