Compare commits

..
Author SHA1 Message Date
Powei Feng edb77fe6a9 docs: add doxygen file for DriverAPI.inc
This has been mostly auto-generated by an AI assistant. But
the documentation looks correct and can serve as a base point
for improvements.
2025-07-02 13:15:45 -07:00
Sungun Park 954d1021c7 Add #include <cstdlib> to avoid compilation error (#8922) 2025-07-02 18:54:03 +00:00
Sungun Park bf598071a3 gl: move shader code instead of copy (#8918)
In Debug mode, remove unnecessary memory reallocation & copy by moving
shader code blob.
2025-07-02 17:41:43 +00:00
Powei Feng 5cbc2321c7 Add feature flag for attribute stride check (#8913)
We also add a define to log (instead of assert) failtures of
certain correctness checks.  This allows clients to gradually
fix their code.
2025-07-02 17:06:37 +00:00
Mathias Agopian 6bee5fb56f fix PixelBufferDescriptor overflow calculation
We simply compute the coordinate of the last pixel that need to be 
accessed and deduce its offset, which must be smaller than the 
buffer size.
2025-07-01 16:08:58 -07:00
Mathias Agopian eb0fcc065b workaround gcc permissive warning. Fixes #8883. 2025-07-01 16:08:26 -07:00
Andy Hovingh 117535364b webgpu: support auto-resolve render passes (render target samples != attached texture samples)
The solution resembles that of the other backends, to implement msaa sidecar textures.

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

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

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

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

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

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

  `adb shell am start -n com.google.android.filament.gltf/.MainActivity --ez "use-static-model" true`
2025-06-26 19:15:54 +00:00
Powei Feng 42a0a41be0 webgpu: remove float filterable requirement (#8895)
This will enable webgpu for devices such as pixel 4, which does
not support this feature.
2025-06-26 18:15:41 +00:00
Powei Feng 468d9cccb7 Add Engine config for gpu priority (#8893) 2025-06-26 17:47:57 +00:00
Powei Feng 6cfd058e3d docs: fix commit hash parsing bug (#8882)
- Re-enable docs presbumit check (checks if there has been direct
    edits of /docs).
 - Fix script to download and install mdbook
 - Update Materials.md.html and re-run the build script
    (build/run.py).
2025-06-26 17:31:03 +00:00
Andy Hovingh 8234549cb9 webgpu: implement render pass based mipmap generation (supports more texture formats than compute pass based mipmap generation) 2025-06-26 08:25:23 -05:00
Benjamin Doherty c84d04254e Release Filament 1.61.2 2025-06-25 12:29:24 -07:00
Ben Doherty 573a51c430 Fix segfault in matinfo (#8896) 2025-06-25 12:25:19 -07:00
Ben Doherty b4d26bd32b Fix ASAN use-after-stack-free in VulkanPlatform (#8897) 2025-06-25 19:06:36 +00:00
Ben Doherty 5227ea36ce Fix error in PBD size check (#8886) 2025-06-25 11:49:21 -07:00
Doris Wu 6f29ff988c Fix the inconsistent comment (#8887) 2025-06-25 03:05:45 +00:00
Sungun Park 3424222d6e Add flag to control the GPU priority (#8892)
Add a knob to control the priority of the GPU work submitted to the
application. Currently it's only supported for EGL.

BUGS=[416589006]
2025-06-24 15:10:07 -07:00
Anish Goyal a9ab9b6cd4 Fix staging buffer alignment (#8872)
* Fix staging buffer alignment

Note: we were not aligning texture copies properly, so this broke
certain use cases on the Pixel 4.

* Address PR comment

Convert optional type to a simple int type with a default value, for
the alignment arg for acquiring a staging buffer.
2025-06-24 13:47:21 -07:00
Jeremy Nelson 5d11dd653a only use depthstencil for setuprenderpassattachments 2025-06-23 14:30:04 -07:00
Jeremy Nelson 7d1cb3ee85 add requestedDepth 2025-06-23 14:30:04 -07:00
Jeremy Nelson 8304cd9c32 simplified attachment logic 2025-06-23 14:30:04 -07:00
Jeremy Nelson 6d1c337363 use Target Buffer Flags for Depth and Stencil 2025-06-23 14:30:04 -07:00
rafadevaiandSerge Metral e59036c731 VK: Simple staging bypass for free uniform buffers (#8852)
* VK: Simple staging bypass for free uniform buffers

When the uniform VulkanBuffer is currently not in
use and under UMA, just perform a direct memcpy to
GPU memory.

Add a flag to control disable this flow and always
use staging to update buffers.

* Addressing review comments

Use a constant instead of a custommization option
to known if we always use staging for updates or not.

---------

Co-authored-by: Serge Metral <sergemetral@google.com>
2025-06-23 09:26:26 -07:00
Juan Caldas dd7d78b2a6 webgpu: Set Load and Store Ops (#8889) 2025-06-23 15:27:36 +00:00
Powei Feng f096c4d8de Add unfilterable to Material sampler definition (#8873)
`unfiterable : true` indicates that the sampling of the texture
will not apply filtering.

This is mainly to satisfy the webgpu requirement for
bindGroupLayouts.

 - Documentation has been updated for both `unfilterable` and
   `multisample`.
 - Internal materials have been updated where necessary (depth
   samplers have to be marked as `unfilterable`.)
 - webgpu code has been changed appropriately.

BUGS=420745987
2025-06-18 21:14:42 +00:00
Juan Caldas da5ea821a8 webgpu: Add alphaToCoverage (#8885) 2025-06-18 20:56:55 +00:00
Powei Feng 1b944a6582 webgpu: fix two things wrt linux build (#8881)
- Added an explicit link flag for libxcb in backend/CMakeLists.txt
 - Made a string func inline, otherwise we'd get duplicate
   definition error during linking.

FIXES=419788369
2025-06-18 19:58:56 +00:00
Andy Hovingh 318a9002e7 webgpu: implement fence 2025-06-18 12:47:17 -05:00
utzcoz eb5d9ac15d android: Update gradlew files to the upstream version (#8802)
Run twice:

./gradlew wrapper --gradle-version 8.8 --distribution-type bin
2025-06-18 05:22:59 +00:00
Benjamin Doherty 5cae0ee082 Switch instance of slog.w to Abseil-style logging 2025-06-17 17:00:02 -07:00
Powei Feng 75d641ae8a renderdiff: fix artifact upload (#8879)
- Fix a problem where the output directory's name  has been
   changed
- Add git commit hash to the golden directory
2025-06-17 23:20:45 +00:00
Konrad Piascik c2c744b06d webgpu: Implement driver limits 2025-06-17 09:23:40 -04:00
Konrad Piascik 1e246d1332 webgpu: Remove warning since most samples are now functional 2025-06-17 09:23:40 -04:00
Matthew Hoffman 7d242341f2 Let backend test binary run from anywhere. (#8864) 2025-06-16 20:49:05 +00:00
bridgewaterrobbie e625c7024c webgpu: fix max uniform buffer size not being set properly. This resolves a validation error seen with TransmissionSuzanne.gltf 2025-06-16 16:15:06 -04:00
bridgewaterrobbie 192a61a06b Wait for work of first frame to be done before any presenting 2025-06-16 11:21:27 -04:00
Powei Feng 6a93e3a765 vk: clean-up ycbcr conversion enums (#8859)
Moved them out of DriverEnums because only the vk backend needs
them.
2025-06-16 05:58:48 +00:00
Powei Feng e1fb1391f9 ds: emit undefined param warning once per descriptorset (#8862) 2025-06-16 05:42:45 +00:00
Sungun Park a3922ac3a9 Bump MATERIAL_VERSION to 61 2025-06-13 15:39:09 -07:00
Sungun Park 5f1112d85e Update RELEASE_NOTES for the revert 2025-06-13 15:26:56 -07:00
Sungun Park 09545690bf Revert "materials: introduce mutable spec constants (#8795)"
This reverts commit 8a1a0b0fd2.
2025-06-13 15:02:55 -07:00
Powei Feng 3ce752c47b gltfio: enable passing in variant filters (#8851)
This enables filtering variants when using the JitMaterialProvider.
2025-06-13 21:06:22 +00:00
bridgewaterrobbie 1f9f3ddb91 Use the mipmap generator to properly generate mipmaps. 2025-06-13 16:46:18 -04:00
bridgewaterrobbie 8e94688fbf Request higher maxStorageTexturesPerShaderStage if the device supports it. 2025-06-13 16:46:18 -04:00
bridgewaterrobbie 438ee40f40 Account for array layers, utilizing maxStorageTexturesPerShaderStage to configure 2025-06-13 16:46:18 -04:00
bridgewaterrobbie 7f22d733d7 webgpu: Significant cleanup of mipmap generator interface and logic. Now properly queries and uses device limits of maxMipsPerPass. Still fairly hacky though 2025-06-13 16:46:18 -04:00
Andy Hovingh 77d77f0a5c webgpu: fix logging 2025-06-13 07:23:56 -05:00
Sungun Park 0aabd3dd6a Fix: Prevent deadlock when loading programs from cache (#8849)
When a program was created directly from a cached blob in
`ShaderCompilerService`, its associated token was not signaled as ready
in the THREAD_POOL mode. This oversight caused a deadlock at the
`token->wait()` call during program destruction.

This commit resolves the issue by skipping the token's readiness check
upon destruction if the program was created from the cache blob.

BUGS=[423221474]
2025-06-12 21:05:59 +00:00
Benjamin Doherty 72a54b30f9 Temporary workaround for PlatformMetal 2025-06-12 13:23:49 -07:00
bridgewaterrobbie 00b2ab7ac4 webgpu: Use C++ converted mipmap generator. First pass, likely to be tweaked substantially in the future 2025-06-12 15:13:10 -04:00
dependabot[bot]andPowei Feng 39e15d67fc Bump brace-expansion (#8855)
Bumps the npm_and_yarn group with 1 update in the /build/common/upload-release-assets directory: [brace-expansion](https://github.com/juliangruber/brace-expansion).


Updates `brace-expansion` from 1.1.11 to 1.1.12
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Powei Feng <powei@google.com>
2025-06-12 17:55:35 +00:00
Juan Caldas 921dbddad9 webgpu: add condition to sample (#8860)
* Add option for enabling MSAA
2025-06-12 12:14:54 -04:00
Andy Hovingh 6406d4602c webgpu: texture changes to better support mipmap generation later 2025-06-11 22:35:42 -05:00
116 changed files with 5227 additions and 1079 deletions
+5 -7
View File
@@ -54,7 +54,6 @@ jobs:
build-android:
name: build-android
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -73,7 +72,6 @@ jobs:
build-ios:
name: build-iOS
runs-on: macos-14-xlarge
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -89,7 +87,6 @@ jobs:
build-web:
name: build-web
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -107,11 +104,12 @@ jobs:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- id: get_commit_msg
uses: ./.github/actions/get-commit-msg
- name: Check for manual edits to /docs
run: |
echo "${{ github.event.pull_request.head.sha }} -- ${{ github.event.pull_request.head.sha }}"
# disable for now
# bash docs_src/build/presubmit_check.sh ${{ github.event.pull_request.head.sha }}
COMMIT_ID=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | head -n 1 | sed "s/commit //g")
bash docs_src/build/presubmit_check.sh ${COMMIT_ID}
test-renderdiff:
name: test-renderdiff
@@ -138,7 +136,7 @@ jobs:
- uses: actions/upload-artifact@v4
with:
name: presubmit-renderdiff-result
path: ./out/renderdiff_tests
path: ./out/renderdiff
validate-wgsl-webgpu:
name: validate-wgsl-webgpu
+1 -1
View File
@@ -6,4 +6,4 @@ We are chaning the way Vulkan buffers are handled. We need to switch over to a m
**If you are cherry-picking a commit into an rc/ branch**: add the release note under the
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
## Release notes for next branch cut
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.61.2'
implementation 'com.google.android.filament:filament-android:1.62.0'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.61.2'
pod 'Filament', '~> 1.62.0'
```
## Documentation
+9
View File
@@ -7,6 +7,15 @@ 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**]
## v1.61.2
- samples: samples now have a CLI to select backend api
@@ -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);
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.61.2
VERSION_NAME=1.62.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
Binary file not shown.
+2 -1
View File
@@ -1,6 +1,7 @@
#Wed Nov 17 10:40:18 PST 2021
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+179 -115
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright 2015 the original author or authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,67 +17,99 @@
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -87,9 +119,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,88 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+19 -16
View File
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
@@ -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)
+7 -6
View File
@@ -204,9 +204,10 @@
"license": "Apache-2.0"
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -409,9 +410,9 @@
"integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
+5 -2
View File
@@ -1027,8 +1027,11 @@ samplerCubemap | Cubemap texture
[Table [materialParamsTypes]: Material parameter types]
Samplers
: Sampler types can also specify a `format` which can be either `int` or `float` (defaults to
`float`).
: Sampler types can have the following fields:
- `format` : which can be either `int` or `float` (defaults to `float`).
- `multisample` : a boolean to indicate whether the sampler is meant for multisampling (defaults to `false`)
- `unfilterable` : a boolean to indicate whether the sampling is not filtered (defaults to `false`)
Arrays
: A parameter can define an array of values by appending `[size]` after the type name, where
-5
View File
@@ -30,11 +30,6 @@ from utils import execute, ArgParseImpl
CUR_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT_DIR = os.path.join(CUR_DIR, '../../')
def get_last_commit_hash():
res, ret = execute('git rev-parse HEAD', cwd=ROOT_DIR)
assert res == 0, 'Failed to get the last commit hash'
return ret.strip()
def get_edited_files(commit_hash):
INSERT = '#####?????'
res, ret = execute(f'git show --name-only --pretty=%b{INSERT} {commit_hash}')
+6 -5
View File
@@ -39,13 +39,13 @@ function download_mdbook() {
fi
local CHECK_UNAME="
import sys;
parts=[a.lower() for a in sys.stdin.read().strip().split(' ')];
import sys
parts=[a.lower() for a in sys.stdin.read().strip().split(' ')]
def get_type():
if 'darwin' in parts:
if 'x86_64' in parts:
return ${DARWIN_X86}
elif 'aarch' in parts:
elif 'aarch' in parts or 'arm64' in parts:
return ${DARWIN_ARM}
elif 'linux' in parts:
if 'x86_64' in parts:
@@ -63,11 +63,12 @@ print(get_type())
echo "*** Need to install Rust ***"
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
fi
source "${HOME}/.cargo/env"
if ! (command -v cargo >/dev/null 2>&1); then
echo "*** Still cannot find `cargo` ***"
echo "*** Still cannot find cargo ***"
exit 1
fi
cargo install mdbook
cargo install --force mdbook
else
# Download prebuilts from github
mkdir -p ${HOME}/.cargo/bin
+30 -23
View File
@@ -13,7 +13,7 @@ This document is part of the [Filament project](https://github.com/google/filame
## Authors
- [Romain Guy](https://github.com/romainguy), [@romainguy](https://twitter.com/romainguy)
- [Mathias Agopian](https://github.com/pixelflinger), [@darthmoosious](https://twitter.com/darthmoosious)
- [Mathias Agopian](https://github.com/pixelflinger), [@pixelflinger](https://bsky.app/profile/pixelflinger.bsky.social)
# Overview
@@ -78,26 +78,27 @@ in table [standardProperties].
Property | Definition
-----------------------:|:---------------------
**baseColor** | Diffuse albedo for non-metallic surfaces, and specular color for metallic surfaces
**metallic** | Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1)
**roughness** | Perceived smoothness (1.0) or roughness (0.0) of a surface. Smooth surfaces exhibit sharp reflections
**metallic** | Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1)
**reflectance** | Fresnel reflectance at normal incidence for dielectric surfaces. This directly controls the strength of the reflections
**sheenColor** | Strength of the sheen layer
**sheenRoughness** | Perceived smoothness or roughness of the sheen layer
**ambientOcclusion** | Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0
**clearCoat** | Strength of the clear coat layer
**clearCoatRoughness** | Perceived smoothness or roughness of the clear coat layer
**clearCoatNormal** | A detail normal used to perturb the clear coat layer using _bump mapping_ (_normal mapping_)
**anisotropy** | Amount of anisotropy in either the tangent or bitangent direction
**anisotropyDirection** | Local surface direction in tangent space
**ambientOcclusion** | Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0
**normal** | A detail normal used to perturb the surface using _bump mapping_ (_normal mapping_)
**bentNormal** | A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality
**clearCoatNormal** | A detail normal used to perturb the clear coat layer using _bump mapping_ (_normal mapping_)
**emissive** | Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass
**postLightingColor** | Additional color that can be blended with the result of the lighting computations. See `postLightingBlending`
**ior** | Index of refraction, either for refractive objects or as an alternative to reflectance
**transmission** | Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is
**absorption** | Absorption factor for refractive objects
**microThickness** | Thickness of the thin layer of refractive objects
**thickness** | Thickness of the solid volume of refractive objects
**sheenColor** | Strength of the sheen layer
**sheenRoughness** | Perceived smoothness or roughness of the sheen layer
**emissive** | Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass
**normal** | A detail normal used to perturb the surface using _bump mapping_ (_normal mapping_)
**postLightingColor** | Additional color that can be blended with the result of the lighting computations. See `postLightingBlending`
**absorption** | Absorption factor for refractive objects
**transmission** | Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is
**ior** | Index of refraction, either for refractive objects or as an alternative to reflectance
**microThickness** | Thickness of the thin layer of refractive objects
**bentNormal** | A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality
**shadowStrength** | Strength factor between 0 and 1 for all shadows received by this material
[Table [standardProperties]: Properties of the standard model]
The type and range of each property is described in table [standardPropertiesTypes].
@@ -991,10 +992,13 @@ Type
Value
: Each entry is an object with the properties `name` and `type`, both of `string` type. The
name must be a valid GLSL identifier. Entries also have an optional `precision`, which can be
name must be a valid GLSL identifier. Entries have an optional `precision`, which can be
one of `default` (best precision for the platform, typically `high` on desktop, `medium` on
mobile), `low`, `medium`, `high`. The type must be one of the types described in
table [materialParamsTypes].
table [materialParamsTypes]. For Android external textures, entries also have an optional
transformName parameter to specify the name of the material parameter that will be
used to expose the transform matrix associated with the external sampler. In iOS and Vulkan,
this will always be identity.
Type | Description
:----------------------|:---------------------------------
@@ -1023,12 +1027,8 @@ samplerCubemap | Cubemap texture
[Table [materialParamsTypes]: Material parameter types]
Samplers
: Sampler types can specify additional options:
- `format`: either `int` or `float` (defaults to `float`).
- `stages`: array of strings containing the list of shader stages this
sampler can be accessed from. Each entry must be either `vertex` or
`fragment` (defaults to both).
: Sampler types can also specify a `format` which can be either `int` or `float` (defaults to
`float`).
Arrays
: A parameter can define an array of values by appending `[size]` after the type name, where
@@ -1273,6 +1273,9 @@ Description
when selecting any shading model that is not `unlit`. See the shader sections of this document
for more information on how to access these attributes from the shaders.
!!! Note: Interaction with custom variables
When the `color` attribute is specified, only four custom variables are available instead of five.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
material {
parameters : [
@@ -1303,7 +1306,7 @@ Type
: array of `string`
Value
: Up to 4 strings, each must be a valid GLSL identifier.
: Up to 5 strings, each must be a valid GLSL identifier.
Description
: Defines custom interpolants (or variables) that are output by the material's vertex shader.
@@ -1319,6 +1322,10 @@ Description
particular if `default` is specified the default precision is used is the fragment shader
(`mediump`) and in the vertex shader (`highp`).
!!! Warning: Interaction with required attributes
If the `color` attribute is specified in the `required` list, then only four variables can be used
instead of five.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
material {
name : Skybox,
+30 -19
View File
@@ -146,7 +146,7 @@ This document is part of the <a href="https://github.com/google/filament">Filame
<ul>
<li class="minus"><a href="https://github.com/romainguy">Romain Guy</a>, <a href="https://twitter.com/romainguy">@romainguy</a>
</li>
<li class="minus"><a href="https://github.com/pixelflinger">Mathias Agopian</a>, <a href="https://twitter.com/darthmoosious">@darthmoosious</a></li></ul>
<li class="minus"><a href="https://github.com/pixelflinger">Mathias Agopian</a>, <a href="https://bsky.app/profile/pixelflinger.bsky.social">@pixelflinger</a></li></ul>
</p>
<a class="target" name="overview">&nbsp;</a><a class="target" name="overview">&nbsp;</a><a class="target" name="toc2">&nbsp;</a><h1>Overview</h1>
<p>
@@ -217,26 +217,27 @@ in <a href="#table_standardproperties" target="_self">table&nbsp;1</a>.
<div class='table'>
<a class="target" name="table_standardproperties">&nbsp;</a><table class="table longtable"><tr><th style="text-align:right"> Property </th><th style="text-align:left"> Definition </th></tr>
<tr><td style="text-align:right"> <strong class="asterisk">baseColor</strong> </td><td style="text-align:left"> Diffuse albedo for non-metallic surfaces, and specular color for metallic surfaces </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">metallic</strong> </td><td style="text-align:left"> Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1) </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">roughness</strong> </td><td style="text-align:left"> Perceived smoothness (1.0) or roughness (0.0) of a surface. Smooth surfaces exhibit sharp reflections </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">metallic</strong> </td><td style="text-align:left"> Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1) </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">reflectance</strong> </td><td style="text-align:left"> Fresnel reflectance at normal incidence for dielectric surfaces. This directly controls the strength of the reflections </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">sheenColor</strong> </td><td style="text-align:left"> Strength of the sheen layer </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">sheenRoughness</strong> </td><td style="text-align:left"> Perceived smoothness or roughness of the sheen layer </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">ambientOcclusion</strong> </td><td style="text-align:left"> Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0 </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">clearCoat</strong> </td><td style="text-align:left"> Strength of the clear coat layer </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">clearCoatRoughness</strong> </td><td style="text-align:left"> Perceived smoothness or roughness of the clear coat layer </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">clearCoatNormal</strong> </td><td style="text-align:left"> A detail normal used to perturb the clear coat layer using <em class="underscore">bump mapping</em> (<em class="underscore">normal mapping</em>) </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">anisotropy</strong> </td><td style="text-align:left"> Amount of anisotropy in either the tangent or bitangent direction </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">anisotropyDirection</strong> </td><td style="text-align:left"> Local surface direction in tangent space </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">ambientOcclusion</strong> </td><td style="text-align:left"> Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0 </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">normal</strong> </td><td style="text-align:left"> A detail normal used to perturb the surface using <em class="underscore">bump mapping</em> (<em class="underscore">normal mapping</em>) </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">bentNormal</strong> </td><td style="text-align:left"> A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">clearCoatNormal</strong> </td><td style="text-align:left"> A detail normal used to perturb the clear coat layer using <em class="underscore">bump mapping</em> (<em class="underscore">normal mapping</em>) </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">emissive</strong> </td><td style="text-align:left"> Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">postLightingColor</strong> </td><td style="text-align:left"> Additional color that can be blended with the result of the lighting computations. See <code>postLightingBlending</code> </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">ior</strong> </td><td style="text-align:left"> Index of refraction, either for refractive objects or as an alternative to reflectance </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">transmission</strong> </td><td style="text-align:left"> Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">absorption</strong> </td><td style="text-align:left"> Absorption factor for refractive objects </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">microThickness</strong> </td><td style="text-align:left"> Thickness of the thin layer of refractive objects </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">thickness</strong> </td><td style="text-align:left"> Thickness of the solid volume of refractive objects </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">sheenColor</strong> </td><td style="text-align:left"> Strength of the sheen layer </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">sheenRoughness</strong> </td><td style="text-align:left"> Perceived smoothness or roughness of the sheen layer </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">emissive</strong> </td><td style="text-align:left"> Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">normal</strong> </td><td style="text-align:left"> A detail normal used to perturb the surface using <em class="underscore">bump mapping</em> (<em class="underscore">normal mapping</em>) </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">postLightingColor</strong> </td><td style="text-align:left"> Additional color that can be blended with the result of the lighting computations. See <code>postLightingBlending</code> </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">absorption</strong> </td><td style="text-align:left"> Absorption factor for refractive objects </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">transmission</strong> </td><td style="text-align:left"> Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">ior</strong> </td><td style="text-align:left"> Index of refraction, either for refractive objects or as an alternative to reflectance </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">microThickness</strong> </td><td style="text-align:left"> Thickness of the thin layer of refractive objects </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">bentNormal</strong> </td><td style="text-align:left"> A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality </td></tr>
<tr><td style="text-align:right"> <strong class="asterisk">shadowStrength</strong> </td><td style="text-align:left"> Strength factor between 0 and 1 for all shadows received by this material </td></tr>
</table><center><div class="tablecaption"><b style="font-style:normal;">Table&nbsp;1:</b> Properties of the standard model</div></center></div>
</p><p>
The type and range of each property is described in <a href="#table_standardpropertiestypes" target="_self">table&nbsp;2</a>.
@@ -1097,10 +1098,13 @@ non-shader data.
<dl><dt>Type</dt><dd><p> array of parameter objects
</p></dd><dt>Value</dt><dd><p> Each entry is an object with the properties <code>name</code> and <code>type</code>, both of <code>string</code> type. The
name must be a valid GLSL identifier. Entries also have an optional <code>precision</code>, which can be
name must be a valid GLSL identifier. Entries have an optional <code>precision</code>, which can be
one of <code>default</code> (best precision for the platform, typically <code>high</code> on desktop, <code>medium</code> on
mobile), <code>low</code>, <code>medium</code>, <code>high</code>. The type must be one of the types described in
<a href="#table_materialparamstypes" target="_self">table&nbsp;14</a>.
<a href="#table_materialparamstypes" target="_self">table&nbsp;14</a>. For Android external textures, entries also have an optional
transformName parameter to specify the name of the material parameter that will be
used to expose the transform matrix associated with the external sampler. In iOS and Vulkan,
this will always be identity.
</p></dd></dl><div class='table'>
<a class="target" name="table_materialparamstypes">&nbsp;</a><table class="table longtable"><tr><th style="text-align:left"> Type </th><th style="text-align:left"> Description </th></tr>
<tr><td style="text-align:left"> bool </td><td style="text-align:left"> Single boolean </td></tr>
@@ -1322,7 +1326,10 @@ non-shader data.
required and does not need to be specified. The <code>tangents</code> attribute is automatically required
when selecting any shading model that is not <code>unlit</code>. See the shader sections of this document
for more information on how to access these attributes from the shaders.
</p></dd></dl></p><pre class="listing tilde"><code><span class="line"></span>material <span class="hljs-punctuation">{</span>
</p></dd></dl><div class="admonition note"><div class="admonition-title"> Interaction with custom variables</div>
</p><p>
When the <code>color</code> attribute is specified, only four custom variables are available instead of five.</div>
</p><pre class="listing tilde"><code><span class="line"></span>material <span class="hljs-punctuation">{</span>
<span class="line"></span> parameters <span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
<span class="line"></span> <span class="hljs-punctuation">{</span>
<span class="line"></span> type <span class="hljs-punctuation">:</span> sampler2d<span class="hljs-punctuation">,</span>
@@ -1347,7 +1354,7 @@ non-shader data.
<p>
<dl><dt>Type</dt><dd><p> array of <code>string</code>
</p></dd><dt>Value</dt><dd><p> Up to 4 strings, each must be a valid GLSL identifier.
</p></dd><dt>Value</dt><dd><p> Up to 5 strings, each must be a valid GLSL identifier.
</p></dd><dt>Description</dt><dd><p> Defines custom interpolants (or variables) that are output by the material's vertex shader.
Each entry of the array defines the name of an interpolant. The full name in the fragment
shader is the name of the interpolant with the <code>variable_</code> prefix. For instance, if you
@@ -1360,7 +1367,11 @@ non-shader data.
In this case the specified precision is used as-is in both fragment and vertex stages, in
particular if <code>default</code> is specified the default precision is used is the fragment shader
(<code>mediump</code>) and in the vertex shader (<code>highp</code>).
</p></dd></dl></p><pre class="listing tilde"><code><span class="line"></span>material <span class="hljs-punctuation">{</span>
</p></dd></dl><div class="admonition warning"><div class="admonition-title"> Interaction with required attributes</div>
</p><p>
If the <code>color</code> attribute is specified in the <code>required</code> list, then only four variables can be used
instead of five.</div>
</p><pre class="listing tilde"><code><span class="line"></span>material <span class="hljs-punctuation">{</span>
<span class="line"></span> name <span class="hljs-punctuation">:</span> Skybox<span class="hljs-punctuation">,</span>
<span class="line"></span> parameters <span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
<span class="line"></span> <span class="hljs-punctuation">{</span>
+17 -1
View File
@@ -257,6 +257,7 @@ if (FILAMENT_SUPPORTS_WEBGPU)
list(APPEND SRCS
include/backend/platforms/WebGPUPlatform.h
src/webgpu/platform/WebGPUPlatform.cpp
src/webgpu/SpdMipmapGenerator/SpdMipmapGenerator.cpp
src/webgpu/WebGPUBufferBase.cpp
src/webgpu/WebGPUBufferBase.h
src/webgpu/WebGPUBufferObject.cpp
@@ -268,12 +269,18 @@ if (FILAMENT_SUPPORTS_WEBGPU)
src/webgpu/WebGPUDescriptorSetLayout.h
src/webgpu/WebGPUDriver.cpp
src/webgpu/WebGPUDriver.h
src/webgpu/WebGPUFence.cpp
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
@@ -502,11 +509,20 @@ if (FILAMENT_SUPPORTS_WEBGPU)
target_compile_definitions(${TARGET} PRIVATE $<$<BOOL:${FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING}>:FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING>)
endif()
# ==================================================================================================
# Linker flags
# ==================================================================================================
target_link_libraries(${TARGET} PRIVATE
${OSMESA_LINKER_FLAGS}
$<$<AND:$<PLATFORM_ID:Linux>,$<CONFIG:Release>>:${LINUX_LINKER_OPTIMIZATION_FLAGS}>
)
if (LINUX AND FILAMENT_SUPPORTS_WEBGPU)
# Unclear why this needs to be explicit while other linux libs do not, but it is necessary for
# WebGPU to build on linux.
target_link_libraries(${TARGET} PRIVATE -lxcb)
endif()
# ==================================================================================================
# Installation
# ==================================================================================================
+6 -112
View File
@@ -474,7 +474,12 @@ constexpr std::string_view to_string(DescriptorType type) noexcept {
enum class DescriptorFlags : uint8_t {
NONE = 0x00,
DYNAMIC_OFFSET = 0x01
// Indicate a UNIFORM_BUFFER will have dynamic offsets.
DYNAMIC_OFFSET = 0x01,
// To indicate a texture/sampler type should be unfiltered.
UNFILTERABLE = 0x02,
};
using descriptor_set_t = uint8_t;
@@ -495,18 +500,12 @@ struct DescriptorSetLayoutBinding {
DescriptorFlags flags = DescriptorFlags::NONE;
uint16_t count = 0;
// TODO: uncomment when needed. Note that this class is used as hash key. We need to ensure
// no uninitialized padding bytes.
// uint8_t externalSamplerDataIndex = EXTERNAL_SAMPLER_DATA_INDEX_UNUSED;
friend bool operator==(DescriptorSetLayoutBinding const& lhs,
DescriptorSetLayoutBinding const& rhs) noexcept {
return lhs.type == rhs.type &&
lhs.flags == rhs.flags &&
lhs.count == rhs.count &&
lhs.stageFlags == rhs.stageFlags;
// lhs.stageFlags == rhs.stageFlags &&
// lhs.externalSamplerDataIndex == rhs.externalSamplerDataIndex;
}
};
@@ -1254,26 +1253,6 @@ enum class SamplerCompareFunc : uint8_t {
N //!< Never. The depth / stencil test always fails.
};
//! this API is copied from (and only applies to) the Vulkan spec.
//! These specify YUV to RGB conversions.
enum class SamplerYcbcrModelConversion : uint8_t {
RGB_IDENTITY = 0,
YCBCR_IDENTITY = 1,
YCBCR_709 = 2,
YCBCR_601 = 3,
YCBCR_2020 = 4,
};
enum class SamplerYcbcrRange : uint8_t {
ITU_FULL = 0,
ITU_NARROW = 1,
};
enum class ChromaLocation : uint8_t {
COSITED_EVEN = 0,
MIDPOINT = 1,
};
//! Sampler parameters
struct SamplerParams { // NOLINT
SamplerMagFilter filterMag : 1; //!< magnification filter (NEAREST)
@@ -1342,94 +1321,9 @@ static_assert(sizeof(SamplerParams) == 4);
static_assert(sizeof(SamplerParams) <= sizeof(uint64_t),
"SamplerParams must be no more than 64 bits");
//! Sampler parameters
struct SamplerYcbcrConversion {// NOLINT
SamplerYcbcrModelConversion ycbcrModel : 4;
TextureSwizzle r : 4;
TextureSwizzle g : 4;
TextureSwizzle b : 4;
TextureSwizzle a : 4;
SamplerYcbcrRange ycbcrRange : 1;
ChromaLocation xChromaOffset : 1;
ChromaLocation yChromaOffset : 1;
SamplerMagFilter chromaFilter : 1;
uint8_t padding;
struct Hasher {
size_t operator()(const SamplerYcbcrConversion p) const noexcept {
// we don't use std::hash<> here, so we don't have to include <functional>
return *reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&p));
}
};
struct EqualTo {
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
assert_invariant(lhs.padding == 0);
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
return *pLhs == *pRhs;
}
};
struct LessThan {
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
assert_invariant(lhs.padding == 0);
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
return *pLhs < *pRhs;
}
};
private:
friend bool operator == (SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs)
noexcept {
return SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
}
friend bool operator != (SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs)
noexcept {
return !SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
}
friend bool operator < (SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs)
noexcept {
return SamplerYcbcrConversion::LessThan{}(lhs, rhs);
}
};
static_assert(sizeof(SamplerYcbcrConversion) == 4);
static_assert(sizeof(SamplerYcbcrConversion) <= sizeof(uint64_t),
"SamplerYcbcrConversion must be no more than 64 bits");
struct ExternalSamplerDatum {
ExternalSamplerDatum(SamplerYcbcrConversion ycbcr, SamplerParams spm, uint32_t extFmt)
: YcbcrConversion(ycbcr),
samplerParams(spm),
externalFormat(extFmt) {}
bool operator==(ExternalSamplerDatum const& rhs) const {
return (YcbcrConversion == rhs.YcbcrConversion && samplerParams == rhs.samplerParams &&
externalFormat == rhs.externalFormat);
}
struct EqualTo {
bool operator()(const ExternalSamplerDatum& lhs,
const ExternalSamplerDatum& rhs) const noexcept {
return (lhs.YcbcrConversion == rhs.YcbcrConversion &&
lhs.samplerParams == rhs.samplerParams &&
lhs.externalFormat == rhs.externalFormat);
}
};
SamplerYcbcrConversion YcbcrConversion;
SamplerParams samplerParams;
uint32_t externalFormat;
};
// No implicit padding allowed due to it being a hash key.
static_assert(sizeof(ExternalSamplerDatum) == 12);
struct DescriptorSetLayout {
std::variant<utils::StaticString, utils::CString, std::monostate> label;
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
// TODO: uncomment when needed
// utils::FixedCapacityVector<ExternalSamplerDatum> externalSamplerData;
};
//! blending equation function
@@ -107,6 +107,36 @@ public:
MULTIVIEW,
};
/**
* This controls the priority level for GPU work scheduling, which helps prioritize the
* submitted GPU work and enables preemption.
*/
enum class GpuContextPriority : uint8_t {
/**
* 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,
};
struct DriverConfig {
/**
* Size of handle arena in bytes. Setting to 0 indicates default value is to be used.
@@ -156,6 +186,13 @@ public:
* - PlatformMetal
*/
bool metalDisablePanicOnDrawableFailure = false;
/**
* GPU context priority level. Controls GPU work scheduling and preemption.
* This is only supported for:
* - PlatformEGL
*/
GpuContextPriority gpuContextPriority = GpuContextPriority::DEFAULT;
};
Platform() noexcept;
@@ -0,0 +1,764 @@
/** @file DriverAPI.dox
* @brief External documentation for the backend::Driver API.
*
* This file contains Doxygen documentation for the functions declared in DriverAPI.inc.
* This is used to keep the documentation separate from the macro-heavy header file.
*/
// General lifecycle and frame management
/** @fn Driver::tick()
* @brief Called periodically by the system to perform maintenance tasks.
*/
/** @fn Driver::beginFrame(int64_t monotonic_clock_ns, int64_t refreshIntervalNs, uint32_t frameId)
* @brief Signals the beginning of a new frame to the driver.
* @param monotonic_clock_ns The current monotonic clock time in nanoseconds.
* @param refreshIntervalNs The display's refresh interval in nanoseconds.
* @param frameId A unique identifier for the new frame.
*/
/** @fn Driver::setFrameScheduledCallback(backend::SwapChainHandle sch, backend::CallbackHandler* handler, backend::FrameScheduledCallback&& callback, uint64_t flags)
* @brief Schedules a callback to be executed when the frame associated with the given swap chain has been scheduled for presentation.
* @param sch The handle of the swap chain.
* @param handler The callback handler that will execute the callback.
* @param callback The function to be called.
* @param flags Flags to control the callback behavior.
*/
/** @fn Driver::setFrameCompletedCallback(backend::SwapChainHandle sch, backend::CallbackHandler* handler, utils::Invocable<void(void)>&& callback)
* @brief Sets a callback to be executed when rendering to the given swap chain is complete for the current frame.
* @param sch The handle of the swap chain.
* @param handler The callback handler that will execute the callback.
* @param callback The function to be called upon frame completion.
*/
/** @fn Driver::setPresentationTime(int64_t monotonic_clock_ns)
* @brief Informs the driver of the intended presentation time for the current frame.
* @param monotonic_clock_ns The desired presentation time in nanoseconds, based on the monotonic clock.
*/
/** @fn Driver::endFrame(uint32_t frameId)
* @brief Signals the end of a frame to the driver.
* @param frameId The unique identifier of the frame that is ending.
*/
/** @fn Driver::flush()
* @brief Submits all pending commands to the GPU for execution, without waiting for them to complete.
*/
/** @fn Driver::finish()
* @brief Submits all pending commands and waits for the GPU to finish executing them.
*/
/** @fn Driver::resetState()
* @brief Resets any cached or tracked driver state, forcing the driver to re-evaluate and set all states.
*/
/** @fn Driver::setDebugTag(backend::HandleBase::HandleId handleId, utils::CString tag)
* @brief Associates a user-provided string tag with a driver handle for debugging purposes.
* @param handleId The ID of the handle to tag.
* @param tag The debug string to associate with the handle.
*/
// Resource creation
/** @fn backend::VertexBufferInfoHandle Driver::createVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount, backend::AttributeArray attributes)
* @brief Creates a VertexBufferInfo object which describes the layout of a vertex buffer.
* @param bufferCount The number of buffer objects in the vertex buffer.
* @param attributeCount The number of attributes in the vertex buffer.
* @param attributes An array describing each vertex attribute.
* @return A handle to the new VertexBufferInfo object.
*/
/** @fn backend::VertexBufferHandle Driver::createVertexBuffer(uint32_t vertexCount, backend::VertexBufferInfoHandle vbih)
* @brief Creates a vertex buffer with a specified vertex count and layout.
* @param vertexCount The number of vertices in the buffer.
* @param vbih A handle to a VertexBufferInfo object describing the vertex layout.
* @return A handle to the new vertex buffer.
*/
/** @fn backend::IndexBufferHandle Driver::createIndexBuffer(backend::ElementType elementType, uint32_t indexCount, backend::BufferUsage usage)
* @brief Creates an index buffer for indexed drawing.
* @param elementType The data type of an index (e.g., USHORT, UINT).
* @param indexCount The number of indices in the buffer.
* @param usage The expected usage pattern of the buffer (e.g., STATIC, DYNAMIC).
* @return A handle to the new index buffer.
*/
/** @fn backend::BufferObjectHandle Driver::createBufferObject(uint32_t byteCount, backend::BufferObjectBinding bindingType, backend::BufferUsage usage)
* @brief Creates a generic buffer object for storing arbitrary data on the GPU.
* @param byteCount The size of the buffer in bytes.
* @param bindingType The purpose of the buffer object (e.g., VERTEX, UNIFORM).
* @param usage The expected usage pattern of the buffer.
* @return A handle to the new buffer object.
*/
/** @fn backend::TextureHandle Driver::createTexture(backend::SamplerType target, uint8_t levels, backend::TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, backend::TextureUsage usage)
* @brief Creates a new texture object.
* @param target The type of the texture (e.g. 2D, 3D, CUBEMAP).
* @param levels The number of mipmap levels.
* @param format The internal format of the texture's pixels.
* @param samples The number of samples for multisampling (1 for non-MSAA).
* @param width The width of the texture in texels.
* @param height The height of the texture in texels.
* @param depth The depth of the texture in texels (for 3D or array textures).
* @param usage A bitmask specifying the intended usage of the texture.
* @return A handle to the new texture.
*/
/** @fn backend::TextureHandle Driver::createTextureView(backend::TextureHandle texture, uint8_t baseLevel, uint8_t levelCount)
* @brief Creates a new texture view from an existing texture, allowing access to a subset of its mipmap levels.
* @param texture The handle of the original texture.
* @param baseLevel The first mipmap level to include in the view.
* @param levelCount The number of mipmap levels to include in the view.
* @return A handle to the new texture view.
*/
/** @fn backend::TextureHandle Driver::createTextureViewSwizzle(backend::TextureHandle texture, backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b, backend::TextureSwizzle a)
* @brief Creates a new texture view with remapped color channels (swizzling).
* @param texture The handle of the original texture.
* @param r The swizzle operation for the red channel.
* @param g The swizzle operation for the green channel.
* @param b The swizzle operation for the blue channel.
* @param a The swizzle operation for the alpha channel.
* @return A handle to the new texture view.
*/
/** @fn backend::TextureHandle Driver::createTextureExternalImage2(backend::SamplerType target, backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, backend::Platform::ExternalImageHandleRef image)
* @brief Creates a texture from a platform-specific external image handle.
* @param target The type of the texture.
* @param format The format of the texture's pixels.
* @param width The width of the texture.
* @param height The height of the texture.
* @param usage The expected usage of the texture.
* @param image A reference to the platform-specific external image.
* @return A handle to the new texture.
*/
/** @fn backend::TextureHandle Driver::createTextureExternalImage(backend::SamplerType target, backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, void* image)
* @brief Creates a texture from a platform-specific external image pointer.
* @param target The type of the texture.
* @param format The format of the texture's pixels.
* @param width The width of the texture.
* @param height The height of the texture.
* @param usage The expected usage of the texture.
* @param image A pointer to the external image data.
* @return A handle to the new texture.
*/
/** @fn backend::TextureHandle Driver::createTextureExternalImagePlane(backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, void* image, uint32_t plane)
* @brief Creates a texture from a single plane of a multi-planar external image.
* @param format The format of the texture's pixels.
* @param width The width of the texture.
* @param height The height of the texture.
* @param usage The expected usage of the texture.
* @param image A pointer to the external image data.
* @param plane The index of the plane to create the texture from.
* @return A handle to the new texture.
*/
/** @fn backend::TextureHandle Driver::importTexture(intptr_t id, backend::SamplerType target, uint8_t levels, backend::TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, backend::TextureUsage usage)
* @brief Imports an existing backend-native texture into Filament.
* @param id The backend-specific texture ID or handle.
* @param target The type of the texture.
* @param levels The number of mipmap levels.
* @param format The format of the texture's pixels.
* @param samples The number of samples for multisampling.
* @param width The width of the texture.
* @param height The height of the texture.
* @param depth The depth of the texture.
* @param usage The expected usage of the texture.
* @return A handle to the imported texture.
*/
/** @fn backend::RenderPrimitiveHandle Driver::createRenderPrimitive(backend::VertexBufferHandle vbh, backend::IndexBufferHandle ibh, backend::PrimitiveType pt)
* @brief Creates a render primitive, which represents a piece of geometry to be rendered.
* @param vbh A handle to a vertex buffer.
* @param ibh A handle to an index buffer.
* @param pt The type of primitive to render (e.g., TRIANGLES, LINES).
* @return A handle to the new render primitive.
*/
/** @fn backend::ProgramHandle Driver::createProgram(backend::Program&& program)
* @brief Creates a shader program from a backend-specific program object.
* @param program A Program object containing the shader code and metadata.
* @return A handle to the new program.
*/
/** @fn backend::RenderTargetHandle Driver::createDefaultRenderTarget()
* @brief Creates a render target that represents the default framebuffer (e.g., the screen).
* @return A handle to the default render target.
*/
/** @fn backend::RenderTargetHandle Driver::createRenderTarget(backend::TargetBufferFlags targetBufferFlags, uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount, backend::MRT color, backend::TargetBufferInfo depth, backend::TargetBufferInfo stencil)
* @brief Creates an offscreen render target.
* @param targetBufferFlags A bitmask specifying which buffers (color, depth, stencil) are included.
* @param width The width of the render target in pixels.
* @param height The height of the render target in pixels.
* @param samples The number of samples for multisampling.
* @param layerCount The number of layers for layered rendering.
* @param color An array of color attachments.
* @param depth The depth attachment.
* @param stencil The stencil attachment.
* @return A handle to the new render target.
*/
/** @fn backend::FenceHandle Driver::createFence()
* @brief Creates a fence for synchronization between the CPU and GPU.
* @return A handle to the new fence.
*/
/** @fn backend::SwapChainHandle Driver::createSwapChain(void* nativeWindow, uint64_t flags)
* @brief Creates a swap chain for a native window, used for presenting rendered frames.
* @param nativeWindow A platform-specific pointer to the native window.
* @param flags Configuration flags for the swap chain.
* @return A handle to the new swap chain.
*/
/** @fn backend::SwapChainHandle Driver::createSwapChainHeadless(uint32_t width, uint32_t height, uint64_t flags)
* @brief Creates a headless swap chain for offscreen rendering without a native window.
* @param width The width of the swap chain.
* @param height The height of the swap chain.
* @param flags Configuration flags for the swap chain.
* @return A handle to the new swap chain.
*/
/** @fn backend::TimerQueryHandle Driver::createTimerQuery()
* @brief Creates a timer query object for measuring GPU execution time.
* @return A handle to the new timer query.
*/
/** @fn backend::DescriptorSetLayoutHandle Driver::createDescriptorSetLayout(backend::DescriptorSetLayout&& info)
* @brief Creates a descriptor set layout, which defines the layout of bindings in a descriptor set.
* @param info The layout information for the descriptor set.
* @return A handle to the new descriptor set layout.
*/
/** @fn backend::DescriptorSetHandle Driver::createDescriptorSet(backend::DescriptorSetLayoutHandle dslh)
* @brief Creates a descriptor set based on a given layout.
* @param dslh A handle to the descriptor set layout.
* @return A handle to the new descriptor set.
*/
/** @fn Driver::updateDescriptorSetBuffer(backend::DescriptorSetHandle dsh, backend::descriptor_binding_t binding, backend::BufferObjectHandle boh, uint32_t offset, uint32_t size)
* @brief Updates a buffer descriptor within a descriptor set.
* @param dsh The descriptor set to update.
* @param binding The binding point of the descriptor.
* @param boh The handle of the buffer object to bind.
* @param offset The offset into the buffer.
* @param size The size of the buffer region to bind.
*/
/** @fn Driver::updateDescriptorSetTexture(backend::DescriptorSetHandle dsh, backend::descriptor_binding_t binding, backend::TextureHandle th, SamplerParams params)
* @brief Updates a texture descriptor within a descriptor set.
* @param dsh The descriptor set to update.
* @param binding The binding point of the descriptor.
* @param th The handle of the texture to bind.
* @param params The sampler parameters for the texture.
*/
/** @fn Driver::bindDescriptorSet(backend::DescriptorSetHandle dsh, backend::descriptor_set_t set, backend::DescriptorSetOffsetArray&& offsets)
* @brief Binds a descriptor set to the current pipeline.
* @param dsh The descriptor set to bind.
* @param set The index of the descriptor set to bind.
* @param offsets An array of dynamic offsets for the descriptor set.
*/
// Resource destruction
/** @fn Driver::destroyVertexBuffer(backend::VertexBufferHandle vbh)
* @brief Destroys a vertex buffer and releases its resources.
* @param vbh The handle of the vertex buffer to destroy.
*/
/** @fn Driver::destroyVertexBufferInfo(backend::VertexBufferInfoHandle vbih)
* @brief Destroys a VertexBufferInfo object.
* @param vbih The handle of the VertexBufferInfo object to destroy.
*/
/** @fn Driver::destroyIndexBuffer(backend::IndexBufferHandle ibh)
* @brief Destroys an index buffer and releases its resources.
* @param ibh The handle of the index buffer to destroy.
*/
/** @fn Driver::destroyBufferObject(backend::BufferObjectHandle boh)
* @brief Destroys a buffer object and releases its resources.
* @param boh The handle of the buffer object to destroy.
*/
/** @fn Driver::destroyRenderPrimitive(backend::RenderPrimitiveHandle rph)
* @brief Destroys a render primitive.
* @param rph The handle of the render primitive to destroy.
*/
/** @fn Driver::destroyProgram(backend::ProgramHandle ph)
* @brief Destroys a shader program.
* @param ph The handle of the program to destroy.
*/
/** @fn Driver::destroyTexture(backend::TextureHandle th)
* @brief Destroys a texture and releases its resources.
* @param th The handle of the texture to destroy.
*/
/** @fn Driver::destroyRenderTarget(backend::RenderTargetHandle rth)
* @brief Destroys a render target.
* @param rth The handle of the render target to destroy.
*/
/** @fn Driver::destroySwapChain(backend::SwapChainHandle sch)
* @brief Destroys a swap chain.
* @param sch The handle of the swap chain to destroy.
*/
/** @fn Driver::destroyStream(backend::StreamHandle sh)
* @brief Destroys a stream.
* @param sh The handle of the stream to destroy.
*/
/** @fn Driver::destroyTimerQuery(backend::TimerQueryHandle tqh)
* @brief Destroys a timer query object.
* @param tqh The handle of the timer query to destroy.
*/
/** @fn Driver::destroyFence(backend::FenceHandle fh)
* @brief Destroys a fence.
* @param fh The handle of the fence to destroy.
*/
/** @fn Driver::destroyDescriptorSetLayout(backend::DescriptorSetLayoutHandle dslh)
* @brief Destroys a descriptor set layout.
* @param dslh The handle of the descriptor set layout to destroy.
*/
/** @fn Driver::destroyDescriptorSet(backend::DescriptorSetHandle dsh)
* @brief Destroys a descriptor set.
* @param dsh The handle of the descriptor set to destroy.
*/
// Synchronous APIs
/** @fn void Driver::terminate()
* @brief Terminates the driver and releases all associated resources. This is a synchronous operation.
*/
/** @fn backend::StreamHandle Driver::createStreamNative(void* stream)
* @brief Creates a stream from a native stream object (e.g., a SurfaceTexture on Android).
* @param stream A pointer to the native stream object.
* @return A handle to the new stream.
*/
/** @fn backend::StreamHandle Driver::createStreamAcquired()
* @brief Creates a stream that will be populated with images acquired via `setAcquiredImage`.
* @return A handle to the new stream.
*/
/** @fn void Driver::setAcquiredImage(backend::StreamHandle stream, void* image, const math::mat3f& transform, backend::CallbackHandler* handler, backend::StreamCallback cb, void* userData)
* @brief Provides an image to an acquired stream, with a callback for when the image is no longer in use.
* @param stream The handle of the stream.
* @param image A pointer to the image data.
* @param transform The transformation matrix for the image.
* @param handler The callback handler.
* @param cb The callback function to be invoked when the image is released.
* @param userData User data for the callback.
*/
/** @fn void Driver::setStreamDimensions(backend::StreamHandle stream, uint32_t width, uint32_t height)
* @brief Sets the dimensions of a stream, which may be necessary for certain stream types.
* @param stream The handle of the stream.
* @param width The new width of the stream.
* @param height The new height of the stream.
*/
/** @fn int64_t Driver::getStreamTimestamp(backend::StreamHandle stream)
* @brief Gets the timestamp of the last frame from a stream, if available.
* @param stream The handle of the stream.
* @return The timestamp in nanoseconds, or 0 if not available.
*/
/** @fn void Driver::updateStreams(backend::DriverApi* driver)
* @brief Updates all active streams, typically called once per frame.
* @param driver The driver API pointer.
*/
/** @fn backend::FenceStatus Driver::getFenceStatus(backend::FenceHandle fh)
* @brief Gets the current status of a fence.
* @param fh The handle of the fence.
* @return The status of the fence (e.g., SIGNALED, TIMEOUT_EXPIRED).
*/
/** @fn bool Driver::isTextureFormatSupported(backend::TextureFormat format)
* @brief Checks if a specific texture format is supported by the driver.
* @param format The texture format to check.
* @return True if the format is supported, false otherwise.
*/
/** @fn bool Driver::isTextureSwizzleSupported()
* @brief Checks if texture channel swizzling is supported by the driver.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isTextureFormatMipmappable(backend::TextureFormat format)
* @brief Checks if a texture format can have mipmaps automatically generated.
* @param format The texture format to check.
* @return True if the format is mipmappable, false otherwise.
*/
/** @fn bool Driver::isRenderTargetFormatSupported(backend::TextureFormat format)
* @brief Checks if a texture format is supported for use as a render target attachment.
* @param format The texture format to check.
* @return True if the format is supported, false otherwise.
*/
/** @fn bool Driver::isFrameBufferFetchSupported()
* @brief Checks if framebuffer fetch (reading from the framebuffer in a shader) is supported.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isFrameBufferFetchMultiSampleSupported()
* @brief Checks if multisampled framebuffer fetch is supported.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isFrameTimeSupported()
* @brief Checks if frame time queries are supported for performance measurement.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isAutoDepthResolveSupported()
* @brief Checks if automatic resolution of multisampled depth buffers is supported.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isSRGBSwapChainSupported()
* @brief Checks if sRGB swap chains are supported for correct color space handling.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isProtectedContentSupported()
* @brief Checks if rendering protected content (e.g., for DRM) is supported.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isStereoSupported()
* @brief Checks if stereoscopic rendering is supported.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isParallelShaderCompileSupported()
* @brief Checks if the driver can compile shaders in parallel for improved performance.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isDepthStencilResolveSupported()
* @brief Checks if resolving multisampled depth/stencil buffers is supported.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isDepthStencilBlitSupported(backend::TextureFormat format)
* @brief Checks if blitting (copying) depth/stencil data is supported for a given format.
* @param format The texture format.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isProtectedTexturesSupported()
* @brief Checks if creating protected textures is supported.
* @return True if supported, false otherwise.
*/
/** @fn bool Driver::isDepthClampSupported()
* @brief Checks if depth clamping is supported by the hardware.
* @return True if supported, false otherwise.
*/
/** @fn uint8_t Driver::getMaxDrawBuffers()
* @brief Gets the maximum number of simultaneous draw buffers (Multiple Render Targets).
* @return The maximum number of draw buffers.
*/
/** @fn size_t Driver::getMaxUniformBufferSize()
* @brief Gets the maximum size of a uniform buffer in bytes.
* @return The maximum size in bytes.
*/
/** @fn size_t Driver::getMaxTextureSize(backend::SamplerType target)
* @brief Gets the maximum texture dimension (width, height, or depth) for a given target type.
* @param target The texture target type.
* @return The maximum size in texels.
*/
/** @fn size_t Driver::getMaxArrayTextureLayers()
* @brief Gets the maximum number of layers in an array texture.
* @return The maximum number of layers.
*/
/** @fn math::float2 Driver::getClipSpaceParams()
* @brief Gets the clip space parameters for the current backend.
* @return A float2 containing clip space parameters.
*/
/** @fn void Driver::setupExternalImage2(backend::Platform::ExternalImageHandleRef image)
* @brief Performs any necessary setup for an external image before it can be used.
* @param image A reference to the external image.
*/
/** @fn void Driver::setupExternalImage(void* image)
* @brief Performs any necessary setup for an external image before it can be used.
* @param image A pointer to the external image.
*/
/** @fn backend::TimerQueryResult Driver::getTimerQueryValue(backend::TimerQueryHandle query, uint64_t* elapsedTime)
* @brief Gets the result of a timer query, providing the elapsed GPU time.
* @param query The timer query handle.
* @param elapsedTime A pointer to store the elapsed time in nanoseconds.
* @return The result of the query (e.g., AVAILABLE, NOT_READY).
*/
/** @fn bool Driver::isWorkaroundNeeded(backend::Workaround workaround)
* @brief Checks if a specific driver or hardware workaround is needed.
* @param workaround The workaround to check.
* @return True if the workaround is needed, false otherwise.
*/
/** @fn backend::FeatureLevel Driver::getFeatureLevel()
* @brief Gets the feature level supported by the driver.
* @return The driver's feature level.
*/
// Resource updates
/** @fn Driver::setVertexBufferObject(backend::VertexBufferHandle vbh, uint32_t index, backend::BufferObjectHandle bufferObject)
* @brief Associates a buffer object with a specific buffer slot in a vertex buffer.
* @param vbh The handle of the vertex buffer.
* @param index The index of the buffer slot to set.
* @param bufferObject The handle of the buffer object to associate.
*/
/** @fn Driver::updateIndexBuffer(backend::IndexBufferHandle ibh, backend::BufferDescriptor&& data, uint32_t byteOffset)
* @brief Updates the data of an index buffer.
* @param ibh The handle of the index buffer.
* @param data The new data to upload.
* @param byteOffset The offset in bytes into the buffer to start writing to.
*/
/** @fn Driver::updateBufferObject(backend::BufferObjectHandle boh, backend::BufferDescriptor&& data, uint32_t byteOffset)
* @brief Updates the data of a buffer object.
* @param boh The handle of the buffer object.
* @param data The new data to upload.
* @param byteOffset The offset in bytes into the buffer to start writing to.
*/
/** @fn Driver::registerBufferObjectStreams(backend::BufferObjectHandle boh, backend::BufferObjectStreamDescriptor&& streams)
* @brief Registers external streams with a buffer object for efficient data transfer.
* @param boh The handle of the buffer object.
* @param streams The stream descriptors.
*/
/** @fn Driver::updateBufferObjectUnsynchronized(backend::BufferObjectHandle boh, backend::BufferDescriptor&& data, uint32_t byteOffset)
* @brief Updates a buffer object without synchronization, for use when manual synchronization is handled.
* @param boh The handle of the buffer object.
* @param data The new data to upload.
* @param byteOffset The offset in bytes into the buffer to start writing to.
*/
/** @fn Driver::resetBufferObject(backend::BufferObjectHandle boh)
* @brief Resets a buffer object, potentially discarding its contents.
* @param boh The handle of the buffer object.
*/
/** @fn Driver::update3DImage(backend::TextureHandle th, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth, backend::PixelBufferDescriptor&& data)
* @brief Updates a sub-region of a 3D texture.
* @param th The handle of the texture.
* @param level The mipmap level to update.
* @param xoffset The x offset of the sub-region.
* @param yoffset The y offset of the sub-region.
* @param zoffset The z offset of the sub-region.
* @param width The width of the sub-region.
* @param height The height of the sub-region.
* @param depth The depth of the sub-region.
* @param data The pixel data to upload.
*/
/** @fn Driver::generateMipmaps(backend::TextureHandle th)
* @brief Generates mipmaps for a texture automatically.
* @param th The handle of the texture.
*/
/** @fn Driver::setExternalStream(backend::TextureHandle th, backend::StreamHandle sh)
* @brief Associates an external stream with a texture for video or camera input.
* @param th The handle of the texture.
* @param sh The handle of the stream.
*/
// Render passes
/** @fn Driver::beginRenderPass(backend::RenderTargetHandle rth, const backend::RenderPassParams& params)
* @brief Begins a new render pass, targeting a specific render target.
* @param rth The handle of the render target.
* @param params The parameters for the render pass (e.g., clear values, load/store actions).
*/
/** @fn Driver::endRenderPass()
* @brief Ends the current render pass.
*/
/** @fn Driver::nextSubpass()
* @brief Moves to the next subpass within the current render pass.
*/
// Timer queries
/** @fn Driver::beginTimerQuery(backend::TimerQueryHandle query)
* @brief Begins a timer query to measure GPU time.
* @param query The handle of the timer query.
*/
/** @fn Driver::endTimerQuery(backend::TimerQueryHandle query)
* @brief Ends a timer query.
* @param query The handle of the timer query.
*/
/** @fn Driver::compilePrograms(backend::CompilerPriorityQueue priority, backend::CallbackHandler* handler, backend::CallbackHandler::Callback callback, void* user)
* @brief Compiles a queue of pending shader programs.
* @param priority The priority queue to use for compilation.
* @param handler The callback handler.
* @param callback The callback function to be invoked upon completion.
* @param user User data for the callback.
*/
// Swap chain
/** @fn Driver::makeCurrent(backend::SwapChainHandle schDraw, backend::SwapChainHandle schRead)
* @brief Makes a swap chain current for drawing and reading.
* @param schDraw The draw swap chain.
* @param schRead The read swap chain.
*/
/** @fn Driver::commit(backend::SwapChainHandle sch)
* @brief Commits the back buffer of a swap chain, making it visible.
* @param sch The handle of the swap chain.
*/
// Rendering state
/** @fn Driver::setPushConstant(backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant value)
* @brief Sets a push constant value for a specific shader stage.
* @param stage The shader stage to set the constant for.
* @param index The index of the push constant.
* @param value The value to set.
*/
/** @fn Driver::insertEventMarker(const char* string)
* @brief Inserts a debug event marker into the command stream.
* @param string The marker string.
*/
/** @fn Driver::pushGroupMarker(const char* string)
* @brief Pushes a debug group marker onto the command stream stack.
* @param string The marker string.
*/
/** @fn Driver::popGroupMarker()
* @brief Pops a debug group marker from the command stream stack.
*/
/** @fn Driver::startCapture()
* @brief Starts a graphics capture session.
*/
/** @fn Driver::stopCapture()
* @brief Stops a graphics capture session.
*/
// Read-back
/** @fn Driver::readPixels(backend::RenderTargetHandle src, uint32_t x, uint32_t y, uint32_t width, uint32_t height, backend::PixelBufferDescriptor&& data)
* @brief Reads a block of pixels from a render target into a client-side buffer.
* @param src The source render target.
* @param x The x coordinate of the region to read.
* @param y The y coordinate of the region to read.
* @param width The width of the region to read.
* @param height The height of the region to read.
* @param data The buffer to store the pixel data.
*/
/** @fn Driver::readBufferSubData(backend::BufferObjectHandle src, uint32_t offset, uint32_t size, backend::BufferDescriptor&& data)
* @brief Reads a sub-region of a buffer object into a client-side buffer.
* @param src The source buffer object.
* @param offset The offset to start reading from.
* @param size The number of bytes to read.
* @param data The buffer to store the data.
*/
// Rendering
/** @fn Driver::blitDEPRECATED(backend::TargetBufferFlags buffers, backend::RenderTargetHandle dst, backend::Viewport dstRect, backend::RenderTargetHandle src, backend::Viewport srcRect, backend::SamplerMagFilter filter)
* @brief Blits (copies) a region from one render target to another (deprecated).
* @param buffers The buffers to blit (e.g., color, depth).
* @param dst The destination render target.
* @param dstRect The destination rectangle.
* @param src The source render target.
* @param srcRect The source rectangle.
* @param filter The filter to use for scaling.
*/
/** @fn Driver::resolve(backend::TextureHandle dst, uint8_t dstLevel, uint8_t dstLayer, backend::TextureHandle src, uint8_t srcLevel, uint8_t srcLayer)
* @brief Resolves a multisampled texture into a non-multisampled texture.
* @param dst The destination texture.
* @param dstLevel The destination mipmap level.
* @param dstLayer The destination layer.
* @param src The source multisampled texture.
* @param srcLevel The source mipmap level.
* @param srcLayer The source layer.
*/
/** @fn Driver::blit(backend::TextureHandle dst, uint8_t dstLevel, uint8_t dstLayer, math::uint2 dstOrigin, backend::TextureHandle src, uint8_t srcLevel, uint8_t srcLayer, math::uint2 srcOrigin, math::uint2 size)
* @brief Blits (copies) a region from one texture to another.
* @param dst The destination texture.
* @param dstLevel The destination mipmap level.
* @param dstLayer The destination layer.
* @param dstOrigin The origin of the destination region.
* @param src The source texture.
* @param srcLevel The source mipmap level.
* @param srcLayer The source layer.
* @param srcOrigin The origin of the source region.
* @param size The size of the region to blit.
*/
/** @fn Driver::bindPipeline(const backend::PipelineState& state)
* @brief Binds a pipeline state object, including shaders and render states.
* @param state The pipeline state to bind.
*/
/** @fn Driver::bindRenderPrimitive(backend::RenderPrimitiveHandle rph)
* @brief Binds a render primitive for subsequent draw calls.
* @param rph The handle of the render primitive.
*/
/** @fn Driver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount)
* @brief Draws a render primitive using the currently bound pipeline and primitive.
* @param indexOffset The offset into the index buffer.
* @param indexCount The number of indices to draw.
* @param instanceCount The number of instances to draw.
*/
/** @fn Driver::draw(backend::PipelineState state, backend::RenderPrimitiveHandle rph, uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount)
* @brief A combined call to bind a pipeline, bind a primitive, and draw.
* @param state The pipeline state to bind.
* @param rph The render primitive to bind.
* @param indexOffset The offset into the index buffer.
* @param indexCount The number of indices to draw.
* @param instanceCount The number of instances to draw.
*/
/** @fn Driver::dispatchCompute(backend::ProgramHandle program, math::uint3 workGroupCount)
* @brief Dispatches a compute shader.
* @param program The handle of the compute program.
* @param workGroupCount The number of work groups to dispatch in each dimension.
*/
/** @fn Driver::scissor(Viewport scissor)
* @brief Sets the scissor rectangle for clipping.
* @param scissor The scissor rectangle.
*/
@@ -116,7 +116,7 @@ public:
* Destroys the object D at Handle<B> and construct a new D in its place
* e.g.:
* Handle<ConcreteTexture> h = allocateAndConstruct(w, h);
* ConcreteTexture* p = reconstruct(h, w, h);
* ConcreteTexture* p = destroyAndConstruct(h, w, h);
*/
template<typename D, typename B, typename ... ARGS>
std::enable_if_t<std::is_base_of_v<B, D>, D>*
@@ -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);
@@ -209,10 +209,42 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& drive
}
#endif
// Configure GPU context priority level for scheduling and preemption
if (driverConfig.gpuContextPriority != Platform::GpuContextPriority::DEFAULT) {
if (extensions.has("EGL_IMG_context_priority")) {
EGLint priorityLevel = EGL_CONTEXT_PRIORITY_MEDIUM_IMG;
const char* priorityName;
switch (driverConfig.gpuContextPriority) {
case Platform::GpuContextPriority::DEFAULT:
assert_invariant(false);
break;
case Platform::GpuContextPriority::LOW:
priorityLevel = EGL_CONTEXT_PRIORITY_LOW_IMG;
priorityName = "LOW";
break;
case Platform::GpuContextPriority::MEDIUM:
priorityLevel = EGL_CONTEXT_PRIORITY_MEDIUM_IMG;
priorityName = "MEDIUM";
break;
case Platform::GpuContextPriority::HIGH:
priorityLevel = EGL_CONTEXT_PRIORITY_HIGH_IMG;
priorityName = "HIGH";
break;
case Platform::GpuContextPriority::REALTIME:
priorityLevel = EGL_CONTEXT_PRIORITY_HIGH_IMG;
priorityName = "REALTIME(=HIGH)";
break;
}
contextAttribs[EGL_CONTEXT_PRIORITY_LEVEL_IMG] = priorityLevel;
LOG(INFO) << "EGL: Enabling GPU context priority: " << priorityName;
} else {
LOG(WARNING) << "EGL: GPU context priority requested but not supported";
}
}
// config use for creating the context
EGLConfig eglConfig = EGL_NO_CONFIG_KHR;
if (UTILS_UNLIKELY(!ext.egl.KHR_no_config_context)) {
// find a config we can use if we don't have "EGL_KHR_no_config_context" and that we can use
// for the dummy pbuffer surface.
@@ -15,10 +15,10 @@
*/
#include "VulkanBufferProxy.h"
#include "VulkanCommands.h"
#include "VulkanMemory.h"
#include "VulkanBufferCache.h"
#include "VulkanCommands.h"
#include "VulkanContext.h"
#include "VulkanMemory.h"
using namespace bluevk;
@@ -36,6 +36,38 @@ VulkanBufferProxy::VulkanBufferProxy(VmaAllocator allocator, VulkanStagePool& st
void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* cpuData,
uint32_t byteOffset, uint32_t numBytes) {
// The VulkanBuffer is available if the only object reference is hold by the
// `VulkanBufferProxy`. This means that the buffer is not currently referenced by a
// `VulkanCommandBuffer`.
bool const isAvailable = mBuffer->getCount() == 1;
if (isAvailable) {
// We are up to date with all the update operations, so no need to synchronize with previous
// updates.
mUpdatedBytes = 0;
mUpdatedOffset = 0;
}
// Keep track of the VulkanBuffer usage
commands.acquire(mBuffer);
// Check if we can just memcpy directly to the GPU memory.
// This is only allowed for UNIFORMS that are AVAILABLE and the memory is HOST_VISIBLE
// (supports memcpy from host). This works regardless if it's a full or partial update of the
// buffer.
bool const isMemcopyable = mBuffer->getGpuBuffer()->allocationInfo.pMappedData != nullptr;
bool const isUniform = getUsage() == VulkanBufferUsage::UNIFORM;
bool const useMemcpy =
isUniform && isMemcopyable && isAvailable && !FVK_FORCE_STAGING_FOR_BUFFER_UPDATES;
if (useMemcpy) {
char* dest = static_cast<char*>(mBuffer->getGpuBuffer()->allocationInfo.pMappedData) +
byteOffset;
memcpy(dest, cpuData, numBytes);
vmaFlushAllocation(mAllocator, mBuffer->getGpuBuffer()->vmaAllocation, byteOffset,
numBytes);
return;
}
// Note: this should be stored within the command buffer before going out of
// scope, so that the command buffer can manage its lifecycle.
fvkmemory::resource_ptr<VulkanStage::Segment> stage = mStagePool.acquireStage(numBytes);
@@ -128,4 +160,4 @@ VulkanBufferUsage VulkanBufferProxy::getUsage() const noexcept {
return mBuffer->getGpuBuffer()->usage;
}
}// namespace filament::backend
} // namespace filament::backend
@@ -49,6 +49,6 @@ private:
uint32_t mUpdatedBytes = 0;
};
}// namespace filament::backend
} // namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_VULKANBUFFERPROXY_H
#endif // TNT_FILAMENT_BACKEND_VULKANBUFFERPROXY_H
@@ -214,4 +214,8 @@ constexpr static const int FVK_MAX_PIPELINE_AGE = FVK_MAX_COMMAND_BUFFERS;
// destroying any unused pipeline object.
static_assert(FVK_MAX_PIPELINE_AGE >= FVK_MAX_COMMAND_BUFFERS);
// Whether the buffer updates always use staging or not. Otherwise its allowed to also use memcpy
// for buffer updates. Default is false
constexpr static const bool FVK_FORCE_STAGING_FOR_BUFFER_UPDATES = false;
#endif
+9 -2
View File
@@ -394,7 +394,13 @@ void VulkanDriver::collectGarbage() {
void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
int64_t refreshIntervalNs, uint32_t frameId) {
FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
// Do nothing.
// Check if any command have finished and reset all its used resources. The resources
// wont be destroyed but their reference count will decreased if the command is already
// completed.
//
// This will let us check if any VulkanBuffer is currently in flight or not.
mCommands.gc();
if (mAppState.hasExternalSamplers()) {
mExternalImageManager.onBeginFrame();
@@ -1236,7 +1242,8 @@ void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor
scheduleDestroy(std::move(p));
}
void VulkanDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh, BufferObjectStreamDescriptor&& streams) {
void VulkanDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh,
BufferObjectStreamDescriptor&& streams) {
// Noop
}
+41 -20
View File
@@ -34,16 +34,23 @@ namespace {
static constexpr uint32_t MAX_EMPTY_STAGES_TO_RETAIN = 1;
constexpr uint32_t STAGE_SIZE = 1048576;
uint32_t alignValue(uint32_t valueToAlign, uint32_t alignment) {
if (alignment == 0) {
return valueToAlign;
}
uint32_t remainder = valueToAlign % alignment;
return remainder == 0 ? valueToAlign : valueToAlign + (alignment - remainder);
}
}// namespace
fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStage::acquireSegment(
fvkmemory::ResourceManager* resManager, uint32_t numBytes) {
auto segment = fvkmemory::resource_ptr<Segment>::construct(
resManager, this, numBytes, mCurrentOffset, [this](uint32_t offset) {
mSegments.erase(offset);
});
mSegments.insert({mCurrentOffset, segment.get()});
mCurrentOffset += numBytes;
fvkmemory::ResourceManager* resManager, uint32_t segmentOffset, uint32_t numBytes) {
auto segment = fvkmemory::resource_ptr<Segment>::construct(resManager, this, numBytes,
segmentOffset, [this](uint32_t offset) { mSegments.erase(offset); });
mSegments.insert({ segmentOffset, segment.get() });
mCurrentOffset = segmentOffset + numBytes;
return segment;
}
@@ -54,7 +61,8 @@ VulkanStagePool::VulkanStagePool(VmaAllocator allocator, fvkmemory::ResourceMana
mCommands(commands),
mDeviceLimits(deviceLimits) {}
fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStagePool::acquireStage(uint32_t numBytes) {
fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStagePool::acquireStage(uint32_t numBytes,
uint32_t alignment) {
// Apply alignment to the byte count to ensure that, when we later flush
// data written by the host, we only flush the atoms that we modified, and
// no adjacent atoms.
@@ -64,17 +72,35 @@ fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStagePool::acquireStage(uint
// equal to the requested size.
auto iter = mStages.lower_bound(numBytes);
VulkanStage* pStage;
if (iter != mStages.end()) {
pStage = iter->second;
mStages.erase(iter);
} else {
// If we have potential buffer candidates, loop through and try to find one
// that works with our alignment. Also, set the offset for the buffer.
// If we have no buffer, create a new one.
VulkanStage* pStage = nullptr;
uint32_t segmentOffset = 0;
while (iter != mStages.end()) {
segmentOffset = alignValue(iter->second->currentOffset(), alignment);
// Check for overflow + if there's space.
if (segmentOffset >= iter->second->currentOffset() &&
iter->second->capacity() - numBytes >= segmentOffset) {
// This buffer has enough space.
pStage = iter->second;
mStages.erase(iter);
break;
}
// This buffer wasn't suitable. Check the next larger one.
++iter;
}
if (!pStage) {
pStage = allocateNewStage(std::max(numBytes, STAGE_SIZE));
}
// Note: this allocation updates `currentOffset` and `segments` within
// the parent stage. When destroyed, it will update `segments`.
fvkmemory::resource_ptr<VulkanStage::Segment> pSegment = pStage->acquireSegment(mResManager, numBytes);
fvkmemory::resource_ptr<VulkanStage::Segment> pSegment =
pStage->acquireSegment(mResManager, segmentOffset, numBytes);
// Update the stage's metadata, and reinsert it with the remaining segment
// capacity.
@@ -86,12 +112,7 @@ fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStagePool::acquireStage(uint
uint32_t VulkanStagePool::alignToNonCoherentAtomSize(uint32_t bytes) {
VkDeviceSize alignment = mDeviceLimits->nonCoherentAtomSize;
if (alignment == 0) {
return bytes;
}
uint32_t remainder = bytes % alignment;
return remainder == 0 ? bytes : bytes + (alignment - remainder);
return alignValue(bytes, alignment);
}
VulkanStage* VulkanStagePool::allocateNewStage(uint32_t capacity) {
@@ -105,8 +105,11 @@ public:
// Marks a region of the block as "in-use", and provides information about
// the allocated region to the caller. Note: this assumes that numBytes
// is aligned to the physical device's nonCoherentAtomSize.
// Takes the offset to use as the segment's start offset. It is assumed
// that this is greater than the segment's current offset, and it is
// undefined behavior if not.
fvkmemory::resource_ptr<Segment> acquireSegment(fvkmemory::ResourceManager* resManager,
uint32_t numBytes);
uint32_t segmentOffset, uint32_t numBytes);
private:
const VmaAllocation mMemory;
@@ -144,7 +147,10 @@ public:
// buffers so that we have less objects around that we have to keep track
// of.
// This function is NOT thread-safe.
fvkmemory::resource_ptr<VulkanStage::Segment> acquireStage(uint32_t numBytes);
// numBytes - the number of bytes required by this segment.
// alignment - the multiple to align the buffer offset to
fvkmemory::resource_ptr<VulkanStage::Segment> acquireStage(uint32_t numBytes,
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,
+13 -5
View File
@@ -227,7 +227,7 @@ void adjustedMemcpy(void* mapped, PixelBufferDescriptor const& p, size_t width,
// Slow path of copying row by row
assert_invariant(pbdStride >= width);
if (UTILS_UNLIKELY(p.left > 0 || p.top > 0 || pbdStride > width)) {
if (UTILS_UNLIKELY(pixelSize > 0 && (p.left > 0 || p.top > 0 || pbdStride > width))) {
size_t const pbdRowSize =
PixelBufferDescriptor::computeDataSize(p.format, p.type, pbdStride, 1, p.alignment);
size_t const pbdHeight = p.size / pixelSize / pbdStride / depth;
@@ -248,7 +248,9 @@ void adjustedMemcpy(void* mapped, PixelBufferDescriptor const& p, size_t width,
}
}
} else {
size_t const writeSize = pixelSize * (width * height * depth);
// Note: if pixelSize = 0, we're dealing with a compressed format; we
// should fall back to the buffer's size.
size_t const writeSize = pixelSize > 0 ? pixelSize * (width * height * depth) : p.size;
memcpy(mapped, buf, writeSize);
}
}
@@ -517,13 +519,17 @@ 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
// staging buffer pool.
uint8_t alignment =
fvkutils::getTexelBlockSize(fvkutils::getVkFormat(hostData->format, hostData->type));
fvkmemory::resource_ptr<VulkanStage::Segment> stageSegment =
mState->mStagePool.acquireStage(writeSize);
mState->mStagePool.acquireStage(writeSize, alignment);
assert_invariant(stageSegment->memory());
adjustedMemcpy(stageSegment->mapping(), *hostData, width, height, depth);
vmaFlushAllocation(mState->mAllocator, stageSegment->memory(), stageSegment->offset(),
@@ -591,7 +597,9 @@ 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
@@ -29,6 +29,8 @@ using namespace bluevk;
namespace filament::backend {
using namespace fvkutils;
VulkanYcbcrConversionCache::VulkanYcbcrConversionCache(VkDevice device)
: mDevice(device) {}
@@ -17,6 +17,8 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
#define TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
#include "utils/Definitions.h"
#include <backend/DriverEnums.h>
#include <utils/Hash.h>
@@ -30,7 +32,7 @@ namespace filament::backend {
class VulkanYcbcrConversionCache {
public:
struct Params {
SamplerYcbcrConversion conversion = {}; // 4
fvkutils::SamplerYcbcrConversion conversion = {}; // 4
VkFormat format; // 4
uint64_t externalFormat = 0; // 8
};
@@ -45,16 +47,15 @@ private:
struct ConversionEqualTo {
bool operator()(Params lhs, Params rhs) const noexcept {
SamplerYcbcrConversion::EqualTo equal;
fvkutils::SamplerYcbcrConversion::EqualTo equal;
return equal(lhs.conversion, rhs.conversion) &&
lhs.externalFormat == rhs.externalFormat &&
lhs.format == rhs.format;
lhs.externalFormat == rhs.externalFormat && lhs.format == rhs.format;
}
};
using ConversionHashFn = utils::hash::MurmurHashFn<Params>;
tsl::robin_map<Params, VkSamplerYcbcrConversion, ConversionHashFn, ConversionEqualTo> mCache;
};
}// namespace filament::backend
} // namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
@@ -71,6 +71,8 @@ struct Resource {
restype(ResourceType::UNDEFINED_TYPE),
mHandleConsideredDestroyed(false) {}
uint32_t getCount() const { return mCount; }
private:
inline void inc() noexcept {
mCount++;
@@ -832,6 +832,9 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
context.mPhysicalDeviceFeatures.features.shaderClipDistance = VK_FALSE;
}
// TODO: Add support of VK_KHR_global_priority with `driverConfig.gpuContextPriority`
// in VulkanPlatform::createDriver.
ExtensionSet deviceExts;
// If using a shared context, we do not assume any extensions.
if (!mImpl->mSharedContext) {
@@ -233,6 +233,212 @@ VkFormat getVkFormat(TextureFormat format) {
}
}
// As per VUID-vkCmdCopyBufferToImage-dstImage-07975 and
// VUID-vkCmdCopyBufferToImage-dstImage-07978, these provide the texel block
// sizes for each format (as confirmed using the table at
// https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#formats-compatibility-classes).
// We have not listed values for multi-plane formats, as we do not support them
// unless they're externally provided.
uint8_t getTexelBlockSize(VkFormat format) {
switch (format) {
// 8-bit formats.
case VK_FORMAT_R8_UNORM:
case VK_FORMAT_R8_SNORM:
case VK_FORMAT_R8_USCALED:
case VK_FORMAT_R8_SSCALED:
case VK_FORMAT_R8_UINT:
case VK_FORMAT_R8_SINT:
case VK_FORMAT_R8_SRGB:
return 1;
// 16-bit formats.
case VK_FORMAT_R16_UNORM:
case VK_FORMAT_R16_SNORM:
case VK_FORMAT_R16_USCALED:
case VK_FORMAT_R16_SSCALED:
case VK_FORMAT_R16_UINT:
case VK_FORMAT_R16_SINT:
case VK_FORMAT_R16_SFLOAT:
case VK_FORMAT_R8G8_UNORM:
case VK_FORMAT_R8G8_SNORM:
case VK_FORMAT_R8G8_USCALED:
case VK_FORMAT_R8G8_SSCALED:
case VK_FORMAT_R8G8_UINT:
case VK_FORMAT_R8G8_SINT:
case VK_FORMAT_R8G8_SRGB:
case VK_FORMAT_R5G6B5_UNORM_PACK16:
case VK_FORMAT_R5G5B5A1_UNORM_PACK16:
case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
return 2;
// 24-bit formats.
case VK_FORMAT_R8G8B8_UNORM:
case VK_FORMAT_R8G8B8_SNORM:
case VK_FORMAT_R8G8B8_USCALED:
case VK_FORMAT_R8G8B8_SSCALED:
case VK_FORMAT_R8G8B8_UINT:
case VK_FORMAT_R8G8B8_SINT:
case VK_FORMAT_R8G8B8_SRGB:
case VK_FORMAT_B8G8R8_UNORM:
case VK_FORMAT_B8G8R8_SNORM:
case VK_FORMAT_B8G8R8_USCALED:
case VK_FORMAT_B8G8R8_SSCALED:
case VK_FORMAT_B8G8R8_UINT:
case VK_FORMAT_B8G8R8_SINT:
case VK_FORMAT_B8G8R8_SRGB:
return 3;
// 32-bit formats.
case VK_FORMAT_R32_UINT:
case VK_FORMAT_R32_SINT:
case VK_FORMAT_R32_SFLOAT:
case VK_FORMAT_R16G16_UNORM:
case VK_FORMAT_R16G16_SNORM:
case VK_FORMAT_R16G16_USCALED:
case VK_FORMAT_R16G16_SSCALED:
case VK_FORMAT_R16G16_UINT:
case VK_FORMAT_R16G16_SINT:
case VK_FORMAT_R16G16_SFLOAT:
case VK_FORMAT_R8G8B8A8_UNORM:
case VK_FORMAT_R8G8B8A8_SNORM:
case VK_FORMAT_R8G8B8A8_USCALED:
case VK_FORMAT_R8G8B8A8_SSCALED:
case VK_FORMAT_R8G8B8A8_UINT:
case VK_FORMAT_R8G8B8A8_SINT:
case VK_FORMAT_R8G8B8A8_SRGB:
case VK_FORMAT_B8G8R8A8_UNORM:
case VK_FORMAT_B8G8R8A8_SNORM:
case VK_FORMAT_B8G8R8A8_USCALED:
case VK_FORMAT_B8G8R8A8_SSCALED:
case VK_FORMAT_B8G8R8A8_UINT:
case VK_FORMAT_B8G8R8A8_SINT:
case VK_FORMAT_B8G8R8A8_SRGB:
case VK_FORMAT_A8B8G8R8_UNORM_PACK32:
case VK_FORMAT_A8B8G8R8_SNORM_PACK32:
case VK_FORMAT_A8B8G8R8_USCALED_PACK32:
case VK_FORMAT_A8B8G8R8_SSCALED_PACK32:
case VK_FORMAT_A8B8G8R8_UINT_PACK32:
case VK_FORMAT_A8B8G8R8_SINT_PACK32:
case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
// Depth and stencil formats.
case VK_FORMAT_S8_UINT:
case VK_FORMAT_D16_UNORM:
case VK_FORMAT_X8_D24_UNORM_PACK32:
case VK_FORMAT_D32_SFLOAT:
case VK_FORMAT_D24_UNORM_S8_UINT:
case VK_FORMAT_D32_SFLOAT_S8_UINT:
case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
return 4;
// 48-bit formats.
case VK_FORMAT_R16G16B16_UNORM:
case VK_FORMAT_R16G16B16_SNORM:
case VK_FORMAT_R16G16B16_USCALED:
case VK_FORMAT_R16G16B16_SSCALED:
case VK_FORMAT_R16G16B16_UINT:
case VK_FORMAT_R16G16B16_SINT:
case VK_FORMAT_R16G16B16_SFLOAT:
return 6;
// 64-bit formats.
case VK_FORMAT_R32G32_UINT:
case VK_FORMAT_R32G32_SINT:
case VK_FORMAT_R32G32_SFLOAT:
case VK_FORMAT_R16G16B16A16_UNORM:
case VK_FORMAT_R16G16B16A16_SNORM:
case VK_FORMAT_R16G16B16A16_USCALED:
case VK_FORMAT_R16G16B16A16_SSCALED:
case VK_FORMAT_R16G16B16A16_UINT:
case VK_FORMAT_R16G16B16A16_SINT:
case VK_FORMAT_R16G16B16A16_SFLOAT:
// Compressed formats.
case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
case VK_FORMAT_BC4_UNORM_BLOCK:
case VK_FORMAT_BC4_SNORM_BLOCK:
case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
case VK_FORMAT_EAC_R11_UNORM_BLOCK:
case VK_FORMAT_EAC_R11_SNORM_BLOCK:
return 8;
// 96-bit formats.
case VK_FORMAT_R32G32B32_UINT:
case VK_FORMAT_R32G32B32_SINT:
case VK_FORMAT_R32G32B32_SFLOAT:
return 12;
// 128-bit formats.
case VK_FORMAT_R32G32B32A32_UINT:
case VK_FORMAT_R32G32B32A32_SINT:
case VK_FORMAT_R32G32B32A32_SFLOAT:
// Compressed formats.
case VK_FORMAT_BC2_UNORM_BLOCK:
case VK_FORMAT_BC2_SRGB_BLOCK:
case VK_FORMAT_BC3_UNORM_BLOCK:
case VK_FORMAT_BC3_SRGB_BLOCK:
case VK_FORMAT_BC5_UNORM_BLOCK:
case VK_FORMAT_BC5_SNORM_BLOCK:
case VK_FORMAT_BC6H_SFLOAT_BLOCK:
case VK_FORMAT_BC6H_UFLOAT_BLOCK:
case VK_FORMAT_BC7_UNORM_BLOCK:
case VK_FORMAT_BC7_SRGB_BLOCK:
case VK_FORMAT_ASTC_4x4_UNORM_BLOCK:
case VK_FORMAT_ASTC_5x4_UNORM_BLOCK:
case VK_FORMAT_ASTC_5x5_UNORM_BLOCK:
case VK_FORMAT_ASTC_6x5_UNORM_BLOCK:
case VK_FORMAT_ASTC_6x6_UNORM_BLOCK:
case VK_FORMAT_ASTC_8x5_UNORM_BLOCK:
case VK_FORMAT_ASTC_8x6_UNORM_BLOCK:
case VK_FORMAT_ASTC_8x8_UNORM_BLOCK:
case VK_FORMAT_ASTC_10x5_UNORM_BLOCK:
case VK_FORMAT_ASTC_10x6_UNORM_BLOCK:
case VK_FORMAT_ASTC_10x8_UNORM_BLOCK:
case VK_FORMAT_ASTC_10x10_UNORM_BLOCK:
case VK_FORMAT_ASTC_12x10_UNORM_BLOCK:
case VK_FORMAT_ASTC_12x12_UNORM_BLOCK:
case VK_FORMAT_ASTC_4x4_SRGB_BLOCK:
case VK_FORMAT_ASTC_5x4_SRGB_BLOCK:
case VK_FORMAT_ASTC_5x5_SRGB_BLOCK:
case VK_FORMAT_ASTC_6x5_SRGB_BLOCK:
case VK_FORMAT_ASTC_6x6_SRGB_BLOCK:
case VK_FORMAT_ASTC_8x5_SRGB_BLOCK:
case VK_FORMAT_ASTC_8x6_SRGB_BLOCK:
case VK_FORMAT_ASTC_8x8_SRGB_BLOCK:
case VK_FORMAT_ASTC_10x5_SRGB_BLOCK:
case VK_FORMAT_ASTC_10x6_SRGB_BLOCK:
case VK_FORMAT_ASTC_10x8_SRGB_BLOCK:
case VK_FORMAT_ASTC_10x10_SRGB_BLOCK:
case VK_FORMAT_ASTC_12x10_SRGB_BLOCK:
case VK_FORMAT_ASTC_12x12_SRGB_BLOCK:
case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
return 16;
case VK_FORMAT_UNDEFINED:
// In cases where we've explicitly already determined that the
// format is not supported, let the rest of the system handle
// that explicitly. Return '1' for no alignment.
return 1;
default:
// This is the case where there's a format that we added support
// for in getVkFormat that we have not added support for in this
// function, and should be treated as an error.
assert_invariant(false && "Unknown data type, conversion is not supported.");
return 0;
}
}
VkFormat getVkFormat(PixelDataFormat format, PixelDataType type) {
if (type == PixelDataType::USHORT_565) return VK_FORMAT_R5G6B5_UNORM_PACK16;
if (type == PixelDataType::UINT_2_10_10_10_REV) return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
@@ -17,6 +17,8 @@
#ifndef TNT_FILAMENT_BACKEND_VULKAN_UTILS_CONVERSION_H
#define TNT_FILAMENT_BACKEND_VULKAN_UTILS_CONVERSION_H
#include "Definitions.h"
#include <backend/DriverEnums.h>
#include <private/backend/BackendUtils.h> // for getFormatSize()
@@ -47,6 +49,9 @@ VkFormat getVkFormatLinear(VkFormat format);
// than a driver-level Texture format, and can account for a specified byte alignment.
uint32_t getBytesPerPixel(TextureFormat format);
// Required for aligning textures in staging buffers.
uint8_t getTexelBlockSize(VkFormat format);
VkCompareOp getCompareOp(SamplerCompareFunc func);
VkBlendFactor getBlendFactor(BlendFunction mode);
VkCullModeFlags getCullMode(CullingMode mode);
@@ -17,6 +17,8 @@
#ifndef TNT_FILAMENT_BACKEND_VULKAN_UTILS_DEFINITIONS_H
#define TNT_FILAMENT_BACKEND_VULKAN_UTILS_DEFINITIONS_H
#include <backend/DriverEnums.h>
#include <utils/bitset.h>
#include <utils/FixedCapacityVector.h>
@@ -379,6 +381,81 @@ static constexpr uint8_t getFragmentStageShift() noexcept {
// We have at most 4 descriptor sets. This is to indicate which ones are active.
using DescriptorSetMask = utils::bitset8;
//! this API is copied from (and only applies to) the Vulkan spec.
//! These specify YUV to RGB conversions.
enum class SamplerYcbcrModelConversion : uint8_t {
RGB_IDENTITY = 0,
YCBCR_IDENTITY = 1,
YCBCR_709 = 2,
YCBCR_601 = 3,
YCBCR_2020 = 4,
};
enum class SamplerYcbcrRange : uint8_t {
ITU_FULL = 0,
ITU_NARROW = 1,
};
enum class ChromaLocation : uint8_t {
COSITED_EVEN = 0,
MIDPOINT = 1,
};
//! Sampler parameters
struct SamplerYcbcrConversion { // NOLINT
SamplerYcbcrModelConversion ycbcrModel : 4;
TextureSwizzle r : 4;
TextureSwizzle g : 4;
TextureSwizzle b : 4;
TextureSwizzle a : 4;
SamplerYcbcrRange ycbcrRange : 1;
ChromaLocation xChromaOffset : 1;
ChromaLocation yChromaOffset : 1;
SamplerMagFilter chromaFilter : 1;
uint8_t padding;
struct Hasher {
size_t operator()(const SamplerYcbcrConversion p) const noexcept {
// we don't use std::hash<> here, so we don't have to include <functional>
return *reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&p));
}
};
struct EqualTo {
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
assert_invariant(lhs.padding == 0);
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
return *pLhs == *pRhs;
}
};
struct LessThan {
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
assert_invariant(lhs.padding == 0);
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
return *pLhs < *pRhs;
}
};
private:
friend bool operator==(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) noexcept {
return SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
}
friend bool operator!=(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) noexcept {
return !SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
}
friend bool operator<(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) noexcept {
return SamplerYcbcrConversion::LessThan{}(lhs, rhs);
}
};
static_assert(sizeof(SamplerYcbcrConversion) == 4);
static_assert(sizeof(SamplerYcbcrConversion) <= sizeof(uint64_t),
"SamplerYcbcrConversion must be no more than 64 bits");
} // namespace filament::backend::fvkutils
#endif // TNT_FILAMENT_BACKEND_VULKAN_UTILS_DEFINITIONS_H
@@ -0,0 +1,5 @@
WebGPU does not include a trivial way to create mipmaps, and thus we must provide a render pass or compute pass to generate them.
This directory is the result of porting https://github.com/JolifantoBambla/webgpu-spd to C++, which itself is a WebGPU port of https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/blob/main/docs/samples/single-pass-downsampler.md.
The first version of our port is primarily from a Gemini "Convert this to C++" request, which worked as an MVP with very few changes. Future commits will likely alter this version significantly, but it is being merged in this early state to facilitate rapid iteration.
@@ -0,0 +1,956 @@
#include "SpdMipmapGenerator.h"
#include <sstream>
#include <stdexcept>
// C++ port of https://github.com/JolifantoBambla/webgpu-spd for early experiments
namespace spd {
// Helper to convert enums to strings for map keys or shader code
const char* to_string(wgpu::TextureFormat format) {
switch (format) {
case wgpu::TextureFormat::RGBA8Unorm:
return "rgba8unorm";
case wgpu::TextureFormat::BGRA8Unorm:
return "bgra8unorm";
case wgpu::TextureFormat::R32Float:
return "r32float";
case wgpu::TextureFormat::RG32Float:
return "rg32float";
case wgpu::TextureFormat::RGBA32Float:
return "rgba32float";
case wgpu::TextureFormat::R16Float:
return "r16float";
case wgpu::TextureFormat::RG16Float:
return "rg16float";
case wgpu::TextureFormat::RGBA16Float:
return "rgba16float";
// Add other formats as needed
default:
return "rgba8unorm";
}
}
std::string MipmapGenerator::GetFilterCode(SPDFilter filter) {
switch (filter) {
case SPDFilter::Min:
return R"(
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
return min(min(v0, v1), min(v2, v3));
}
)";
case SPDFilter::Max:
return R"(
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
return max(max(v0, v1), max(v2, v3));
}
)";
case SPDFilter::MinMax:
return R"(
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
let max4 = max(max(v0.xy, v1.xy), max(v2.xy, v3.xy));
return vec4<SPDScalar>(min(min(v0.x, v1.x), min(v2.x, v3.x)), max(max4.x, max4.y), 0.0, 0.0);
}
)";
case SPDFilter::Average:
default:
return R"(
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
return (v0 + v1 + v2 + v3) * 0.25;
}
)";
}
}
MipmapGenerator::MipmapGenerator(const wgpu::Device& device)
: m_device(device) {
wgpu::BindGroupLayoutEntry bglEntry{};
bglEntry.binding = 0;
bglEntry.visibility = wgpu::ShaderStage::Compute;
bglEntry.buffer.type = wgpu::BufferBindingType::Uniform;
bglEntry.buffer.minBindingSize = 16;
wgpu::BindGroupLayoutDescriptor bglDesc{};
bglDesc.entryCount = 1;
bglDesc.entries = &bglEntry;
m_internalResourcesBindGroupLayout =
m_device.CreateBindGroupLayout(&bglDesc); // Basic BGL for <= 6 mips
wgpu::Limits deviceLimits;
m_device.GetLimits(&deviceLimits);
// Max number of *output* mips we can generate in a single pass.
// This is device.maxStorageTexturesPerShaderStage - 1 (for the source texture binding),
// further capped at 11 output mips (which means 12 total texture bindings: 1 source + 11
// outputs). WebGPU minspec for maxStorageTexturesPerShaderStage is 4, so
// deviceLimits.maxStorageTexturesPerShaderStage >= 4. Thus, m_maxMipsPerPass will be at least
// std::min(4u - 1, 11u) = 3.
m_maxMipsPerPass =
std::min(static_cast<uint32_t>(deviceLimits.maxStorageTexturesPerShaderStage) - 1, 11u);
m_maxArrayLayers = deviceLimits.maxStorageTexturesPerShaderStage;
// If we can generate more than 6 mips in a pass, we need an advanced BGL
// that includes atomic counter and mid-mip buffer for levels > 5.
if (m_maxMipsPerPass > 6) {
std::vector<wgpu::BindGroupLayoutEntry> entriesAdvanced(3);
// Entry 0: Uniform buffer (DownsamplePassMeta)
entriesAdvanced[0].binding = 0;
entriesAdvanced[0].visibility = wgpu::ShaderStage::Compute;
entriesAdvanced[0].buffer.type = wgpu::BufferBindingType::Uniform;
entriesAdvanced[0].buffer.minBindingSize = 16; // sizeof(DownsamplePassMeta) effectively
// Entry 1: Storage buffer (spd_global_counter)
entriesAdvanced[1].binding = 1;
entriesAdvanced[1].visibility = wgpu::ShaderStage::Compute;
entriesAdvanced[1].buffer.type = wgpu::BufferBindingType::Storage;
entriesAdvanced[1].buffer.minBindingSize = 4; // sizeof(atomic<u32>)
// Entry 2: Storage buffer (mip_dst_6_buffer)
entriesAdvanced[2].binding = 2;
entriesAdvanced[2].visibility = wgpu::ShaderStage::Compute;
entriesAdvanced[2].buffer.type = wgpu::BufferBindingType::Storage;
// Size for array<array<array<vec4<f32>, 64>, 64>> which is 16 (bytes for vec4f32) * 64 * 64
// * arrayLayers. The shader defines it as [slice][uv.y][uv.x], so 64x64 per slice.
// minBindingSize here is for one slice as a baseline. Actual buffer will be larger for
// multiple array layers.
entriesAdvanced[2].buffer.minBindingSize = 16 * 64 * 64;
wgpu::BindGroupLayoutDescriptor bglDescAdvanced{};
bglDescAdvanced.label = "SPD Internal BGL (Advanced)";
bglDescAdvanced.entryCount = static_cast<uint32_t>(entriesAdvanced.size());
bglDescAdvanced.entries = entriesAdvanced.data();
m_internalResourcesBindGroupLayout_Advanced =
m_device.CreateBindGroupLayout(&bglDescAdvanced);
}
}
void MipmapGenerator::PreparePipelines(wgpu::TextureFormat format, SPDFilter filter,
bool halfPrecision) {
SPDScalarType scalarType = SanitizeScalarType(format, halfPrecision);
// Prepare for a reasonable number of mips, up to m_maxMipsPerPass
// The loop should go from 1 up to and including m_maxMipsPerPass.
// If m_maxMipsPerPass is 0 (e.g. maxStorageTexturesPerShaderStage <=1 ), this loop won't run.
for (uint32_t i = 1; i <= m_maxMipsPerPass; ++i) {
GetOrCreatePipeline({ format, scalarType, filter, i });
}
}
SPDScalarType MipmapGenerator::SanitizeScalarType(wgpu::TextureFormat format, bool halfPrecision) {
std::string formatStr = to_string(format);
std::transform(formatStr.begin(), formatStr.end(), formatStr.begin(), ::tolower);
SPDScalarType texelType = SPDScalarType::F32; // Default to F32
if (formatStr.find("sint") != std::string::npos) {
texelType = SPDScalarType::I32;
} else if (formatStr.find("uint") != std::string::npos) {
texelType = SPDScalarType::U32;
}
if (halfPrecision && texelType == SPDScalarType::F32) {
if (m_device.HasFeature(wgpu::FeatureName::ShaderF16)) {
return SPDScalarType::F16;
} else {
// Optional: Log a warning here that halfPrecision was requested for a float format
// but the device does not support ShaderF16, so F32 will be used.
}
} else if (halfPrecision && texelType != SPDScalarType::F32) {
// Optional: Log a warning here that halfPrecision was requested for a non-float format.
}
return texelType;
}
SPDPipeline& MipmapGenerator::GetOrCreatePipeline(const PipelineCacheKey& key) {
uint32_t numMipsForPipeline = std::min(key.numMips, m_maxMipsPerPass);
if (m_pipelines.find(key) == m_pipelines.end()) {
// Create the pipeline
SPDPipeline spdPipeline;
std::vector<wgpu::BindGroupLayoutEntry> mipsBglEntries;
// Bindings are 0 for source, 1 to numMipsForPipeline for destinations
for (uint32_t i = 0; i <= numMipsForPipeline; ++i) {
wgpu::BindGroupLayoutEntry entry{};
entry.binding = i;
entry.visibility = wgpu::ShaderStage::Compute;
if (i == 0) {
entry.texture.sampleType = (key.scalarType == SPDScalarType::I32)
? wgpu::TextureSampleType::Sint
: (key.scalarType == SPDScalarType::U32)
? wgpu::TextureSampleType::Uint
: wgpu::TextureSampleType::UnfilterableFloat;
entry.texture.viewDimension = wgpu::TextureViewDimension::e2DArray;
} else {
entry.storageTexture.access = wgpu::StorageTextureAccess::WriteOnly;
entry.storageTexture.format = key.format;
entry.storageTexture.viewDimension = wgpu::TextureViewDimension::e2DArray;
}
mipsBglEntries.push_back(entry);
}
wgpu::BindGroupLayoutDescriptor mipsBglDesc{};
mipsBglDesc.entryCount = mipsBglEntries.size();
mipsBglDesc.entries = mipsBglEntries.data();
spdPipeline.mipsBindGroupLayout = m_device.CreateBindGroupLayout(&mipsBglDesc);
std::string shaderCode = MakeShaderCode(key.format, GetFilterCode(key.filter),
numMipsForPipeline, key.scalarType);
wgpu::ShaderModuleWGSLDescriptor wgslDesc{};
wgslDesc.code = shaderCode.c_str();
wgpu::ShaderModuleDescriptor shaderModuleDesc{};
shaderModuleDesc.nextInChain = &wgslDesc;
wgpu::ShaderModule shaderModule = m_device.CreateShaderModule(&shaderModuleDesc);
wgpu::BindGroupLayout chosenInternalBGL =
(numMipsForPipeline > 6 && m_internalResourcesBindGroupLayout_Advanced)
? m_internalResourcesBindGroupLayout_Advanced
: m_internalResourcesBindGroupLayout;
wgpu::BindGroupLayout bgls[] = { spdPipeline.mipsBindGroupLayout, chosenInternalBGL };
wgpu::PipelineLayoutDescriptor layoutDesc{};
layoutDesc.bindGroupLayoutCount = 2;
layoutDesc.bindGroupLayouts = bgls;
wgpu::ComputePipelineDescriptor pipelineDesc{};
pipelineDesc.layout = m_device.CreatePipelineLayout(&layoutDesc);
pipelineDesc.compute.module = shaderModule;
pipelineDesc.compute.entryPoint = "downsample";
spdPipeline.pipeline = m_device.CreateComputePipeline(&pipelineDesc);
m_pipelines[key] = std::move(spdPipeline);
}
return m_pipelines[key];
}
void MipmapGenerator::Generate(wgpu::CommandEncoder& commandEncoder, wgpu::Texture srcTexture,
const SPDPassConfig& config) {
if (config.numMips <= 1 || config.numMips <= config.sourceMipLevel + 1) {
return;
}
if (m_maxMipsPerPass == 0) {
return;
}
PreparePipelines(srcTexture.GetFormat(), config.filter, config.halfPrecision);
uint32_t totalSrcArrayLayers = srcTexture.GetDepthOrArrayLayers();
// Loop over array layer chunks
for (uint32_t currentBaseArrayLayer = 0; currentBaseArrayLayer < totalSrcArrayLayers;
currentBaseArrayLayer += m_maxArrayLayers) {
uint32_t arrayLayersThisPass =
std::min(m_maxArrayLayers, totalSrcArrayLayers - currentBaseArrayLayer);
uint32_t mipsToGenerateCount = config.numMips - (config.sourceMipLevel + 1);
uint32_t currentSourceMipLevel = config.sourceMipLevel;
while (mipsToGenerateCount > 0) {
uint32_t mipsThisPass = std::min(mipsToGenerateCount, m_maxMipsPerPass);
SPDPassConfig passSpecificConfig = {};
passSpecificConfig.filter = config.filter;
passSpecificConfig.halfPrecision = config.halfPrecision;
passSpecificConfig.targetTexture =
config.targetTexture ? config.targetTexture : srcTexture;
passSpecificConfig.numMips = mipsThisPass;
passSpecificConfig.sourceMipLevel = currentSourceMipLevel;
// Pass baseArrayLayer and numArrayLayers directly to generatePass
generatePass(commandEncoder, srcTexture, passSpecificConfig, currentBaseArrayLayer,
arrayLayersThisPass);
mipsToGenerateCount -= mipsThisPass;
currentSourceMipLevel += mipsThisPass;
}
}
}
void MipmapGenerator::generatePass(wgpu::CommandEncoder& commandEncoder, wgpu::Texture srcTexture,
const SPDPassConfig& passConfig, uint32_t baseArrayLayer, uint32_t numArrayLayers) {
uint32_t width = srcTexture.GetWidth() >> passConfig.sourceMipLevel;
uint32_t height = srcTexture.GetHeight() >> passConfig.sourceMipLevel;
uint32_t arrayLayerCountForThisPass = numArrayLayers;
wgpu::Texture target = passConfig.targetTexture ? passConfig.targetTexture : srcTexture;
uint32_t numMipsThisPass = passConfig.numMips;
if (numMipsThisPass == 0) return;
SPDScalarType scalarType = SanitizeScalarType(srcTexture.GetFormat(), passConfig.halfPrecision);
SPDPipeline& spdPipeline = GetOrCreatePipeline(
{ target.GetFormat(), scalarType, passConfig.filter, numMipsThisPass });
// --- Create Bind Group 0 (Mips) ---
std::vector<wgpu::BindGroupEntry> mipEntries;
wgpu::TextureViewDescriptor srcViewDesc{};
srcViewDesc.dimension = wgpu::TextureViewDimension::e2DArray;
srcViewDesc.baseMipLevel = passConfig.sourceMipLevel;
srcViewDesc.mipLevelCount = 1;
srcViewDesc.baseArrayLayer = baseArrayLayer;
srcViewDesc.arrayLayerCount = arrayLayerCountForThisPass;
wgpu::BindGroupEntry srcEntry{};
srcEntry.binding = 0;
srcEntry.textureView = srcTexture.CreateView(&srcViewDesc);
mipEntries.push_back(srcEntry);
for (uint32_t i = 0; i < numMipsThisPass; ++i) {
wgpu::TextureViewDescriptor dstViewDesc{};
dstViewDesc.dimension = wgpu::TextureViewDimension::e2DArray;
dstViewDesc.baseMipLevel = passConfig.sourceMipLevel + i + 1;
dstViewDesc.mipLevelCount = 1;
dstViewDesc.baseArrayLayer = baseArrayLayer;
dstViewDesc.arrayLayerCount = arrayLayerCountForThisPass;
wgpu::BindGroupEntry dstEntry{};
dstEntry.binding = i + 1;
dstEntry.textureView = target.CreateView(&dstViewDesc);
mipEntries.push_back(dstEntry);
}
wgpu::BindGroupDescriptor mipBindGroupDesc{};
mipBindGroupDesc.layout = spdPipeline.mipsBindGroupLayout;
mipBindGroupDesc.entryCount = mipEntries.size();
mipBindGroupDesc.entries = mipEntries.data();
wgpu::BindGroup mipsBindGroup = m_device.CreateBindGroup(&mipBindGroupDesc);
// --- Create Bind Group 1 (Internal Resources) ---
uint32_t widthDiv64 = (width + 63) / 64;
uint32_t heightDiv64 = (height + 63) / 64;
uint32_t numWorkGroups = widthDiv64 * heightDiv64;
struct DownsamplePassMeta {
uint32_t work_group_offset[2] = { 0, 0 };
uint32_t num_work_groups;
uint32_t mips;
uint32_t padding[12];
} meta;
meta.num_work_groups = numWorkGroups;
meta.mips = numMipsThisPass;
wgpu::BufferDescriptor metaBufferDesc{};
metaBufferDesc.size = sizeof(DownsamplePassMeta);
metaBufferDesc.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst;
wgpu::Buffer metaBuffer = m_device.CreateBuffer(&metaBufferDesc);
m_device.GetQueue().WriteBuffer(metaBuffer, 0, &meta, sizeof(meta));
wgpu::BindGroup internalBindGroup;
wgpu::BindGroupLayout chosenInternalBGL =
(numMipsThisPass > 6 && m_internalResourcesBindGroupLayout_Advanced)
? m_internalResourcesBindGroupLayout_Advanced
: m_internalResourcesBindGroupLayout;
if (numMipsThisPass > 6 && m_internalResourcesBindGroupLayout_Advanced) {
wgpu::BufferDescriptor atomicCounterBufferDesc{};
atomicCounterBufferDesc.size = sizeof(uint32_t) * arrayLayerCountForThisPass;
atomicCounterBufferDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst;
wgpu::Buffer atomicCounterBuffer = m_device.CreateBuffer(&atomicCounterBufferDesc);
commandEncoder.ClearBuffer(atomicCounterBuffer, 0, WGPU_WHOLE_SIZE);
wgpu::BufferDescriptor midMipBufferDesc{};
midMipBufferDesc.size = 64 * 64 * 16 * arrayLayerCountForThisPass;
midMipBufferDesc.usage = wgpu::BufferUsage::Storage;
wgpu::Buffer midMipBuffer = m_device.CreateBuffer(&midMipBufferDesc);
std::vector<wgpu::BindGroupEntry> internalEntries(3);
internalEntries[0].binding = 0;
internalEntries[0].buffer = metaBuffer;
internalEntries[0].size = sizeof(DownsamplePassMeta);
internalEntries[1].binding = 1;
internalEntries[1].buffer = atomicCounterBuffer;
internalEntries[1].size = atomicCounterBuffer.GetSize();
internalEntries[2].binding = 2;
internalEntries[2].buffer = midMipBuffer;
internalEntries[2].size = midMipBuffer.GetSize();
wgpu::BindGroupDescriptor internalBindGroupDesc{};
internalBindGroupDesc.layout = chosenInternalBGL;
internalBindGroupDesc.entryCount = static_cast<uint32_t>(internalEntries.size());
internalBindGroupDesc.entries = internalEntries.data();
internalBindGroup = m_device.CreateBindGroup(&internalBindGroupDesc);
} else {
wgpu::BindGroupEntry metaEntry{};
metaEntry.binding = 0;
metaEntry.buffer = metaBuffer;
metaEntry.size = sizeof(DownsamplePassMeta);
wgpu::BindGroupDescriptor internalBindGroupDesc{};
internalBindGroupDesc.layout = chosenInternalBGL;
internalBindGroupDesc.entryCount = 1;
internalBindGroupDesc.entries = &metaEntry;
internalBindGroup = m_device.CreateBindGroup(&internalBindGroupDesc);
}
// --- Dispatch ---
wgpu::ComputePassEncoder pass = commandEncoder.BeginComputePass();
pass.SetPipeline(spdPipeline.pipeline);
pass.SetBindGroup(0, mipsBindGroup);
pass.SetBindGroup(1, internalBindGroup);
pass.DispatchWorkgroups(widthDiv64, heightDiv64, arrayLayerCountForThisPass);
pass.End();
}
// Main shader generation logic
// Helper function to check if a string is in a vector of strings
bool includes(const std::vector<std::string>& vec, const std::string& str) {
for (const auto& s: vec) {
if (s == str) {
return true;
}
}
return false;
}
std::string MakeShaderCode(wgpu::TextureFormat outputFormat, const std::string& filterOp,
unsigned int numMips, // Assuming a default value for numMips
SPDScalarType scalarType) { // Default scalarType
std::stringstream ss;
std::string texelType;
if (scalarType == SPDScalarType::I32) {
texelType = "i32";
} else if (scalarType == SPDScalarType::U32) {
texelType = "u32";
} else {
texelType = "f32";
}
bool useF16 = (scalarType == SPDScalarType::F16);
std::string filterCode = filterOp;
if (filterOp == SPD_FILTER_AVERAGE && !includes({ "f32", "f16" }, texelType)) {
// Replace "* 0.25" with "/ 4"
size_t pos = filterCode.find("* 0.25");
if (pos != std::string::npos) {
filterCode.replace(pos, std::string("* 0.25").length(), "/ 4");
}
}
// Generate mipsBindings
std::string mipsBindings;
for (unsigned int i = 0; i < numMips; ++i) {
mipsBindings += "@group(0) @binding(" + std::to_string(i + 1) + ") var dst_mip_" +
std::to_string(i + 1) + ": texture_storage_2d_array<" +
to_string(outputFormat) + ", write>;\n";
}
// Generate mipsAccessorBody
std::string mipsAccessorBody;
for (unsigned int i = 0; i < numMips; ++i) {
if (i == 5 && numMips > 6) {
mipsAccessorBody += " else if mip == 6 {\n";
mipsAccessorBody += " textureStore(dst_mip_6, uv, slice, " +
(useF16 ? "vec4<" + texelType + ">(value)" : "value") + ");\n";
mipsAccessorBody += " mip_dst_6_buffer[slice][uv.y][uv.x] = value;\n";
mipsAccessorBody += " }";
} else {
if (i != 0) {
mipsAccessorBody += " else ";
}
mipsAccessorBody += "if mip == " + std::to_string(i + 1) + " {\n";
mipsAccessorBody += " textureStore(dst_mip_" + std::to_string(i + 1) +
", uv, slice, " +
(useF16 ? "vec4<" + texelType + ">(value)" : "value") + ");\n";
mipsAccessorBody += " }";
}
}
std::string mipsAccessor =
"fn store_dst_mip(value: vec4<SPDScalar>, uv: vec2<u32>, slice: u32, mip: u32) {\n" +
mipsAccessorBody + "\n}";
std::string midMipAccessor = "return mip_dst_6_buffer[slice][uv.y][uv.x];";
// Start building the final shader code string
ss << R"(
// This file is part of the FidelityFX SDK.
//
// Copyright (C) 2023 Advanced Micro Devices, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the “Software”), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Definitions --------------------------------------------------------------------------------------------------------
)";
if (useF16) {
ss << "enable f16;\n";
}
ss << "alias SPDScalar = " << texelType
<< ";\n\n"; // Using texelType here, assuming SPDScalar maps to it.
ss << R"(
// Helpers ------------------------------------------------------------------------------------------------------------
/**
* A helper function performing a remap 64x1 to 8x8 remapping which is necessary for 2D wave reductions.
* * The 64-wide lane indices to 8x8 remapping is performed as follows:
* 00 01 08 09 10 11 18 19
* 02 03 0a 0b 12 13 1a 1b
* 04 05 0c 0d 14 15 1c 1d
* 06 07 0e 0f 16 17 1e 1f
* 20 21 28 29 30 31 38 39
* 22 23 2a 2b 32 33 3a 3b
* 24 25 2c 2d 34 35 3c 3d
* 26 27 2e 2f 36 37 3e 3f
* * @param a: The input 1D coordinate to remap.
*
* @returns The remapped 2D coordinates.
*/
fn remap_for_wave_reduction(a: u32) -> vec2<u32> {
return vec2<u32>(
insertBits(extractBits(a, 2u, 3u), a, 0u, 1u),
insertBits(extractBits(a, 3u, 3u), extractBits(a, 1u, 2u), 0u, 2u)
);
}
fn map_to_xy(local_invocation_index: u32) -> vec2<u32> {
let sub_xy: vec2<u32> = remap_for_wave_reduction(local_invocation_index % 64);
return vec2<u32>(
sub_xy.x + 8 * ((local_invocation_index >> 6) % 2),
sub_xy.y + 8 * ((local_invocation_index >> 7))
);
}
/*
* Compute a linear value from a SRGB value.
* * @param value: The value to convert to linear from SRGB.
* * @returns A value in SRGB space.
*/
/*
fn srgb_to_linear(value: SPDScalar) -> SPDScalar {
let j = vec3<SPDScalar>(0.0031308 * 12.92, 12.92, 1.0 / 2.4);
let k = vec2<SPDScalar>(1.055, -0.055);
return clamp(j.x, value * j.y, pow(value, j.z) * k.x + k.y);
}
*/
// Resources & Accessors -----------------------------------------------------------------------------------------------
struct DownsamplePassMeta {
work_group_offset: vec2<u32>,
num_work_groups: u32,
mips: u32,
}
// In the original version dst_mip_i is an image2Darray [SPD_MAX_MIP_LEVELS+1], i.e., 12+1, but WGSL doesn't support arrays of textures yet
// Also these are read_write because for mips 7-13, the workgroup reads from mip level 6 - since most formats don't support read_write access in WGSL yet, we use a single read_write buffer in such cases instead
@group(0) @binding(0) var src_mip_0: texture_2d_array<)"
<< texelType << R"(>;
)" << mipsBindings
<< R"(
@group(1) @binding(0) var<uniform> downsample_pass_meta : DownsamplePassMeta;
@group(1) @binding(1) var<storage, read_write> spd_global_counter: array<atomic<u32>>;
@group(1) @binding(2) var<storage, read_write> mip_dst_6_buffer: array<array<array<vec4<f32>, 64>, 64>>;
fn get_mips() -> u32 {
return downsample_pass_meta.mips;
}
fn get_num_work_groups() -> u32 {
return downsample_pass_meta.num_work_groups;
}
fn get_work_group_offset() -> vec2<u32> {
return downsample_pass_meta.work_group_offset;
}
fn load_src_image(uv: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
return vec4<SPDScalar>(textureLoad(src_mip_0, uv, slice, 0));
}
fn load_mid_mip_image(uv: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
)";
if (numMips > 6) {
ss << midMipAccessor;
} else {
ss << "return vec4<SPDScalar>();";
}
ss << R"(
}
)" << mipsAccessor
<< R"(
// Workgroup -----------------------------------------------------------------------------------------------------------
var<workgroup> spd_intermediate: array<array<vec4<SPDScalar>, 16>, 16>;
var<workgroup> spd_counter: atomic<u32>;
fn spd_increase_atomic_counter(slice: u32) {
atomicStore(&spd_counter, atomicAdd(&spd_global_counter[slice], 1));
}
fn spd_get_atomic_counter() -> u32 {
return atomicLoad(&spd_counter);
}
fn spd_reset_atomic_counter(slice: u32) {
atomicStore(&spd_global_counter[slice], 0);
}
// Cotnrol flow --------------------------------------------------------------------------------------------------------
fn spd_barrier() {
// in glsl this does: groupMemoryBarrier(); barrier();
workgroupBarrier();
}
// Only last active workgroup should proceed
fn spd_exit_workgroup(num_work_groups: u32, local_invocation_index: u32, slice: u32) -> bool {
// global atomic counter
if (local_invocation_index == 0) {
spd_increase_atomic_counter(slice);
}
spd_barrier();
return spd_get_atomic_counter() != (num_work_groups - 1);
}
// Pixel access --------------------------------------------------------------------------------------------------------
)" << filterCode
<< R"(
fn spd_store(pix: vec2<u32>, out_value: vec4<SPDScalar>, mip: u32, slice: u32) {
store_dst_mip(out_value, pix, slice, mip + 1);
}
fn spd_load_intermediate(x: u32, y: u32) -> vec4<SPDScalar> {
return spd_intermediate[x][y];
}
fn spd_store_intermediate(x: u32, y: u32, value: vec4<SPDScalar>) {
spd_intermediate[x][y] = value;
}
fn spd_reduce_intermediate(i0: vec2<u32>, i1: vec2<u32>, i2: vec2<u32>, i3: vec2<u32>) -> vec4<SPDScalar> {
let v0 = spd_load_intermediate(i0.x, i0.y);
let v1 = spd_load_intermediate(i1.x, i1.y);
let v2 = spd_load_intermediate(i2.x, i2.y);
let v3 = spd_load_intermediate(i3.x, i3.y);
return spd_reduce_4(v0, v1, v2, v3);
}
fn spd_reduce_load_4(base: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
let v0 = load_src_image(base + vec2<u32>(0, 0), slice);
let v1 = load_src_image(base + vec2<u32>(0, 1), slice);
let v2 = load_src_image(base + vec2<u32>(1, 0), slice);
let v3 = load_src_image(base + vec2<u32>(1, 1), slice);
return spd_reduce_4(v0, v1, v2, v3);
}
fn spd_reduce_load_mid_mip_4(base: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
let v0 = load_mid_mip_image(base + vec2<u32>(0, 0), slice);
let v1 = load_mid_mip_image(base + vec2<u32>(0, 1), slice);
let v2 = load_mid_mip_image(base + vec2<u32>(1, 0), slice);
let v3 = load_mid_mip_image(base + vec2<u32>(1, 1), slice);
return spd_reduce_4(v0, v1, v2, v3);
}
// Main logic ---------------------------------------------------------------------------------------------------------
fn spd_downsample_mips_0_1(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
var v: array<vec4<SPDScalar>, 4>;
let workgroup64 = workgroup_id.xy * 64;
let workgroup32 = workgroup_id.xy * 32;
let workgroup16 = workgroup_id.xy * 16;
var tex = workgroup64 + vec2<u32>(x * 2, y * 2);
var pix = workgroup32 + vec2<u32>(x, y);
v[0] = spd_reduce_load_4(tex, slice);
spd_store(pix, v[0], 0, slice);
tex = workgroup64 + vec2<u32>(x * 2 + 32, y * 2);
pix = workgroup32 + vec2<u32>(x + 16, y);
v[1] = spd_reduce_load_4(tex, slice);
spd_store(pix, v[1], 0, slice);
tex = workgroup64 + vec2<u32>(x * 2, y * 2 + 32);
pix = workgroup32 + vec2<u32>(x, y + 16);
v[2] = spd_reduce_load_4(tex, slice);
spd_store(pix, v[2], 0, slice);
tex = workgroup64 + vec2<u32>(x * 2 + 32, y * 2 + 32);
pix = workgroup32 + vec2<u32>(x + 16, y + 16);
v[3] = spd_reduce_load_4(tex, slice);
spd_store(pix, v[3], 0, slice);
if mip <= 1 {
return;
}
for (var i = 0u; i < 4u; i++) {
spd_store_intermediate(x, y, v[i]);
spd_barrier();
if local_invocation_index < 64 {
v[i] = spd_reduce_intermediate(
vec2<u32>(x * 2 + 0, y * 2 + 0),
vec2<u32>(x * 2 + 1, y * 2 + 0),
vec2<u32>(x * 2 + 0, y * 2 + 1),
vec2<u32>(x * 2 + 1, y * 2 + 1)
);
spd_store(workgroup16 + vec2<u32>(x + (i % 2) * 8, y + (i / 2) * 8), v[i], 1, slice);
}
spd_barrier();
}
if local_invocation_index < 64 {
spd_store_intermediate(x + 0, y + 0, v[0]);
spd_store_intermediate(x + 8, y + 0, v[1]);
spd_store_intermediate(x + 0, y + 8, v[2]);
spd_store_intermediate(x + 8, y + 8, v[3]);
}
}
fn spd_downsample_mip_2(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
if local_invocation_index < 64u {
let v = spd_reduce_intermediate(
vec2<u32>(x * 2 + 0, y * 2 + 0),
vec2<u32>(x * 2 + 1, y * 2 + 0),
vec2<u32>(x * 2 + 0, y * 2 + 1),
vec2<u32>(x * 2 + 1, y * 2 + 1)
);
spd_store(workgroup_id.xy * 8 + vec2<u32>(x, y), v, mip, slice);
// store to LDS, try to reduce bank conflicts
// x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 x
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
// ...
// x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
spd_store_intermediate(x * 2 + y % 2, y * 2, v);
}
}
fn spd_downsample_mip_3(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
if local_invocation_index < 16u {
// x 0 x 0
// 0 0 0 0
// 0 x 0 x
// 0 0 0 0
let v = spd_reduce_intermediate(
vec2<u32>(x * 4 + 0 + 0, y * 4 + 0),
vec2<u32>(x * 4 + 2 + 0, y * 4 + 0),
vec2<u32>(x * 4 + 0 + 1, y * 4 + 2),
vec2<u32>(x * 4 + 2 + 1, y * 4 + 2)
);
spd_store(workgroup_id.xy * 4 + vec2<u32>(x, y), v, mip, slice);
// store to LDS
// x 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0 0
// ...
// 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0
// ...
// 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x
// ...
spd_store_intermediate(x * 4 + y, y * 4, v);
}
}
fn spd_downsample_mip_4(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
if local_invocation_index < 4u {
// x 0 0 0 x 0 0 0
// ...
// 0 x 0 0 0 x 0 0
let v = spd_reduce_intermediate(
vec2<u32>(x * 8 + 0 + 0 + y * 2, y * 8 + 0),
vec2<u32>(x * 8 + 4 + 0 + y * 2, y * 8 + 0),
vec2<u32>(x * 8 + 0 + 1 + y * 2, y * 8 + 4),
vec2<u32>(x * 8 + 4 + 1 + y * 2, y * 8 + 4)
);
spd_store(workgroup_id.xy * 2 + vec2<u32>(x, y), v, mip, slice);
// store to LDS
// x x x x 0 ...
// 0 ...
spd_store_intermediate(x + y * 2, 0, v);
}
}
fn spd_downsample_mip_5(workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
if local_invocation_index < 1u {
// x x x x 0 ...
// 0 ...
let v = spd_reduce_intermediate(vec2<u32>(0, 0), vec2<u32>(1, 0), vec2<u32>(2, 0), vec2<u32>(3, 0));
spd_store(workgroup_id.xy, v, mip, slice);
}
}
fn spd_downsample_next_four(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, base_mip: u32, mips: u32, slice: u32) {
if mips <= base_mip {
return;
}
spd_barrier();
spd_downsample_mip_2(x, y, workgroup_id, local_invocation_index, base_mip, slice);
if mips <= base_mip + 1 {
return;
}
spd_barrier();
spd_downsample_mip_3(x, y, workgroup_id, local_invocation_index, base_mip + 1, slice);
if mips <= base_mip + 2 {
return;
}
spd_barrier();
spd_downsample_mip_4(x, y, workgroup_id, local_invocation_index, base_mip + 2, slice);
if mips <= base_mip + 3 {
return;
}
spd_barrier();
spd_downsample_mip_5(workgroup_id, local_invocation_index, base_mip + 3, slice);
}
fn spd_downsample_last_four(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, base_mip: u32, mips: u32, slice: u32, exit: bool) {
if mips <= base_mip {
return;
}
spd_barrier();
if !exit {
spd_downsample_mip_2(x, y, workgroup_id, local_invocation_index, base_mip, slice);
}
if mips <= base_mip + 1 {
return;
}
spd_barrier();
if !exit {
spd_downsample_mip_3(x, y, workgroup_id, local_invocation_index, base_mip + 1, slice);
}
if mips <= base_mip + 2 {
return;
}
spd_barrier();
if !exit {
spd_downsample_mip_4(x, y, workgroup_id, local_invocation_index, base_mip + 2, slice);
}
if mips <= base_mip + 3 {
return;
}
spd_barrier();
if !exit {
spd_downsample_mip_5(workgroup_id, local_invocation_index, base_mip + 3, slice);
}
}
fn spd_downsample_mips_6_7(x: u32, y: u32, mips: u32, slice: u32) {
var tex = vec2<u32>(x * 4 + 0, y * 4 + 0);
var pix = vec2<u32>(x * 2 + 0, y * 2 + 0);
let v0 = spd_reduce_load_mid_mip_4(tex, slice);
spd_store(pix, v0, 6, slice);
tex = vec2<u32>(x * 4 + 2, y * 4 + 0);
pix = vec2<u32>(x * 2 + 1, y * 2 + 0);
let v1 = spd_reduce_load_mid_mip_4(tex, slice);
spd_store(pix, v1, 6, slice);
tex = vec2<u32>(x * 4 + 0, y * 4 + 2);
pix = vec2<u32>(x * 2 + 0, y * 2 + 1);
let v2 = spd_reduce_load_mid_mip_4(tex, slice);
spd_store(pix, v2, 6, slice);
tex = vec2<u32>(x * 4 + 2, y * 4 + 2);
pix = vec2<u32>(x * 2 + 1, y * 2 + 1);
let v3 = spd_reduce_load_mid_mip_4(tex, slice);
spd_store(pix, v3, 6, slice);
if mips <= 7 {
return;
}
// no barrier needed, working on values only from the same thread
let v = spd_reduce_4(v0, v1, v2, v3);
spd_store(vec2<u32>(x, y), v, 7, slice);
spd_store_intermediate(x, y, v);
}
fn spd_downsample_last_6(x: u32, y: u32, local_invocation_index: u32, mips: u32, num_work_groups: u32, slice: u32) {
if mips <= 6 {
return;
}
// increase the global atomic counter for the given slice and check if it's the last remaining thread group:
// terminate if not, continue if yes.
let exit = spd_exit_workgroup(num_work_groups, local_invocation_index, slice);
// can't exit directly because subsequent barrier calls break uniform control flow...
if !exit {
// reset the global atomic counter back to 0 for the next spd dispatch
spd_reset_atomic_counter(slice);
// After mip 5 there is only a single workgroup left that downsamples the remaining up to 64x64 texels.
// compute MIP level 6 and 7
spd_downsample_mips_6_7(x, y, mips, slice);
}
// compute MIP level 8, 9, 10, 11
spd_downsample_last_four(x, y, vec2<u32>(0, 0), local_invocation_index, 8, mips, slice, exit);
}
/// Downsamples a 64x64 tile based on the work group id.
/// If after downsampling it's the last active thread group, computes the remaining MIP levels.
///
/// @param [in] workGroupID index of the work group / thread group
/// @param [in] localInvocationIndex index of the thread within the thread group in 1D
/// @param [in] mips the number of total MIP levels to compute for the input texture
/// @param [in] numWorkGroups the total number of dispatched work groups / thread groups for this slice
/// @param [in] slice the slice of the input texture
fn spd_downsample(workgroup_id: vec2<u32>, local_invocation_index: u32, mips: u32, num_work_groups: u32, slice: u32) {
let xy = map_to_xy(local_invocation_index);
spd_downsample_mips_0_1(xy.x, xy.y, workgroup_id, local_invocation_index, mips, slice);
spd_downsample_next_four(xy.x, xy.y, workgroup_id, local_invocation_index, 2, mips, slice);
)";
if (numMips > 6) {
ss << " spd_downsample_last_6(xy.x, xy.y, local_invocation_index, mips, "
"num_work_groups, slice);\n";
}
ss << R"(}
// Entry points -------------------------------------------------------------------------------------------------------
@compute
@workgroup_size(256, 1, 1)
fn downsample(@builtin(local_invocation_index) local_invocation_index: u32, @builtin(workgroup_id) workgroup_id: vec3<u32>) {
spd_downsample(
workgroup_id.xy + get_work_group_offset(),
local_invocation_index,
get_mips(),
get_num_work_groups(),
workgroup_id.z
);
}
)";
return ss.str();
}
} // namespace spd
@@ -0,0 +1,99 @@
#pragma once
#include <optional>
#include <string>
#include <tsl/robin_map.h>
#include <vector>
#include <webgpu/webgpu_cpp.h>
// C++ port of https://github.com/JolifantoBambla/webgpu-spd for early experiments
namespace spd {
// Enum for selecting the downsampling filter.
enum class SPDFilter { Average, Min, Max, MinMax };
// Enum for shader scalar types.
enum class SPDScalarType { F32, F16, I32, U32 };
// Configuration for a single mipmap generation pass.
struct SPDPassConfig {
SPDFilter filter = SPDFilter::Average;
wgpu::Texture targetTexture = nullptr;
uint32_t numMips = 0; // For the public API, this is total desired mips. Internally, mips for
// the current pass.
bool halfPrecision = false;
uint32_t sourceMipLevel = 0;
};
// Holds a pipeline and its corresponding bind group layout.
struct SPDPipeline {
wgpu::BindGroupLayout mipsBindGroupLayout = nullptr;
wgpu::ComputePipeline pipeline = nullptr;
};
// Manages pipeline creation, caching, and execution for mipmap generation.
class MipmapGenerator {
public:
MipmapGenerator(const wgpu::Device& device);
// Pre-creates pipelines for specified formats and filters.
void PreparePipelines(wgpu::TextureFormat format, SPDFilter filter, bool halfPrecision = false);
// Generates a compute pass for creating mipmaps.
void Generate(wgpu::CommandEncoder& commandEncoder, wgpu::Texture srcTexture,
const SPDPassConfig& config);
private:
void generatePass(wgpu::CommandEncoder& commandEncoder, wgpu::Texture srcTexture,
const SPDPassConfig& passConfig, uint32_t baseArrayLayer,
uint32_t numArrayLayers); // Internal pass config might differ slightly if needed
wgpu::Device m_device;
wgpu::BindGroupLayout m_internalResourcesBindGroupLayout;
wgpu::BindGroupLayout m_internalResourcesBindGroupLayout_Advanced = nullptr;
// Maximum number of mips that can be generated in a single pass.
uint32_t m_maxMipsPerPass;
uint32_t m_maxArrayLayers;
// Key for the pipeline cache.
struct PipelineCacheKey {
wgpu::TextureFormat format;
SPDScalarType scalarType;
SPDFilter filter;
uint32_t numMips;
bool operator==(const PipelineCacheKey& other) const {
return format == other.format && scalarType == other.scalarType &&
filter == other.filter && numMips == other.numMips;
}
};
// Hash function for PipelineCacheKey.
struct PipelineCacheKeyHash {
std::size_t operator()(const PipelineCacheKey& key) const {
std::size_t h1 = std::hash<wgpu::TextureFormat>()(key.format);
std::size_t h2 = std::hash<SPDScalarType>()(key.scalarType);
std::size_t h3 = std::hash<SPDFilter>()(key.filter);
std::size_t h4 = std::hash<uint32_t>()(key.numMips);
// A simple way to combine hashes.
return h1 ^ (h2 << 1) ^ (h3 << 2) ^ (h4 << 3);
}
};
// Cached pipelines.
tsl::robin_map<PipelineCacheKey, SPDPipeline, PipelineCacheKeyHash> m_pipelines;
// Helper methods
SPDPipeline& GetOrCreatePipeline(const PipelineCacheKey& key);
SPDScalarType SanitizeScalarType(wgpu::TextureFormat format, bool halfPrecision);
std::string GetFilterCode(SPDFilter filter);
};
// Assuming SPD_FILTER_AVERAGE is a string constant
const std::string SPD_FILTER_AVERAGE = "value * 0.25"; // Original filter operation
// Generates the WGSL shader code dynamically.
std::string MakeShaderCode(wgpu::TextureFormat outputFormat,
const std::string& filterOp = SPD_FILTER_AVERAGE, uint32_t numMips = 0,
SPDScalarType scalarType = SPDScalarType::F32);
} // namespace spd
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Lukas Herzberger
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -117,10 +117,10 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
samplerEntryInfo.binding = samplerEntry.binding;
samplerEntry.visibility = wEntry.visibility;
wEntry.texture.multisampled = isMultiSampledTypeDescriptor(fEntry.type);
// TODO: Set once we have the filtering values
if (isDepthDescriptor(fEntry.type)) {
samplerEntry.sampler.type = wgpu::SamplerBindingType::Comparison;
} else if (isIntDescriptor(fEntry.type)) {
} else if (isIntDescriptor(fEntry.type) ||
any(fEntry.flags & DescriptorFlags::UNFILTERABLE)) {
samplerEntry.sampler.type = wgpu::SamplerBindingType::NonFiltering;
} else {
samplerEntry.sampler.type = wgpu::SamplerBindingType::Filtering;
@@ -151,8 +151,11 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
if (isDepthDescriptor(fEntry.type)) {
wEntry.texture.sampleType = wgpu::TextureSampleType::Depth;
} else if (isFloatDescriptor(fEntry.type)) {
// TODO: Set once we have the filtering values
wEntry.texture.sampleType = wgpu::TextureSampleType::Float;
if (any(fEntry.flags & DescriptorFlags::UNFILTERABLE)) {
wEntry.texture.sampleType = wgpu::TextureSampleType::UnfilterableFloat;
} else {
wEntry.texture.sampleType = wgpu::TextureSampleType::Float;
}
} else if (isIntDescriptor(fEntry.type)) {
wEntry.texture.sampleType = wgpu::TextureSampleType::Sint;
} else if (isUnsignedIntDescriptor(fEntry.type)) {
+288 -158
View File
@@ -18,11 +18,13 @@
#include "WebGPUBufferObject.h"
#include "WebGPUDescriptorSet.h"
#include "WebGPUDescriptorSetLayout.h"
#include "WebGPUFence.h"
#include "WebGPUIndexBuffer.h"
#include "WebGPUPipelineCreation.h"
#include "WebGPUProgram.h"
#include "WebGPURenderPrimitive.h"
#include "WebGPURenderTarget.h"
#include "WebGPUStrings.h"
#include "WebGPUSwapChain.h"
#include "WebGPUTexture.h"
#include "WebGPUVertexBuffer.h"
@@ -41,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>
@@ -53,6 +55,8 @@
#include <sstream>
#include <utility>
using namespace std::chrono_literals;
namespace filament::backend {
Driver* WebGPUDriver::create(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept {
@@ -64,13 +68,15 @@ Driver* WebGPUDriver::create(WebGPUPlatform& platform, const Platform::DriverCon
WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
const Platform::DriverConfig& driverConfig) noexcept
: mPlatform(platform),
mHandleAllocator("Handles", driverConfig.handleArenaSize,
driverConfig.disableHandleUseAfterFreeCheck, driverConfig.disableHeapHandleTags) {
mAdapter = mPlatform.requestAdapter(nullptr);
mDevice = mPlatform.requestDevice(mAdapter);
: mPlatform{ platform },
mAdapter{ mPlatform.requestAdapter(nullptr) },
mDevice{ mPlatform.requestDevice(mAdapter) },
mQueue{ mDevice.GetQueue() },
mRenderPassMipmapGenerator{ mDevice },
mSpdComputePassMipmapGenerator{ mDevice },
mHandleAllocator{ "Handles", driverConfig.handleArenaSize,
driverConfig.disableHandleUseAfterFreeCheck, driverConfig.disableHeapHandleTags } {
mDevice.GetLimits(&mDeviceLimits);
mQueue = mDevice.GetQueue();
}
WebGPUDriver::~WebGPUDriver() noexcept = default;
@@ -100,6 +106,7 @@ void WebGPUDriver::terminate() {
void WebGPUDriver::tick(int) {
mDevice.Tick();
mAdapter.GetInstance().ProcessEvents();
}
void WebGPUDriver::beginFrame(int64_t monotonic_clock_ns,
@@ -261,7 +268,9 @@ Handle<HwProgram> WebGPUDriver::createProgramS() noexcept {
}
Handle<HwFence> WebGPUDriver::createFenceS() noexcept {
return Handle<HwFence>((Handle<HwFence>::HandleId) mNextFakeHandle++);
// The handle must be constructed here, as a synchronous call to get the status
// might happen before createFenceR is executed.
return allocAndConstructHandle<WebGPUFence, HwFence>();
}
Handle<HwTimerQuery> WebGPUDriver::createTimerQueryS() noexcept {
@@ -334,9 +343,6 @@ void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
mDevice, flags);
assert_invariant(mSwapChain);
FWGPU_LOGW << "WebGPU support is highly experimental, in development, and tested for only a "
"small set of simple samples (e.g. hellotriangle and texturedquad), thus issues "
"are likely to be encountered at this stage.";
#if !FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) && !defined(NDEBUG)
char printSystemHex[16];
snprintf(printSystemHex, sizeof(printSystemHex), "%#x", FWGPU_PRINT_SYSTEM);
@@ -448,15 +454,23 @@ void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> renderTarge
}
void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> renderTargetHandle,
const TargetBufferFlags targets, const uint32_t width, const uint32_t height,
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);
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> fh, int) {
//todo
void WebGPUDriver::createFenceR(Handle<HwFence> fenceHandle, const int /* dummy */) {
// the handle was already constructed in createFenceS
const auto fence = handleCast<WebGPUFence>(fenceHandle);
assert_invariant(mQueue);
fence->addMarkerToQueueState(mQueue);
}
void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {}
@@ -509,13 +523,18 @@ void WebGPUDriver::updateStreams(CommandStream* driver) {
//todo
}
void WebGPUDriver::destroyFence(Handle<HwFence> fh) {
//todo
void WebGPUDriver::destroyFence(Handle<HwFence> fenceHandle) {
if (fenceHandle) {
destructHandle<WebGPUFence>(fenceHandle);
}
}
FenceStatus WebGPUDriver::getFenceStatus(Handle<HwFence> fh) {
//todo
return FenceStatus::CONDITION_SATISFIED;
FenceStatus WebGPUDriver::getFenceStatus(Handle<HwFence> fenceHandle) {
const auto fence = handleCast<WebGPUFence>(fenceHandle);
if (!fence) {
return FenceStatus::ERROR;
}
return fence->getStatus();
}
// We create all textures using VK_IMAGE_TILING_OPTIMAL, so our definition of "supported" is that
@@ -529,8 +548,8 @@ bool WebGPUDriver::isTextureSwizzleSupported() {
}
bool WebGPUDriver::isTextureFormatMipmappable(const TextureFormat format) {
//todo
return true;
return WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(
WebGPUTexture::fToWGPUTextureFormat(format));
}
bool WebGPUDriver::isRenderTargetFormatSupported(const TextureFormat format) {
@@ -605,15 +624,28 @@ uint8_t WebGPUDriver::getMaxDrawBuffers() {
}
size_t WebGPUDriver::getMaxUniformBufferSize() {
return 16384u;
return mDeviceLimits.maxUniformBufferBindingSize;
}
size_t WebGPUDriver::getMaxTextureSize(const SamplerType target) {
return 2048u;
size_t result = 2048u;
switch (target) {
case SamplerType::SAMPLER_2D:
case SamplerType::SAMPLER_2D_ARRAY:
case SamplerType::SAMPLER_EXTERNAL:
case SamplerType::SAMPLER_CUBEMAP:
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
result = mDeviceLimits.maxTextureDimension2D;
break;
case SamplerType::SAMPLER_3D:
result = mDeviceLimits.maxTextureDimension3D;
break;
}
return result;
}
size_t WebGPUDriver::getMaxArrayTextureLayers() {
return 256u;
return mDeviceLimits.maxTextureArrayLayers;
}
void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
@@ -692,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 };
@@ -738,58 +749,62 @@ void WebGPUDriver::setExternalStream(Handle<HwTexture> textureHandle,
}
void WebGPUDriver::generateMipmaps(Handle<HwTexture> textureHandle) {
if (!mCommandEncoder) {
mMipQueue.push_back(textureHandle);
return;
}
auto texture = handleCast<WebGPUTexture>(textureHandle);
assert_invariant(texture);
wgpu::Texture wgpuTexture = texture->getTexture();
assert_invariant(wgpuTexture);
FILAMENT_CHECK_PRECONDITION(wgpuTexture.GetUsage() & wgpu::TextureUsage::CopySrc)
<< "Texture intended for mipmap generation (as source) must have CopySrc usage.";
FILAMENT_CHECK_PRECONDITION(wgpuTexture.GetUsage() & wgpu::TextureUsage::CopyDst)
<< "Texture intended for mipmap generation (as destination) must have CopyDst usage.";
uint32_t mipLevelCount = wgpuTexture.GetMipLevelCount();
if (mipLevelCount <= 1) {
return;
const uint32_t totalMipLevels = wgpuTexture.GetMipLevelCount();
if (totalMipLevels <= 1) {
return; // nothing to do
}
uint32_t width = wgpuTexture.GetWidth();
uint32_t height = wgpuTexture.GetHeight();
// For 3D textures, depth is > 1. For 2D/Cube/Array, effectively 1 for mip-level copies.
uint32_t depth =
(texture->target == SamplerType::SAMPLER_3D) ? wgpuTexture.GetDepthOrArrayLayers() : 1;
const auto usage = wgpuTexture.GetUsage();
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::TextureBinding)
<< "Texture for mipmap generation must have TextureBinding usage.";
for (uint32_t mipLevel = 0; mipLevel < mipLevelCount - 1; ++mipLevel) {
wgpu::TexelCopyTextureInfo sourceCopyInfo{
.texture = wgpuTexture,
.mipLevel = mipLevel,
.aspect = texture->getAspect(),
};
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);
wgpu::TexelCopyTextureInfo destinationCopyInfo{
.texture = wgpuTexture,
.mipLevel = mipLevel + 1,
.aspect = texture->getAspect(),
};
spd::SPDPassConfig spdConfig = { .filter = spd::SPDFilter::Average,
.targetTexture = wgpuTexture,
.numMips = totalMipLevels,
.halfPrecision = false,
.sourceMipLevel = 0 };
uint32_t dstWidth = std::max(1u, width >> 1);
uint32_t dstHeight = std::max(1u, height >> 1);
uint32_t dstDepth = std::max(1u, depth >> 1);
mSpdComputePassMipmapGenerator.Generate(encoder, wgpuTexture, spdConfig);
wgpu::Extent3D copySize{ .width = dstWidth,
.height = dstHeight,
.depthOrArrayLayers = (texture->target == SamplerType::SAMPLER_3D)
? dstDepth
: texture->getArrayLayerCount() };
mCommandEncoder.CopyTextureToTexture(&sourceCopyInfo, &destinationCopyInfo, &copySize);
width = dstWidth;
height = dstHeight;
depth = dstDepth;
// 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;
}
}
@@ -809,12 +824,16 @@ 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 customDepthView = nullptr;
wgpu::TextureFormat customDepthFormat = wgpu::TextureFormat::Undefined;
wgpu::TextureFormat customStencilFormat = wgpu::TextureFormat::Undefined;
wgpu::TextureView customStencilView = nullptr;
wgpu::TextureView customDepthStencilView = nullptr;
wgpu::TextureView customDepthStencilMsaaSidecarTextureView = nullptr;
wgpu::TextureFormat customDepthStencilFormat = wgpu::TextureFormat::Undefined;
mCurrentRenderTarget = renderTarget;
if (renderTarget->isDefaultRenderTarget()) {
@@ -822,6 +841,20 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
defaultColorView = mTextureView;
defaultDepthStencilView = mSwapChain->getDepthTextureView();
defaultDepthStencilFormat = mSwapChain->getDepthFormat();
if (any(renderTarget->getTargetFlags() & TargetBufferFlags::STENCIL) &&
!(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) &&
!(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
const auto& colorInfos = renderTarget->getColorAttachmentInfos();
@@ -836,41 +869,78 @@ 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++;
}
}
}
const auto& depthInfo = renderTarget->getDepthAttachmentInfo();
if (depthInfo.handle) {
FILAMENT_CHECK_POSTCONDITION(depthInfo.layer < renderTarget->getLayerCount())
<< "Depth attachment requests layer " << depthInfo.layer
<< "but render target has only " << renderTarget->getLayerCount() << ".";
auto depthTexture = handleCast<WebGPUTexture>(depthInfo.handle);
if (depthTexture) {
const uint8_t depthMipLevel = depthInfo.level;
const uint32_t depthArrayLayer = depthInfo.layer;
customDepthView =
depthTexture->getOrMakeTextureView(depthMipLevel, depthArrayLayer);
customDepthFormat = depthTexture->getWebGPUFormat();
}
const auto& stencilInfo = renderTarget->getStencilAttachmentInfo();
Handle<HwTexture> depthStencilSourceHandle = {};
uint8_t depthStencilMipLevel = 0;
uint32_t depthStencilArrayLayer = 0;
if (depthInfo.handle && stencilInfo.handle) {
auto const depthTexture{handleCast<WebGPUTexture>(depthInfo.handle)->getTexture()};
auto const stencilTexture{handleCast<WebGPUTexture>(stencilInfo.handle)->getTexture()};
FILAMENT_CHECK_POSTCONDITION(depthTexture.Get() == stencilTexture.Get())
<< "Filament must reference the same resource if both depth and stencil "
"textures are present";
}
const auto& stencilInfo = renderTarget->getStencilAttachmentInfo();
if (stencilInfo.handle) {
// If depth and stencil use the same texture handle, this will re-cast but that's fine.
auto stencilTexture = handleCast<WebGPUTexture>(stencilInfo.handle);
if (stencilTexture) {
FILAMENT_CHECK_POSTCONDITION(stencilInfo.layer < renderTarget->getLayerCount())
<< "Stencil attachment requests layer " << stencilInfo.layer
<< " but render target has only " << renderTarget->getLayerCount()
<< " layers.";
const uint8_t stencilMipLevel = stencilInfo.level;
const uint32_t stencilArrayLayer = stencilInfo.layer;
customStencilView =
stencilTexture->getOrMakeTextureView(stencilMipLevel, stencilArrayLayer);
customStencilFormat = stencilTexture->getWebGPUFormat();
if (depthInfo.handle) {
depthStencilSourceHandle = depthInfo.handle;
depthStencilMipLevel = depthInfo.level;
depthStencilArrayLayer = depthInfo.layer;
} else if (stencilInfo.handle) {
depthStencilSourceHandle = stencilInfo.handle;
depthStencilMipLevel = stencilInfo.level;
depthStencilArrayLayer = stencilInfo.layer;
}
if (depthStencilSourceHandle) {
auto dsTexture = handleCast<WebGPUTexture>(depthStencilSourceHandle);
if (dsTexture) {
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) &&
!(hasStencil(customDepthStencilFormat))) {
FILAMENT_CHECK_POSTCONDITION(false)
<< "Custom render target requested stencil, but the provided texture"
"format number"
<< (uint32_t) customDepthStencilFormat
<< " does not have a stencil aspect.";
}
if (any(renderTarget->getTargetFlags() & TargetBufferFlags::DEPTH) &&
!(hasDepth(customDepthStencilFormat))) {
FILAMENT_CHECK_POSTCONDITION(false) << "Custom render target requested depth, "
"but the provided texture format number"
<< (uint32_t) customDepthStencilFormat
<< " does not have a depth aspect.";
}
}
}
}
@@ -879,13 +949,11 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
params,
defaultColorView,
defaultDepthStencilView,
defaultDepthStencilFormat,
customColorViews.data(),
customColorMsaaSidecarViews.data(),
customColorViewCount,
customDepthView,
customStencilView,
customDepthFormat,
customStencilFormat);
customDepthStencilView,
customDepthStencilMsaaSidecarTextureView);
mRenderPassEncoder = mCommandEncoder.BeginRenderPass(&renderPassDescriptor);
@@ -920,16 +988,26 @@ void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
wgpu::Extent2D surfaceSize = mPlatform.getSurfaceExtent(mNativeWindow);
mTextureView = mSwapChain->getCurrentSurfaceTextureView(surfaceSize);
assert_invariant(mTextureView);
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = {
.label = "command_encoder"
};
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
if (!mMipQueue.empty()) {
for (auto& handle: mMipQueue) {
generateMipmaps(handle);
assert_invariant(mDefaultRenderTarget);
wgpu::TextureFormat depthFormat = mSwapChain->getDepthFormat();
TargetBufferFlags newTargetFlags = filament::backend::TargetBufferFlags::NONE;
//Assuming Color always present in default render target.
newTargetFlags |= filament::backend::TargetBufferFlags::COLOR;
if (depthFormat != wgpu::TextureFormat::Undefined) {
if (hasDepth(depthFormat)) {
newTargetFlags |= filament::backend::TargetBufferFlags::DEPTH;
}
if (hasStencil(depthFormat)) {
newTargetFlags |= filament::backend::TargetBufferFlags::STENCIL;
}
mMipQueue.clear();
}
mDefaultRenderTarget->setTargetFlags(newTargetFlags);
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = { .label = "frame_command_encoder" };
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
assert_invariant(mCommandEncoder);
}
@@ -941,6 +1019,22 @@ void WebGPUDriver::commit(Handle<HwSwapChain> sch) {
assert_invariant(mCommandBuffer);
mCommandEncoder = nullptr;
mQueue.Submit(1, &mCommandBuffer);
static bool firstRender = true;
// For the first frame rendered, we need to make sure the work is done before presenting or we
// get a purple flash
if (firstRender) {
auto f = mQueue.OnSubmittedWorkDone(wgpu::CallbackMode::WaitAnyOnly,
[=](wgpu::QueueWorkDoneStatus) {});
const wgpu::Instance instance = mAdapter.GetInstance();
auto wStatus = instance.WaitAny(f,
std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count());
if (wStatus != wgpu::WaitStatus::Success) {
FWGPU_LOGW << "Waiting for first frame work to finish resulted in an error"
<< static_cast<uint32_t>(wStatus);
}
firstRender = false;
}
mCommandBuffer = nullptr;
mTextureView = nullptr;
assert_invariant(mSwapChain);
@@ -993,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,
@@ -1003,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);
@@ -1047,14 +1178,15 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
assert_invariant(vertexBufferInfo);
std::vector<wgpu::TextureFormat> pipelineColorFormats;
wgpu::TextureFormat pipelineDepthFormat = wgpu::TextureFormat::Undefined;
wgpu::TextureFormat pipelineDepthStencilFormat = wgpu::TextureFormat::Undefined;
uint8_t pipelineSamples = 1;
bool const requestedDepth = any(mCurrentRenderTarget->getTargetFlags() & TargetBufferFlags::DEPTH);
bool const requestedStencil = any(mCurrentRenderTarget->getTargetFlags() & TargetBufferFlags::STENCIL);
pipelineSamples = mCurrentRenderTarget->getSamples();
if (mCurrentRenderTarget->isDefaultRenderTarget()) {
pipelineColorFormats.push_back(mSwapChain->getColorFormat());
pipelineDepthFormat = mSwapChain->getDepthFormat();
pipelineSamples =
mCurrentRenderTarget->getSamples();// Default RT should have samples (usually 1)
pipelineDepthStencilFormat = mSwapChain->getDepthFormat();
} else {
const auto& mrtColorAttachments = mCurrentRenderTarget->getColorAttachmentInfos();
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; ++i) {
@@ -1068,21 +1200,19 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
const auto& depthInfo = mCurrentRenderTarget->getDepthAttachmentInfo();
const auto& stencilInfo = mCurrentRenderTarget->getStencilAttachmentInfo();
Handle<HwTexture> depthStencilHandle = {};
if (depthInfo.handle) {
FILAMENT_CHECK_POSTCONDITION(!stencilInfo.handle)
<< "depth and stencil attachments cannot both be provided for WebGPU";
const auto depthTexture = handleCast<WebGPUTexture>(depthInfo.handle);
if (depthTexture) pipelineDepthFormat = depthTexture->getTexture().GetFormat();
} else {
if (stencilInfo.handle) {
const auto stencilTexture = handleCast<WebGPUTexture>(stencilInfo.handle);
// Assuming combined depth/stencil format if only stencil is present
if (stencilTexture) {
pipelineDepthFormat = stencilTexture->getTexture().GetFormat();
}
depthStencilHandle = depthInfo.handle;
} else if (stencilInfo.handle) {
depthStencilHandle = stencilInfo.handle;
}
if (depthStencilHandle) {
const auto dsTexture = handleCast<WebGPUTexture>(depthStencilHandle);
if (dsTexture) {
pipelineDepthStencilFormat = dsTexture->getTexture().GetFormat();
}
}
pipelineSamples = mCurrentRenderTarget->getSamples();
}
// TODO: We expected this to be a sane check, however it complains when running shadowtest.
@@ -1094,9 +1224,9 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
wgpu::RenderPipeline pipeline = createWebGPURenderPipeline(mDevice, *program, *vertexBufferInfo,
layout, pipelineState.rasterState, pipelineState.stencilState,
pipelineState.polygonOffset, pipelineState.primitiveType, pipelineColorFormats,
pipelineDepthFormat, pipelineSamples);
pipelineDepthStencilFormat, pipelineSamples, requestedDepth, requestedStencil);
assert_invariant(pipeline);
mPipelineMap[hash] = pipeline;
mPipelineMap[pipelineKey] = pipeline;
mRenderPassEncoder.SetPipeline(pipeline);
}
+17 -3
View File
@@ -19,6 +19,8 @@
#include "WebGPURenderTarget.h"
#include "webgpu/WebGPUConstants.h"
#include "webgpu/WebGPUMsaaTextureResolver.h"
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
#include <backend/platforms/WebGPUPlatform.h>
#include "DriverBase.h"
@@ -29,6 +31,8 @@
#include <utils/compiler.h>
#include "SpdMipmapGenerator/SpdMipmapGenerator.h"
#include <tsl/robin_map.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
@@ -53,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);
@@ -70,14 +74,16 @@ private:
WebGPUSwapChain* mSwapChain = nullptr;
uint64_t mNextFakeHandle = 1;
wgpu::CommandEncoder mCommandEncoder = nullptr;
std::vector<Handle<HwTexture>> mMipQueue;
wgpu::TextureView mTextureView = nullptr;
wgpu::RenderPassEncoder mRenderPassEncoder = nullptr;
wgpu::CommandBuffer mCommandBuffer = nullptr;
WebGPURenderTarget* mDefaultRenderTarget = nullptr;
WebGPURenderTarget* mCurrentRenderTarget = nullptr;
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;
@@ -85,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
*/
@@ -119,6 +128,11 @@ private:
return mHandleAllocator.construct<D>(handle, std::forward<ARGS>(args)...);
}
template<typename D, typename B, typename... ARGS>
Handle<B> allocAndConstructHandle(ARGS&&... args) {
return mHandleAllocator.allocateAndConstruct<D>(std::forward<ARGS>(args)...);
}
template<typename D, typename B>
D* handleCast(Handle<B> handle) noexcept {
return mHandleAllocator.handle_cast<D*>(handle);
@@ -0,0 +1,50 @@
/*
* 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 "WebGPUFence.h"
#include <backend/DriverEnums.h>
#include <webgpu/webgpu_cpp.h>
#include <atomic>
namespace filament::backend {
FenceStatus WebGPUFence::getStatus() { return mStatus.load(); }
void WebGPUFence::addMarkerToQueueState(wgpu::Queue const& queue) {
queue.OnSubmittedWorkDone(
wgpu::CallbackMode::AllowSpontaneous,
[](const wgpu::QueueWorkDoneStatus status,
std::atomic<FenceStatus>* const fenceStatus) {
if (!fenceStatus) {
return;
}
switch (status) {
case wgpu::QueueWorkDoneStatus::Success:
fenceStatus->store(FenceStatus::CONDITION_SATISFIED);
break;
case wgpu::QueueWorkDoneStatus::CallbackCancelled:
case wgpu::QueueWorkDoneStatus::Error:
fenceStatus->store(FenceStatus::ERROR);
break;
}
},
&mStatus);
}
} // namespace filament::backend
+43
View File
@@ -0,0 +1,43 @@
/*
* 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_WEBGPUFENCE_H
#define TNT_FILAMENT_BACKEND_WEBGPUFENCE_H
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#include <atomic>
namespace wgpu {
class Queue;
} // namespace wgpu
namespace filament::backend {
class WebGPUFence final : public HwFence {
public:
[[nodiscard]] FenceStatus getStatus();
void addMarkerToQueueState(wgpu::Queue const&);
private:
std::atomic<FenceStatus> mStatus{ FenceStatus::TIMEOUT_EXPIRED };
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUFENCE_H
@@ -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
@@ -169,39 +169,52 @@ wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device,
wgpu::PipelineLayout const& layout, RasterState const& rasterState,
StencilState const& stencilState, PolygonOffset const& polygonOffset,
const PrimitiveType primitiveType, std::vector<wgpu::TextureFormat> const& colorFormats,
const wgpu::TextureFormat depthFormat, const uint8_t samplesCount) {
wgpu::TextureFormat const& depthStencilFormat, const uint8_t samplesCount, const bool requestedDepth, const bool requestedStencil) {
assert_invariant(program.vertexShaderModule);
wgpu::DepthStencilState depthStencilState{};
if (depthFormat != wgpu::TextureFormat::Undefined) {
depthStencilState.format = depthFormat;
depthStencilState.depthWriteEnabled = rasterState.depthWrite;
depthStencilState.depthCompare = toWebGPU(rasterState.depthFunc);
depthStencilState.stencilFront = {
.compare = toWebGPU(stencilState.front.stencilFunc),
.failOp = toWebGPU(stencilState.front.stencilOpStencilFail),
.depthFailOp = toWebGPU(stencilState.front.stencilOpDepthFail),
.passOp = toWebGPU(stencilState.front.stencilOpDepthStencilPass),
};
depthStencilState.stencilBack = {
.compare = toWebGPU(stencilState.back.stencilFunc),
.failOp = toWebGPU(stencilState.back.stencilOpStencilFail),
.depthFailOp = toWebGPU(stencilState.back.stencilOpDepthFail),
.passOp = toWebGPU(stencilState.back.stencilOpDepthStencilPass),
};
depthStencilState.stencilReadMask =
stencilState.front.readMask; // Use front face's comparison mask for read mask
depthStencilState.stencilWriteMask = stencilState.stencilWrite ? 0xFFFFFFFF : 0u;
depthStencilState.depthBias = static_cast<int32_t>(polygonOffset.constant);
depthStencilState.depthBiasSlopeScale = polygonOffset.slope;
depthStencilState.depthBiasClamp = 0.0f;
const bool depthOrStencilRequested = (requestedDepth || requestedStencil);
if (!hasStencilAspect(depthFormat)) {
depthStencilState.stencilFront.compare = wgpu::CompareFunction::Always;
if (depthOrStencilRequested) {
FILAMENT_CHECK_PRECONDITION(depthStencilFormat != wgpu::TextureFormat::Undefined)
<< "Depth or Stencil requested for pipeline, but depthStencilFormat is "
"wgpu::TextureFormat::Undefined.";
depthStencilState.format = depthStencilFormat;
if (requestedDepth) {
depthStencilState.depthWriteEnabled = rasterState.depthWrite;
depthStencilState.depthCompare = toWebGPU(rasterState.depthFunc);
depthStencilState.depthBias = static_cast<int32_t>(polygonOffset.constant);
depthStencilState.depthBiasSlopeScale = polygonOffset.slope;
depthStencilState.depthBiasClamp = 0.0f;
} else {
depthStencilState.depthWriteEnabled = false;
depthStencilState.depthCompare = wgpu::CompareFunction::Undefined;
depthStencilState.depthBias = 0;
depthStencilState.depthBiasSlopeScale = 0.0f;
depthStencilState.depthBiasClamp = 0.0f;
}
if (hasStencilAspect(depthStencilFormat) && requestedStencil) {
depthStencilState.stencilFront = {
.compare = toWebGPU(stencilState.front.stencilFunc),
.failOp = toWebGPU(stencilState.front.stencilOpStencilFail),
.depthFailOp = toWebGPU(stencilState.front.stencilOpDepthFail),
.passOp = toWebGPU(stencilState.front.stencilOpDepthStencilPass),
};
depthStencilState.stencilBack = {
.compare = toWebGPU(stencilState.back.stencilFunc),
.failOp = toWebGPU(stencilState.back.stencilOpStencilFail),
.depthFailOp = toWebGPU(stencilState.back.stencilOpDepthFail),
.passOp = toWebGPU(stencilState.back.stencilOpDepthStencilPass),
};
depthStencilState.stencilReadMask = stencilState.front.readMask;
depthStencilState.stencilWriteMask = stencilState.stencilWrite ? stencilState.front.writeMask : 0u;
} else {
depthStencilState.stencilFront.compare = wgpu::CompareFunction::Undefined;
depthStencilState.stencilFront.failOp = wgpu::StencilOperation::Keep;
depthStencilState.stencilFront.depthFailOp = wgpu::StencilOperation::Keep;
depthStencilState.stencilFront.passOp = wgpu::StencilOperation::Keep;
depthStencilState.stencilBack =
depthStencilState.stencilFront; // Keep back and front consistent
depthStencilState.stencilBack = depthStencilState.stencilFront;
depthStencilState.stencilReadMask = 0;
depthStencilState.stencilWriteMask = 0;
}
@@ -245,11 +258,11 @@ wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device,
.unclippedDepth = !rasterState.depthClamp &&
device.HasFeature(wgpu::FeatureName::DepthClipControl)
},
.depthStencil = depthFormat != wgpu::TextureFormat::Undefined ? &depthStencilState: nullptr,
.depthStencil = depthOrStencilRequested ? &depthStencilState: nullptr,
.multisample = {
.count = samplesCount,
.mask = 0xFFFFFFFF,
.alphaToCoverageEnabled = rasterState.alphaToCoverage
.alphaToCoverageEnabled = (samplesCount > 1) && rasterState.alphaToCoverage
},
.fragment = nullptr // will add below if fragment module is included
};
@@ -40,8 +40,8 @@ class WebGPUProgram;
[[nodiscard]] wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const&,
WebGPUProgram const&, WebGPUVertexBufferInfo const&, wgpu::PipelineLayout const&,
RasterState const&, StencilState const&, PolygonOffset const&, PrimitiveType primitiveType,
std::vector<wgpu::TextureFormat> const& colorFormats, wgpu::TextureFormat depthFormat,
uint8_t samplesCount);
std::vector<wgpu::TextureFormat> const& colorFormats, wgpu::TextureFormat const& depthStencilFormat,
uint8_t samplesCount, bool requestedDepth, bool requestedStencil);
}// namespace filament::backend
@@ -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
+204 -101
View File
@@ -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,30 +30,120 @@
#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)
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 },
mSamples{ samples },
mLayerCount{ layerCount },
mColorAttachments{ colorAttachmentsMRT },
mDepthAttachment{ depthAttachmentInfo },
mStencilAttachment{ stencilAttachmentInfo } {
// TODO consider making this an array
mColorAttachmentDescriptors.reserve(MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
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);
}
// Default constructor for the default render target
WebGPURenderTarget::WebGPURenderTarget()
: HwRenderTarget{ 0, 0 },
mDefaultRenderTarget{ true },
mTargetFlags{ TargetBufferFlags::NONE },
mSamples{ 1 },
mLayerCount{ 1 } {}
mLayerCount{ 1 },
mSampleCountPerAttachment{ 1 } {}
wgpu::LoadOp WebGPURenderTarget::getLoadOperation(RenderPassParams const& params,
const TargetBufferFlags bufferToOperateOn) {
@@ -57,7 +151,7 @@ wgpu::LoadOp WebGPURenderTarget::getLoadOperation(RenderPassParams const& params
return wgpu::LoadOp::Clear;
}
if (any(params.flags.discardStart & bufferToOperateOn)) {
return wgpu::LoadOp::Clear; // Or wgpu::LoadOp::Undefined if clear is not desired on discard
return wgpu::LoadOp::Clear;
}
return wgpu::LoadOp::Load;
}
@@ -73,128 +167,137 @@ 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::TextureFormat const& defaultDepthStencilFormat,
wgpu::TextureView const* customColorTextureViews, uint32_t customColorTextureViewCount,
wgpu::TextureView const& customDepthTextureView,
wgpu::TextureView const& customStencilTextureView,
const wgpu::TextureFormat customDepthFormat,
const wgpu::TextureFormat customStencilFormat) {
mColorAttachmentDescriptors.clear();
mHasDepthStencilAttachment = false;
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;
// Color attachments
if (mDefaultRenderTarget) {
assert_invariant(defaultColorTextureView);
mColorAttachmentDescriptors.push_back({ .view = defaultColorTextureView,
mColorAttachmentDesc.push_back({
.view = defaultColorTextureView,
.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 } });
if (defaultDepthStencilTextureView) {
mDepthStencilAttachmentDescriptor = {
.view = defaultDepthStencilTextureView,
.depthLoadOp =
WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::DEPTH),
.depthStoreOp =
WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::DEPTH),
.depthClearValue = static_cast<float>(params.clearDepth),
.depthReadOnly =
(params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0,
.stencilLoadOp = wgpu::LoadOp::Undefined,
.stencilStoreOp = wgpu::StoreOp::Undefined,
.stencilClearValue = params.clearStencil,
.stencilReadOnly =
(params.readOnlyDepthStencil & RenderPassParams::READONLY_STENCIL) > 0,
};
if (defaultDepthStencilFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
defaultDepthStencilFormat == wgpu::TextureFormat::Depth32FloatStencil8 ||
defaultDepthStencilFormat == wgpu::TextureFormat::Stencil8) {
mDepthStencilAttachmentDescriptor.stencilLoadOp =
WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::STENCIL);
mDepthStencilAttachmentDescriptor.stencilStoreOp =
WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::STENCIL);
}
mHasDepthStencilAttachment = true;
}
} else { // Custom Render Target
.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]) {
mColorAttachmentDescriptors.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,
},
});
}
}
}
outDescriptor.colorAttachmentCount = mColorAttachmentDesc.size();
outDescriptor.colorAttachments = mColorAttachmentDesc.data();
// Depth/Stencil Attachments
if (mHasDepthStencilAttachment) {
wgpu::TextureView depthStencilViewToUse{ nullptr };
if (mDefaultRenderTarget) {
depthStencilViewToUse = defaultDepthStencilTextureView;
assert_invariant(depthStencilViewToUse);
} else {
if (customDepthStencilTextureView) {
depthStencilViewToUse = hasMsaaSidecars ? customDepthStencilMsaaSidecarTextureView
: customDepthStencilTextureView;
}
}
FILAMENT_CHECK_POSTCONDITION(!(customDepthTextureView && customStencilTextureView))
<< "WebGPU CANNOT support separate texture views for depth + stencil. depth + "
"stencil needs to be in one texture view";
const bool hasStencil =
customStencilTextureView ||
(customDepthFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
customDepthFormat == wgpu::TextureFormat::Depth32FloatStencil8);
const bool hasDepth =
customDepthTextureView ||
(customStencilFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
customDepthFormat == wgpu::TextureFormat::Depth32FloatStencil8);
if (customDepthTextureView || customStencilTextureView) {
assert_invariant((hasDepth || hasStencil) &&
"Depth or Texture view without a valid texture format");
mDepthStencilAttachmentDescriptor = {};
mDepthStencilAttachmentDescriptor.view =
customDepthTextureView ? customDepthTextureView : customStencilTextureView;
if (depthStencilViewToUse) {
mDepthStencilAttachmentDesc = {};
mDepthStencilAttachmentDesc.view = depthStencilViewToUse;
if (hasDepth) {
mDepthStencilAttachmentDescriptor.depthLoadOp =
WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::DEPTH);
mDepthStencilAttachmentDescriptor.depthStoreOp =
WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::DEPTH);
mDepthStencilAttachmentDescriptor.depthClearValue =
static_cast<float>(params.clearDepth);
mDepthStencilAttachmentDescriptor.depthReadOnly =
(params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0;
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 {
mDepthStencilAttachmentDescriptor.depthLoadOp = wgpu::LoadOp::Undefined;
mDepthStencilAttachmentDescriptor.depthStoreOp = wgpu::StoreOp::Undefined;
mDepthStencilAttachmentDescriptor.depthReadOnly = true;
mDepthStencilAttachmentDesc.depthLoadOp = wgpu::LoadOp::Undefined;
mDepthStencilAttachmentDesc.depthStoreOp = wgpu::StoreOp::Undefined;
mDepthStencilAttachmentDesc.depthReadOnly = true;
}
if (hasStencil) {
mDepthStencilAttachmentDescriptor.stencilLoadOp =
WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::STENCIL);
mDepthStencilAttachmentDescriptor.stencilStoreOp =
WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::STENCIL);
mDepthStencilAttachmentDescriptor.stencilClearValue = params.clearStencil;
mDepthStencilAttachmentDescriptor.stencilReadOnly =
mDepthStencilAttachmentDesc.stencilLoadOp =
getLoadOperation(params, TargetBufferFlags::STENCIL);
mDepthStencilAttachmentDesc.stencilStoreOp =
getStoreOperation(params, TargetBufferFlags::STENCIL);
mDepthStencilAttachmentDesc.stencilClearValue = params.clearStencil;
mDepthStencilAttachmentDesc.stencilReadOnly =
(params.readOnlyDepthStencil & RenderPassParams::READONLY_STENCIL) > 0;
} else {
mDepthStencilAttachmentDescriptor.stencilLoadOp = wgpu::LoadOp::Undefined;
mDepthStencilAttachmentDescriptor.stencilStoreOp = wgpu::StoreOp::Undefined;
mDepthStencilAttachmentDescriptor.stencilReadOnly = true;
mDepthStencilAttachmentDesc.stencilLoadOp = wgpu::LoadOp::Undefined;
mDepthStencilAttachmentDesc.stencilStoreOp = wgpu::StoreOp::Undefined;
mDepthStencilAttachmentDesc.stencilReadOnly = true;
}
mHasDepthStencilAttachment = true;
outDescriptor.depthStencilAttachment = &mDepthStencilAttachmentDesc;
} else {
outDescriptor.depthStencilAttachment = nullptr;
mDepthStencilAttachmentDesc = {};
}
} else {
outDescriptor.depthStencilAttachment = nullptr;
mDepthStencilAttachmentDesc = {};
}
outDescriptor.colorAttachmentCount = mColorAttachmentDescriptors.size();
outDescriptor.colorAttachments = mColorAttachmentDescriptors.data();
outDescriptor.depthStencilAttachment =
mHasDepthStencilAttachment ? &mDepthStencilAttachmentDescriptor : nullptr;
// 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
} // 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,44 +37,44 @@ public:
WebGPURenderTarget(uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount,
MRT const& colorAttachments, Attachment const& depthAttachment,
Attachment const& stencilAttachment);
Attachment const& stencilAttachment, TargetBufferFlags const& targetFlags,
std::function<WebGPUTexture*(const Handle<HwTexture>)> const&, wgpu::Device const&);
// Default constructor for the default render target
WebGPURenderTarget();
// Updated signature: takes resolved views for custom RTs, and default views for default RT
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,
wgpu::TextureFormat const& defaultDepthStencilFormat,
// 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& customDepthTextureView,
wgpu::TextureView const& customStencilTextureView,
wgpu::TextureFormat customDepthFormat,
wgpu::TextureFormat customStencilFormat);
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; }
// Accessors for the driver to get stored attachment info
[[nodiscard]] MRT const& getColorAttachmentInfos() const { return mColorAttachments; }
[[nodiscard]] Attachment const& getDepthAttachmentInfo() const { return mDepthAttachment; }
[[nodiscard]] Attachment const& getStencilAttachmentInfo() const { return mStencilAttachment; }
// Static helpers for load/store operations
[[nodiscard]] static wgpu::LoadOp getLoadOperation(RenderPassParams const& params,
TargetBufferFlags buffer);
[[nodiscard]] static wgpu::StoreOp getStoreOperation(RenderPassParams const& params,
TargetBufferFlags buffer);
[[nodiscard]] TargetBufferFlags getTargetFlags() const { return mTargetFlags; }
void setTargetFlags( TargetBufferFlags value) { mTargetFlags = value; }
private:
bool mDefaultRenderTarget = false;
TargetBufferFlags mTargetFlags = TargetBufferFlags::NONE;
uint8_t mSamples = 1;
uint8_t mLayerCount = 1;
@@ -81,9 +84,11 @@ private:
Attachment mDepthAttachment{};
Attachment mStencilAttachment{};
uint8_t mSampleCountPerAttachment = 0;
// Cached descriptors for the render pass
std::vector<wgpu::RenderPassColorAttachment> mColorAttachmentDescriptors;
wgpu::RenderPassDepthStencilAttachment mDepthStencilAttachmentDescriptor{};
std::vector<wgpu::RenderPassColorAttachment> mColorAttachmentDesc;
wgpu::RenderPassDepthStencilAttachment mDepthStencilAttachmentDesc{};
bool mHasDepthStencilAttachment = false;
};
+130 -66
View File
@@ -21,50 +21,27 @@
#include <backend/DriverEnums.h>
#include <utils/Panic.h>
#include <utils/ostream.h>
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
#include <dawn/webgpu_cpp_print.h>
#endif
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
#include <sstream>
#endif
#include <string>
#include <string_view>
/**
* Reusable set of convenience functions for strings -- generally string views, literals, &
* streaming -- used in the WebGPU backend
* strings -- used in the WebGPU backend
*/
namespace filament::backend {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
/**
* Convenience template to print WGPU types (which don't support Filament's utils::io::ostream)
* @tparam WebGPUPrintable e.g. wgpu::FeatureName type
* @param out stream to print to
* @param printable the instance to print
* @return the stream printed to
*
* Example usage (because including the template operator override itself does not compile):
* #if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
* utils::io::ostream& operator<<(utils::io::ostream& out,
* const wgpu::FeatureName featureName) noexcept {
* return streamInsertWebGPUPrintable(out, featureName);
* }
* #endif
*/
template<typename WebGPUPrintable>
utils::io::ostream& streamInsertWebGPUPrintable(utils::io::ostream& out,
const WebGPUPrintable printable) {
std::stringstream printableStream;
printableStream << printable;
out << printableStream.str();
return out;
[[nodiscard]] inline std::string webGPUPrintableToString(const WebGPUPrintable printable) {
std::stringstream out;
out << printable;
return out.str();
}
#endif
@@ -115,46 +92,17 @@ utils::io::ostream& streamInsertWebGPUPrintable(utils::io::ostream& out,
}
}
/**
* Convenience template to print wgpu::RequestAdapterOptions
* @tparam STREAM_TYPE useful namely for streaming to either an utils::io::ostream OR
* utils::details::PanicStream, but also for any kind of stream
* @param out stream to print to
* @param options instance to be printed
* @return stream printed to
*
* Example usage (because including the template operator override itself does not compile):
* template<typename STREAM_TYPE>
* STREAM_TYPE& operator<<(STREAM_TYPE& out, wgpu::RequestAdapterOptions const& options) noexcept {
* return streamInsertRequestAdapterOptions(out, options);
* }
*/
template<typename STREAM_TYPE>
STREAM_TYPE& streamInsertRequestAdapterOptions(STREAM_TYPE& out,
wgpu::RequestAdapterOptions const& options) noexcept {
[[nodiscard]] inline std::string adapterOptionsToString(
wgpu::RequestAdapterOptions const& options) {
std::stringstream out;
out << "power preference " << powerPreferenceToString(options.powerPreference)
<< " force fallback adapter " << bool(options.forceFallbackAdapter) << " backend type "
<< backendTypeToString(options.backendType);
return out;
return out.str();
}
/**
* Convenience template to print wgpu::AdapterInfo
* @tparam STREAM_TYPE useful namely for streaming to either an utils::io::ostream OR
* utils::details::PanicStream, but also for any kind of stream
* @param out stream to print to
* @param options instance to be printed
* @return stream printed to
*
* Example usage (because including the template operator override itself does not compile):
* template<typename STREAM_TYPE>
* STREAM_TYPE& operator<<(STREAM_TYPE& out, wgpu::AdapterInfo const& info) noexcept {
* return streamInsertRequestAdapterInfo(out, info);
* }
*/
template<class STREAM_TYPE>
STREAM_TYPE& streamInsertRequestAdapterInfo(STREAM_TYPE& out,
wgpu::AdapterInfo const& info) noexcept {
[[nodiscard]] inline std::string adapterInfoToString(wgpu::AdapterInfo const& info) {
std::stringstream out;
out << "vendor (" << info.vendorID << ") '" << info.vendor
<< "' device (" << info.deviceID << ") '" << info.device
<< "' adapter " << adapterTypeToString(info.adapterType)
@@ -162,7 +110,7 @@ STREAM_TYPE& streamInsertRequestAdapterInfo(STREAM_TYPE& out,
<< " architecture '" << info.architecture
<< "' subgroupMinSize " << info.subgroupMinSize
<< " subgroupMaxSize " << info.subgroupMaxSize;
return out;
return out.str();
}
[[nodiscard]] constexpr std::string_view deviceLostReasonToString(
@@ -175,7 +123,123 @@ STREAM_TYPE& streamInsertRequestAdapterInfo(STREAM_TYPE& out,
}
}
[[nodiscard]] constexpr std::string_view filamentShaderStageToString(ShaderStage stage) {
[[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";
case ShaderStage::FRAGMENT: return "fragment";
+23 -35
View File
@@ -24,43 +24,25 @@
#include "backend/DriverEnums.h"
#include <utils/Panic.h>
#include <utils/ostream.h>
#include <webgpu/webgpu_cpp.h>
#include <algorithm>
#include <cstdint>
namespace filament::backend {
namespace {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
utils::io::ostream& operator<<(utils::io::ostream& out, const wgpu::TextureFormat format) noexcept {
return filament::backend::streamInsertWebGPUPrintable(out, format);
}
utils::io::ostream& operator<<(utils::io::ostream& out,
const wgpu::TextureUsage textureUsage) noexcept {
return filament::backend::streamInsertWebGPUPrintable(out, textureUsage);
}
utils::io::ostream& operator<<(utils::io::ostream& out,
const wgpu::PresentMode presentMode) noexcept {
return filament::backend::streamInsertWebGPUPrintable(out, presentMode);
}
utils::io::ostream& operator<<(utils::io::ostream& out,
const wgpu::CompositeAlphaMode alphaMode) noexcept {
return filament::backend::streamInsertWebGPUPrintable(out, alphaMode);
}
void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabilities) {
FWGPU_LOGI << "WebGPU surface capabilities:";
FWGPU_LOGI << " surface usages: " << capabilities.usages;
FWGPU_LOGI << " surface usages: " << webGPUPrintableToString(capabilities.usages);
FWGPU_LOGI << " surface formats (" << capabilities.formatCount << "):";
if (capabilities.formatCount > 0 && capabilities.formats != nullptr) {
std::for_each(capabilities.formats, capabilities.formats + capabilities.formatCount,
[](wgpu::TextureFormat const format) {
FWGPU_LOGI << " " << format;
FWGPU_LOGI << " " << webGPUTextureFormatToString(format);
});
}
FWGPU_LOGI << " surface present modes (" << capabilities.presentModeCount << "):";
@@ -68,7 +50,7 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
std::for_each(capabilities.presentModes,
capabilities.presentModes + capabilities.presentModeCount,
[](wgpu::PresentMode const presentMode) {
FWGPU_LOGI << " " << presentMode;
FWGPU_LOGI << " " << webGPUPrintableToString(presentMode);
});
}
FWGPU_LOGI << " surface alpha modes (" << capabilities.alphaModeCount << "):";
@@ -76,7 +58,7 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
std::for_each(capabilities.alphaModes,
capabilities.alphaModes + capabilities.alphaModeCount,
[](wgpu::CompositeAlphaMode const alphaMode) {
FWGPU_LOGI << " " << alphaMode;
FWGPU_LOGI << " " << webGPUPrintableToString(alphaMode);
});
}
}
@@ -84,20 +66,20 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config,
wgpu::TextureFormat depthFormat) {
FWGPU_LOGI << "WebGPU surface configuration:";
FWGPU_LOGI << " surface format: " << config.format;
FWGPU_LOGI << " surface usage: " << config.usage;
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) {
std::for_each(config.viewFormats, config.viewFormats + config.viewFormatCount,
[](wgpu::TextureFormat const viewFormat) {
FWGPU_LOGI << " " << viewFormat;
FWGPU_LOGI << " " << webGPUPrintableToString(viewFormat);
});
}
FWGPU_LOGI << " surface alpha mode: " << config.alphaMode;
FWGPU_LOGI << " surface alpha mode: " << webGPUPrintableToString(config.alphaMode);
FWGPU_LOGI << " surface width: " << config.width;
FWGPU_LOGI << " surface height: " << config.height;
FWGPU_LOGI << " surface present mode: " << config.presentMode;
FWGPU_LOGI << "WebGPU selected depth format: " << depthFormat;
FWGPU_LOGI << " surface present mode: " << webGPUPrintableToString(config.presentMode);
FWGPU_LOGI << "WebGPU selected depth format: " << webGPUTextureFormatToString(depthFormat);
}
#endif// FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
@@ -128,7 +110,6 @@ void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config,
return wgpu::TextureFormat::Depth24PlusStencil8;
}
} else {
// other options: Depth16Unorm or Depth24Plus
return wgpu::TextureFormat::Depth32Float;
}
}
@@ -235,10 +216,9 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
}
[[nodiscard]] wgpu::TextureView createDepthTextureView(wgpu::Texture const& depthTexture,
wgpu::TextureFormat depthFormat, bool needStencil) {
wgpu::TextureFormat const& depthFormat, bool const needStencil) {
wgpu::TextureViewDescriptor descriptor{
.label = "depth_texture_view",
.format = depthFormat,
.dimension = wgpu::TextureViewDimension::e2D,
.baseMipLevel = 0,
.mipLevelCount = 1,
@@ -249,6 +229,16 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
};
if (needStencil) {
descriptor.aspect = wgpu::TextureAspect::All;
descriptor.format = depthFormat;
} else {
descriptor.aspect = wgpu::TextureAspect::DepthOnly;
if (depthFormat == wgpu::TextureFormat::Depth32FloatStencil8) {
descriptor.format = wgpu::TextureFormat::Depth32Float;
} else if (depthFormat == wgpu::TextureFormat::Depth24PlusStencil8) {
descriptor.format = wgpu::TextureFormat::Depth24Plus;
} else {
descriptor.format = depthFormat;
}
}
wgpu::TextureView depthTextureView = depthTexture.CreateView(&descriptor);
FILAMENT_CHECK_POSTCONDITION(depthTextureView) << "Failed to create depth texture view";
@@ -257,8 +247,6 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
}// namespace
namespace filament::backend {
WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize,
wgpu::Adapter const& adapter, wgpu::Device const& device, uint64_t flags)
: mDevice(device),
+260 -48
View File
@@ -17,6 +17,8 @@
#include "WebGPUTexture.h"
#include "WebGPUConstants.h"
#include "WebGPURenderPassMipmapGenerator.h"
#include "WebGPUStrings.h"
#include "DriverBase.h"
#include "private/backend/BackendUtils.h"
@@ -25,9 +27,12 @@
#include <utils/BitmaskEnum.h>
#include <utils/Panic.h>
#include <utils/debug.h>
#include <utils/ostream.h>
#include <webgpu/webgpu_cpp.h>
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
#include <webgpu/webgpu_cpp_print.h>
#include <sstream>
#endif
#include <cstdint>
@@ -59,19 +64,119 @@ namespace {
}
}
/**
* @param format texture format to potentially be used for storage binding
* @return true if the format is compatible with wgpu::TextureUsage::StorageBinding
*/
[[nodiscard]] constexpr bool isFormatStorageCompatible(const wgpu::TextureFormat format) {
switch (format) {
// List of formats that support storage binding
case wgpu::TextureFormat::R32Float:
case wgpu::TextureFormat::R32Sint:
case wgpu::TextureFormat::R32Uint:
case wgpu::TextureFormat::RG32Float:
case wgpu::TextureFormat::RG32Sint:
case wgpu::TextureFormat::RG32Uint:
case wgpu::TextureFormat::RGBA16Float:
case wgpu::TextureFormat::RGBA16Sint:
case wgpu::TextureFormat::RGBA16Uint:
case wgpu::TextureFormat::RGBA32Float:
case wgpu::TextureFormat::RGBA32Sint:
case wgpu::TextureFormat::RGBA32Uint:
case wgpu::TextureFormat::RGBA8Unorm:
case wgpu::TextureFormat::RGBA8Snorm:
case wgpu::TextureFormat::RGBA8Uint:
case wgpu::TextureFormat::RGBA8Sint:
return true;
default:
// All other formats, including packed floats (RG11B10Ufloat),
// depth/stencil, and sRGB formats do not support storage.
return false;
}
}
/**
* @param viewFormat potential view format to a texture
* @return an underlying texture format that is storage binding compatible for which this view
* texture format can be used. If no such format exists, Undefined is returned.
* If the view texture is already storage binding compatible then the view texture itself
* is returned.
*/
[[nodiscard]] constexpr wgpu::TextureFormat storageBindingCompatibleFormatForViewFormat(
const wgpu::TextureFormat viewFormat) {
switch (viewFormat) {
case wgpu::TextureFormat::RGBA8UnormSrgb: return wgpu::TextureFormat::RGBA8Unorm;
case wgpu::TextureFormat::BGRA8UnormSrgb: return wgpu::TextureFormat::BGRA8Unorm;
case wgpu::TextureFormat::BC1RGBAUnormSrgb: return wgpu::TextureFormat::BC1RGBAUnorm;
case wgpu::TextureFormat::BC2RGBAUnormSrgb: return wgpu::TextureFormat::BC2RGBAUnorm;
case wgpu::TextureFormat::BC3RGBAUnormSrgb: return wgpu::TextureFormat::BC3RGBAUnorm;
case wgpu::TextureFormat::BC7RGBAUnormSrgb: return wgpu::TextureFormat::BC7RGBAUnorm;
case wgpu::TextureFormat::ETC2RGB8UnormSrgb: return wgpu::TextureFormat::ETC2RGB8Unorm;
case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb: return wgpu::TextureFormat::ETC2RGB8A1Unorm;
case wgpu::TextureFormat::ETC2RGBA8UnormSrgb: return wgpu::TextureFormat::ETC2RGBA8Unorm;
case wgpu::TextureFormat::ASTC4x4UnormSrgb: return wgpu::TextureFormat::ASTC4x4Unorm;
case wgpu::TextureFormat::ASTC5x4UnormSrgb: return wgpu::TextureFormat::ASTC5x4Unorm;
case wgpu::TextureFormat::ASTC5x5UnormSrgb: return wgpu::TextureFormat::ASTC5x5Unorm;
case wgpu::TextureFormat::ASTC6x5UnormSrgb: return wgpu::TextureFormat::ASTC6x5Unorm;
case wgpu::TextureFormat::ASTC6x6UnormSrgb: return wgpu::TextureFormat::ASTC6x6Unorm;
case wgpu::TextureFormat::ASTC8x5UnormSrgb: return wgpu::TextureFormat::ASTC8x5Unorm;
case wgpu::TextureFormat::ASTC8x6UnormSrgb: return wgpu::TextureFormat::ASTC8x6Unorm;
case wgpu::TextureFormat::ASTC8x8UnormSrgb: return wgpu::TextureFormat::ASTC8x8Unorm;
case wgpu::TextureFormat::ASTC10x5UnormSrgb: return wgpu::TextureFormat::ASTC10x5Unorm;
case wgpu::TextureFormat::ASTC10x6UnormSrgb: return wgpu::TextureFormat::ASTC10x6Unorm;
case wgpu::TextureFormat::ASTC10x8UnormSrgb: return wgpu::TextureFormat::ASTC10x8Unorm;
case wgpu::TextureFormat::ASTC10x10UnormSrgb: return wgpu::TextureFormat::ASTC10x10Unorm;
case wgpu::TextureFormat::ASTC12x10UnormSrgb: return wgpu::TextureFormat::ASTC12x10Unorm;
case wgpu::TextureFormat::ASTC12x12UnormSrgb: return wgpu::TextureFormat::ASTC12x12Unorm;
default:
if (isFormatStorageCompatible(viewFormat)) {
return viewFormat; // view format is itself already storage binding compatible
} else {
return wgpu::TextureFormat::Undefined; // no valid format in this case
}
}
}
/**
* @param fUsage Filament's requested texture usage
* @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 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
// require CopySrc, CopyDst, TextureBinding, & StorageBinding
// Basing this mapping off of VulkanTexture.cpp's getUsage func and suggestions from Gemini
// TODO Validate assumptions, revisit if issues.
if (any(TextureUsage::BLIT_SRC & fUsage)) {
if (needsComputeStorageSupport || any(TextureUsage::BLIT_SRC & fUsage)) {
retUsage |= wgpu::TextureUsage::CopySrc;
}
if (any((TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE) & fUsage)) {
if (needsComputeStorageSupport ||
any((TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE) & fUsage)) {
retUsage |= wgpu::TextureUsage::CopyDst;
}
if (any(TextureUsage::SAMPLEABLE & fUsage)) {
if (needsComputeStorageSupport || any(TextureUsage::SAMPLEABLE & fUsage)) {
retUsage |= wgpu::TextureUsage::TextureBinding;
}
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
@@ -100,7 +205,6 @@ namespace {
retUsage |= wgpu::TextureUsage::TransientAttachment;
}
// NOTE: Unused wgpu flags:
// StorageBinding
// StorageAttachment
// NOTE: Unused Filament flags:
@@ -169,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) {
@@ -205,11 +329,22 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
const uint32_t height, const uint32_t depth, const TextureUsage usage,
wgpu::Device const& device) noexcept
: HwTexture{ samplerType, levels, samples, width, height, depth, format, usage },
mWebGPUFormat{ fToWGPUTextureFormat(format) },
mViewFormat{ fToWGPUTextureFormat(format) },
mMipmapGenerationStrategy{ determineMipmapGenerationStrategy(mViewFormat, samplerType,
samples, levels) },
mWebGPUFormat{ mMipmapGenerationStrategy == MipmapGenerationStrategy::SPD_COMPUTE_PASS
? storageBindingCompatibleFormatForViewFormat(mViewFormat)
: mViewFormat },
mAspect{ fToWGPUTextureViewAspect(usage, format) },
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples) },
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) } {
mBlockHeight{ filament::backend::getBlockHeight(format) },
mDefaultMipLevel{ 0 },
mDefaultBaseArrayLayer{ 0 } {
assert_invariant(
samples == 1 ||
samples == 4 &&
@@ -225,10 +360,8 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
.format = mWebGPUFormat,
.mipLevelCount = levels,
.sampleCount = samples,
// TODO Is this fine? Could do all-the-things, a naive mapping or get something from
// Filament
.viewFormatCount = 0,
.viewFormats = nullptr,
.viewFormatCount = 1,
.viewFormats = &mViewFormat,
};
mArrayLayerCount = toArrayLayerCount(samplerType, textureDescriptor.size.depthOrArrayLayers);
assert_invariant(textureDescriptor.format != wgpu::TextureFormat::Undefined &&
@@ -240,23 +373,54 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
mArrayLayerCount, samplerType);
FILAMENT_CHECK_POSTCONDITION(mDefaultTextureView)
<< "Failed to create default texture view for " << textureDescriptor.label;
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
if (mViewFormat != mWebGPUFormat) {
std::stringstream viewFormatStream;
viewFormatStream << mViewFormat;
std::stringstream textureFormatStream;
textureFormatStream << mWebGPUFormat;
FWGPU_LOGD << "Texture '" << textureDescriptor.label << "' has view format "
<< viewFormatStream.str() << " and texture format " << textureFormatStream.str();
}
#endif
}
WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
const uint8_t levelCount) noexcept
: HwTexture{ src->target, levelCount, src->samples, src->width, src->height, src->depth,
src->format, src->usage },
mTexture{ src->mTexture },
mViewFormat{ src->mViewFormat },
mMipmapGenerationStrategy{ src->mMipmapGenerationStrategy },
mWebGPUFormat{ src->mWebGPUFormat },
mAspect{ src->mAspect },
mWebGPUUsage{ src->mWebGPUUsage },
mArrayLayerCount{ src->mArrayLayerCount },
mViewUsage{ src->mViewUsage },
mBlockWidth{ src->mBlockWidth },
mBlockHeight{ src->mBlockHeight },
mDefaultTextureView{ makeTextureView(baseLevel, levelCount, 0, src->mArrayLayerCount,
src->target) },
mArrayLayerCount{ src->mArrayLayerCount },
mTexture{ src->mTexture },
mDefaultMipLevel{ baseLevel },
mDefaultBaseArrayLayer{ src->mArrayLayerCount } {}
mDefaultBaseArrayLayer{ src->mArrayLayerCount },
mDefaultTextureView{ makeTextureView(mDefaultMipLevel, levelCount, 0, mDefaultBaseArrayLayer,
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;
}
wgpu::TextureView WebGPUTexture::getOrMakeTextureView(const uint8_t mipLevel,
const uint32_t arrayLayer) {
@@ -267,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;
@@ -356,51 +582,43 @@ wgpu::TextureFormat WebGPUTexture::fToWGPUTextureFormat(TextureFormat const& fFo
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
// and discard the alpha and lower precision.
FWGPU_LOGW << "Requested Filament texture format RGB565 but getting "
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)"
<< utils::io::endl;
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
return wgpu::TextureFormat::Undefined;
case TextureFormat::RGB9_E5: return wgpu::TextureFormat::RGB9E5Ufloat;
case TextureFormat::RGB5_A1:
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
// and handle the packing/unpacking in shaders.
FWGPU_LOGW << "Requested Filament texture format RGB5_A1 but getting "
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)"
<< utils::io::endl;
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
return wgpu::TextureFormat::Undefined;
case TextureFormat::RGBA4:
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
// and handle the packing/unpacking in shaders.
FWGPU_LOGW << "Requested Filament texture format RGBA4 but getting "
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)"
<< utils::io::endl;
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
return wgpu::TextureFormat::Undefined;
case TextureFormat::RGB8:
FWGPU_LOGW << "Requested Filament texture format RGB8 but getting "
"wgpu::TextureFormat::RGBA8Unorm (no direct sRGB equivalent in wgpu "
"without alpha)"
<< utils::io::endl;
"without alpha)";
return wgpu::TextureFormat::RGBA8Unorm;
case TextureFormat::SRGB8:
FWGPU_LOGW << "Requested Filament texture format SRGB8 but getting "
"wgpu::TextureFormat::RGBA8UnormSrgb (no direct sRGB equivalent in wgpu "
"without alpha)"
<< utils::io::endl;
"without alpha)";
return wgpu::TextureFormat::RGBA8UnormSrgb;
case TextureFormat::RGB8_SNORM:
FWGPU_LOGW
<< "Requested Filament texture format RGB8_SNORM but getting "
"wgpu::TextureFormat::RGBA8Snorm (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
"wgpu::TextureFormat::RGBA8Snorm (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA8Snorm;
case TextureFormat::RGB8UI:
FWGPU_LOGW << "Requested Filament texture format RGB8UI but getting "
"wgpu::TextureFormat::RGBA8Uint (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
"wgpu::TextureFormat::RGBA8Uint (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA8Uint;
case TextureFormat::RGB8I:
FWGPU_LOGW << "Requested Filament texture format RGB8I but getting "
"wgpu::TextureFormat::RGBA8Sint (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
"wgpu::TextureFormat::RGBA8Sint (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA8Sint;
case TextureFormat::R11F_G11F_B10F: return wgpu::TextureFormat::RG11B10Ufloat;
case TextureFormat::UNUSED: return wgpu::TextureFormat::Undefined;
@@ -408,38 +626,32 @@ wgpu::TextureFormat WebGPUTexture::fToWGPUTextureFormat(TextureFormat const& fFo
case TextureFormat::RGB16F:
FWGPU_LOGW
<< "Requested Filament texture format RGB16F but getting "
"wgpu::TextureFormat::RGBA16Float (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
"wgpu::TextureFormat::RGBA16Float (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA16Float;
case TextureFormat::RGB16UI:
FWGPU_LOGW
<< "Requested Filament texture format RGB16UI but getting "
"wgpu::TextureFormat::RGBA16Uint (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
"wgpu::TextureFormat::RGBA16Uint (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA16Uint;
case TextureFormat::RGB16I:
FWGPU_LOGW
<< "Requested Filament texture format RGB16I but getting "
"wgpu::TextureFormat::RGBA16Sint (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
"wgpu::TextureFormat::RGBA16Sint (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA16Sint;
case TextureFormat::RGB32F:
FWGPU_LOGW
<< "Requested Filament texture format RGB32F but getting "
"wgpu::TextureFormat::RGBA32Float (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
"wgpu::TextureFormat::RGBA32Float (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA32Float;
case TextureFormat::RGB32UI:
FWGPU_LOGW
<< "Requested Filament texture format RGB32UI but getting "
"wgpu::TextureFormat::RGBA32Uint (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
"wgpu::TextureFormat::RGBA32Uint (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA32Uint;
case TextureFormat::RGB32I:
FWGPU_LOGW
<< "Requested Filament texture format RGB32I but getting "
"wgpu::TextureFormat::RGBA32Sint (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
"wgpu::TextureFormat::RGBA32Sint (no direct mapping in wgpu without alpha)";
return wgpu::TextureFormat::RGBA32Sint;
case TextureFormat::DXT1_RGB: return wgpu::TextureFormat::BC1RGBAUnorm;
case TextureFormat::DXT1_RGBA: return wgpu::TextureFormat::BC1RGBAUnorm;
@@ -457,14 +669,14 @@ wgpu::TextureView WebGPUTexture::makeTextureView(const uint8_t& baseLevel,
const SamplerType samplerType) const noexcept {
const wgpu::TextureViewDescriptor textureViewDescriptor{
.label = getUserTextureViewLabel(target),
.format = mWebGPUFormat,
.format = mViewFormat,
.dimension = toWebGPUTextureViewDimension(samplerType),
.baseMipLevel = baseLevel,
.mipLevelCount = levelCount,
.baseArrayLayer = baseArrayLayer,
.arrayLayerCount = arrayLayerCount,
.aspect = mAspect,
.usage = mWebGPUUsage };
.usage = mViewUsage };
wgpu::TextureView textureView = mTexture.CreateView(&textureViewDescriptor);
FILAMENT_CHECK_POSTCONDITION(textureView)
<< "Failed to create texture view " << textureViewDescriptor.label;
+81 -7
View File
@@ -26,8 +26,28 @@
namespace filament::backend {
[[nodiscard]] constexpr bool hasStencil(const wgpu::TextureFormat textureFormat) {
return textureFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
textureFormat == wgpu::TextureFormat::Depth32FloatStencil8 ||
textureFormat == wgpu::TextureFormat::Stencil8;
}
[[nodiscard]] constexpr bool hasDepth(const wgpu::TextureFormat textureFormat) {
return textureFormat == wgpu::TextureFormat::Depth16Unorm ||
textureFormat == wgpu::TextureFormat::Depth32Float ||
textureFormat == wgpu::TextureFormat::Depth24Plus ||
textureFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
textureFormat == wgpu::TextureFormat::Depth32FloatStencil8;
}
class WebGPUTexture : public HwTexture {
public:
enum class MipmapGenerationStrategy : uint8_t {
RENDER_PASS,
SPD_COMPUTE_PASS,
NONE,
};
WebGPUTexture(SamplerType, uint8_t levels, TextureFormat, uint8_t samples, uint32_t width,
uint32_t height, uint32_t depth, TextureUsage, wgpu::Device const&) noexcept;
@@ -41,35 +61,89 @@ 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::TextureFormat getWebGPUFormat() const { return mWebGPUFormat; }
[[nodiscard]] wgpu::TextureViewDimension getViewDimension() const { return mDimension; }
[[nodiscard]] wgpu::TextureFormat getViewFormat() const { return mViewFormat; }
[[nodiscard]] uint32_t getArrayLayerCount() const { return mArrayLayerCount; }
[[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);
/**
* @param format a required texture format (can be a view to a different underlying texture
* format)
* @return true if multiple mip levels can be generated via a compute shader + storage binding
* on the texture, false otherwise.
*/
[[nodiscard]] static bool supportsMultipleMipLevelsViaStorageBinding(
wgpu::TextureFormat format);
private:
// CreateTextureR has info for a texture and sampler. Texture Views are needed for binding,
// along with a sampler Current plan: Inherit the sampler and Texture to always exist (It is a
// ref counted pointer) when making views. View is optional
wgpu::Texture mTexture = nullptr;
// the texture view's format, which may differ from the underlying texture's format itself,
// an example of when this is needed is when certain underlying shaders don't support
// 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;
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;
wgpu::TextureAspect mAspect = wgpu::TextureAspect::Undefined;
// usage is inherited from HwTexture. This naming is to distinguish it from Filament's usage
wgpu::TextureUsage mWebGPUUsage = wgpu::TextureUsage::None;
uint32_t mArrayLayerCount = 1;
wgpu::TextureUsage mViewUsage = wgpu::TextureUsage::None;
wgpu::TextureViewDimension mDimension = wgpu::TextureViewDimension::Undefined;
size_t mBlockWidth = 0;
size_t mBlockHeight = 0;
wgpu::TextureView mDefaultTextureView = nullptr;
uint32_t mArrayLayerCount = 1;
wgpu::Texture mTexture = nullptr;
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,
@@ -22,7 +22,6 @@
#include <backend/DriverEnums.h>
#include <utils/Panic.h>
#include <utils/ostream.h>
#include <webgpu/webgpu_cpp.h>
@@ -57,26 +56,22 @@ namespace {
case ElementType::BYTE3:
FWGPU_LOGW
<< "Requested Filament vertex format BYTE3 (normalized) but getting "
"wgpu::VertexFormat::Snorm8x4 (no direct mapping in wgpu for x3 byte)"
<< utils::io::endl;
"wgpu::VertexFormat::Snorm8x4 (no direct mapping in wgpu for x3 byte)";
return VertexFormat::Snorm8x4; // NOT MINSPEC
case ElementType::UBYTE3:
FWGPU_LOGW
<< "Requested Filament vertex format UBYTE3 (normalized) but getting "
"wgpu::VertexFormat::Unorm8x4 (no direct mapping in wgpu for x3 byte)"
<< utils::io::endl;
"wgpu::VertexFormat::Unorm8x4 (no direct mapping in wgpu for x3 byte)";
return VertexFormat::Unorm8x4; // NOT MINSPEC
case ElementType::SHORT3:
FWGPU_LOGW << "Requested Filament vertex format SHORT3 (normalized) but getting "
"wgpu::VertexFormat::Snorm16x4 (no direct mapping in wgpu for x3 "
"half/short)"
<< utils::io::endl;
"half/short)";
return VertexFormat::Snorm16x4; // NOT MINSPEC
case ElementType::USHORT3:
FWGPU_LOGW << "Requested Filament vertex format USHORT3 (normalized) but getting "
"wgpu::VertexFormat::Unorm16x4 (no direct mapping in wgpu for x3 "
"half/short)"
<< utils::io::endl;
"half/short)";
return VertexFormat::Unorm16x4; // NOT MINSPEC
// Four Component Types
case ElementType::BYTE4: return VertexFormat::Snorm8x4;
@@ -97,26 +92,22 @@ namespace {
case ElementType::BYTE:
if (integer) return VertexFormat::Sint8;
FWGPU_LOGW << "Requested Filament vertex format BYTE (float) but getting "
"wgpu::VertexFormat::Float16 (no direct mapping in wgpu for 8 bit float)"
<< utils::io::endl;
"wgpu::VertexFormat::Float16 (no direct mapping in wgpu for 8 bit float)";
return wgpu::VertexFormat::Float16;
case ElementType::UBYTE:
if (integer) return VertexFormat::Uint8;
FWGPU_LOGW << "Requested Filament vertex format UBYTE (float) but getting "
"wgpu::VertexFormat::Float16 (no direct mapping in wgpu for 8 bit float)"
<< utils::io::endl;
"wgpu::VertexFormat::Float16 (no direct mapping in wgpu for 8 bit float)";
return VertexFormat::Float16;
case ElementType::SHORT:
if (integer) return VertexFormat::Sint16;
FWGPU_LOGW << "Requested Filament vertex format SHORT (float) and getting "
"wgpu::VertexFormat::Float16 (potential loss of precision?)"
<< utils::io::endl;
"wgpu::VertexFormat::Float16 (potential loss of precision?)";
return VertexFormat ::Float16;
case ElementType::USHORT:
if (integer) return VertexFormat::Uint16;
FWGPU_LOGW << "Requested Filament vertex format USHORT (float) and getting "
"wgpu::VertexFormat::Float16 (potential loss of precision?)"
<< utils::io::endl;
"wgpu::VertexFormat::Float16 (potential loss of precision?)";
return VertexFormat::Float16;
case ElementType::HALF: return VertexFormat::Float16;
case ElementType::INT: return VertexFormat::Sint32;
@@ -127,58 +118,49 @@ namespace {
if (integer) return VertexFormat::Sint8x2;
FWGPU_LOGW
<< "Requested Filament vertex format BYTE2 (float) but getting "
"wgpu::VertexFormat::Float16x2 (no direct mapping in wgpu for 8 bit float)"
<< utils::io::endl;
"wgpu::VertexFormat::Float16x2 (no direct mapping in wgpu for 8 bit float)";
return VertexFormat::Float16x2;
case ElementType::UBYTE2:
if (integer) return VertexFormat::Uint8x2;
FWGPU_LOGW
<< "Requested Filament vertex format UBYTE2 (float) but getting "
"wgpu::VertexFormat::Float16x2 (no direct mapping in wgpu for 8 bit float)"
<< utils::io::endl;
"wgpu::VertexFormat::Float16x2 (no direct mapping in wgpu for 8 bit float)";
return VertexFormat::Float16x2;
case ElementType::SHORT2:
if (integer) return VertexFormat::Sint16x2;
FWGPU_LOGW << "Requested Filament vertex format SHORT2 (float) but getting "
"wgpu::VertexFormat::Float16x2 (potential loss of precision?)"
<< utils::io::endl;
"wgpu::VertexFormat::Float16x2 (potential loss of precision?)";
return VertexFormat::Float16x2;
case ElementType::USHORT2:
if (integer) return VertexFormat::Uint16x2;
FWGPU_LOGW << "Requested Filament vertex format USHORT2 (float) but getting "
"wgpu::VertexFormat::Float16x2 (potential loss of precision?)"
<< utils::io::endl;
"wgpu::VertexFormat::Float16x2 (potential loss of precision?)";
return VertexFormat::Float16x2;
case ElementType::HALF2: return VertexFormat::Float16x2;
case ElementType::FLOAT2: return VertexFormat::Float32x2;
// Three Component Types
case ElementType::BYTE3:
FWGPU_LOGW << "Requested Filament vertex format BYTE3 but getting "
"wgpu::VertexFormat::Sint8x4 (no direct mapping in wgpu for x3 byte)"
<< utils::io::endl;
"wgpu::VertexFormat::Sint8x4 (no direct mapping in wgpu for x3 byte)";
return VertexFormat::Sint8x4; // NOT MINSPEC
case ElementType::UBYTE3:
FWGPU_LOGW << "Requested Filament vertex format UBYTE3 but getting "
"wgpu::VertexFormat::Uint8x4 (no direct mapping in wgpu for x3 byte)"
<< utils::io::endl;
"wgpu::VertexFormat::Uint8x4 (no direct mapping in wgpu for x3 byte)";
return VertexFormat::Uint8x4; // NOT MINSPEC
case ElementType::SHORT3:
FWGPU_LOGW
<< "Requested Filament vertex format SHORT3 but getting "
"wgpu::VertexFormat::Sint16x4 (no direct mapping in wgpu for x3 half/short)"
<< utils::io::endl;
"wgpu::VertexFormat::Sint16x4 (no direct mapping in wgpu for x3 half/short)";
return VertexFormat::Sint16x4; // NOT MINSPEC
case ElementType::USHORT3:
FWGPU_LOGW
<< "Requested Filament vertex format USHORT3 but getting "
"wgpu::VertexFormat::Uint16x4 (no direct mapping in wgpu for x3 half/short)"
<< utils::io::endl;
"wgpu::VertexFormat::Uint16x4 (no direct mapping in wgpu for x3 half/short)";
return VertexFormat::Uint16x4; // NOT MINSPEC
case ElementType::HALF3:
FWGPU_LOGW
<< "Requested Filament vertex format HALF3 but getting "
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for x3 half/short)"
<< utils::io::endl;
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for x3 half/short)";
return VertexFormat::Float16x4; // NOT MINSPEC
case ElementType::FLOAT3: return VertexFormat::Float32x3;
// Four Component Types
@@ -186,27 +168,23 @@ namespace {
if (integer) return VertexFormat::Sint8x4;
FWGPU_LOGW
<< "Requested Filament vertex format BYTE4 (float) but getting "
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for 8 bit float)"
<< utils::io::endl;
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for 8 bit float)";
return VertexFormat::Float16x4;
case ElementType::UBYTE4:
if (integer) return VertexFormat::Uint8x4;
FWGPU_LOGW
<< "Requested Filament vertex format UBYTE4 (float) but getting "
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for 8 bit float)"
<< utils::io::endl;
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for 8 bit float)";
return VertexFormat::Float16x4;
case ElementType::SHORT4:
if (integer) return VertexFormat::Sint16x4;
FWGPU_LOGW << "Requested Filament vertex format SHORT4 (float) but getting "
"wgpu::VertexFormat::Float16x4 (potential loss of precision?)"
<< utils::io::endl;
"wgpu::VertexFormat::Float16x4 (potential loss of precision?)";
return VertexFormat::Float16x4;
case ElementType::USHORT4:
if (integer) return VertexFormat::Uint16x4;
FWGPU_LOGW << "Requested Filament vertex format USHORT4 (float) but getting "
"wgpu::VertexFormat::Float16x4 (potential loss of precision?)"
<< utils::io::endl;
"wgpu::VertexFormat::Float16x4 (potential loss of precision?)";
return VertexFormat::Float16x4;
case ElementType::HALF4: return VertexFormat::Float16x4;
case ElementType::FLOAT4: return VertexFormat::Float32x4;
@@ -24,7 +24,6 @@
#include <backend/Platform.h>
#include <utils/Hash.h>
#include <utils/Panic.h>
#include <utils/ostream.h>
#include <dawn/webgpu_cpp_print.h>
#include <webgpu/webgpu_cpp.h>
@@ -39,6 +38,7 @@
#include <utility>
#include <vector>
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
#include <string>
#include <string_view>
#include <variant>
#endif
@@ -53,10 +53,11 @@ namespace filament::backend {
namespace {
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,
// 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,
// 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
@@ -117,28 +118,6 @@ constexpr void forEachLimitToValidate(std::function<bool(LimitToValidate const)>
}
}
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
utils::io::ostream& operator<<(utils::io::ostream& out,
const wgpu::WGSLLanguageFeatureName languageFeatureName) noexcept {
return streamInsertWebGPUPrintable(out, languageFeatureName);
}
utils::io::ostream& operator<<(utils::io::ostream& out,
const wgpu::FeatureName featureName) noexcept {
return streamInsertWebGPUPrintable(out, featureName);
}
#endif
template<typename STREAM_TYPE>
STREAM_TYPE& operator<<(STREAM_TYPE& out, wgpu::RequestAdapterOptions const& options) noexcept {
return streamInsertRequestAdapterOptions(out, options);
}
template<class STREAM_TYPE>
STREAM_TYPE& operator<<(STREAM_TYPE& out, wgpu::AdapterInfo const& info) noexcept {
return streamInsertRequestAdapterInfo(out, info);
}
constexpr bool isDefined(uint32_t limit) { return limit < wgpu::kLimitU32Undefined; }
[[maybe_unused]] constexpr bool isDefined(uint64_t limit) {
@@ -259,7 +238,7 @@ void printInstanceDetails(wgpu::Instance const& instance) {
supportedWGSLLanguageFeatures.features +
supportedWGSLLanguageFeatures.featureCount,
[](wgpu::WGSLLanguageFeatureName const featureName) {
FWGPU_LOGI << " " << featureName;
FWGPU_LOGI << " " << webGPUPrintableToString(featureName);
});
}
}
@@ -299,23 +278,22 @@ void printInstanceDetails(wgpu::Instance const& instance) {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printLimit(std::string_view name, const std::variant<uint32_t, uint64_t> value) {
FWGPU_LOGI << " " << name.data() << ": ";
static constexpr std::string_view indent = " ";
bool undefined = true;
if (std::holds_alternative<uint32_t>(value)) {
if (std::get<uint32_t>(value) != WGPU_LIMIT_U32_UNDEFINED) {
undefined = false;
FWGPU_LOGI << std::get<uint32_t>(value);
FWGPU_LOGI << indent << name.data() << ": " << std::get<uint32_t>(value);
}
} else if (std::holds_alternative<uint64_t>(value)) {
if (std::get<uint64_t>(value) != WGPU_LIMIT_U64_UNDEFINED) {
undefined = false;
FWGPU_LOGI << std::get<uint64_t>(value);
FWGPU_LOGI << indent << name.data() << ": " << std::get<uint64_t>(value);
}
}
if (undefined) {
FWGPU_LOGI << "UNDEFINED";
FWGPU_LOGI << indent << name.data() << ": UNDEFINED";
}
FWGPU_LOGI << "";
}
#endif// FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
@@ -393,16 +371,16 @@ struct AdapterDetails final {
}
};
template<class STREAM_TYPE>
STREAM_TYPE& operator<<(STREAM_TYPE& out, AdapterDetails const& details) noexcept {
out << details.info
[[nodiscard]] std::string toString(AdapterDetails const& details) {
std::stringstream out;
out << adapterInfoToString(details.info)
<< " power preference " << powerPreferenceToString(details.powerPreference);
return out;
return out.str();
}
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printAdapterDetails(AdapterDetails const& details) {
FWGPU_LOGI << "Selected WebGPU adapter info: " << details;
FWGPU_LOGI << "Selected WebGPU adapter info: " << toString(details);
wgpu::SupportedFeatures supportedFeatures{};
details.adapter.GetFeatures(&supportedFeatures);
FWGPU_LOGI << "WebGPU adapter supported features (" << supportedFeatures.featureCount
@@ -411,7 +389,7 @@ void printAdapterDetails(AdapterDetails const& details) {
std::for_each(supportedFeatures.features,
supportedFeatures.features + supportedFeatures.featureCount,
[](wgpu::FeatureName const featureName) {
FWGPU_LOGI << " " << featureName;
FWGPU_LOGI << " " << webGPUPrintableToString(featureName);
});
}
wgpu::Limits supportedLimits{};
@@ -444,12 +422,12 @@ struct AdapterDetailsHash final {
return details.adapter.HasFeature(featureName);
})) {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
FWGPU_LOGI << "WebGPU adapter " << details
FWGPU_LOGI << "WebGPU adapter " << toString(details)
<< " does not have the minimum required features.";
FWGPU_LOGI << "Missing required feature(s): ";
for (wgpu::FeatureName const& requiredFeature: REQUIRED_FEATURES) {
if (!details.adapter.HasFeature(requiredFeature)) {
FWGPU_LOGI << requiredFeature;
FWGPU_LOGI << webGPUPrintableToString(requiredFeature);
}
}
FWGPU_LOGI << "";
@@ -458,10 +436,10 @@ struct AdapterDetailsHash final {
}
wgpu::Limits supportedLimits {};
FILAMENT_CHECK_POSTCONDITION(details.adapter.GetLimits(&supportedLimits))
<< "Failed to get limits for WebGPU adapter: " << details;
<< "Failed to get limits for WebGPU adapter: " << toString(details);
if (!satisfiesLimits(supportedLimits)) {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
FWGPU_LOGI << " (for WebGPU adapter " << details << ")";
FWGPU_LOGI << " (for WebGPU adapter " << toString(details) << ")";
#endif
return false;
}
@@ -487,14 +465,15 @@ struct AdapterDetailsHash final {
status != wgpu::RequestAdapterStatus::CallbackCancelled)
<< "Failed to request a WebGPU adapter due to the request callback "
"being cancelled? Options: "
<< options << " " << message.data;
<< adapterOptionsToString(options) << " " << message.data;
FILAMENT_CHECK_POSTCONDITION(status != wgpu::RequestAdapterStatus::Error)
<< "Failed to request a WebGPU adapter due to an error. Options: "
<< options << " Error: " << message.data;
<< adapterOptionsToString(options) << " Error: " << message.data;
if (status == wgpu::RequestAdapterStatus::Success) {
AdapterDetails details = AdapterDetails(readyAdapter);
FILAMENT_CHECK_POSTCONDITION(readyAdapter.GetInfo(&details.info))
<< "Failed to get info for adapter (options: " << options << ")";
<< "Failed to get info for adapter (options: "
<< adapterOptionsToString(options) << ")";
const std::lock_guard<std::mutex> lock(adaptersMutex);
compatibleAdapters.emplace(std::move(details.info), details.powerPreference,
std::move(details.adapter));
@@ -509,9 +488,11 @@ struct AdapterDetailsHash final {
wgpu::Future& future = futures[i];
wgpu::WaitStatus status = instance.WaitAny(future, REQUEST_ADAPTER_TIMEOUT_NANOSECONDS);
FILAMENT_CHECK_POSTCONDITION(status != wgpu::WaitStatus::TimedOut)
<< "Timed out requesting a WebGPU adapter with options " << options;
<< "Timed out requesting a WebGPU adapter with options "
<< adapterOptionsToString(options);
FILAMENT_CHECK_POSTCONDITION(status != wgpu::WaitStatus::Error)
<< "Failed to request a WebGPU adapter with options " << options
<< "Failed to request a WebGPU adapter with options "
<< adapterOptionsToString(options)
<< " due to an error (as request was made synchronous)";
assert_invariant(status == wgpu::WaitStatus::Success);
}
@@ -519,7 +500,7 @@ struct AdapterDetailsHash final {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
FWGPU_LOGI << compatibleAdapters.size() << " WebGPU adapter(s) found:";
for (auto& details: compatibleAdapters) {
FWGPU_LOGI << " WebGPU adapter: " << details;
FWGPU_LOGI << " WebGPU adapter: " << toString(details);
}
#endif
return compatibleAdapters;
@@ -586,7 +567,7 @@ void printDeviceDetails(wgpu::Device const& device) {
std::for_each(supportedFeatures.features,
supportedFeatures.features + supportedFeatures.featureCount,
[](wgpu::FeatureName const featureName) {
FWGPU_LOGI << " " << featureName;
FWGPU_LOGI << " " << webGPUPrintableToString(featureName);
});
}
wgpu::Limits supportedLimits{};
@@ -628,7 +609,21 @@ wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
deviceDescriptor.defaultQueue.label = "default_queue";
deviceDescriptor.requiredFeatureCount = enabledFeatures.size();
deviceDescriptor.requiredFeatures = enabledFeatures.data();
deviceDescriptor.requiredLimits = &REQUIRED_LIMITS;
// It is helpful to increase maxStorageTexturesPerShaderStage if the hardware supports it, but
// not required. This allows our mipmap generation to perform as well as possible for the given
// hardware. If we have more cases of "Nice to increase" limits like this we can generalize it
// in the future.
wgpu::Limits supportedLimits{};
FILAMENT_CHECK_POSTCONDITION(
adapter.GetLimits(&supportedLimits).status == wgpu::Status::Success)
<< "Failed to get limits for WebGPU adapter";
auto limitsToRequest = REQUIRED_LIMITS;
limitsToRequest.maxStorageTexturesPerShaderStage =
std::min(MAX_MIPMAP_STORAGE_TEXTURES_PER_STAGE,
supportedLimits.maxStorageTexturesPerShaderStage);
deviceDescriptor.requiredLimits = &limitsToRequest;
deviceDescriptor.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous,
[](wgpu::Device const&, wgpu::DeviceLostReason const& reason,
wgpu::StringView message) {
@@ -684,7 +679,6 @@ wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
"adapter should support them: %s\n",
missingFeatures, featureNamesStream.str().data());
}
wgpu::Limits supportedLimits {};
FILAMENT_CHECK_POSTCONDITION(device.GetLimits(&supportedLimits))
<< "Failed to get limits for the device?";
FILAMENT_CHECK_POSTCONDITION(satisfiesLimits(supportedLimits))
+15 -3
View File
@@ -40,18 +40,24 @@ using namespace filament::math;
using namespace image;
#endif
#include <iostream>
namespace test {
Backend BackendTest::sBackend = Backend::NOOP;
OperatingSystem BackendTest::sOperatingSystem = OperatingSystem::OTHER;
bool BackendTest::sIsMobilePlatform = false;
int BackendTest::sArgc = 0;
char** BackendTest::sArgv = nullptr;
std::vector<std::string> BackendTest::sFailedImages;
void BackendTest::init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform) {
void BackendTest::init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform,
int argc, char** argv) {
sBackend = backend;
sOperatingSystem = operatingSystem;
sIsMobilePlatform = isMobilePlatform;
sArgc = argc;
sArgv = argv;
}
BackendTest::BackendTest() : commandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE,
@@ -160,6 +166,11 @@ void BackendTest::markImageAsFailure(std::string failedImageName) {
sFailedImages.emplace_back(std::move(failedImageName));
}
std::filesystem::path BackendTest::binaryDirectory() {
assert(sArgc >= 1);
return std::filesystem::path(sArgv[0]).remove_filename().string();
}
void BackendTest::recordFailedImages() {
if (!sFailedImages.empty()) {
std::string failedImages;
@@ -187,8 +198,9 @@ public:
}
};
void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile, int& argc, char* argv[]) {
BackendTest::init(backend, operatingSystem, isMobile);
void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile, int& argc,
char* argv[]) {
BackendTest::init(backend, operatingSystem, isMobile, argc, argv);
::testing::InitGoogleTest(&argc, argv);
::testing::AddGlobalTestEnvironment(new Environment);
}
+8 -2
View File
@@ -19,6 +19,8 @@
#include <gtest/gtest.h>
#include <filesystem>
#include <backend/Platform.h>
#include "private/backend/CommandBufferQueue.h"
@@ -31,16 +33,20 @@ namespace test {
class BackendTest : public ::testing::Test {
public:
static void init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform);
static void init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform,
int argc, char** argv);
static Backend sBackend;
static OperatingSystem sOperatingSystem;
static bool sIsMobilePlatform;
static int sArgc;
static char** sArgv;
// Takes the name of the image that wasn't correct, without the .png suffix
static void markImageAsFailure(std::string failedImageName);
static std::filesystem::path binaryDirectory();
protected:
BackendTest();
+8 -8
View File
@@ -59,28 +59,28 @@ uint32_t ScreenshotParams::expectedHash() const {
return mExpectedPixelHash;
}
std::string ScreenshotParams::actualDirectoryPath() {
return "images/actual_images";
std::filesystem::path ScreenshotParams::actualDirectoryPath() {
return BackendTest::binaryDirectory().append("images/actual_images");
}
std::string ScreenshotParams::actualFileName() const {
return absl::StrFormat("%s_actual.png", mFileName);
}
std::string ScreenshotParams::actualFilePath() const {
return absl::StrFormat("%s/%s", actualDirectoryPath(), actualFileName());
std::filesystem::path ScreenshotParams::actualFilePath() const {
return actualDirectoryPath().append(actualFileName());
}
std::string ScreenshotParams::expectedDirectoryPath() {
return "images/expected_images";
std::filesystem::path ScreenshotParams::expectedDirectoryPath() {
return BackendTest::binaryDirectory().append("images/expected_images");
}
std::string ScreenshotParams::expectedFileName() const {
return absl::StrFormat("%s.png", mFileName);
}
std::string ScreenshotParams::expectedFilePath() const {
return absl::StrFormat("%s/%s", expectedDirectoryPath(), expectedFileName());
std::filesystem::path ScreenshotParams::expectedFilePath() const {
return expectedDirectoryPath().append(expectedFileName());
}
const std::string ScreenshotParams::filePrefix() const {
+5 -4
View File
@@ -17,6 +17,7 @@
#ifndef TNT_IMAGE_EXPECTATIONS_H
#define TNT_IMAGE_EXPECTATIONS_H
#include <filesystem>
#include <vector>
#include "gtest/gtest.h"
@@ -51,12 +52,12 @@ public:
bool isSrgb() const;
uint32_t expectedHash() const;
static std::string actualDirectoryPath();
static std::filesystem::path actualDirectoryPath();
std::string actualFileName() const;
std::string actualFilePath() const;
static std::string expectedDirectoryPath();
std::filesystem::path actualFilePath() const;
static std::filesystem::path expectedDirectoryPath();
std::string expectedFileName() const;
std::string expectedFilePath() const;
std::filesystem::path expectedFilePath() const;
const std::string filePrefix() const;
private:
+20
View File
@@ -139,6 +139,24 @@ TEST_F(ReadPixelsTest, ReadPixels) {
return bufferDimension;
}
void exportScreenshot(void* pixelData) const {
#ifndef FILAMENT_IOS
const size_t width = readRect.width, height = readRect.height;
LinearImage image(width, height, 4);
if (format == PixelDataFormat::RGBA && type == PixelDataType::UBYTE) {
image = toLinearWithAlpha<uint8_t>(width, height, width * 4, (uint8_t*)pixelData);
}
if (format == PixelDataFormat::RGBA && type == PixelDataType::FLOAT) {
memcpy(image.getPixelRef(), pixelData, width * height * sizeof(math::float4));
}
std::string png = std::string(testName) + ".png";
std::filesystem::path path = ScreenshotParams::actualDirectoryPath();
path.append(png);
std::ofstream outputStream(path.c_str(), std::ios::binary | std::ios::trunc);
ImageEncoder::encode(outputStream, ImageEncoder::Format::PNG, image, "", png);
#endif
}
// The format and type for the readPixels call.
PixelDataFormat format = PixelDataFormat::RGBA;
PixelDataType type = PixelDataType::UBYTE;
@@ -311,6 +329,8 @@ TEST_F(ReadPixelsTest, ReadPixels) {
const auto* test = (const TestCase*)user;
assert_invariant(test);
test->exportScreenshot(buffer);
// Hash the contents of the buffer and check that they match.
uint32_t hash = utils::hash::murmur3((const uint32_t*)buffer, size / 4, 0);
+1
View File
@@ -118,6 +118,7 @@ INPUT = ../libs/filabridge/include \
../libs/gltfio/include \
../libs/utils/include \
backend/include \
backend/include/private/backend/DriverAPI.dox \
include
INPUT_ENCODING = UTF-8
+6
View File
@@ -190,6 +190,7 @@ public:
using FeatureLevel = backend::FeatureLevel;
using StereoscopicType = backend::StereoscopicType;
using Driver = backend::Driver;
using GpuContextPriority = backend::Platform::GpuContextPriority;
/**
* Config is used to define the memory footprint used by the engine, such as the
@@ -410,6 +411,11 @@ public:
* @deprecated use "backend.opengl.assert_native_window_is_valid" feature flag instead
*/
bool assertNativeWindowIsValid = false;
/**
* GPU context priority level. Controls GPU work scheduling and preemption.
*/
GpuContextPriority gpuContextPriority = GpuContextPriority::DEFAULT;
};
+2 -1
View File
@@ -57,7 +57,8 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI {
public:
using CullingMode = backend::CullingMode;
using TransparencyMode = TransparencyMode;
// ReSharper disable once CppRedundantQualifier
using TransparencyMode = filament::TransparencyMode;
using DepthFunc = backend::SamplerCompareFunc;
using StencilCompareFunc = backend::SamplerCompareFunc;
using StencilOperation = backend::StencilOperation;
+6
View File
@@ -481,6 +481,7 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
uint8_t fieldType = 0;
uint8_t fieldFormat = 0;
uint8_t fieldPrecision = 0;
bool fieldUnfilterable = false;
bool fieldMultisample = false;
if (!unflattener.read(&fieldName)) {
@@ -503,6 +504,10 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
return false;
}
if (!unflattener.read(&fieldUnfilterable)) {
return false;
}
if (!unflattener.read(&fieldMultisample)) {
return false;
}
@@ -512,6 +517,7 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
SamplerInterfaceBlock::Type(fieldType),
SamplerInterfaceBlock::Format(fieldFormat),
SamplerInterfaceBlock::Precision(fieldPrecision),
fieldUnfilterable,
fieldMultisample);
}
+11 -18
View File
@@ -2989,7 +2989,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscale(FrameGraph& fg, bool
}
if (dsrOptions.quality == QualityLevel::LOW) {
return upscaleBilinear(fg, translucent, dsrOptions, input, vp, outDesc, filter);
return upscaleBilinear(fg, dsrOptions, input, vp, outDesc, filter);
}
if (dsrOptions.quality == QualityLevel::MEDIUM) {
return upscaleSGSR1(fg, sourceHasLuminance, dsrOptions, input, vp, outDesc);
@@ -2997,7 +2997,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscale(FrameGraph& fg, bool
return upscaleFSR1(fg, dsrOptions, input, vp, outDesc);
}
FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph& fg, bool translucent,
FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph& fg,
DynamicResolutionOptions dsrOptions, FrameGraphId<FrameGraphTexture> const input,
filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
SamplerMagFilter filter) noexcept {
@@ -3016,18 +3016,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
.attachments = { .color = { data.output } },
.clearFlags = TargetBufferFlags::DEPTH });
},
[this, vp, translucent, filter](FrameGraphResources const& resources,
[this, vp, filter](FrameGraphResources const& resources,
auto const& data, DriverApi& driver) {
bindPostProcessDescriptorSet(driver);
// helper to enable blending
auto enableTranslucentBlending = [](PipelineState& pipeline) {
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_ALPHA;
pipeline.rasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_ALPHA;
};
auto color = resources.getTexture(data.input);
auto const& inputDesc = resources.getDescriptor(data.input);
@@ -3055,9 +3047,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
auto out = resources.getRenderPassInfo();
auto pipeline = getPipelineState(material.getMaterial(mEngine));
if (translucent) {
enableTranslucentBlending(pipeline);
}
renderFullScreenQuad(out, pipeline, driver);
});
@@ -3065,7 +3054,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
// if we had to take the low quality fallback, we still do the "sharpen pass"
if (dsrOptions.sharpness > 0.0f) {
output = rcas(fg, dsrOptions.sharpness, output, outDesc, translucent);
output = rcas(fg, dsrOptions.sharpness, output, outDesc,
false /* translucent=false, because dst buffer is allocated */);
}
// we rely on automatic culling of unused render passes
@@ -3443,9 +3433,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
return input;
}
// we currently don't support stencil resolve
assert_invariant(!isStencilFormat(inDesc.format));
// The Metal / Vulkan backends currently don't support depth/stencil resolve.
if (isDepthFormat(inDesc.format) && (!mEngine.getDriverApi().isDepthStencilResolveSupported())) {
return resolveDepth(fg, outputBufferName, input, outDesc);
@@ -3463,6 +3450,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
auto const& ppResolve = fg.addPass<ResolveData>("resolve",
[&](FrameGraph::Builder& builder, auto& data) {
// we currently don't support stencil resolve.
assert_invariant(!isStencilFormat(inDesc.format));
data.input = builder.read(input, FrameGraphTexture::Usage::BLIT_SRC);
data.output = builder.createTexture(outputBufferName, outDesc);
data.output = builder.write(data.output, FrameGraphTexture::Usage::BLIT_DST);
@@ -3514,6 +3504,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolveDepth(FrameGraph& fg,
auto const& ppResolve = fg.addPass<ResolveData>("resolveDepth",
[&](FrameGraph::Builder& builder, auto& data) {
// we currently don't support stencil resolve
assert_invariant(!isStencilFormat(inDesc.format));
data.input = builder.sample(input);
data.output = builder.createTexture(outputBufferName, outDesc);
data.output = builder.write(data.output, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
+1 -1
View File
@@ -257,7 +257,7 @@ public:
FrameGraphId<FrameGraphTexture> input, Viewport const& vp,
FrameGraphTexture::Descriptor const& outDesc, backend::SamplerMagFilter filter) noexcept;
FrameGraphId<FrameGraphTexture> upscaleBilinear(FrameGraph& fg, bool translucent,
FrameGraphId<FrameGraphTexture> upscaleBilinear(FrameGraph& fg,
DynamicResolutionOptions dsrOptions, FrameGraphId<FrameGraphTexture> input,
Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
backend::SamplerMagFilter filter) noexcept;
+4 -2
View File
@@ -141,6 +141,7 @@ Engine* FEngine::create(Builder const& builder) {
.stereoscopicType = instance->getConfig().stereoscopicType,
.assertNativeWindowIsValid = instance->features.backend.opengl.assert_native_window_is_valid,
.metalDisablePanicOnDrawableFailure = instance->getConfig().metalDisablePanicOnDrawableFailure,
.gpuContextPriority = instance->getConfig().gpuContextPriority,
};
instance->mDriver = platform->createDriver(sharedContext, driverConfig);
@@ -761,6 +762,7 @@ int FEngine::loop() {
.stereoscopicType = mConfig.stereoscopicType,
.assertNativeWindowIsValid = features.backend.opengl.assert_native_window_is_valid,
.metalDisablePanicOnDrawableFailure = mConfig.metalDisablePanicOnDrawableFailure,
.gpuContextPriority = mConfig.gpuContextPriority,
};
mDriver = mPlatform->createDriver(mSharedGLContext, driverConfig);
@@ -822,9 +824,9 @@ int FEngine::loop() {
#if FILAMENT_ENABLE_MATDBG
if(debug.server) {
delete debug.server;
}
}
#endif
#if FILAMENT_ENABLE_FGVIEWER
#if FILAMENT_ENABLE_FGVIEWER
if(debug.fgviewerServer) {
delete debug.fgviewerServer;
}
+19 -4
View File
@@ -98,6 +98,14 @@ namespace filament::fgviewer {
} // namespace filament::fgviewer
#endif
// We have added correctness assertions that breaks clients' projects. We add this define to allow
// for the client's to address these assertions at a more gradual pace.
#if defined(FILAMENT_RELAXED_CORRECTNESS_ASSERTIONS)
#define CORRECTNESS_ASSERTION_DEFAULT false
#else
#define CORRECTNESS_ASSERTION_DEFAULT true
#endif
namespace filament {
class Renderer;
@@ -715,10 +723,14 @@ public:
bool use_shadow_atlas = false;
} shadows;
struct {
// TODO: default the following two flags to true.
bool assert_material_instance_in_use = false;
bool assert_destroy_material_before_material_instance = false;
bool assert_vertex_buffer_count_exceeds_8 = false;
// TODO: clean-up the following flags (equivalent to setting them to true) when
// clients have addressed their usages.
bool assert_material_instance_in_use = CORRECTNESS_ASSERTION_DEFAULT;
bool assert_destroy_material_before_material_instance =
CORRECTNESS_ASSERTION_DEFAULT;
bool assert_vertex_buffer_count_exceeds_8 = CORRECTNESS_ASSERTION_DEFAULT;
bool assert_vertex_buffer_attribute_stride_mult_of_4 =
CORRECTNESS_ASSERTION_DEFAULT;
} debug;
} engine;
struct {
@@ -759,6 +771,9 @@ public:
{ "features.engine.debug.assert_vertex_buffer_count_exceeds_8",
"Assert when a client's number of buffers for a VertexBuffer exceeds 8.",
&features.engine.debug.assert_vertex_buffer_count_exceeds_8, false },
{ "features.engine.debug.assert_vertex_buffer_attribute_stride_mult_of_4",
"Assert that the attribute stride of a vertex buffer is a multiple of 4.",
&features.engine.debug.assert_vertex_buffer_attribute_stride_mult_of_4, false },
}};
utils::Slice<const FeatureFlag> getFeatureFlags() const noexcept {
+34 -15
View File
@@ -386,8 +386,9 @@ void FTexture::setImage(FEngine& engine, size_t const level,
FILAMENT_CHECK_PRECONDITION(any(mUsage & Texture::Usage::UPLOADABLE))
<< "Texture is not uploadable.";
FILAMENT_CHECK_PRECONDITION(mSampleCount <= 1) << "Operation not supported with multisample ("
<< unsigned(mSampleCount) << ") texture.";
FILAMENT_CHECK_PRECONDITION(mSampleCount <= 1)
<< "Operation not supported with multisample ("
<< unsigned(mSampleCount) << ") texture.";
FILAMENT_CHECK_PRECONDITION(xoffset + width <= valueForLevel(level, mWidth))
<< "xoffset (" << unsigned(xoffset) << ") + width (" << unsigned(width)
@@ -428,28 +429,46 @@ void FTexture::setImage(FEngine& engine, size_t const level,
<< ") > texture depth (" << effectiveTextureDepthOrLayers << ") at level ("
<< unsigned(level) << ")";
if (UTILS_UNLIKELY(!width || !height || !depth)) {
// The operation is a no-op, return immediately. The PixelBufferDescriptor callback
// should be called automatically when the object is destroyed.
// The precondition check below assumes width, height, depth non null.
return;
}
if (p.type != PixelDataType::COMPRESSED) {
using PBD = PixelBufferDescriptor;
size_t const stride = p.stride ? p.stride : width;
size_t const bpp = PBD::computePixelSize(p.format, p.type);
size_t const bpp = PBD::computeDataSize(p.format, p.type, 1, 1, 1);
size_t const bpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment);
size_t const pbdWidth = stride;
// TODO: PBD should have a "layer stride" (using "depth" as a substitute).
size_t const pbdDepth = depth;
size_t const pbdHeight = p.size / bpr / pbdDepth;
assert_invariant(p.size % bpr == 0 && (p.size / bpr) % pbdDepth == 0);
size_t const bpl = bpr * height; // TODO: PBD should have a "layer stride"
// TODO: PBD should have a p.depth (# layers to skip)
FILAMENT_CHECK_PRECONDITION(
bpp * (pbdWidth - p.left) * (pbdHeight - p.top) * (pbdDepth - 0) >=
bpp * (width - xoffset) * (height - yoffset) * (depth - zoffset))
/* 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,
+10 -5
View File
@@ -162,8 +162,7 @@ VertexBuffer* VertexBuffer::Builder::build(Engine& engine) {
FILAMENT_CHECK_PRECONDITION(mImpl->mBufferCount <= DOCUMENTED_MAX_VERTEX_BUFFER_COUNT)
<< "bufferCount cannot be more than " << DOCUMENTED_MAX_VERTEX_BUFFER_COUNT;
} else if (mImpl->mBufferCount > DOCUMENTED_MAX_VERTEX_BUFFER_COUNT) {
utils::slog.w << "bufferCount cannot be more than " << DOCUMENTED_MAX_VERTEX_BUFFER_COUNT
<< utils::io::endl;
LOG(WARNING) << "bufferCount cannot be more than " << DOCUMENTED_MAX_VERTEX_BUFFER_COUNT;
}
// Next we check if any unused buffer slots have been allocated. This helps prevent errors
@@ -178,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))
+3 -1
View File
@@ -66,6 +66,7 @@ DescriptorSet& DescriptorSet::operator=(DescriptorSet&& rhs) noexcept {
mDirty = rhs.mDirty;
mValid = rhs.mValid;
mSetAfterCommitWarning = rhs.mSetAfterCommitWarning;
mSetUndefinedParameterWarning = rhs.mSetUndefinedParameterWarning;
}
return *this;
}
@@ -106,11 +107,12 @@ void DescriptorSet::commitSlow(DescriptorSetLayout const& layout,
});
auto const unsetValidDescriptors = layout.getValidDescriptors() & ~mValid;
if (UTILS_VERY_UNLIKELY(!unsetValidDescriptors.empty())) {
if (UTILS_VERY_UNLIKELY(!unsetValidDescriptors.empty() && !mSetUndefinedParameterWarning)) {
unsetValidDescriptors.forEachSetBit([&](auto i) {
LOG(WARNING) << (layout.isSampler(i) ? "Sampler" : "Buffer") << " descriptor " << i
<< " of " << mName.c_str() << " is not set. Please report this issue.";
});
mSetUndefinedParameterWarning = true;
}
}
+1
View File
@@ -110,6 +110,7 @@ private:
mutable utils::bitset64 mValid; // 8
backend::DescriptorSetHandle mDescriptorSetHandle; // 4
mutable bool mSetAfterCommitWarning = false; // 1
mutable bool mSetUndefinedParameterWarning = false; // 1
utils::StaticString mName; // 16
};
+2 -1
View File
@@ -8,7 +8,8 @@ material {
{
type : sampler2d,
name : depth,
precision: high
precision: high,
unfilterable: true
},
{
type : float,
+2 -1
View File
@@ -4,7 +4,8 @@ material {
{
type : sampler2d,
name : depth,
precision: medium
precision: medium,
unfilterable: true
},
{
type : float4,
@@ -9,7 +9,8 @@ material {
{
type : sampler2d,
name : depth,
precision: high
precision: high,
unfilterable: true
},
{
type : float4,
@@ -77,4 +78,3 @@ fragment {
postProcess.color = color;
}
}
+2 -1
View File
@@ -9,7 +9,8 @@ material {
{
type : sampler2d,
name : depth,
precision: medium
precision: medium,
unfilterable: true
},
{
type : float2,
+2 -1
View File
@@ -9,7 +9,8 @@ material {
{
type : sampler2d,
name : depth,
precision: medium
precision: medium,
unfilterable: true
},
{
type : float2,
+2 -1
View File
@@ -5,7 +5,8 @@ material {
type : sampler2d,
name : depth,
precision: high,
multisample : true
multisample : true,
unfilterable: true
}
],
outputs : [
+2 -1
View File
@@ -4,7 +4,8 @@ material {
{
type : sampler2d,
name : depth,
precision: high
precision: high,
unfilterable: true
},
{
type : mat4,
@@ -4,7 +4,8 @@ material {
{
type : sampler2d,
name : depth,
precision: high
precision: high,
unfilterable: true
},
{
type : mat4,
+2 -1
View File
@@ -4,7 +4,8 @@ material {
{
type : sampler2d,
name : depth,
precision: high
precision: high,
unfilterable: true
}
],
variables : [
+2 -1
View File
@@ -4,7 +4,8 @@ material {
{
type : sampler2d,
name : depth,
precision: high
precision: high,
unfilterable: true
},
{
type : mat4,
@@ -4,7 +4,8 @@ material {
{
type : sampler2d,
name : depth,
precision: high
precision: high,
unfilterable: true
},
{
type : mat4,
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.61.2"
spec.version = "1.62.0"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.61.2/filament-v1.61.2-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.62.0/filament-v1.62.0-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {
@@ -28,7 +28,7 @@
namespace filament {
// update this when a new version of filament wouldn't work with older materials
static constexpr size_t MATERIAL_VERSION = 61;
static constexpr size_t MATERIAL_VERSION = 62;
/**
* Supported shading models
@@ -61,6 +61,7 @@ public:
Type type; // type of this sampler
Format format; // format of this sampler
Precision precision; // precision of this sampler
bool unfilterable; // whether the sampling should be unfiltered.
bool multisample; // multisample capable
ShaderStageFlags stages; // stages the sampler can be accessed from
};
@@ -83,6 +84,7 @@ public:
Type type; // type of this sampler
Format format; // format of this sampler
Precision precision; // precision of this sampler
bool unfilterable = false; // whether the sampling should be unfiltered.
bool multisample = false; // multisample capable
ShaderStageFlags stages =
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS; // shader stages using this sampler
@@ -95,7 +97,8 @@ public:
// Add a sampler
Builder& add(std::string_view samplerName, Binding binding, Type type, Format format,
Precision precision = Precision::MEDIUM, bool multisample = false,
Precision precision = Precision::MEDIUM, bool unfilterable = false,
bool multisample = false,
ShaderStageFlags stages = ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) noexcept;
// Add multiple samplers
+1 -1
View File
@@ -96,7 +96,7 @@ static constexpr std::initializer_list<DescriptorSetLayoutBinding> perRenderable
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::OBJECT_UNIFORMS, DescriptorFlags::DYNAMIC_OFFSET },
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::BONES_UNIFORMS, DescriptorFlags::DYNAMIC_OFFSET },
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::MORPHING_UNIFORMS },
{ DescriptorType::SAMPLER_2D_ARRAY_FLOAT, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_POSITIONS },
{ DescriptorType::SAMPLER_2D_ARRAY_FLOAT, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_POSITIONS, DescriptorFlags::UNFILTERABLE },
{ DescriptorType::SAMPLER_2D_ARRAY_INT, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_TANGENTS },
{ DescriptorType::SAMPLER_2D_ARRAY_FLOAT, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::BONES_INDICES_AND_WEIGHTS },
};
@@ -50,12 +50,12 @@ SamplerInterfaceBlock::Builder::stageFlags(backend::ShaderStageFlags stageFlags)
}
SamplerInterfaceBlock::Builder& SamplerInterfaceBlock::Builder::add(std::string_view samplerName,
Binding binding, Type type, Format format, Precision precision, bool multisample,
ShaderStageFlags stages) noexcept {
Binding binding, Type type, Format format, Precision precision, bool unfilterable,
bool multisample, ShaderStageFlags stages) noexcept {
mEntries.push_back({
{ samplerName.data(), samplerName.size() }, // name
{ }, // uniform name
binding, type, format, precision, multisample, stages });
{ samplerName.data(), samplerName.size() }, // name
{}, // uniform name
binding, type, format, precision, unfilterable, multisample, stages });
return *this;
}
@@ -65,8 +65,9 @@ SamplerInterfaceBlock SamplerInterfaceBlock::Builder::build() {
SamplerInterfaceBlock::Builder& SamplerInterfaceBlock::Builder::add(
std::initializer_list<ListEntry> list) noexcept {
for (auto& e : list) {
add(e.name, e.binding, e.type, e.format, e.precision, e.multisample, e.stages);
for (auto& e: list) {
add(e.name, e.binding, e.type, e.format, e.precision, e.unfilterable, e.multisample,
e.stages);
}
return *this;
}
+31 -7
View File
@@ -27,6 +27,7 @@
#include <backend/DriverEnums.h>
#include <backend/TargetBufferInfo.h>
#include <mutex>
#include <utils/BitmaskEnum.h>
#include <utils/bitset.h>
#include <utils/compiler.h>
@@ -323,8 +324,8 @@ public:
*/
MaterialBuilder& parameter(const char* name, SamplerType samplerType,
SamplerFormat format = SamplerFormat::FLOAT,
ParameterPrecision precision = ParameterPrecision::DEFAULT, bool multisample = false,
const char* transformName = "",
ParameterPrecision precision = ParameterPrecision::DEFAULT, bool unfilterable = false,
bool multisample = false, const char* transformName = "",
ShaderStageFlags stages = ShaderStageFlags::ALL_SHADER_STAGE_FLAGS);
MaterialBuilder& buffer(filament::BufferInterfaceBlock bib);
@@ -658,17 +659,39 @@ public:
// Sampler
Parameter(const char* paramName, SamplerType t, SamplerFormat f, ParameterPrecision p,
bool ms, const char* tn, ShaderStageFlags s)
: name(paramName), size(1), precision(p), samplerType(t), format(f),
parameterType(SAMPLER), multisample(ms), transformName(tn), stages(s) { }
bool unfilterable, bool ms, const char* tn, ShaderStageFlags s)
: name(paramName),
size(1),
precision(p),
samplerType(t),
format(f),
unfilterable(unfilterable),
multisample(ms),
transformName(tn),
stages(s),
parameterType(SAMPLER) {}
// Uniform
Parameter(const char* paramName, UniformType t, size_t typeSize, ParameterPrecision p)
: name(paramName), size(typeSize), uniformType(t), precision(p), parameterType(UNIFORM) { }
: name(paramName),
size(typeSize),
uniformType(t),
precision(p),
format{ 0 },
unfilterable(false),
multisample(false),
parameterType(UNIFORM) {}
// Subpass
Parameter(const char* paramName, SubpassType t, SamplerFormat f, ParameterPrecision p)
: name(paramName), size(1), precision(p), subpassType(t), format(f), parameterType(SUBPASS) { }
: name(paramName),
size(1),
precision(p),
subpassType(t),
format(f),
unfilterable(false),
multisample(false),
parameterType(SUBPASS) {}
utils::CString name;
size_t size;
@@ -677,6 +700,7 @@ public:
SamplerType samplerType;
SubpassType subpassType;
SamplerFormat format;
bool unfilterable;
bool multisample;
utils::CString transformName;
ShaderStageFlags stages;
+12 -10
View File
@@ -300,19 +300,19 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, UniformType const
return parameter(name, 1, type, precision);
}
MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType samplerType,
SamplerFormat format, ParameterPrecision precision, bool multisample,
SamplerFormat format, ParameterPrecision precision, bool unfilterable, bool multisample,
const char* transformName, ShaderStageFlags stages) {
FILAMENT_CHECK_PRECONDITION(!multisample ||
(format != SamplerFormat::SHADOW &&
(samplerType == SamplerType::SAMPLER_2D ||
samplerType == SamplerType::SAMPLER_2D_ARRAY)))
FILAMENT_CHECK_PRECONDITION(
!multisample || (format != SamplerFormat::SHADOW &&
(samplerType == SamplerType::SAMPLER_2D ||
samplerType == SamplerType::SAMPLER_2D_ARRAY)))
<< "multisample samplers only possible with SAMPLER_2D or SAMPLER_2D_ARRAY,"
" as long as type is not SHADOW";
FILAMENT_CHECK_POSTCONDITION(mParameterCount < MAX_PARAMETERS_COUNT) << "Too many parameters";
mParameters[mParameterCount++] = { name, samplerType, format, precision, multisample, transformName, stages };
mParameters[mParameterCount++] = { name, samplerType, format, precision, unfilterable,
multisample, transformName, stages };
return *this;
}
@@ -639,10 +639,12 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept {
assert_invariant(!param.isSubpass());
if (param.isSampler()) {
sbb.add({ param.name.data(), param.name.size() }, binding, param.samplerType,
param.format, param.precision, param.multisample, param.stages);
param.format, param.precision, param.unfilterable, param.multisample,
param.stages);
if (!param.transformName.empty()) {
ibb.add({{{ param.transformName.data(), param.transformName.size() }, uint8_t(binding),
0, UniformType::MAT3, Precision::DEFAULT, FeatureLevel::FEATURE_LEVEL_0 }});
ibb.add({ { { param.transformName.data(), param.transformName.size() },
uint8_t(binding), 0, UniformType::MAT3, Precision::DEFAULT,
FeatureLevel::FEATURE_LEVEL_0 } });
}
binding++;
} else if (param.isUniform()) {
@@ -77,6 +77,7 @@ void MaterialSamplerInterfaceBlockChunk::flatten(Flattener& f) {
f.writeUint8(static_cast<uint8_t>(sInfo.type));
f.writeUint8(static_cast<uint8_t>(sInfo.format));
f.writeUint8(static_cast<uint8_t>(sInfo.precision));
f.writeBool(sInfo.unfilterable);
f.writeBool(sInfo.multisample);
}
}
@@ -230,7 +231,11 @@ void MaterialDescriptorSetLayoutChunk::flatten(Flattener& f) {
f.writeUint8(uint8_t(descriptor_sets::getDescriptorType(entry.type, entry.format)));
f.writeUint8(uint8_t(entry.stages));
f.writeUint8(entry.binding);
f.writeUint8(uint8_t(DescriptorFlags::NONE));
if (entry.unfilterable) {
f.writeUint8(uint8_t(DescriptorFlags::UNFILTERABLE));
} else {
f.writeUint8(uint8_t(DescriptorFlags::NONE));
}
f.writeUint16(0);
}
}

Some files were not shown because too many files have changed in this diff Show More