Compare commits

..
Author SHA1 Message Date
Konrad Piascik 04efb9e18d Fix validation error on gltf_viewer
Inline header defined method to remove compiler warning
2025-06-17 09:23:58 -04:00
109 changed files with 1239 additions and 4159 deletions
+7 -5
View File
@@ -54,6 +54,7 @@ jobs:
build-android:
name: build-android
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -72,6 +73,7 @@ jobs:
build-ios:
name: build-iOS
runs-on: macos-14-xlarge
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -87,6 +89,7 @@ jobs:
build-web:
name: build-web
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -104,12 +107,11 @@ 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: |
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}
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 }}
test-renderdiff:
name: test-renderdiff
@@ -136,7 +138,7 @@ jobs:
- uses: actions/upload-artifact@v4
with:
name: presubmit-renderdiff-result
path: ./out/renderdiff
path: ./out/renderdiff_tests
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.62.0'
implementation 'com.google.android.filament:filament-android:1.61.1'
}
```
@@ -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.62.0'
pod 'Filament', '~> 1.61.1'
```
## Documentation
-9
View File
@@ -7,15 +7,6 @@ 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,8 +556,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge,
jboolean disableHandleUseAfterFreeCheck,
jint preferredShaderLanguage,
jboolean forceGLES2Context, jboolean assertNativeWindowIsValid,
jint gpuContextPriority) {
jboolean forceGLES2Context, jboolean assertNativeWindowIsValid) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
Engine::Config config = {
.commandBufferSizeMB = (uint32_t) commandBufferSizeMB,
@@ -575,7 +574,6 @@ 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,36 +175,6 @@ 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.
*/
@@ -263,8 +233,7 @@ public class Engine {
config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge,
config.disableHandleUseAfterFreeCheck,
config.preferredShaderLanguage.ordinal(),
config.forceGLES2Context, config.assertNativeWindowIsValid,
config.gpuContextPriority.ordinal());
config.forceGLES2Context, config.assertNativeWindowIsValid);
return this;
}
@@ -520,11 +489,6 @@ 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) {
@@ -1528,8 +1492,7 @@ public class Engine {
long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge,
boolean disableHandleUseAfterFreeCheck,
int preferredShaderLanguage,
boolean forceGLES2Context, boolean assertNativeWindowIsValid,
int gpuContextPriority);
boolean forceGLES2Context, boolean assertNativeWindowIsValid);
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.62.0
VERSION_NAME=1.61.1
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
Binary file not shown.
+1 -2
View File
@@ -1,7 +1,6 @@
#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
+109 -173
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#!/usr/bin/env sh
#
# Copyright © 2015-2021 the original authors.
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,99 +17,67 @@
#
##############################################################################
#
# 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/.
#
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
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
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
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
# 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
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"'
# 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 ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -119,9 +87,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
@@ -130,120 +98,88 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
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.
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.
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" && ! "$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
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
# 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.
# 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" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=$( cygpath --unix "$JAVACMD" )
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
# 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" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# 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' ' '
)" '"$@"'
# 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"
exec "$JAVACMD" "$@"
+16 -19
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,8 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -41,13 +40,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
if "%ERRORLEVEL%" == "0" goto execute
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
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.
goto fail
@@ -57,11 +56,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
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
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.
goto fail
@@ -76,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
if "%ERRORLEVEL%"=="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!
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%
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
@@ -18,7 +18,6 @@ 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.*
@@ -51,7 +50,6 @@ 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
@@ -71,7 +69,6 @@ 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?) {
@@ -86,12 +83,6 @@ 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
@@ -150,16 +141,7 @@ class MainActivity : Activity() {
}
private fun createDefaultRenderables() {
// 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 buffer = assets.open("models/scene.gltf").use { input ->
val bytes = ByteArray(input.available())
input.read(bytes)
ByteBuffer.wrap(bytes)
+2 -5
View File
@@ -1027,11 +1027,8 @@ samplerCubemap | Cubemap texture
[Table [materialParamsTypes]: Material parameter types]
Samplers
: 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`)
: 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
+5
View File
@@ -30,6 +30,11 @@ 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}')
+5 -6
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 or 'arm64' in parts:
elif 'aarch' in parts:
return ${DARWIN_ARM}
elif 'linux' in parts:
if 'x86_64' in parts:
@@ -63,12 +63,11 @@ 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 --force mdbook
cargo install mdbook
else
# Download prebuilts from github
mkdir -p ${HOME}/.cargo/bin
+24 -31
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), [@pixelflinger](https://bsky.app/profile/pixelflinger.bsky.social)
- [Mathias Agopian](https://github.com/pixelflinger), [@darthmoosious](https://twitter.com/darthmoosious)
# Overview
@@ -78,27 +78,26 @@ in table [standardProperties].
Property | Definition
-----------------------:|:---------------------
**baseColor** | Diffuse albedo for non-metallic surfaces, and specular color for metallic surfaces
**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)
**roughness** | Perceived smoothness (1.0) or roughness (0.0) of a surface. Smooth surfaces exhibit sharp reflections
**reflectance** | Fresnel reflectance at normal incidence for dielectric surfaces. This directly controls the strength of the reflections
**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
**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
**clearCoat** | Strength of the clear coat layer
**clearCoatRoughness** | Perceived smoothness or roughness of the clear coat layer
**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_)
**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
**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
[Table [standardProperties]: Properties of the standard model]
The type and range of each property is described in table [standardPropertiesTypes].
@@ -992,13 +991,10 @@ 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 have an optional `precision`, which can be
name must be a valid GLSL identifier. Entries also 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]. 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.
table [materialParamsTypes].
Type | Description
:----------------------|:---------------------------------
@@ -1027,8 +1023,12 @@ 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 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).
Arrays
: A parameter can define an array of values by appending `[size]` after the type name, where
@@ -1273,9 +1273,6 @@ 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 : [
@@ -1306,7 +1303,7 @@ Type
: array of `string`
Value
: Up to 5 strings, each must be a valid GLSL identifier.
: Up to 4 strings, each must be a valid GLSL identifier.
Description
: Defines custom interpolants (or variables) that are output by the material's vertex shader.
@@ -1322,10 +1319,6 @@ 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,
+20 -31
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://bsky.app/profile/pixelflinger.bsky.social">@pixelflinger</a></li></ul>
<li class="minus"><a href="https://github.com/pixelflinger">Mathias Agopian</a>, <a href="https://twitter.com/darthmoosious">@darthmoosious</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,27 +217,26 @@ 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">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">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">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">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">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">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">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">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>
<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>
</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>.
@@ -1098,13 +1097,10 @@ 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 have an optional <code>precision</code>, which can be
name must be a valid GLSL identifier. Entries also 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>. 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.
<a href="#table_materialparamstypes" target="_self">table&nbsp;14</a>.
</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>
@@ -1326,10 +1322,7 @@ 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><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>
</p></dd></dl></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>
@@ -1354,7 +1347,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 5 strings, each must be a valid GLSL identifier.
</p></dd><dt>Value</dt><dd><p> Up to 4 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
@@ -1367,11 +1360,7 @@ 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><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>
</p></dd></dl></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>
+3 -20
View File
@@ -269,20 +269,12 @@ 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/WebGPUPipelineCache.cpp
src/webgpu/WebGPUPipelineCache.h
src/webgpu/WebGPUPipelineLayoutCache.cpp
src/webgpu/WebGPUPipelineLayoutCache.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
@@ -511,20 +503,11 @@ 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
# ==================================================================================================
@@ -474,12 +474,7 @@ constexpr std::string_view to_string(DescriptorType type) noexcept {
enum class DescriptorFlags : uint8_t {
NONE = 0x00,
// Indicate a UNIFORM_BUFFER will have dynamic offsets.
DYNAMIC_OFFSET = 0x01,
// To indicate a texture/sampler type should be unfiltered.
UNFILTERABLE = 0x02,
DYNAMIC_OFFSET = 0x01
};
using descriptor_set_t = uint8_t;
@@ -107,36 +107,6 @@ 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.
@@ -186,13 +156,6 @@ 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;
@@ -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 = destroyAndConstruct(h, w, h);
* ConcreteTexture* p = reconstruct(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,
Program::ShaderBlob const& sourceCode) noexcept;
CString 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;
Program::ShaderSource shaderSourceCode;
shaders_source_t shaderSourceCode;
void* user = nullptr;
struct {
shaders_t shaders{};
@@ -121,12 +121,6 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
}
}
// Checks the token's completion status. The token is considered ready if the program was
// created successfully or if a shader compilation error occurred.
bool isReady() const noexcept {
return gl.program || compilationFailed;
}
std::optional<CallbackManager::Handle> handle{};
// Only valid when the blob functions are provided by users. The validity of this variable
@@ -140,9 +134,6 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
// Indicate this program was created from the cache blob.
bool retrievedFromBlobCache = false;
// Indicates that shader compilation failed.
bool compilationFailed = false;
};
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() {
@@ -306,33 +297,22 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
assert_invariant(mMode != Mode::THREAD_POOL);
if (token->gl.program) {
// Program linking has been initiated.
if (mMode == Mode::ASYNCHRONOUS) {
if (mMode == Mode::ASYNCHRONOUS) {
// Check link completion if link was initiated.
if (token->gl.program) {
return isLinkCompleted(token);
}
return true; // In sync mode, if program exists, we're done with this job.
}
// Program not linked yet. Check if shaders are compiled.
if (mMode == Mode::ASYNCHRONOUS) {
// Link hasn't been initiated, then check compile completion.
if (!isCompileCompleted(token)) {
return false; // Wait for compilation to finish.
return false;
}
}
// Shaders are compiled (or we're in sync mode). Let's link.
if (!linkProgram(mDriver.getContext(), token)) {
// Shader compilation failed. Stop processing this program.
return true;
if (!token->gl.program) {
linkProgram(mDriver.getContext(), token);
if (mMode == Mode::ASYNCHRONOUS) {
return false;// Wait until the link finishes.
}
}
// Program is now linking.
if (mMode == Mode::ASYNCHRONOUS) {
return false; // Wait for linking to finish.
}
return true;
});
break;
@@ -414,14 +394,6 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) {
assert_invariant(token);// This function should be called when the token is still alive.
ensureTokenIsReady(token);
if (token->compilationFailed) {
// Cleanup the token.
token->compiler.cancelTickOp(token);
token = nullptr;
return 0;
}
assert_invariant(token->gl.program);
// Check status of program linking. If it failed, errors will be logged.
@@ -447,7 +419,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) {
}
void ShaderCompilerService::ensureTokenIsReady(program_token_t const& token) {
if (token->isReady()) {
if (token->gl.program) {
return;// It's ready.
}
@@ -640,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] = std::move(shader);
token->shaderSourceCode[i] = { shader_src, shader_len };
#endif
token->gl.shaders[i] = shaderId;
}
@@ -666,10 +638,9 @@ void ShaderCompilerService::executeTickOps() noexcept {
return true;
}
/* static */ bool ShaderCompilerService::checkCompileStatus(program_token_t const& token) noexcept {
/* static */ void ShaderCompilerService::checkCompileStatus(program_token_t const& token) noexcept {
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
bool success = true;
UTILS_NOUNROLL
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
const GLuint shader = token->gl.shaders[i];
@@ -685,21 +656,15 @@ void ShaderCompilerService::executeTickOps() noexcept {
// Something went wrong. Log the error message.
const ShaderStage type = static_cast<ShaderStage>(i);
logCompilationError(type, token->name.c_str_safe(), shader, token->shaderSourceCode[i]);
success = false;
}
return success;
}
/* static */ bool ShaderCompilerService::linkProgram(OpenGLContext const& context,
/* static */ void ShaderCompilerService::linkProgram(OpenGLContext const& context,
program_token_t const& token) noexcept {
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
// Shader compilation should be completed by now. Check the status and log errors on failure.
if (!checkCompileStatus(token)) {
token->compilationFailed = true;
token->trySubmittingCallback();
return false;
}
checkCompileStatus(token);
// Link program
GLuint const program = glCreateProgram();
@@ -716,7 +681,6 @@ void ShaderCompilerService::executeTickOps() noexcept {
glLinkProgram(program);
token->gl.program = program;
token->trySubmittingCallback();
return true;
}
/* static */ bool ShaderCompilerService::isLinkCompleted(program_token_t const& token) noexcept {
@@ -794,8 +758,7 @@ void ShaderCompilerService::executeTickOps() noexcept {
UTILS_NOINLINE
/* static */ void logCompilationError(ShaderStage shaderType, const char* name,
GLuint const shaderId,
UTILS_UNUSED_IN_RELEASE Program::ShaderBlob const& sourceCode) noexcept {
GLuint const shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
{ // scope for the temporary string storage
auto to_string = [](ShaderStage type) -> const char* {
@@ -822,8 +785,7 @@ UTILS_NOINLINE
}
#ifndef NDEBUG
std::string_view const shader{ reinterpret_cast<const char*>(sourceCode.data()),
sourceCode.size() };
std::string_view const shader{ sourceCode.data(), sourceCode.size() };
size_t lc = 1;
size_t start = 0;
std::string line;
@@ -58,6 +58,7 @@ 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);
@@ -155,14 +156,13 @@ private:
static bool isCompileCompleted(program_token_t const& token) noexcept;
// Check compilation status of the shaders and log errors on failure.
static bool checkCompileStatus(program_token_t const& token) noexcept;
static void checkCompileStatus(program_token_t const& token) noexcept;
// Create a program by linking the compiled shaders. If the previous shader compilation was
// failed, `compilationFailed` is set, then this function returns false. Otherwise `gl.program`
// is populated with a valid program ID, then returns true. However this doesn't necessarily
// mean the program is successfully linked. The link error can be checked by calling
// `checkLinkStatusAndCleanupShaders` later.
static bool linkProgram(OpenGLContext const& context, program_token_t const& token) noexcept;
// Create a program by linking the compiled shaders. `gl.program` is always populated with a
// valid program ID after this method. But this doesn't necessarily mean the program is
// successfully linked. Errors can be checked by calling `checkLinkStatusAndCleanupShaders`
// later.
static void linkProgram(OpenGLContext const& context, program_token_t const& token) noexcept;
// Check if the program link is completed. You may want to call this when the extension
// `KHR_parallel_shader_compile` is enabled.
@@ -209,42 +209,10 @@ 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,38 +36,6 @@ 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);
@@ -160,4 +128,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,8 +214,4 @@ 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
+2 -9
View File
@@ -394,13 +394,7 @@ void VulkanDriver::collectGarbage() {
void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
int64_t refreshIntervalNs, uint32_t frameId) {
FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
// 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();
// Do nothing.
if (mAppState.hasExternalSamplers()) {
mExternalImageManager.onBeginFrame();
@@ -1242,8 +1236,7 @@ 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
}
+60 -70
View File
@@ -34,23 +34,16 @@ 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 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;
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;
return segment;
}
@@ -61,8 +54,7 @@ VulkanStagePool::VulkanStagePool(VmaAllocator allocator, fvkmemory::ResourceMana
mCommands(commands),
mDeviceLimits(deviceLimits) {}
fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStagePool::acquireStage(uint32_t numBytes,
uint32_t alignment) {
fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStagePool::acquireStage(uint32_t numBytes) {
// 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.
@@ -72,35 +64,17 @@ fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStagePool::acquireStage(uint
// equal to the requested size.
auto iter = mStages.lower_bound(numBytes);
// 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) {
VulkanStage* pStage;
if (iter != mStages.end()) {
pStage = iter->second;
mStages.erase(iter);
} else {
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, segmentOffset, numBytes);
fvkmemory::resource_ptr<VulkanStage::Segment> pSegment = pStage->acquireSegment(mResManager, numBytes);
// Update the stage's metadata, and reinsert it with the remaining segment
// capacity.
@@ -112,7 +86,12 @@ fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStagePool::acquireStage(uint
uint32_t VulkanStagePool::alignToNonCoherentAtomSize(uint32_t bytes) {
VkDeviceSize alignment = mDeviceLimits->nonCoherentAtomSize;
return alignValue(bytes, alignment);
if (alignment == 0) {
return bytes;
}
uint32_t remainder = bytes % alignment;
return remainder == 0 ? bytes : bytes + (alignment - remainder);
}
VulkanStage* VulkanStagePool::allocateNewStage(uint32_t capacity) {
@@ -156,27 +135,27 @@ void VulkanStagePool::destroyStage(VulkanStage const*&& stage) {
delete stage;
}
fvkmemory::resource_ptr<VulkanStageImage::Resource> VulkanStagePool::acquireImage(
PixelDataFormat format, PixelDataType type, uint32_t width, uint32_t height) {
// Helper lambda so we can return stage images wrapped as resources that can
// be held by command buffers until no longer needed.
auto wrapAsResource = [this](VulkanStageImage* image) {
auto recycleFn = [this](VulkanStageImage* image) {
this->mFreeImages.insert(image);
};
return fvkmemory::resource_ptr<VulkanStageImage::Resource>::construct(
this->mResManager, image, recycleFn);
};
VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, PixelDataType type,
uint32_t width, uint32_t height) {
const VkFormat vkformat = fvkutils::getVkFormat(format, type);
for (auto stageImage : mFreeImages) {
if (stageImage->format() == vkformat && stageImage->width() == width && stageImage->height() == height) {
mFreeImages.erase(stageImage);
stageImage->mLastAccessed = mCurrentFrame;
return wrapAsResource(stageImage);
for (auto image : mFreeImages) {
if (image->format == vkformat && image->width == width && image->height == height) {
mFreeImages.erase(image);
image->lastAccessed = mCurrentFrame;
mUsedImages.push_back(image);
return image;
}
}
VulkanStageImage* image = new VulkanStageImage({
.format = vkformat,
.width = width,
.height = height,
.lastAccessed = mCurrentFrame,
});
mUsedImages.push_back(image);
const VkImageCreateInfo imageInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
@@ -194,19 +173,14 @@ fvkmemory::resource_ptr<VulkanStageImage::Resource> VulkanStagePool::acquireImag
.usage = VMA_MEMORY_USAGE_CPU_TO_GPU
};
VkImage image;
VmaAllocation memory;
const UTILS_UNUSED VkResult result = vmaCreateImage(mAllocator, &imageInfo, &allocInfo,
&image, &memory, nullptr);
&image->image, &image->memory, nullptr);
assert_invariant(result == VK_SUCCESS);
VkImageAspectFlags const aspectFlags = fvkutils::getImageAspect(vkformat);
VkCommandBuffer const cmdbuffer = mCommands->get().buffer();
VulkanStageImage* stageImage = new VulkanStageImage(
vkformat, width, height, memory, image, mCurrentFrame);
// We use VK_IMAGE_LAYOUT_GENERAL here because the spec says:
// "Host access to image memory is only well-defined for linear images and for image
// subresources of those images which are currently in either the
@@ -214,13 +188,12 @@ fvkmemory::resource_ptr<VulkanStageImage::Resource> VulkanStagePool::acquireImag
// vkGetImageSubresourceLayout for a linear image returns a subresource layout mapping that is
// valid for either of those image layouts."
fvkutils::transitionLayout(cmdbuffer, {
.image = stageImage->image(),
.image = image->image,
.oldLayout = VulkanLayout::UNDEFINED,
.newLayout = VulkanLayout::STAGING, // (= VK_IMAGE_LAYOUT_GENERAL)
.subresources = { aspectFlags, 0, 1, 0, 1 },
});
return wrapAsResource(stageImage);
return image;
}
void VulkanStagePool::gc() noexcept {
@@ -268,14 +241,25 @@ void VulkanStagePool::gc() noexcept {
decltype(mFreeImages) freeImages;
freeImages.swap(mFreeImages);
for (auto image : freeImages) {
if (image->mLastAccessed < evictionTime) {
vmaDestroyImage(mAllocator, image->image(), image->memory());
if (image->lastAccessed < evictionTime) {
vmaDestroyImage(mAllocator, image->image, image->memory);
delete image;
} else {
mFreeImages.insert(image);
}
}
// Reclaim images that are no longer being used by any command buffer.
decltype(mUsedImages) usedImages;
usedImages.swap(mUsedImages);
for (auto image : usedImages) {
if (image->lastAccessed < evictionTime) {
image->lastAccessed = mCurrentFrame;
mFreeImages.insert(image);
} else {
mUsedImages.push_back(image);
}
}
FVK_SYSTRACE_END();
}
@@ -285,8 +269,14 @@ void VulkanStagePool::terminate() noexcept {
}
mStages.clear();
for (auto image : mUsedImages) {
vmaDestroyImage(mAllocator, image->image, image->memory);
delete image;
}
mUsedImages.clear();
for (auto image : mFreeImages) {
vmaDestroyImage(mAllocator, image->image(), image->memory());
vmaDestroyImage(mAllocator, image->image, image->memory);
delete image;
}
mFreeImages.clear();
+12 -74
View File
@@ -105,11 +105,8 @@ 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 segmentOffset, uint32_t numBytes);
uint32_t numBytes);
private:
const VmaAllocation mMemory;
@@ -126,70 +123,13 @@ private:
std::unordered_map<uint32_t, Segment*> mSegments;
};
class VulkanStageImage {
public:
class Resource : public fvkmemory::Resource {
public:
using RecycleFn = std::function<void(VulkanStageImage*)>;
Resource(VulkanStageImage* image, RecycleFn&& onRecycleFn)
: mImage(image),
mOnRecycleFn(onRecycleFn)
{}
~Resource() {
if (mOnRecycleFn) {
mOnRecycleFn(mImage);
}
}
inline VkFormat format() const { return mImage->format(); }
inline uint32_t width() const { return mImage->width(); }
inline uint32_t height() const { return mImage->height(); }
inline VmaAllocation memory() const { return mImage->memory(); }
inline VkImage image() const { return mImage->image(); }
private:
Resource() = delete;
Resource(const Resource& other) = delete;
Resource(Resource&& other) = delete;
Resource& operator=(const Resource& other) = delete;
Resource& operator=(Resource&& other) = delete;
VulkanStageImage* const mImage;
RecycleFn mOnRecycleFn;
};
VulkanStageImage(VkFormat format, uint32_t width, uint32_t height, VmaAllocation memory,
VkImage image, uint64_t lastAccessed)
: mFormat(format),
mWidth(width),
mHeight(height),
mMemory(memory),
mImage(image),
mLastAccessed(lastAccessed) {}
VulkanStageImage(const VulkanStageImage& other) = delete;
VulkanStageImage(VulkanStageImage&& other) = delete;
VulkanStageImage& operator=(const VulkanStageImage& other) = delete;
VulkanStageImage& operator=(VulkanStageImage&& other) = delete;
inline VkFormat format() const { return mFormat; }
inline uint32_t width() const { return mWidth; }
inline uint32_t height() const { return mHeight; }
inline VmaAllocation memory() const { return mMemory; }
inline VkImage image() const { return mImage; }
private:
const VkFormat mFormat;
const uint32_t mWidth;
const uint32_t mHeight;
const VmaAllocation mMemory;
const VkImage mImage;
uint64_t mLastAccessed;
// Denote as a friend so that it can update mLastAccessed.
friend class VulkanStagePool;
struct VulkanStageImage {
VkFormat format;
uint32_t width;
uint32_t height;
mutable uint64_t lastAccessed;
VmaAllocation memory;
VkImage image;
};
// Manages a pool of stages, periodically releasing stages that have been unused for a while.
@@ -204,13 +144,10 @@ public:
// buffers so that we have less objects around that we have to keep track
// of.
// This function is NOT thread-safe.
// 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);
fvkmemory::resource_ptr<VulkanStage::Segment> acquireStage(uint32_t numBytes);
// Images have VK_IMAGE_LAYOUT_GENERAL and must not be transitioned to any other layout
fvkmemory::resource_ptr<VulkanStageImage::Resource> acquireImage(PixelDataFormat format, PixelDataType type,
VulkanStageImage const* acquireImage(PixelDataFormat format, PixelDataType type,
uint32_t width, uint32_t height);
// Evicts old unused stages and bumps the current frame number.
@@ -250,7 +187,8 @@ private:
// Use an ordered multimap for quick (capacity => stage) lookups using lower_bound().
std::multimap<uint32_t, VulkanStage*> mStages;
std::unordered_set<VulkanStageImage*> mFreeImages;
std::unordered_set<VulkanStageImage const*> mFreeImages;
std::vector<VulkanStageImage const*> mUsedImages;
// Store the current "time" (really just a frame count) and LRU eviction parameters.
uint64_t mCurrentFrame = 0;
+10 -33
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(pixelSize > 0 && (p.left > 0 || p.top > 0 || pbdStride > width))) {
if (UTILS_UNLIKELY(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,28 +248,11 @@ void adjustedMemcpy(void* mapped, PixelBufferDescriptor const& p, size_t width,
}
}
} else {
// 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;
size_t const writeSize = pixelSize * (width * height * depth);
memcpy(mapped, buf, writeSize);
}
}
uint8_t getAlignmentForBufferToImageCopy(VkFormat format) {
// VUID-vkCmdCopyBufferToImage-dstImage-07978
if(fvkutils::isVkDepthFormat(format) || fvkutils::isVkStencilFormat(format)) {
return 4;
}
if (fvkutils::isVKYcbcrConversionFormat(format)) {
assert_invariant(false && "Multi planar format is not supported");
return 1;
}
// VUID-vkCmdCopyBufferToImage-dstImage-07975
return fvkutils::getTexelBlockSize(format);
}
} // anonymous namespace
VulkanTextureState::VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands,
@@ -534,16 +517,13 @@ 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);
// 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;
size_t const writeSize = width * height * depth * bpp;
// 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 = getAlignmentForBufferToImageCopy(mState->mVkFormat);
fvkmemory::resource_ptr<VulkanStage::Segment> stageSegment =
mState->mStagePool.acquireStage(writeSize, alignment);
mState->mStagePool.acquireStage(writeSize);
assert_invariant(stageSegment->memory());
adjustedMemcpy(stageSegment->mapping(), *hostData, width, height, depth);
vmaFlushAllocation(mState->mAllocator, stageSegment->memory(), stageSegment->offset(),
@@ -611,21 +591,18 @@ 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);
// 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;
size_t const writeSize = width * height * depth * bpp;
void* mapped = nullptr;
fvkmemory::resource_ptr<VulkanStageImage::Resource> stage
VulkanStageImage const* stage
= mState->mStagePool.acquireImage(data.format, data.type, width, height);
vmaMapMemory(mState->mAllocator, stage->memory(), &mapped);
vmaMapMemory(mState->mAllocator, stage->memory, &mapped);
adjustedMemcpy(mapped, data, width, height, depth);
vmaUnmapMemory(mState->mAllocator, stage->memory());
vmaFlushAllocation(mState->mAllocator, stage->memory(), 0, writeSize);
vmaUnmapMemory(mState->mAllocator, stage->memory);
vmaFlushAllocation(mState->mAllocator, stage->memory, 0, writeSize);
VulkanCommandBuffer& commands = mState->mCommands->get();
VkCommandBuffer const cmdbuf = commands.buffer();
commands.acquire(stage);
commands.acquire(fvkmemory::resource_ptr<VulkanTexture>::cast(this));
// TODO: support blit-based format conversion for 3D images and cubemaps.
@@ -648,7 +625,7 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& data, uint3
VulkanLayout const oldLayout = getLayout(layer, miplevel);
transitionLayout(&commands, range, newLayout);
vkCmdBlitImage(cmdbuf, stage->image(), fvkutils::getVkLayout(VulkanLayout::TRANSFER_SRC),
vkCmdBlitImage(cmdbuf, stage->image, fvkutils::getVkLayout(VulkanLayout::TRANSFER_SRC),
mState->mTextureImage, fvkutils::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST);
transitionLayout(&commands, range, oldLayout);
@@ -27,7 +27,6 @@ template ResourceType getTypeEnum<VulkanProgram>() noexcept;
template ResourceType getTypeEnum<VulkanRenderTarget>() noexcept;
template ResourceType getTypeEnum<VulkanSwapChain>() noexcept;
template ResourceType getTypeEnum<VulkanStage::Segment>() noexcept;
template ResourceType getTypeEnum<VulkanStageImage::Resource>() noexcept;
template ResourceType getTypeEnum<VulkanRenderPrimitive>() noexcept;
template ResourceType getTypeEnum<VulkanTexture>() noexcept;
template ResourceType getTypeEnum<VulkanTextureState>() noexcept;
@@ -59,9 +58,6 @@ ResourceType getTypeEnum() noexcept {
if constexpr (std::is_same_v<D, VulkanStage::Segment>) {
return ResourceType::STAGE_SEGMENT;
}
if constexpr (std::is_same_v<D, VulkanStageImage::Resource>) {
return ResourceType::STAGE_IMAGE;
}
if constexpr (std::is_same_v<D, VulkanRenderPrimitive>) {
return ResourceType::RENDER_PRIMITIVE;
}
@@ -109,8 +105,6 @@ std::string getTypeStr(ResourceType type) {
return "SwapChain";
case ResourceType::STAGE_SEGMENT:
return "Stage::Segment";
case ResourceType::STAGE_IMAGE:
return "Stage::Image";
case ResourceType::RENDER_PRIMITIVE:
return "RenderPrimitive";
case ResourceType::TEXTURE:
@@ -51,8 +51,7 @@ enum class ResourceType : uint8_t {
FENCE = 13,
VULKAN_BUFFER = 14,
STAGE_SEGMENT = 15,
STAGE_IMAGE = 16,
UNDEFINED_TYPE = 17, // Must be the last enum because we use it for iterating over the enums.
UNDEFINED_TYPE = 16, // Must be the last enum because we use it for iterating over the enums.
};
template<typename D>
@@ -72,8 +71,6 @@ struct Resource {
restype(ResourceType::UNDEFINED_TYPE),
mHandleConsideredDestroyed(false) {}
uint32_t getCount() const { return mCount; }
private:
inline void inc() noexcept {
mCount++;
@@ -81,9 +81,6 @@ void ResourceManager::destroyWithType(ResourceType type, HandleId id) {
case ResourceType::STAGE_SEGMENT:
destruct<VulkanStage::Segment>(Handle<VulkanStage::Segment>(id));
break;
case ResourceType::STAGE_IMAGE:
destruct<VulkanStageImage::Resource>(Handle<VulkanStageImage::Resource>(id));
break;
case ResourceType::RENDER_PRIMITIVE:
destruct<VulkanRenderPrimitive>(Handle<VulkanRenderPrimitive>(id));
break;
@@ -832,9 +832,6 @@ 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) {
@@ -926,7 +923,7 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
// Keep a copy of context for swapchains.
mImpl->mContext = context;
return VulkanDriver::create(this, mImpl->mContext, driverConfig);
return VulkanDriver::create(this, context, driverConfig);
}
// This needs to be explictly written for
@@ -233,304 +233,6 @@ VkFormat getVkFormat(TextureFormat format) {
}
}
// As per
// https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#formats-compatibility-classes).
uint8_t getTexelBlockSize(VkFormat format) {
switch (format) {
// 8-bit formats.
case VK_FORMAT_R4G4_UNORM_PACK8:
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:
case VK_FORMAT_S8_UINT:
return 1;
// 16-bit formats.
case VK_FORMAT_R10X6_UNORM_PACK16:
case VK_FORMAT_R12X4_UNORM_PACK16:
case VK_FORMAT_A4R4G4B4_UNORM_PACK16:
case VK_FORMAT_A4B4G4R4_UNORM_PACK16:
case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
case VK_FORMAT_B4G4R4A4_UNORM_PACK16:
case VK_FORMAT_R5G6B5_UNORM_PACK16:
case VK_FORMAT_B5G6R5_UNORM_PACK16:
case VK_FORMAT_R5G5B5A1_UNORM_PACK16:
case VK_FORMAT_B5G5R5A1_UNORM_PACK16:
case VK_FORMAT_A1R5G5B5_UNORM_PACK16:
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_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_D16_UNORM:
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:
case VK_FORMAT_D16_UNORM_S8_UINT:
case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM:
case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
case VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM:
case VK_FORMAT_G8_B8R8_2PLANE_422_UNORM:
case VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM:
case VK_FORMAT_G8_B8R8_2PLANE_444_UNORM:
return 3;
// 32-bit formats.
case VK_FORMAT_R10X6G10X6_UNORM_2PACK16:
case VK_FORMAT_R12X4G12X4_UNORM_2PACK16:
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_A2R10G10B10_UNORM_PACK32:
case VK_FORMAT_A2R10G10B10_SNORM_PACK32:
case VK_FORMAT_A2R10G10B10_USCALED_PACK32:
case VK_FORMAT_A2R10G10B10_SSCALED_PACK32:
case VK_FORMAT_A2R10G10B10_UINT_PACK32:
case VK_FORMAT_A2R10G10B10_SINT_PACK32:
case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
case VK_FORMAT_A2B10G10R10_SNORM_PACK32:
case VK_FORMAT_A2B10G10R10_USCALED_PACK32:
case VK_FORMAT_A2B10G10R10_SSCALED_PACK32:
case VK_FORMAT_A2B10G10R10_UINT_PACK32:
case VK_FORMAT_A2B10G10R10_SINT_PACK32:
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_R32_UINT:
case VK_FORMAT_R32_SINT:
case VK_FORMAT_R32_SFLOAT:
case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
case VK_FORMAT_X8_D24_UNORM_PACK32:
case VK_FORMAT_D32_SFLOAT:
case VK_FORMAT_D24_UNORM_S8_UINT:
case VK_FORMAT_G8B8G8R8_422_UNORM:
case VK_FORMAT_B8G8R8G8_422_UNORM:
return 4;
// 40-bit formats.
case VK_FORMAT_D32_SFLOAT_S8_UINT:
return 5;
// 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:
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16:
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16:
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16:
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16:
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16:
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16:
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16:
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16:
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16:
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16:
case VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM:
case VK_FORMAT_G16_B16R16_2PLANE_420_UNORM:
case VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM:
case VK_FORMAT_G16_B16R16_2PLANE_422_UNORM:
case VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM:
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16:
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16:
case VK_FORMAT_G16_B16R16_2PLANE_444_UNORM:
return 6;
// 64-bit formats.
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:
case VK_FORMAT_R32G32_UINT:
case VK_FORMAT_R32G32_SINT:
case VK_FORMAT_R32G32_SFLOAT:
case VK_FORMAT_R64_UINT:
case VK_FORMAT_R64_SINT:
case VK_FORMAT_R64_SFLOAT:
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:
case VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16:
case VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16:
case VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16:
case VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16:
case VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16:
case VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16:
case VK_FORMAT_G16B16G16R16_422_UNORM:
case VK_FORMAT_B16G16R16G16_422_UNORM:
case VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG:
case VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG:
case VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG:
case VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG:
case VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG:
case VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG:
case VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG:
case VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG:
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_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_UFLOAT_BLOCK:
case VK_FORMAT_BC6H_SFLOAT_BLOCK:
case VK_FORMAT_BC7_UNORM_BLOCK:
case VK_FORMAT_BC7_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:
case VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_4x4_UNORM_BLOCK:
case VK_FORMAT_ASTC_4x4_SRGB_BLOCK:
case VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_5x4_UNORM_BLOCK:
case VK_FORMAT_ASTC_5x4_SRGB_BLOCK:
case VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_5x5_UNORM_BLOCK:
case VK_FORMAT_ASTC_5x5_SRGB_BLOCK:
case VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_6x5_UNORM_BLOCK:
case VK_FORMAT_ASTC_6x5_SRGB_BLOCK:
case VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_6x6_UNORM_BLOCK:
case VK_FORMAT_ASTC_6x6_SRGB_BLOCK:
case VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_8x5_UNORM_BLOCK:
case VK_FORMAT_ASTC_8x5_SRGB_BLOCK:
case VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_8x6_UNORM_BLOCK:
case VK_FORMAT_ASTC_8x6_SRGB_BLOCK:
case VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_8x8_UNORM_BLOCK:
case VK_FORMAT_ASTC_8x8_SRGB_BLOCK:
case VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_10x5_UNORM_BLOCK:
case VK_FORMAT_ASTC_10x5_SRGB_BLOCK:
case VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_10x6_UNORM_BLOCK:
case VK_FORMAT_ASTC_10x6_SRGB_BLOCK:
case VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_10x8_UNORM_BLOCK:
case VK_FORMAT_ASTC_10x8_SRGB_BLOCK:
case VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_10x10_UNORM_BLOCK:
case VK_FORMAT_ASTC_10x10_SRGB_BLOCK:
case VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_12x10_UNORM_BLOCK:
case VK_FORMAT_ASTC_12x10_SRGB_BLOCK:
case VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK:
case VK_FORMAT_ASTC_12x12_UNORM_BLOCK:
case VK_FORMAT_ASTC_12x12_SRGB_BLOCK:
case VK_FORMAT_R32G32B32A32_UINT:
case VK_FORMAT_R32G32B32A32_SINT:
case VK_FORMAT_R32G32B32A32_SFLOAT:
case VK_FORMAT_R64G64_UINT:
case VK_FORMAT_R64G64_SINT:
case VK_FORMAT_R64G64_SFLOAT:
return 16;
// 192-bit formats.
case VK_FORMAT_R64G64B64_UINT:
case VK_FORMAT_R64G64B64_SINT:
case VK_FORMAT_R64G64B64_SFLOAT:
return 24;
// 256-bit formats.
case VK_FORMAT_R64G64B64A64_UINT:
case VK_FORMAT_R64G64B64A64_SINT:
case VK_FORMAT_R64G64B64A64_SFLOAT:
return 32;
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;
@@ -49,9 +49,6 @@ 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);
@@ -172,11 +172,15 @@ SPDPipeline& MipmapGenerator::GetOrCreatePipeline(const PipelineCacheKey& key) {
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;
if (key.scalarType == SPDScalarType::I32) {
entry.texture.sampleType = wgpu::TextureSampleType::Sint;
} else if (key.scalarType == SPDScalarType::U32) {
entry.texture.sampleType = wgpu::TextureSampleType::Uint;
} else if (key.scalarType == SPDScalarType::F32 || key.scalarType == SPDScalarType::F16) {
entry.texture.sampleType = wgpu::TextureSampleType::Float;
} else {
entry.texture.sampleType = wgpu::TextureSampleType::UnfilterableFloat;
}
entry.texture.viewDimension = wgpu::TextureViewDimension::e2DArray;
} else {
entry.storageTexture.access = wgpu::StorageTextureAccess::WriteOnly;
@@ -37,8 +37,8 @@ namespace {
uint32_t size, const char* const label) {
// Write size must be divisible by WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4).
// If the whole buffer is written to as is common, so must the buffer size.
size += (FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - (size % FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS)) %
FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
size += (WEBGPU_BUFFER_SIZE_MODULUS - (size % WEBGPU_BUFFER_SIZE_MODULUS)) %
WEBGPU_BUFFER_SIZE_MODULUS;
wgpu::BufferDescriptor descriptor{
.label = label,
.usage = usage,
@@ -62,15 +62,15 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.size + byteOffset <= mBuffer.GetSize())
<< "Attempting to copy " << bufferDescriptor.size << " bytes into a buffer of size "
<< mBuffer.GetSize() << " at offset " << byteOffset;
FILAMENT_CHECK_PRECONDITION(byteOffset % FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS == 0)
<< "Byte offset must be a multiple of " << FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS
<< " but is " << byteOffset;
FILAMENT_CHECK_PRECONDITION(byteOffset % WEBGPU_BUFFER_SIZE_MODULUS == 0)
<< "Byte offset must be a multiple of " << WEBGPU_BUFFER_SIZE_MODULUS << " but is "
<< byteOffset;
// TODO: All buffer objects are created with CopyDst usage.
// This may have some performance implications. That should be investigated later.
assert_invariant(mBuffer.GetUsage() & wgpu::BufferUsage::CopyDst);
const size_t remainder = bufferDescriptor.size % FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
const size_t remainder = bufferDescriptor.size % WEBGPU_BUFFER_SIZE_MODULUS;
// WriteBuffer is an async call. But cpu buffer data is already written to the staging
// buffer on return from the WriteBuffer.
@@ -82,11 +82,10 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
memcpy(mRemainderChunk.data(), remainderStart, remainder);
// Pad the remainder with zeros to ensure deterministic behavior, though GPU shouldn't
// access this
std::memset(mRemainderChunk.data() + remainder, 0,
FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - remainder);
std::memset(mRemainderChunk.data() + remainder, 0, WEBGPU_BUFFER_SIZE_MODULUS - remainder);
queue.WriteBuffer(mBuffer, byteOffset + legalSize, &mRemainderChunk,
FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS);
WEBGPU_BUFFER_SIZE_MODULUS);
}
}
@@ -39,8 +39,8 @@ protected:
private:
const wgpu::Buffer mBuffer;
// FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4) bytes to hold any extra chunk we need.
std::array<uint8_t, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS> mRemainderChunk{};
// WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4) bytes to hold any extra chunk we need.
std::array<uint8_t, WEBGPU_BUFFER_SIZE_MODULUS> mRemainderChunk{};
};
} // namespace filament::backend
+4 -16
View File
@@ -21,7 +21,7 @@
#include <cstdint>
constexpr size_t FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS = 4;
constexpr size_t WEBGPU_BUFFER_SIZE_MODULUS = 4;
// FWGPU is short for Filament WebGPU
@@ -65,25 +65,13 @@ constexpr size_t FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS = 4;
#define FWGPU_LOGI LOG(INFO)
#endif
constexpr uint64_t FILAMENT_WEBGPU_REQUEST_ADAPTER_TIMEOUT_NANOSECONDS =
constexpr uint64_t REQUEST_ADAPTER_TIMEOUT_NANOSECONDS =
/* milliseconds */ 1000u * /* converted to ns */ 1000000u;
constexpr uint64_t FILAMENT_WEBGPU_REQUEST_DEVICE_TIMEOUT_NANOSECONDS =
constexpr uint64_t REQUEST_DEVICE_TIMEOUT_NANOSECONDS =
/* milliseconds */ 1000u * /* converted to ns */ 1000000u;
constexpr uint64_t FILAMENT_WEBGPU_SHADER_COMPILATION_TIMEOUT_NANOSECONDS =
constexpr uint64_t SHADER_COMPILATION_TIMEOUT_NANOSECONDS =
/* milliseconds */ 1000u * /* converted to ns */ 1000000u;
// if a render pipeline is not used in this number of consecutive frames,
// then expire/release it from the cache.
// A smaller number means more frequent pipeline creation events, taking more time.
// A larger number means more pipelines stored, taking more memory.
constexpr uint64_t FILAMENT_WEBGPU_RENDER_PIPELINE_EXPIRATION_IN_FRAME_COUNT = 45;
// if a pipeline layout is not used in this number of consecutive frames,
// then expire/release it from the cache.
// A smaller number means more frequent pipeline layout creation events, taking more time.
// A larger number means more layouts stored, taking more memory.
constexpr uint64_t FILAMENT_WEBGPU_PIPELINE_LAYOUT_EXPIRATION_IN_FRAME_COUNT = 90;
#endif// TNT_FILAMENT_BACKEND_WEBGPUCONSTANTS_H
@@ -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) ||
any(fEntry.flags & DescriptorFlags::UNFILTERABLE)) {
} else if (isIntDescriptor(fEntry.type)) {
samplerEntry.sampler.type = wgpu::SamplerBindingType::NonFiltering;
} else {
samplerEntry.sampler.type = wgpu::SamplerBindingType::Filtering;
@@ -151,11 +151,8 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
if (isDepthDescriptor(fEntry.type)) {
wEntry.texture.sampleType = wgpu::TextureSampleType::Depth;
} else if (isFloatDescriptor(fEntry.type)) {
if (any(fEntry.flags & DescriptorFlags::UNFILTERABLE)) {
wEntry.texture.sampleType = wgpu::TextureSampleType::UnfilterableFloat;
} else {
wEntry.texture.sampleType = wgpu::TextureSampleType::Float;
}
// TODO: Set once we have the filtering values
wEntry.texture.sampleType = wgpu::TextureSampleType::Float;
} else if (isIntDescriptor(fEntry.type)) {
wEntry.texture.sampleType = wgpu::TextureSampleType::Sint;
} else if (isUnsignedIntDescriptor(fEntry.type)) {
+174 -283
View File
@@ -18,14 +18,11 @@
#include "WebGPUBufferObject.h"
#include "WebGPUDescriptorSet.h"
#include "WebGPUDescriptorSetLayout.h"
#include "WebGPUFence.h"
#include "WebGPUIndexBuffer.h"
#include "WebGPUPipelineCache.h"
#include "WebGPUPipelineLayoutCache.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"
@@ -44,7 +41,7 @@
#include <utils/CString.h>
#include <utils/Hash.h>
#include <utils/Panic.h>
#include <utils/compiler.h>
#include <utils/ostream.h>
#include <webgpu/webgpu_cpp.h>
@@ -73,10 +70,7 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
mAdapter{ mPlatform.requestAdapter(nullptr) },
mDevice{ mPlatform.requestDevice(mAdapter) },
mQueue{ mDevice.GetQueue() },
mPipelineLayoutCache{ mDevice },
mPipelineCache{ mDevice },
mRenderPassMipmapGenerator{ mDevice },
mSpdComputePassMipmapGenerator{ mDevice },
mMipMapGenerator{ mDevice },
mHandleAllocator{ "Handles", driverConfig.handleArenaSize,
driverConfig.disableHandleUseAfterFreeCheck, driverConfig.disableHeapHandleTags } {
mDevice.GetLimits(&mDeviceLimits);
@@ -109,7 +103,6 @@ void WebGPUDriver::terminate() {
void WebGPUDriver::tick(int) {
mDevice.Tick();
mAdapter.GetInstance().ProcessEvents();
}
void WebGPUDriver::beginFrame(int64_t monotonic_clock_ns,
@@ -129,9 +122,7 @@ void WebGPUDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
void WebGPUDriver::setPresentationTime(int64_t monotonic_clock_ns) {
}
void WebGPUDriver::endFrame(const uint32_t /* frameId */) {
mPipelineLayoutCache.onFrameEnd();
mPipelineCache.onFrameEnd();
void WebGPUDriver::endFrame(uint32_t frameId) {
}
void WebGPUDriver::flush(int) {
@@ -273,9 +264,7 @@ Handle<HwProgram> WebGPUDriver::createProgramS() noexcept {
}
Handle<HwFence> WebGPUDriver::createFenceS() noexcept {
// The handle must be constructed here, as a synchronous call to get the status
// might happen before createFenceR is executed.
return allocAndConstructHandle<WebGPUFence, HwFence>();
return Handle<HwFence>((Handle<HwFence>::HandleId) mNextFakeHandle++);
}
Handle<HwTimerQuery> WebGPUDriver::createTimerQueryS() noexcept {
@@ -459,23 +448,15 @@ void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> renderTarge
}
void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> renderTargetHandle,
const TargetBufferFlags targetFlags, const uint32_t width, const uint32_t height,
const TargetBufferFlags targets, const uint32_t width, const uint32_t height,
const uint8_t samples, const uint8_t layerCount, const MRT color,
const TargetBufferInfo depth, const TargetBufferInfo stencil) {
constructHandle<WebGPURenderTarget>(
renderTargetHandle, width, height, samples, layerCount, color, depth, stencil,
targetFlags,
[&](const Handle<HwTexture> textureHandle) {
return handleCast<WebGPUTexture>(textureHandle);
},
mDevice);
constructHandle<WebGPURenderTarget>(renderTargetHandle, width, height, samples, layerCount,
color, depth, stencil);
}
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::createFenceR(Handle<HwFence> fh, int) {
//todo
}
void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {}
@@ -528,18 +509,13 @@ void WebGPUDriver::updateStreams(CommandStream* driver) {
//todo
}
void WebGPUDriver::destroyFence(Handle<HwFence> fenceHandle) {
if (fenceHandle) {
destructHandle<WebGPUFence>(fenceHandle);
}
void WebGPUDriver::destroyFence(Handle<HwFence> fh) {
//todo
}
FenceStatus WebGPUDriver::getFenceStatus(Handle<HwFence> fenceHandle) {
const auto fence = handleCast<WebGPUFence>(fenceHandle);
if (!fence) {
return FenceStatus::ERROR;
}
return fence->getStatus();
FenceStatus WebGPUDriver::getFenceStatus(Handle<HwFence> fh) {
//todo
return FenceStatus::CONDITION_SATISFIED;
}
// We create all textures using VK_IMAGE_TILING_OPTIMAL, so our definition of "supported" is that
@@ -596,7 +572,7 @@ bool WebGPUDriver::isParallelShaderCompileSupported() {
}
bool WebGPUDriver::isDepthStencilResolveSupported() {
return false;
return true;
}
bool WebGPUDriver::isDepthStencilBlitSupported(const TextureFormat format) {
@@ -729,6 +705,27 @@ 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 };
@@ -759,58 +756,41 @@ void WebGPUDriver::generateMipmaps(Handle<HwTexture> textureHandle) {
wgpu::Texture wgpuTexture = texture->getTexture();
assert_invariant(wgpuTexture);
const uint32_t totalMipLevels = wgpuTexture.GetMipLevelCount();
if (totalMipLevels <= 1) {
return; // nothing to do
}
const auto usage = wgpuTexture.GetUsage();
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::TextureBinding)
<< "Texture for mipmap generation must have TextureBinding usage.";
<< "Texture for mipmap generation must have TextureBinding usage. "
"supportsMultipleMipLevels for texture format "
<< static_cast<uint32_t>(wgpuTexture.GetFormat()) << " is "
<< WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(wgpuTexture.GetFormat());
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::StorageBinding)
<< "Texture for mipmap generation must have StorageBinding usage. "
"supportsMultipleMipLevels for texture format "
<< static_cast<uint32_t>(wgpuTexture.GetFormat()) << " is "
<< WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(wgpuTexture.GetFormat());
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);
spd::SPDPassConfig spdConfig = { .filter = spd::SPDFilter::Average,
.targetTexture = wgpuTexture,
.numMips = totalMipLevels,
.halfPrecision = false,
.sourceMipLevel = 0 };
mSpdComputePassMipmapGenerator.Generate(encoder, wgpuTexture, spdConfig);
// Finish the encoder and submit all the passes at once.
wgpu::CommandBufferDescriptor cmdBufferDesc = {};
cmdBufferDesc.label = "Mipmap Command Buffer";
wgpu::CommandBuffer commandBuffer = encoder.Finish(&cmdBufferDesc);
mQueue.Submit(1, &commandBuffer);
break;
const uint32_t totalMipLevels = wgpuTexture.GetMipLevelCount();
if (totalMipLevels <= 1) {
return;
}
// We will record all passes into a single command encoder.
wgpu::CommandEncoderDescriptor encoderDesc = {};
encoderDesc.label = "Mipmap Command Encoder";
wgpu::CommandEncoder encoder = mDevice.CreateCommandEncoder(&encoderDesc);
spd::SPDPassConfig spdConfig = { .filter = spd::SPDFilter::Average,
.targetTexture = wgpuTexture,
.numMips = totalMipLevels,
.halfPrecision = false,
.sourceMipLevel = 0 };
mMipMapGenerator.Generate(encoder, wgpuTexture, spdConfig);
// Finish the encoder and submit all the passes at once.
wgpu::CommandBufferDescriptor cmdBufferDesc = {};
cmdBufferDesc.label = "Mipmap Command Buffer";
wgpu::CommandBuffer commandBuffer = encoder.Finish(&cmdBufferDesc);
mQueue.Submit(1, &commandBuffer);
}
void WebGPUDriver::compilePrograms(CompilerPriorityQueue priority,
@@ -829,16 +809,12 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
wgpu::TextureView defaultDepthStencilView = nullptr;
wgpu::TextureFormat defaultDepthStencilFormat = wgpu::TextureFormat::Undefined;
const bool msaaSidecarsRequired{ renderTarget->getSamples() > 1 &&
renderTarget->getSampleCountPerAttachment() <= 1 };
std::array<wgpu::TextureView, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> customColorViews{};
std::array<wgpu::TextureView, customColorViews.size()> customColorMsaaSidecarViews{};
uint32_t customColorViewCount = 0;
wgpu::TextureView customDepthStencilView = nullptr;
wgpu::TextureView customDepthStencilMsaaSidecarTextureView = nullptr;
wgpu::TextureFormat customDepthStencilFormat = wgpu::TextureFormat::Undefined;
wgpu::TextureView customDepthView = nullptr;
wgpu::TextureFormat customDepthFormat = wgpu::TextureFormat::Undefined;
wgpu::TextureFormat customStencilFormat = wgpu::TextureFormat::Undefined;
wgpu::TextureView customStencilView = nullptr;
mCurrentRenderTarget = renderTarget;
if (renderTarget->isDefaultRenderTarget()) {
@@ -846,20 +822,6 @@ 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();
@@ -874,78 +836,41 @@ 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();
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";
}
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;
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->getViewFormat();
}
}
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.";
}
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->getViewFormat();
}
}
}
@@ -954,11 +879,13 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
params,
defaultColorView,
defaultDepthStencilView,
defaultDepthStencilFormat,
customColorViews.data(),
customColorMsaaSidecarViews.data(),
customColorViewCount,
customDepthStencilView,
customDepthStencilMsaaSidecarTextureView);
customDepthView,
customStencilView,
customDepthFormat,
customStencilFormat);
mRenderPassEncoder = mCommandEncoder.BeginRenderPass(&renderPassDescriptor);
@@ -993,25 +920,9 @@ void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
wgpu::Extent2D surfaceSize = mPlatform.getSurfaceExtent(mNativeWindow);
mTextureView = mSwapChain->getCurrentSurfaceTextureView(surfaceSize);
assert_invariant(mTextureView);
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;
}
}
mDefaultRenderTarget->setTargetFlags(newTargetFlags);
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = { .label = "frame_command_encoder" };
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = {
.label = "command_encoder"
};
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
assert_invariant(mCommandEncoder);
}
@@ -1092,33 +1003,7 @@ 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) {
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);
// todo
}
void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_t sourceLevel,
@@ -1129,93 +1014,99 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
}
void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
assert_invariant(mRenderPassEncoder);
const auto program{ handleCast<WebGPUProgram>(pipelineState.program) };
// 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]);
return;
}
const auto program = handleCast<WebGPUProgram>(pipelineState.program);
assert_invariant(program);
WebGPURenderTarget const* renderTarget{ mCurrentRenderTarget };
assert_invariant(renderTarget);
assert_invariant(program->computeShaderModule == nullptr &&
"WebGPU backend does not (yet) support compute pipelines.");
FILAMENT_CHECK_POSTCONDITION(program->vertexShaderModule)
<< "WebGPU backend requires a vertex shader module for a render pipeline";
const auto vertexBufferInfo{ handleCast<WebGPUVertexBufferInfo>(
pipelineState.vertexBufferInfo) };
assert_invariant(vertexBufferInfo);
std::array<wgpu::BindGroupLayout, MAX_DESCRIPTOR_SET_COUNT> bindGroupLayouts{};
assert_invariant(bindGroupLayouts.size() >= pipelineState.pipelineLayout.setLayout.size());
size_t bindGroupLayoutCount{ 0 };
for (size_t i{ 0 }; i < bindGroupLayouts.size(); i++) {
const auto handle{ pipelineState.pipelineLayout.setLayout[bindGroupLayoutCount] };
size_t bindGroupLayoutCount = 0;
for (size_t i = 0; i < bindGroupLayouts.size(); i++) {
const auto handle = pipelineState.pipelineLayout.setLayout[bindGroupLayoutCount];
if (handle.getId() == HandleBase::nullid) {
continue;
}
bindGroupLayouts[bindGroupLayoutCount++] =
handleCast<WebGPUDescriptorSetLayout>(handle)->getLayout();
}
const WebGPUPipelineLayoutCache::PipelineLayoutRequest pipelineLayoutRequest{
.label = program->name,
.bindGroupLayouts = bindGroupLayouts,
std::stringstream layoutLabelStream;
layoutLabelStream << program->name.c_str() << " layout";
const auto layoutLabel = layoutLabelStream.str();
const wgpu::PipelineLayoutDescriptor layoutDescriptor{
.label = wgpu::StringView(layoutLabel),
.bindGroupLayoutCount = bindGroupLayoutCount,
.bindGroupLayouts = bindGroupLayouts.data()
// TODO investigate immediateDataRangeByteSize
};
wgpu::PipelineLayout const& layout{ mPipelineLayoutCache.getOrCreatePipelineLayout(
pipelineLayoutRequest) };
uint8_t colorFormatCount{ 0 };
std::array<wgpu::TextureFormat, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> colorFormats{
wgpu::TextureFormat::Undefined
};
wgpu::TextureFormat depthStencilFormat{ wgpu::TextureFormat::Undefined };
if (renderTarget->isDefaultRenderTarget()) {
// default render target color(s) (one)...
colorFormatCount = 1;
colorFormats[0] = mSwapChain->getColorFormat();
// default render target depth/stencil...
depthStencilFormat = mSwapChain->getDepthFormat();
const wgpu::PipelineLayout layout = mDevice.CreatePipelineLayout(&layoutDescriptor);
FILAMENT_CHECK_POSTCONDITION(layout)
<< "Failed to create wgpu::PipelineLayout for render pipeline for "
<< layoutDescriptor.label;
const auto vertexBufferInfo =
handleCast<WebGPUVertexBufferInfo>(pipelineState.vertexBufferInfo);
assert_invariant(vertexBufferInfo);
std::vector<wgpu::TextureFormat> pipelineColorFormats;
wgpu::TextureFormat pipelineDepthFormat = wgpu::TextureFormat::Undefined;
uint8_t pipelineSamples = 1;
if (mCurrentRenderTarget->isDefaultRenderTarget()) {
pipelineColorFormats.push_back(mSwapChain->getColorFormat());
pipelineDepthFormat = mSwapChain->getDepthFormat();
pipelineSamples =
mCurrentRenderTarget->getSamples();// Default RT should have samples (usually 1)
} else {
// custom render target color(s)...
MRT const& mrtColorAttachments{ mCurrentRenderTarget->getColorAttachmentInfos() };
for (size_t i{ 0 }; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; ++i) {
const auto& mrtColorAttachments = mCurrentRenderTarget->getColorAttachmentInfos();
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; ++i) {
if (mrtColorAttachments[i].handle) {
const auto colorTexture{ handleCast<WebGPUTexture>(mrtColorAttachments[i].handle) };
const auto colorTexture = handleCast<WebGPUTexture>(mrtColorAttachments[i].handle);
if (colorTexture) {
colorFormats[colorFormatCount++] = colorTexture->getTexture().GetFormat();
pipelineColorFormats.push_back(colorTexture->getTexture().GetFormat());
}
}
}
// custom render target depth/stencil...
const auto& depthInfo = mCurrentRenderTarget->getDepthAttachmentInfo();
const auto& stencilInfo = mCurrentRenderTarget->getStencilAttachmentInfo();
Handle<HwTexture> depthStencilHandle{};
if (depthInfo.handle) {
depthStencilHandle = depthInfo.handle;
} else if (stencilInfo.handle) {
depthStencilHandle = stencilInfo.handle;
}
if (depthStencilHandle) {
const auto depthStencilTexture{ handleCast<WebGPUTexture>(depthStencilHandle) };
if (depthStencilTexture) {
depthStencilFormat = depthStencilTexture->getTexture().GetFormat();
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();
}
}
}
pipelineSamples = mCurrentRenderTarget->getSamples();
}
const WebGPUPipelineCache::RenderPipelineRequest pipelineRequest{
.label = program->name,
.vertexShaderModule = program->vertexShaderModule,
.fragmentShaderModule = program->fragmentShaderModule,
.vertexBufferSlots = vertexBufferInfo->getWebGPUSlotBindingInfos(),
.vertexBufferLayouts = vertexBufferInfo->getVertexBufferLayouts(),
.pipelineLayout = layout,
.primitiveType = pipelineState.primitiveType,
.rasterState = pipelineState.rasterState,
.stencilState = pipelineState.stencilState,
.polygonOffset = pipelineState.polygonOffset,
.targetRenderFlags = renderTarget->getTargetFlags(),
.multisampleCount = renderTarget->getSamples(),
.depthStencilFormat = depthStencilFormat,
.colorFormatCount = colorFormatCount,
.colorFormats = colorFormats.data(),
};
wgpu::RenderPipeline const& pipeline{ mPipelineCache.getOrCreateRenderPipeline(
pipelineRequest) };
// TODO: We expected this to be a sane check, however it complains when running shadowtest.
//if (program->fragmentShaderModule != nullptr) {
// FILAMENT_CHECK_POSTCONDITION(!pipelineColorFormats.empty())
// << "Render pipeline with fragment shader must have at least one color target "
// "format.";
//}
wgpu::RenderPipeline pipeline = createWebGPURenderPipeline(mDevice, *program, *vertexBufferInfo,
layout, pipelineState.rasterState, pipelineState.stencilState,
pipelineState.polygonOffset, pipelineState.primitiveType, pipelineColorFormats,
pipelineDepthFormat, pipelineSamples);
assert_invariant(pipeline);
mPipelineMap[hash] = pipeline;
mRenderPassEncoder.SetPipeline(pipeline);
}
+5 -16
View File
@@ -19,10 +19,6 @@
#include "WebGPURenderTarget.h"
#include "webgpu/WebGPUConstants.h"
#include "webgpu/WebGPUMsaaTextureResolver.h"
#include "webgpu/WebGPUPipelineCache.h"
#include "webgpu/WebGPUPipelineLayoutCache.h"
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
#include <backend/platforms/WebGPUPlatform.h>
#include "DriverBase.h"
@@ -34,6 +30,7 @@
#include <utils/compiler.h>
#include "SpdMipmapGenerator/SpdMipmapGenerator.h"
#include <tsl/robin_map.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
@@ -58,7 +55,7 @@ public:
[[nodiscard]] static Driver* create(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept;
private:
WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept;
explicit 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);
@@ -80,11 +77,9 @@ private:
wgpu::CommandBuffer mCommandBuffer = nullptr;
WebGPURenderTarget* mDefaultRenderTarget = nullptr;
WebGPURenderTarget* mCurrentRenderTarget = nullptr;
WebGPUPipelineLayoutCache mPipelineLayoutCache;
WebGPUPipelineCache mPipelineCache;
WebGPURenderPassMipmapGenerator mRenderPassMipmapGenerator;
spd::MipmapGenerator mSpdComputePassMipmapGenerator;
WebGPUMsaaTextureResolver mMsaaTextureResolver{};
spd::MipmapGenerator mMipMapGenerator;
tsl::robin_map<uint32_t, wgpu::RenderPipeline> mPipelineMap;
struct DescriptorSetBindingInfo{
wgpu::BindGroup bindGroup;
@@ -92,7 +87,6 @@ private:
backend::DescriptorSetOffsetArray offsets;
};
std::array<DescriptorSetBindingInfo,MAX_DESCRIPTOR_SET_COUNT> mCurrentDescriptorSets;
/*
* Driver interface
*/
@@ -127,11 +121,6 @@ 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);
@@ -1,50 +0,0 @@
/*
* 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
@@ -1,43 +0,0 @@
/*
* 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
@@ -1,125 +0,0 @@
/*
* 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
@@ -1,55 +0,0 @@
/*
* 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
@@ -1,382 +0,0 @@
/*
* 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 "WebGPUPipelineCache.h"
#include "WebGPUConstants.h"
#include "WebGPUTexture.h"
#include "WebGPUVertexBufferInfo.h"
#include <backend/DriverEnums.h>
#include <backend/TargetBufferInfo.h>
#include <utils/BitmaskEnum.h>
#include <utils/Panic.h>
#include <webgpu/webgpu_cpp.h>
#include <array>
#include <cstdint>
#include <cstring>
namespace filament::backend {
namespace {
[[nodiscard]] constexpr uint8_t toUint8(const bool value) { return value ? 1 : 0; }
[[nodiscard]] constexpr wgpu::PrimitiveTopology toWebGPU(const PrimitiveType primitiveType) {
switch (primitiveType) {
case PrimitiveType::POINTS: return wgpu::PrimitiveTopology::PointList;
case PrimitiveType::LINES: return wgpu::PrimitiveTopology::LineList;
case PrimitiveType::LINE_STRIP: return wgpu::PrimitiveTopology::LineStrip;
case PrimitiveType::TRIANGLES: return wgpu::PrimitiveTopology::TriangleList;
case PrimitiveType::TRIANGLE_STRIP: return wgpu::PrimitiveTopology::TriangleStrip;
}
}
[[nodiscard]] constexpr wgpu::CullMode toWebGPU(const CullingMode cullMode) {
switch (cullMode) {
case CullingMode::NONE: return wgpu::CullMode::None;
case CullingMode::FRONT: return wgpu::CullMode::Front;
case CullingMode::BACK: return wgpu::CullMode::Back;
case CullingMode::FRONT_AND_BACK:
// no WegGPU equivalent of front and back
FILAMENT_CHECK_POSTCONDITION(false)
<< "WebGPU does not support CullingMode::FRONT_AND_BACK";
return wgpu::CullMode::Undefined;
}
}
[[nodiscard]] constexpr wgpu::CompareFunction toWebGPU(const SamplerCompareFunc compareFunction) {
switch (compareFunction) {
case SamplerCompareFunc::LE: return wgpu::CompareFunction::LessEqual;
case SamplerCompareFunc::GE: return wgpu::CompareFunction::GreaterEqual;
case SamplerCompareFunc::L: return wgpu::CompareFunction::Less;
case SamplerCompareFunc::G: return wgpu::CompareFunction::Greater;
case SamplerCompareFunc::E: return wgpu::CompareFunction::Equal;
case SamplerCompareFunc::NE: return wgpu::CompareFunction::NotEqual;
case SamplerCompareFunc::A: return wgpu::CompareFunction::Always;
case SamplerCompareFunc::N: return wgpu::CompareFunction::Never;
}
}
[[nodiscard]] constexpr wgpu::StencilOperation toWebGPU(const StencilOperation stencilOp) {
switch (stencilOp) {
case StencilOperation::KEEP: return wgpu::StencilOperation::Keep;
case StencilOperation::ZERO: return wgpu::StencilOperation::Zero;
case StencilOperation::REPLACE: return wgpu::StencilOperation::Replace;
case StencilOperation::INCR: return wgpu::StencilOperation::IncrementClamp;
case StencilOperation::INCR_WRAP: return wgpu::StencilOperation::IncrementWrap;
case StencilOperation::DECR: return wgpu::StencilOperation::DecrementClamp;
case StencilOperation::DECR_WRAP: return wgpu::StencilOperation::DecrementWrap;
case StencilOperation::INVERT: return wgpu::StencilOperation::Invert;
}
}
[[nodiscard]] constexpr wgpu::BlendOperation toWebGPU(const BlendEquation blendOp) {
switch (blendOp) {
case BlendEquation::ADD: return wgpu::BlendOperation::Add;
case BlendEquation::SUBTRACT: return wgpu::BlendOperation::Subtract;
case BlendEquation::REVERSE_SUBTRACT: return wgpu::BlendOperation::ReverseSubtract;
case BlendEquation::MIN: return wgpu::BlendOperation::Min;
case BlendEquation::MAX: return wgpu::BlendOperation::Max;
}
}
[[nodiscard]] constexpr wgpu::BlendFactor toWebGPU(const BlendFunction blendFunction) {
switch (blendFunction) {
case BlendFunction::ZERO: return wgpu::BlendFactor::Zero;
case BlendFunction::ONE: return wgpu::BlendFactor::One;
case BlendFunction::SRC_COLOR: return wgpu::BlendFactor::Src;
case BlendFunction::ONE_MINUS_SRC_COLOR: return wgpu::BlendFactor::OneMinusSrc;
case BlendFunction::DST_COLOR: return wgpu::BlendFactor::Dst;
case BlendFunction::ONE_MINUS_DST_COLOR: return wgpu::BlendFactor::OneMinusDst;
case BlendFunction::SRC_ALPHA: return wgpu::BlendFactor::SrcAlpha;
case BlendFunction::ONE_MINUS_SRC_ALPHA: return wgpu::BlendFactor::OneMinusSrcAlpha;
case BlendFunction::DST_ALPHA: return wgpu::BlendFactor::DstAlpha;
case BlendFunction::ONE_MINUS_DST_ALPHA: return wgpu::BlendFactor::OneMinusDstAlpha;
case BlendFunction::SRC_ALPHA_SATURATE: return wgpu::BlendFactor::SrcAlphaSaturated;
}
}
} // namespace
WebGPUPipelineCache::WebGPUPipelineCache(wgpu::Device const& device)
: mDevice{ device } {}
wgpu::RenderPipeline const& WebGPUPipelineCache::getOrCreateRenderPipeline(
RenderPipelineRequest const& request) {
RenderPipelineKey key{};
populateKey(request, key);
if (auto iterator{ mRenderPipelines.find(key) }; iterator != mRenderPipelines.end()) {
RenderPipelineCacheEntry& entry{ iterator.value() };
entry.lastUsedFrameCount = mFrameCount;
return entry.pipeline;
}
const wgpu::RenderPipeline pipeline{ createRenderPipeline(request) };
mRenderPipelines.emplace(key, RenderPipelineCacheEntry{
.pipeline = pipeline,
.lastUsedFrameCount = mFrameCount,
});
return mRenderPipelines[key].pipeline;
}
void WebGPUPipelineCache::onFrameEnd() {
++mFrameCount;
removeExpiredPipelines();
}
void WebGPUPipelineCache::populateKey(RenderPipelineRequest const& request,
RenderPipelineKey& outKey) {
outKey.vertexShaderModuleHandle =
request.vertexShaderModule ? request.vertexShaderModule.Get() : nullptr;
outKey.fragmentShaderModuleHandle =
request.fragmentShaderModule ? request.fragmentShaderModule.Get() : nullptr;
outKey.pipelineLayoutHandle = request.pipelineLayout ? request.pipelineLayout.Get() : nullptr;
outKey.depthBias = static_cast<int32_t>(request.polygonOffset.constant);
outKey.depthBiasSlopeScale = request.polygonOffset.slope;
outKey.primitiveType = request.primitiveType;
outKey.stencilFrontCompare = request.stencilState.front.stencilFunc;
outKey.stencilFrontFailOperation = request.stencilState.front.stencilOpStencilFail;
outKey.stencilFrontDepthFailOperation = request.stencilState.front.stencilOpDepthFail;
outKey.stencilFrontPassOperation = request.stencilState.front.stencilOpDepthStencilPass;
outKey.stencilWrite = toUint8(request.stencilState.stencilWrite);
outKey.stencilFrontReadMask = request.stencilState.front.readMask;
outKey.stencilFrontWriteMask = request.stencilState.front.writeMask;
outKey.stencilBackCompare = request.stencilState.back.stencilFunc;
outKey.stencilBackFailOperation = request.stencilState.back.stencilOpStencilFail;
outKey.stencilBackDepthFailOperation = request.stencilState.back.stencilOpDepthFail;
outKey.stencilBackPassOperation = request.stencilState.back.stencilOpDepthStencilPass;
outKey.cullingMode = request.rasterState.culling;
outKey.inverseFrontFaces = toUint8(request.rasterState.inverseFrontFaces);
outKey.depthWriteEnabled = toUint8(request.rasterState.depthWrite);
outKey.depthCompare = request.rasterState.depthFunc;
outKey.depthClamp = toUint8(request.rasterState.depthClamp);
outKey.colorWrite = toUint8(request.rasterState.colorWrite);
outKey.alphaToCoverageEnabled = toUint8(request.rasterState.alphaToCoverage);
outKey.colorBlendOperation = request.rasterState.blendEquationRGB;
outKey.colorBlendSourceFactor = request.rasterState.blendFunctionSrcRGB;
outKey.colorBlendDestinationFactor = request.rasterState.blendFunctionDstRGB;
outKey.alphaBlendOperation = request.rasterState.blendEquationAlpha;
outKey.alphaBlendSourceFactor = request.rasterState.blendFunctionSrcAlpha;
outKey.alphaBlendDestinationFactor = request.rasterState.blendFunctionDstAlpha;
outKey.targetRenderFlags = request.targetRenderFlags;
outKey.multisampleCount = request.multisampleCount;
outKey.depthStencilFormat = request.depthStencilFormat;
assert_invariant(request.colorFormatCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
outKey.colorFormatCount = request.colorFormatCount;
for (size_t colorIndex{ 0 }; colorIndex < request.colorFormatCount; colorIndex++) {
outKey.colorFormats[colorIndex] = request.colorFormats[colorIndex];
}
// vertex buffers...
for (WebGPUVertexBufferInfo::WebGPUSlotBindingInfo const& vertexBufferSlot:
request.vertexBufferSlots) {
assert_invariant(vertexBufferSlot.sourceBufferIndex < MAX_VERTEX_BUFFER_COUNT);
outKey.vertexBuffers[vertexBufferSlot.sourceBufferIndex].stride = vertexBufferSlot.stride;
outKey.vertexBuffers[vertexBufferSlot.sourceBufferIndex].offset =
vertexBufferSlot.bufferOffset;
}
// vertex attributes...
uint8_t currentAttributeIndex{ 0 };
for (size_t bufferIndex{ 0 }; bufferIndex < request.vertexBufferSlots.size(); bufferIndex++) {
wgpu::VertexBufferLayout const& vertexBufferLayout{
request.vertexBufferLayouts[bufferIndex]
};
for (size_t attributeIndex{ 0 }; attributeIndex < vertexBufferLayout.attributeCount;
attributeIndex++) {
assert_invariant(attributeIndex < MAX_VERTEX_ATTRIBUTE_COUNT);
wgpu::VertexAttribute const& vertexAttribute{
vertexBufferLayout.attributes[attributeIndex]
};
outKey.vertexAttributes[currentAttributeIndex].bufferIndex = bufferIndex;
outKey.vertexAttributes[currentAttributeIndex].offset =
static_cast<uint8_t>(vertexAttribute.offset);
outKey.vertexAttributes[currentAttributeIndex].shaderLocation =
static_cast<uint8_t>(vertexAttribute.shaderLocation);
outKey.vertexAttributes[currentAttributeIndex].format = vertexAttribute.format;
currentAttributeIndex++;
}
}
}
wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
RenderPipelineRequest const& request) {
assert_invariant(request.vertexShaderModule);
wgpu::DepthStencilState depthStencilState{};
const bool requestedDepth{ any(request.targetRenderFlags & TargetBufferFlags::DEPTH) };
const bool requestedStencil{ any(request.targetRenderFlags & TargetBufferFlags::STENCIL) };
const bool depthOrStencilRequested{ requestedDepth || requestedStencil };
// depth/stencil...
if (depthOrStencilRequested) {
FILAMENT_CHECK_PRECONDITION(request.depthStencilFormat != wgpu::TextureFormat::Undefined)
<< "Depth or Stencil requested for pipeline, but depthStencilFormat is "
"wgpu::TextureFormat::Undefined.";
depthStencilState.format = request.depthStencilFormat;
if (requestedDepth) {
assert_invariant(hasDepth(depthStencilState.format));
depthStencilState.depthWriteEnabled = request.rasterState.depthWrite;
depthStencilState.depthCompare = toWebGPU(request.rasterState.depthFunc);
depthStencilState.depthBias = static_cast<int32_t>(request.polygonOffset.constant);
depthStencilState.depthBiasSlopeScale = request.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 (requestedStencil) {
assert_invariant(hasStencil(depthStencilState.format));
depthStencilState.stencilFront = {
.compare = toWebGPU(request.stencilState.front.stencilFunc),
.failOp = toWebGPU(request.stencilState.front.stencilOpStencilFail),
.depthFailOp = toWebGPU(request.stencilState.front.stencilOpDepthFail),
.passOp = toWebGPU(request.stencilState.front.stencilOpDepthStencilPass),
};
depthStencilState.stencilBack = {
.compare = toWebGPU(request.stencilState.back.stencilFunc),
.failOp = toWebGPU(request.stencilState.back.stencilOpStencilFail),
.depthFailOp = toWebGPU(request.stencilState.back.stencilOpDepthFail),
.passOp = toWebGPU(request.stencilState.back.stencilOpDepthStencilPass),
};
// TODO: should we also consider the back readMask and writeMask?
depthStencilState.stencilReadMask = request.stencilState.front.readMask;
depthStencilState.stencilWriteMask =
request.stencilState.stencilWrite ? request.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;
depthStencilState.stencilReadMask = 0;
depthStencilState.stencilWriteMask = 0;
}
}
wgpu::RenderPipelineDescriptor pipelineDescriptor{
.label = wgpu::StringView(request.label.c_str_safe()),
.layout = request.pipelineLayout,
.vertex = {
.module = request.vertexShaderModule,
.entryPoint = "main",
// we do not use WebGPU's override constants due to 2 limitations
// (at least at the time of write this):
// 1. they cannot be used for the size of an array, which is needed
// 2. if we pass the WebGPU API (CPU-side) constants not referenced in the
// shader WebGPU fails. This is a problem with how Filament is designed,
// where certain constants may be optimized out of the shader based
// on build configuration, etc.
//
// to bypass these problems, we do not use override constants in the
// WebGPU backend, instead replacing placeholder constants in the shader
// text before creating the shader module (essentially implementing
// override constants ourselves)
.constantCount = 0,
.constants = nullptr,
.bufferCount = request.vertexBufferSlots.size(),
.buffers = request.vertexBufferLayouts,
},
.primitive = {
.topology = toWebGPU(request.primitiveType),
// TODO should we assume some constant format here or is there a way to get
// this from PipelineState somehow or elsewhere?
// Perhaps, cache/assert format from index buffers as they are requested?
.stripIndexFormat = wgpu::IndexFormat::Undefined,
.frontFace = request.rasterState.inverseFrontFaces ? wgpu::FrontFace::CW : wgpu::FrontFace::CCW,
.cullMode = toWebGPU(request.rasterState.culling),
// TODO no depth clamp in WebGPU supported directly. unclippedDepth is close, so we are
// starting there
.unclippedDepth = !request.rasterState.depthClamp &&
mDevice.HasFeature(wgpu::FeatureName::DepthClipControl),
},
.depthStencil = depthOrStencilRequested ? &depthStencilState: nullptr,
.multisample = {
.count = request.multisampleCount,
.mask = 0xFFFFFFFF,
.alphaToCoverageEnabled = (request.multisampleCount > 1) && request.rasterState.alphaToCoverage
},
.fragment = nullptr // will add below if fragment module is included
};
wgpu::FragmentState fragmentState = {};
const wgpu::BlendState blendState {
.color = {
.operation = toWebGPU(request.rasterState.blendEquationRGB),
.srcFactor = toWebGPU(request.rasterState.blendFunctionSrcRGB),
.dstFactor = toWebGPU(request.rasterState.blendFunctionDstRGB)
},
.alpha = {
.operation = toWebGPU(request.rasterState.blendEquationAlpha),
.srcFactor = toWebGPU(request.rasterState.blendFunctionSrcAlpha),
.dstFactor = toWebGPU(request.rasterState.blendFunctionDstAlpha)
}
};
// According to the WebGPU spec, a pipeline cannot have a fragment stage with zero color
// targets. This situation can arise in Filament during depth-only passes (like shadow map
// generation) if the material variant still includes a fragment shader.
//
// To handle this, we check if any color targets are configured for this pipeline. If not, we
// create a pipeline *without* a fragment stage. This makes the pipeline valid for a
// depth-only pass, allowing depth writes to proceed correctly.
if (request.fragmentShaderModule != nullptr && request.colorFormatCount > 0) {
fragmentState.module = request.fragmentShaderModule;
fragmentState.entryPoint = "main";
// see the comment about constants for the vertex state, as the same reasoning applies
// here
fragmentState.constantCount = 0;
fragmentState.constants = nullptr;
fragmentState.targetCount = request.colorFormatCount;
std::array<wgpu::ColorTargetState, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> colorTargets {};
assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) {
wgpu::ColorTargetState& colorTarget = colorTargets[targetIndex];
colorTarget.format = request.colorFormats[targetIndex];
colorTarget.blend = request.rasterState.hasBlending() ? &blendState : nullptr;
colorTarget.writeMask = request.rasterState.colorWrite ? wgpu::ColorWriteMask::All
: wgpu::ColorWriteMask::None;
}
fragmentState.targets = colorTargets.data();
pipelineDescriptor.fragment = &fragmentState;
}
const wgpu::RenderPipeline pipeline{ mDevice.CreateRenderPipeline(&pipelineDescriptor) };
FILAMENT_CHECK_POSTCONDITION(pipeline)
<< "Failed to create render pipeline for " << pipelineDescriptor.label;
return pipeline;
}
bool WebGPUPipelineCache::RenderPipelineKeyEqual::operator()(RenderPipelineKey const& key1,
RenderPipelineKey const& key2) const {
return 0 == memcmp(reinterpret_cast<void const*>(&key1), reinterpret_cast<void const*>(&key2),
sizeof(key1));
}
void WebGPUPipelineCache::removeExpiredPipelines() {
using Iterator = decltype(mRenderPipelines)::const_iterator;
for (Iterator iterator{ mRenderPipelines.begin() }; iterator != mRenderPipelines.end();) {
RenderPipelineCacheEntry const& entry{ iterator.value() };
if (mFrameCount > (entry.lastUsedFrameCount +
FILAMENT_WEBGPU_RENDER_PIPELINE_EXPIRATION_IN_FRAME_COUNT)) {
// pipeline expired...
iterator = mRenderPipelines.erase(iterator);
} else {
// pipeline not yet expired...
++iterator;
}
}
}
} //namespace filament::backend
@@ -1,192 +0,0 @@
/*
* 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_WEBGPUPIPELINECACHE_H
#define TNT_FILAMENT_BACKEND_WEBGPUPIPELINECACHE_H
#include "WebGPUVertexBufferInfo.h"
#include <backend/DriverEnums.h>
#include <backend/TargetBufferInfo.h>
#include <utils/CString.h>
#include <utils/Hash.h>
#include <tsl/robin_map.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
#include <type_traits>
#include <vector>
namespace filament::backend {
class WebGPUPipelineCache final {
public:
struct RenderPipelineRequest final {
utils::CString const& label;
wgpu::ShaderModule const& vertexShaderModule;
wgpu::ShaderModule const& fragmentShaderModule;
std::vector<WebGPUVertexBufferInfo::WebGPUSlotBindingInfo> const& vertexBufferSlots;
wgpu::VertexBufferLayout const* vertexBufferLayouts;
wgpu::PipelineLayout const& pipelineLayout;
const PrimitiveType primitiveType;
RasterState const& rasterState;
StencilState const& stencilState;
PolygonOffset const& polygonOffset;
const TargetBufferFlags targetRenderFlags;
const uint8_t multisampleCount;
const wgpu::TextureFormat depthStencilFormat;
const uint8_t colorFormatCount;
wgpu::TextureFormat const* colorFormats;
};
explicit WebGPUPipelineCache(wgpu::Device const&);
WebGPUPipelineCache(WebGPUPipelineCache const&) = delete;
WebGPUPipelineCache(WebGPUPipelineCache const&&) = delete;
WebGPUPipelineCache& operator=(WebGPUPipelineCache const&) = delete;
WebGPUPipelineCache& operator=(WebGPUPipelineCache const&&) = delete;
[[nodiscard]] wgpu::RenderPipeline const& getOrCreateRenderPipeline(
RenderPipelineRequest const&);
void onFrameEnd();
private:
/**
* Part of the pipeline key specifically about one of the vertex attributes
*/
struct VertexAttribute final { // size : offset (need multiples of 4 bytes for hashing)
uint8_t bufferIndex{ 0 }; // 1 : 0
// this is the webgpu offset, //
// bytes from the start of //
// the vertex data //
// (interleaved offset) //
uint8_t offset{ 0 }; // 1 : 1
uint8_t shaderLocation{ 0 }; // 1 : 2
uint8_t padding { 0 }; // 1 : 3
wgpu::VertexFormat format{ 0 }; // 4 : 4
};
static_assert(sizeof(VertexAttribute) == 8, "VertexAttribute must not have implicit padding.");
static_assert(std::is_trivially_copyable<VertexAttribute>::value,
"VertexAttribute must be a trivially copyable POD for fast hashing.");
/**
* Part of the pipeline key specifically about one of the vertex buffers
*/
struct VertexBuffer final { // size : offset (need multiples of 4 bytes for hashing)
uint8_t stride{ 0 }; // 1 : 0
uint8_t padding[3]{ 0 }; // 3 : 1
// offset in bytes from //
// the start of the //
// (physical) GPU buffer //
// (not logical buffer //
// partition) //
uint32_t offset{ 0 }; // 4 : 4
};
static_assert(sizeof(VertexBuffer) == 8, "VertexBuffer must not have implicit padding.");
static_assert(std::is_trivially_copyable<VertexBuffer>::value,
"VertexAttribute must be a trivially copyable POD for fast hashing.");
/**
* Key designed for efficient hashing and uniquely identifying all the parameters for
* creating a render pipeline.
* The efficient hashing requires a small memory footprint
* (using the smallest representations of enums, just handle instances instead of wrapper class
* instances, single bytes for booleans etc.), trivial copying and comparison (byte by byte),
* and a word-aligned structure with a size in bytes as a multiple of 4 (for murmer hash).
*/
struct RenderPipelineKey final { // size : offset (need multiples of 4 bytes for hashing)
// shaders... //
WGPUShaderModule vertexShaderModuleHandle{ nullptr }; // 8 : 0
WGPUShaderModule fragmentShaderModuleHandle{ nullptr }; // 8 : 8
// vertex attributes... //
VertexAttribute vertexAttributes[MAX_VERTEX_ATTRIBUTE_COUNT]{}; // 128 : 16
VertexBuffer vertexBuffers[MAX_VERTEX_BUFFER_COUNT]{}; // 128 : 144
// pipeline layout... //
WGPUPipelineLayout pipelineLayoutHandle{ nullptr }; // 8 : 272
// general settings... //
int32_t depthBias{ 0 }; // 4 : 280
float depthBiasSlopeScale { 0.0f }; // 4 : 284
PrimitiveType primitiveType{ PrimitiveType::POINTS }; // 1 : 288
// stencil state... //
SamplerCompareFunc stencilFrontCompare{ SamplerCompareFunc::LE }; // 1 : 289
StencilOperation stencilFrontFailOperation{ StencilOperation::KEEP }; // 1 : 290
StencilOperation stencilFrontDepthFailOperation{ StencilOperation::KEEP }; // 1 : 291
StencilOperation stencilFrontPassOperation{ StencilOperation::KEEP }; // 1 : 292
/* bool, 0 -> false, 1 -> true */ uint8_t stencilWrite{ 0 }; // 1 : 293
uint8_t stencilFrontReadMask{ 0 }; // 1 : 294
uint8_t stencilFrontWriteMask{ 0 }; // 1 : 295
SamplerCompareFunc stencilBackCompare{ SamplerCompareFunc::LE }; // 1 : 296
StencilOperation stencilBackFailOperation{ StencilOperation::KEEP }; // 1 : 297
StencilOperation stencilBackDepthFailOperation{ StencilOperation::KEEP }; // 1 : 298
StencilOperation stencilBackPassOperation{ StencilOperation::KEEP }; // 1 : 299
// general rasterization settings... //
CullingMode cullingMode{ CullingMode::NONE }; // 1 : 300
/* bool, 0 -> counter-clockwise, 1 -> clockwise */ uint8_t inverseFrontFaces{ 0 }; // 1 : 301
// rasterization depth state... //
/* bool, 0 -> false, 1 -> true */ uint8_t depthWriteEnabled{ 0 }; // 1 : 302
SamplerCompareFunc depthCompare{ SamplerCompareFunc::LE }; // 1 : 303
/* bool, 0 -> false, 1 -> true */ uint8_t depthClamp{ 0 }; // 1 : 304
// more rasterization flags... //
/* bool, 0 -> false, 1 -> true */ uint8_t colorWrite{ 0 }; // 1 : 305
/* bool, 0 -> false, 1 -> true */ uint8_t alphaToCoverageEnabled{ 0 }; // 1 : 306
// color blending... //
BlendEquation colorBlendOperation{ BlendEquation::ADD }; // 1 : 307
BlendFunction colorBlendSourceFactor{ BlendFunction::ZERO }; // 1 : 308
BlendFunction colorBlendDestinationFactor{ BlendFunction::ZERO }; // 1 : 309
// alpha blending... //
BlendEquation alphaBlendOperation{ BlendEquation::ADD }; // 1 : 310
BlendFunction alphaBlendSourceFactor{ BlendFunction::ZERO }; // 1 : 311
BlendFunction alphaBlendDestinationFactor{ BlendFunction::ZERO }; // 1 : 312
// render targets... //
uint8_t multisampleCount{ 0 }; // 1 : 313
uint8_t colorFormatCount{ 0 }; // 1 : 314
uint8_t padding[5]{ 0 }; // 5 : 319
TargetBufferFlags targetRenderFlags{ TargetBufferFlags::NONE }; // 4 : 320
wgpu::TextureFormat depthStencilFormat { wgpu::TextureFormat::Undefined }; // 4 : 324
wgpu::TextureFormat colorFormats[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]{ //
wgpu::TextureFormat::Undefined //
}; // 32 : 328
};
static_assert(sizeof(RenderPipelineKey) == 360,
"RenderPipelineKey must not have implicit padding.");
static_assert(std::is_trivially_copyable<RenderPipelineKey>::value,
"RenderPipelineKey must be a trivially copyable POD for fast hashing.");
struct RenderPipelineKeyEqual {
bool operator()(RenderPipelineKey const&, RenderPipelineKey const&) const;
};
struct RenderPipelineCacheEntry final {
wgpu::RenderPipeline pipeline{ nullptr };
uint64_t lastUsedFrameCount{ 0 };
};
static void populateKey(RenderPipelineRequest const&, RenderPipelineKey& outKey);
[[nodiscard]] wgpu::RenderPipeline createRenderPipeline(RenderPipelineRequest const&);
void removeExpiredPipelines();
wgpu::Device mDevice;
tsl::robin_map<RenderPipelineKey, RenderPipelineCacheEntry,
utils::hash::MurmurHashFn<RenderPipelineKey>, RenderPipelineKeyEqual>
mRenderPipelines{};
uint64_t mFrameCount{ 0 };
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUPIPELINECACHE_H
@@ -0,0 +1,299 @@
/*
* 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 "WebGPUPipelineCreation.h"
#include "WebGPUProgram.h"
#include "WebGPURenderTarget.h"
#include "WebGPUVertexBufferInfo.h"
#include <backend/DriverEnums.h>
#include <backend/TargetBufferInfo.h>
#include <utils/Panic.h>
#include <utils/debug.h>
#include <webgpu/webgpu_cpp.h>
#include <array>
#include <cstdint>
#include <sstream>
namespace filament::backend {
namespace {
constexpr wgpu::PrimitiveTopology toWebGPU(PrimitiveType primitiveType) {
switch (primitiveType) {
case PrimitiveType::POINTS:
return wgpu::PrimitiveTopology::PointList;
case PrimitiveType::LINES:
return wgpu::PrimitiveTopology::LineList;
case PrimitiveType::LINE_STRIP:
return wgpu::PrimitiveTopology::LineStrip;
case PrimitiveType::TRIANGLES:
return wgpu::PrimitiveTopology::TriangleList;
case PrimitiveType::TRIANGLE_STRIP:
return wgpu::PrimitiveTopology::TriangleStrip;
}
}
constexpr wgpu::CullMode toWebGPU(CullingMode cullMode) {
switch (cullMode) {
case CullingMode::NONE:
return wgpu::CullMode::None;
case CullingMode::FRONT:
return wgpu::CullMode::Front;
case CullingMode::BACK:
return wgpu::CullMode::Back;
case CullingMode::FRONT_AND_BACK:
// no WegGPU equivalent of front and back
FILAMENT_CHECK_POSTCONDITION(false)
<< "WebGPU does not support CullingMode::FRONT_AND_BACK";
return wgpu::CullMode::Undefined;
}
}
bool hasStencilAspect(wgpu::TextureFormat format) {
switch (format) {
case wgpu::TextureFormat::Stencil8:
case wgpu::TextureFormat::Depth24PlusStencil8:
case wgpu::TextureFormat::Depth32FloatStencil8:
return true;
default:
return false;
}
}
constexpr wgpu::CompareFunction toWebGPU(SamplerCompareFunc compareFunction) {
switch (compareFunction) {
case SamplerCompareFunc::LE:
return wgpu::CompareFunction::LessEqual;
case SamplerCompareFunc::GE:
return wgpu::CompareFunction::GreaterEqual;
case SamplerCompareFunc::L:
return wgpu::CompareFunction::Less;
case SamplerCompareFunc::G:
return wgpu::CompareFunction::Greater;
case SamplerCompareFunc::E:
return wgpu::CompareFunction::Equal;
case SamplerCompareFunc::NE:
return wgpu::CompareFunction::NotEqual;
case SamplerCompareFunc::A:
return wgpu::CompareFunction::Always;
case SamplerCompareFunc::N:
return wgpu::CompareFunction::Never;
}
}
constexpr wgpu::StencilOperation toWebGPU(StencilOperation stencilOp) {
switch (stencilOp) {
case StencilOperation::KEEP:
return wgpu::StencilOperation::Keep;
case StencilOperation::ZERO:
return wgpu::StencilOperation::Zero;
case StencilOperation::REPLACE:
return wgpu::StencilOperation::Replace;
case StencilOperation::INCR:
return wgpu::StencilOperation::IncrementClamp;
case StencilOperation::INCR_WRAP:
return wgpu::StencilOperation::IncrementWrap;
case StencilOperation::DECR:
return wgpu::StencilOperation::DecrementClamp;
case StencilOperation::DECR_WRAP:
return wgpu::StencilOperation::DecrementWrap;
case StencilOperation::INVERT:
return wgpu::StencilOperation::Invert;
}
}
constexpr wgpu::BlendOperation toWebGPU(BlendEquation blendOp) {
switch (blendOp) {
case BlendEquation::ADD:
return wgpu::BlendOperation::Add;
case BlendEquation::SUBTRACT:
return wgpu::BlendOperation::Subtract;
case BlendEquation::REVERSE_SUBTRACT:
return wgpu::BlendOperation::ReverseSubtract;
case BlendEquation::MIN:
return wgpu::BlendOperation::Min;
case BlendEquation::MAX:
return wgpu::BlendOperation::Max;
}
}
constexpr wgpu::BlendFactor toWebGPU(BlendFunction blendFunction) {
switch (blendFunction) {
case BlendFunction::ZERO:
return wgpu::BlendFactor::Zero;
case BlendFunction::ONE:
return wgpu::BlendFactor::One;
case BlendFunction::SRC_COLOR:
return wgpu::BlendFactor::Src;
case BlendFunction::ONE_MINUS_SRC_COLOR:
return wgpu::BlendFactor::OneMinusSrc;
case BlendFunction::DST_COLOR:
return wgpu::BlendFactor::Dst;
case BlendFunction::ONE_MINUS_DST_COLOR:
return wgpu::BlendFactor::OneMinusDst;
case BlendFunction::SRC_ALPHA:
return wgpu::BlendFactor::SrcAlpha;
case BlendFunction::ONE_MINUS_SRC_ALPHA:
return wgpu::BlendFactor::OneMinusSrcAlpha;
case BlendFunction::DST_ALPHA:
return wgpu::BlendFactor::DstAlpha;
case BlendFunction::ONE_MINUS_DST_ALPHA:
return wgpu::BlendFactor::OneMinusDstAlpha;
case BlendFunction::SRC_ALPHA_SATURATE:
return wgpu::BlendFactor::SrcAlphaSaturated;
}
}
}// namespace
wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device,
WebGPUProgram const& program, WebGPUVertexBufferInfo const& vertexBufferInfo,
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) {
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;
if (!hasStencilAspect(depthFormat)) {
depthStencilState.stencilFront.compare = wgpu::CompareFunction::Always;
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.stencilReadMask = 0;
depthStencilState.stencilWriteMask = 0;
}
}
std::stringstream pipelineLabelStream;
pipelineLabelStream << program.name.c_str() << " pipeline";
const auto pipelineLabel = pipelineLabelStream.str();
wgpu::RenderPipelineDescriptor pipelineDescriptor{
.label = wgpu::StringView(pipelineLabel),
.layout = layout,
.vertex = { .module = program.vertexShaderModule,
.entryPoint = "main",
// we do not use WebGPU's override constants due to 2 limitations
// (at least at the time of write this):
// 1. they cannot be used for the size of an array, which is needed
// 2. if we pass the WebGPU API (CPU-side) constants not referenced in the
// shader WebGPU fails. This is a problem with how Filament is designed,
// where certain constants may be optimized out of the shader based
// on build configuration, etc.
//
// to bypass these problems, we do not use override constants in the
// WebGPU backend, instead replacing placeholder constants in the shader
// text before creating the shader module (essentially implementing
// override constants ourselves)
.constantCount = 0,
.constants = nullptr,
.bufferCount = vertexBufferInfo.getVertexBufferLayoutCount(),
.buffers = vertexBufferInfo.getVertexBufferLayouts()
},
.primitive = {
.topology = toWebGPU(primitiveType),
// TODO should we assume some constant format here or is there a way to get
// this from PipelineState somehow or elsewhere?
// Perhaps, cache/assert format from index buffers as they are requested?
.stripIndexFormat = wgpu::IndexFormat::Undefined,
.frontFace = rasterState.inverseFrontFaces ? wgpu::FrontFace::CW : wgpu::FrontFace::CCW,
.cullMode = toWebGPU(rasterState.culling),
// TODO no depth clamp in WebGPU supported directly. unclippedDepth is close, so we are
// starting there
.unclippedDepth = !rasterState.depthClamp &&
device.HasFeature(wgpu::FeatureName::DepthClipControl)
},
.depthStencil = depthFormat != wgpu::TextureFormat::Undefined ? &depthStencilState: nullptr,
.multisample = {
.count = samplesCount,
.mask = 0xFFFFFFFF,
.alphaToCoverageEnabled = rasterState.alphaToCoverage
},
.fragment = nullptr // will add below if fragment module is included
};
wgpu::FragmentState fragmentState = {};
std::array<wgpu::ColorTargetState, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> colorTargets {};
const wgpu::BlendState blendState {
.color = {
.operation = toWebGPU(rasterState.blendEquationRGB),
.srcFactor = toWebGPU(rasterState.blendFunctionSrcRGB),
.dstFactor = toWebGPU(rasterState.blendFunctionDstRGB)
},
.alpha = {
.operation = toWebGPU(rasterState.blendEquationAlpha),
.srcFactor = toWebGPU(rasterState.blendFunctionSrcAlpha),
.dstFactor = toWebGPU(rasterState.blendFunctionDstAlpha)
}
};
if (program.fragmentShaderModule != nullptr) {
fragmentState.module = program.fragmentShaderModule;
fragmentState.entryPoint = "main";
// see the comment about constants for the vertex state, as the same reasoning applies
// here
fragmentState.constantCount = 0,
fragmentState.constants = nullptr,
fragmentState.targetCount = colorFormats.size();
fragmentState.targets = colorTargets.data();
assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
// We expect a fragment shader implies at least one color target if it outputs color.
// This should be guaranteed by the caller ensuring colorFormats is not empty.
// However, this fails on shadowtest.cpp, TODO investigate why
// assert_invariant(fragmentState.targetCount > 0);
for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) {
auto& colorTarget = colorTargets[targetIndex];
colorTarget.format = colorFormats[targetIndex];
colorTarget.blend = rasterState.hasBlending() ? &blendState : nullptr;
colorTarget.writeMask =
rasterState.colorWrite ? wgpu::ColorWriteMask::All : wgpu::ColorWriteMask::None;
}
pipelineDescriptor.fragment = &fragmentState;
}
const wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor);
FILAMENT_CHECK_POSTCONDITION(pipeline)
<< "Failed to create render pipeline for " << pipelineDescriptor.label;
return pipeline;
}
}// namespace filament::backend
@@ -0,0 +1,48 @@
/*
* 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_WEBGPUPIPELINECREATION_H
#define TNT_FILAMENT_BACKEND_WEBGPUPIPELINECREATION_H
#include <cstdint>
#include <vector>
namespace wgpu {
class Device;
class PipelineLayout;
class RenderPipeline;
enum class TextureFormat : uint32_t;
}// namespace wgpu
namespace filament::backend {
struct PolygonOffset;
enum class PrimitiveType : uint8_t;
struct RasterState;
struct StencilState;
class WebGPUVertexBufferInfo;
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);
}// namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_WEBGPUPIPELINECREATION_H
@@ -1,105 +0,0 @@
/*
* 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 "WebGPUPipelineLayoutCache.h"
#include "WebGPUConstants.h"
#include <backend/DriverEnums.h>
#include <utils/CString.h>
#include <utils/Panic.h>
#include <webgpu/webgpu_cpp.h>
#include <array>
#include <cstring>
namespace filament::backend {
WebGPUPipelineLayoutCache::WebGPUPipelineLayoutCache(wgpu::Device const& device)
: mDevice{ device } {}
wgpu::PipelineLayout const& WebGPUPipelineLayoutCache::getOrCreatePipelineLayout(
PipelineLayoutRequest const& request) {
PipelineLayoutKey key{};
populateKey(request, key);
if (auto iterator{ mPipelineLayouts.find(key) }; iterator != mPipelineLayouts.end()) {
PipelineLayoutCacheEntry& entry{ iterator.value() };
entry.lastUsedFrameCount = mFrameCount;
return entry.layout;
}
const wgpu::PipelineLayout layout{ createPipelineLayout(request) };
mPipelineLayouts.emplace(key, PipelineLayoutCacheEntry{
.layout = layout,
.lastUsedFrameCount = mFrameCount,
});
return mPipelineLayouts[key].layout;
}
void WebGPUPipelineLayoutCache::onFrameEnd() {
++mFrameCount;
removeExpiredPipelineLayouts();
}
void WebGPUPipelineLayoutCache::populateKey(PipelineLayoutRequest const& request,
PipelineLayoutKey& outKey) {
outKey.bindGroupLayoutCount = static_cast<uint8_t>(request.bindGroupLayoutCount);
for (size_t bindGroupIndex{ 0 }; bindGroupIndex < request.bindGroupLayoutCount;
++bindGroupIndex) {
outKey.bindGroupLayoutHandles[bindGroupIndex] =
request.bindGroupLayouts[bindGroupIndex]
? request.bindGroupLayouts[bindGroupIndex].Get()
: nullptr;
}
}
wgpu::PipelineLayout WebGPUPipelineLayoutCache::createPipelineLayout(
PipelineLayoutRequest const& request) {
const wgpu::PipelineLayoutDescriptor descriptor{
.label = wgpu::StringView(request.label.c_str_safe()),
.bindGroupLayoutCount = request.bindGroupLayoutCount,
.bindGroupLayouts = request.bindGroupLayouts.data(),
// TODO investigate immediateDataRangeByteSize
};
const wgpu::PipelineLayout layout{ mDevice.CreatePipelineLayout(&descriptor) };
FILAMENT_CHECK_POSTCONDITION(layout)
<< "Failed to create pipeline layout " << descriptor.label << "?";
return layout;
}
bool WebGPUPipelineLayoutCache::PipelineLayoutKeyEqual::operator()(PipelineLayoutKey const& key1,
PipelineLayoutKey const& key2) const {
return 0 == memcmp(reinterpret_cast<void const*>(&key1), reinterpret_cast<void const*>(&key2),
sizeof(key1));
}
void WebGPUPipelineLayoutCache::removeExpiredPipelineLayouts() {
using Iterator = decltype(mPipelineLayouts)::const_iterator;
for (Iterator iterator{ mPipelineLayouts.begin() }; iterator != mPipelineLayouts.end();) {
PipelineLayoutCacheEntry const& entry{ iterator.value() };
if (mFrameCount > (entry.lastUsedFrameCount +
FILAMENT_WEBGPU_PIPELINE_LAYOUT_EXPIRATION_IN_FRAME_COUNT)) {
// pipeline layout expired...
iterator = mPipelineLayouts.erase(iterator);
} else {
// pipeline layout not yet expired...
++iterator;
}
}
}
} // namespace filament::backend
@@ -1,96 +0,0 @@
/*
* 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_WEBGPUPIPELINELAYOUTCACHE_H
#define TNT_FILAMENT_BACKEND_WEBGPUPIPELINELAYOUTCACHE_H
#include <backend/DriverEnums.h>
#include <utils/CString.h>
#include <utils/Hash.h>
#include <tsl/robin_map.h>
#include <webgpu/webgpu_cpp.h>
#include <array>
#include <cstdint>
#include <type_traits>
namespace filament::backend {
class WebGPUPipelineLayoutCache final {
public:
struct PipelineLayoutRequest final {
utils::CString const& label;
std::array<wgpu::BindGroupLayout, MAX_DESCRIPTOR_SET_COUNT> const& bindGroupLayouts;
size_t bindGroupLayoutCount;
};
explicit WebGPUPipelineLayoutCache(wgpu::Device const&);
WebGPUPipelineLayoutCache(WebGPUPipelineLayoutCache const&) = delete;
WebGPUPipelineLayoutCache(WebGPUPipelineLayoutCache const&&) = delete;
WebGPUPipelineLayoutCache& operator=(WebGPUPipelineLayoutCache const&) = delete;
WebGPUPipelineLayoutCache& operator=(WebGPUPipelineLayoutCache const&&) = delete;
[[nodiscard]] wgpu::PipelineLayout const& getOrCreatePipelineLayout(
PipelineLayoutRequest const&);
void onFrameEnd();
private:
/**
* Key designed for efficient hashing and uniquely identifying all the parameters for
* creating a pipeline layout.
* The efficient hashing requires a small memory footprint
* (using the smallest representations of enums, just handle instances instead of wrapper class
* instances, single bytes for booleans etc.), trivial copying and comparison (byte by byte),
* and a word-aligned structure with a size in bytes as a multiple of 4 (for murmer hash).
*/
struct PipelineLayoutKey final { // size : offset (need multiples of 4 bytes for hashing)
WGPUBindGroupLayout bindGroupLayoutHandles[MAX_DESCRIPTOR_SET_COUNT]{ nullptr }; // 32 : 0
uint8_t bindGroupLayoutCount{ 0 }; // 1 : 32
uint8_t padding[7]{ 0 }; // 7 : 33
};
static_assert(sizeof(PipelineLayoutKey) == 40,
"PipelineLayoutKey must not have implicit padding.");
static_assert(std::is_trivially_copyable<PipelineLayoutKey>::value,
"PipelineLayoutKey must be a trivially copyable POD for fast hashing.");
struct PipelineLayoutKeyEqual {
bool operator()(PipelineLayoutKey const&, PipelineLayoutKey const&) const;
};
struct PipelineLayoutCacheEntry final {
wgpu::PipelineLayout layout{ nullptr };
uint64_t lastUsedFrameCount{ 0 };
};
static void populateKey(PipelineLayoutRequest const&, PipelineLayoutKey& outKey);
[[nodiscard]] wgpu::PipelineLayout createPipelineLayout(PipelineLayoutRequest const&);
void removeExpiredPipelineLayouts();
wgpu::Device mDevice;
tsl::robin_map<PipelineLayoutKey, PipelineLayoutCacheEntry,
utils::hash::MurmurHashFn<PipelineLayoutKey>, PipelineLayoutKeyEqual>
mPipelineLayouts{};
uint64_t mFrameCount{ 0 };
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUPIPELINELAYOUTCACHE_H
@@ -234,7 +234,7 @@ namespace {
FWGPU_LOGD << descriptor.label << " compiled successfully";
#endif
}),
FILAMENT_WEBGPU_SHADER_COMPILATION_TIMEOUT_NANOSECONDS);
SHADER_COMPILATION_TIMEOUT_NANOSECONDS);
switch (waitResult) {
case wgpu::WaitStatus::Success:
break;
@@ -1,552 +0,0 @@
/*
* 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
@@ -1,69 +0,0 @@
/*
* 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 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
+101 -204
View File
@@ -16,11 +16,7 @@
#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>
@@ -30,120 +26,30 @@
#include <webgpu/webgpu_cpp.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <functional>
#include <string_view>
namespace filament::backend {
namespace {
/**
* Panics if the number of samples in the original attachment textures do not match.
* @return The number of samples in the original attachment textures (the number/count in each one).
* Returns 0 if there are no attachments.
*/
[[nodiscard]] uint8_t createMsaaSidecarTextures(const uint8_t renderTargetSampleCount,
const TargetBufferFlags targetFlags, MRT const& colorAttachments,
TargetBufferInfo const& depthAttachment, TargetBufferInfo const& stencilAttachment,
std::function<WebGPUTexture*(const Handle<HwTexture>)> const& getWebGPUTexture,
wgpu::Device const& device) {
struct Target final {
std::string_view name;
TargetBufferFlags flag{ TargetBufferFlags::NONE };
size_t colorIndex{ 0 };
};
// assuming 8 colors. hopefully the following static_assert will get tripped if another
// color ever gets added
static_assert(
(TargetBufferFlags::COLOR0 | TargetBufferFlags::COLOR1 | TargetBufferFlags::COLOR2 |
TargetBufferFlags::COLOR3 | TargetBufferFlags::COLOR4 |
TargetBufferFlags::COLOR5 | TargetBufferFlags::COLOR6 |
TargetBufferFlags::COLOR7) == TargetBufferFlags::COLOR_ALL);
const static std::array TARGETS{
Target{ .name = "COLOR0", .flag = TargetBufferFlags::COLOR0, .colorIndex = 0 },
Target{ .name = "COLOR1", .flag = TargetBufferFlags::COLOR1, .colorIndex = 1 },
Target{ .name = "COLOR2", .flag = TargetBufferFlags::COLOR2, .colorIndex = 2 },
Target{ .name = "COLOR3", .flag = TargetBufferFlags::COLOR3, .colorIndex = 3 },
Target{ .name = "COLOR4", .flag = TargetBufferFlags::COLOR4, .colorIndex = 4 },
Target{ .name = "COLOR5", .flag = TargetBufferFlags::COLOR5, .colorIndex = 5 },
Target{ .name = "COLOR6", .flag = TargetBufferFlags::COLOR6, .colorIndex = 6 },
Target{ .name = "COLOR7", .flag = TargetBufferFlags::COLOR7, .colorIndex = 7 },
Target{ .name = "DEPTH", .flag = TargetBufferFlags::DEPTH },
Target{ .name = "STENCIL", .flag = TargetBufferFlags::STENCIL },
};
#ifndef NDEBUG
for (auto const& target: TARGETS) {
assert_invariant(target.colorIndex <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT &&
"color index is >= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT, which should "
"not be possible and could lead to us indexing into the colorAttachments "
"array beyond its bounds.");
}
#endif
bool firstAttachment{ true };
uint8_t sampleCountPerAttachment{ 0 };
for (auto const& target: TARGETS) {
if (any(targetFlags & target.flag)) {
const Handle<HwTexture> textureHandle{
target.flag == TargetBufferFlags::DEPTH
? depthAttachment.handle
: (target.flag == TargetBufferFlags::STENCIL
? stencilAttachment.handle
: colorAttachments[target.colorIndex].handle)
};
WebGPUTexture* const texture{ getWebGPUTexture(textureHandle) };
assert_invariant(texture != nullptr && "target flag indicate the use of an attachment "
"for which we do not have a texture?");
if (firstAttachment) {
sampleCountPerAttachment = texture->samples;
firstAttachment = false;
}
FILAMENT_CHECK_PRECONDITION(texture->samples == sampleCountPerAttachment)
<< target.name << " attachment texture has " << +texture->samples
<< " but the other attachment(s) have " << +sampleCountPerAttachment;
if (renderTargetSampleCount > 1 && sampleCountPerAttachment == 1) {
texture->createMsaaSidecarTextureIfNotAlreadyCreated(renderTargetSampleCount,
device);
}
}
}
assert_invariant(sampleCountPerAttachment);
return sampleCountPerAttachment;
}
} // namespace
WebGPURenderTarget::WebGPURenderTarget(const uint32_t width, const uint32_t height,
const uint8_t samples, const uint8_t layerCount, MRT const& colorAttachmentsMRT,
Attachment const& depthAttachmentInfo, Attachment const& stencilAttachmentInfo,
TargetBufferFlags const& targetFlags,
std::function<WebGPUTexture*(const Handle<HwTexture>)> const& getWebGPUTexture,
wgpu::Device const& device)
Attachment const& depthAttachmentInfo, Attachment const& stencilAttachmentInfo)
: HwRenderTarget{ width, height },
mDefaultRenderTarget{ false },
mTargetFlags{ targetFlags },
mSamples{ samples },
mLayerCount{ layerCount },
mColorAttachments{ colorAttachmentsMRT },
mDepthAttachment{ depthAttachmentInfo },
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);
mStencilAttachment{ stencilAttachmentInfo } {
// TODO consider making this an array
mColorAttachmentDescriptors.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 },
mSampleCountPerAttachment{ 1 } {}
mLayerCount{ 1 } {}
wgpu::LoadOp WebGPURenderTarget::getLoadOperation(RenderPassParams const& params,
const TargetBufferFlags bufferToOperateOn) {
@@ -151,7 +57,7 @@ wgpu::LoadOp WebGPURenderTarget::getLoadOperation(RenderPassParams const& params
return wgpu::LoadOp::Clear;
}
if (any(params.flags.discardStart & bufferToOperateOn)) {
return wgpu::LoadOp::Clear;
return wgpu::LoadOp::Clear; // Or wgpu::LoadOp::Undefined if clear is not desired on discard
}
return wgpu::LoadOp::Load;
}
@@ -167,137 +73,128 @@ wgpu::StoreOp WebGPURenderTarget::getStoreOperation(RenderPassParams const& para
void WebGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor& outDescriptor,
RenderPassParams const& params, wgpu::TextureView const& defaultColorTextureView,
wgpu::TextureView const& defaultDepthStencilTextureView,
wgpu::TextureView const* customColorTextureViews,
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.";
}
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;
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);
mColorAttachmentDesc.push_back({
.view = defaultColorTextureView,
mColorAttachmentDescriptors.push_back({ .view = defaultColorTextureView,
.resolveTarget = nullptr,
.loadOp = WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::COLOR0),
.storeOp = WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::COLOR0),
.clearValue = {
.r = params.clearColor.r,
.g = params.clearColor.g,
.b = params.clearColor.b,
.a = params.clearColor.a,
},
});
} else {
.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
for (uint32_t i = 0; i < customColorTextureViewCount; ++i) {
if (customColorTextureViews[i]) {
const wgpu::TextureView msaaSidecar{ customColorMsaaSidecarTextureViews[i] };
mColorAttachmentDesc.push_back({
.view = hasMsaaSidecars ? msaaSidecar : customColorTextureViews[i],
.resolveTarget = hasMsaaSidecars ? customColorTextureViews[i] : nullptr,
mColorAttachmentDescriptors.push_back({ .view = customColorTextureViews[i],
// .resolveTarget = nullptr; // TODO: MSAA resolve for custom RT
.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,
},
});
}
}
}
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;
.a = params.clearColor.a } });
}
}
if (depthStencilViewToUse) {
mDepthStencilAttachmentDesc = {};
mDepthStencilAttachmentDesc.view = depthStencilViewToUse;
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 (hasDepth) {
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;
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;
} else {
mDepthStencilAttachmentDesc.depthLoadOp = wgpu::LoadOp::Undefined;
mDepthStencilAttachmentDesc.depthStoreOp = wgpu::StoreOp::Undefined;
mDepthStencilAttachmentDesc.depthReadOnly = true;
mDepthStencilAttachmentDescriptor.depthLoadOp = wgpu::LoadOp::Undefined;
mDepthStencilAttachmentDescriptor.depthStoreOp = wgpu::StoreOp::Undefined;
mDepthStencilAttachmentDescriptor.depthReadOnly = true;
}
if (hasStencil) {
mDepthStencilAttachmentDesc.stencilLoadOp =
getLoadOperation(params, TargetBufferFlags::STENCIL);
mDepthStencilAttachmentDesc.stencilStoreOp =
getStoreOperation(params, TargetBufferFlags::STENCIL);
mDepthStencilAttachmentDesc.stencilClearValue = params.clearStencil;
mDepthStencilAttachmentDesc.stencilReadOnly =
mDepthStencilAttachmentDescriptor.stencilLoadOp =
WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::STENCIL);
mDepthStencilAttachmentDescriptor.stencilStoreOp =
WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::STENCIL);
mDepthStencilAttachmentDescriptor.stencilClearValue = params.clearStencil;
mDepthStencilAttachmentDescriptor.stencilReadOnly =
(params.readOnlyDepthStencil & RenderPassParams::READONLY_STENCIL) > 0;
} else {
mDepthStencilAttachmentDesc.stencilLoadOp = wgpu::LoadOp::Undefined;
mDepthStencilAttachmentDesc.stencilStoreOp = wgpu::StoreOp::Undefined;
mDepthStencilAttachmentDesc.stencilReadOnly = true;
mDepthStencilAttachmentDescriptor.stencilLoadOp = wgpu::LoadOp::Undefined;
mDepthStencilAttachmentDescriptor.stencilStoreOp = wgpu::StoreOp::Undefined;
mDepthStencilAttachmentDescriptor.stencilReadOnly = true;
}
outDescriptor.depthStencilAttachment = &mDepthStencilAttachmentDesc;
} else {
outDescriptor.depthStencilAttachment = nullptr;
mDepthStencilAttachmentDesc = {};
mHasDepthStencilAttachment = true;
}
} 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,8 +17,6 @@
#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>
@@ -26,7 +24,6 @@
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
#include <functional>
#include <vector>
namespace filament::backend {
@@ -37,44 +34,44 @@ public:
WebGPURenderTarget(uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount,
MRT const& colorAttachments, Attachment const& depthAttachment,
Attachment const& stencilAttachment, TargetBufferFlags const& targetFlags,
std::function<WebGPUTexture*(const Handle<HwTexture>)> const&, wgpu::Device const&);
Attachment const& stencilAttachment);
// Default constructor for the default render target
WebGPURenderTarget();
void setUpRenderPassAttachments(wgpu::RenderPassDescriptor& outDescriptor,
// Updated signature: takes resolved views for custom RTs, and default views for default RT
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* customColorMsaaSidecarTextureViews, // nullptrs if N/A
wgpu::TextureView const* customColorTextureViews, // Array of views
uint32_t customColorTextureViewCount,
wgpu::TextureView const& customDepthStencilTextureView,
wgpu::TextureView const& customDepthStencilMsaaSidecarTextureView /* nullptr if N/A */);
wgpu::TextureView const& customDepthTextureView,
wgpu::TextureView const& customStencilTextureView,
wgpu::TextureFormat customDepthFormat,
wgpu::TextureFormat customStencilFormat);
[[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;
@@ -84,11 +81,9 @@ private:
Attachment mDepthAttachment{};
Attachment mStencilAttachment{};
uint8_t mSampleCountPerAttachment = 0;
// Cached descriptors for the render pass
std::vector<wgpu::RenderPassColorAttachment> mColorAttachmentDesc;
wgpu::RenderPassDepthStencilAttachment mDepthStencilAttachmentDesc{};
std::vector<wgpu::RenderPassColorAttachment> mColorAttachmentDescriptors;
wgpu::RenderPassDepthStencilAttachment mDepthStencilAttachmentDescriptor{};
bool mHasDepthStencilAttachment = false;
};
-116
View File
@@ -123,122 +123,6 @@ template<typename WebGPUPrintable>
}
}
[[nodiscard]] constexpr std::string_view webGPUTextureFormatToString(
const wgpu::TextureFormat format) {
switch (format) {
case wgpu::TextureFormat::Undefined: return "Undefined";
case wgpu::TextureFormat::R8Unorm: return "R8Unorm";
case wgpu::TextureFormat::R8Snorm: return "R8Snorm";
case wgpu::TextureFormat::R8Uint: return "R8Uint";
case wgpu::TextureFormat::R8Sint: return "R8Sint";
case wgpu::TextureFormat::R16Uint: return "R16Uint";
case wgpu::TextureFormat::R16Sint: return "R16Sint";
case wgpu::TextureFormat::R16Float: return "R16Float";
case wgpu::TextureFormat::RG8Unorm: return "RG8Unorm";
case wgpu::TextureFormat::RG8Snorm: return "RG8Snorm";
case wgpu::TextureFormat::RG8Uint: return "RG8Uint";
case wgpu::TextureFormat::RG8Sint: return "RG8Sint";
case wgpu::TextureFormat::R32Float: return "R32Float";
case wgpu::TextureFormat::R32Uint: return "R32Uint";
case wgpu::TextureFormat::R32Sint: return "R32Sint";
case wgpu::TextureFormat::RG16Uint: return "RG16Uint";
case wgpu::TextureFormat::RG16Sint: return "RG16Sint";
case wgpu::TextureFormat::RG16Float: return "RG16Float";
case wgpu::TextureFormat::RGBA8Unorm: return "RGBA8Unorm";
case wgpu::TextureFormat::RGBA8UnormSrgb: return "RGBA8UnormSrgb";
case wgpu::TextureFormat::RGBA8Snorm: return "RGBA8Snorm";
case wgpu::TextureFormat::RGBA8Uint: return "RGBA8Uint";
case wgpu::TextureFormat::RGBA8Sint: return "RGBA8Sint";
case wgpu::TextureFormat::BGRA8Unorm: return "BGRA8Unorm";
case wgpu::TextureFormat::BGRA8UnormSrgb: return "BGRA8UnormSrgb";
case wgpu::TextureFormat::RGB10A2Uint: return "RGB10A2Uint";
case wgpu::TextureFormat::RGB10A2Unorm: return "RGB10A2Unorm";
case wgpu::TextureFormat::RG11B10Ufloat: return "RG11B10Ufloat";
case wgpu::TextureFormat::RGB9E5Ufloat: return "RGB9E5Ufloat";
case wgpu::TextureFormat::RG32Float: return "RG32Float";
case wgpu::TextureFormat::RG32Uint: return "RG32Uint";
case wgpu::TextureFormat::RG32Sint: return "RG32Sint";
case wgpu::TextureFormat::RGBA16Uint: return "RGBA16Uint";
case wgpu::TextureFormat::RGBA16Sint: return "RGBA16Sint";
case wgpu::TextureFormat::RGBA16Float: return "RGBA16Float";
case wgpu::TextureFormat::RGBA32Float: return "RGBA32Float";
case wgpu::TextureFormat::RGBA32Uint: return "RGBA32Uint";
case wgpu::TextureFormat::RGBA32Sint: return "RGBA32Sint";
case wgpu::TextureFormat::Stencil8: return "Stencil8";
case wgpu::TextureFormat::Depth16Unorm: return "Depth16Unorm";
case wgpu::TextureFormat::Depth24Plus: return "Depth24Plus";
case wgpu::TextureFormat::Depth24PlusStencil8: return "Depth24PlusStencil8";
case wgpu::TextureFormat::Depth32Float: return "Depth32Float";
case wgpu::TextureFormat::Depth32FloatStencil8: return "Depth32FloatStencil8";
case wgpu::TextureFormat::BC1RGBAUnorm: return "BC1RGBAUnorm";
case wgpu::TextureFormat::BC1RGBAUnormSrgb: return "BC1RGBAUnormSrgb";
case wgpu::TextureFormat::BC2RGBAUnorm: return "BC2RGBAUnorm";
case wgpu::TextureFormat::BC2RGBAUnormSrgb: return "BC2RGBAUnormSrgb";
case wgpu::TextureFormat::BC3RGBAUnorm: return "BC3RGBAUnorm";
case wgpu::TextureFormat::BC3RGBAUnormSrgb: return "BC3RGBAUnormSrgb";
case wgpu::TextureFormat::BC4RUnorm: return "BC4RUnorm";
case wgpu::TextureFormat::BC4RSnorm: return "BC4RSnorm";
case wgpu::TextureFormat::BC5RGUnorm: return "BC5RGUnorm";
case wgpu::TextureFormat::BC5RGSnorm: return "BC5RGSnorm";
case wgpu::TextureFormat::BC6HRGBUfloat: return "BC6HRGBUfloat";
case wgpu::TextureFormat::BC6HRGBFloat: return "BC6HRGBFloat";
case wgpu::TextureFormat::BC7RGBAUnorm: return "BC7RGBAUnorm";
case wgpu::TextureFormat::BC7RGBAUnormSrgb: return "BC7RGBAUnormSrgb";
case wgpu::TextureFormat::ETC2RGB8Unorm: return "ETC2RGB8Unorm";
case wgpu::TextureFormat::ETC2RGB8UnormSrgb: return "ETC2RGB8UnormSrgb";
case wgpu::TextureFormat::ETC2RGB8A1Unorm: return "ETC2RGB8A1Unorm";
case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb: return "ETC2RGB8A1UnormSrgb";
case wgpu::TextureFormat::ETC2RGBA8Unorm: return "ETC2RGBA8Unorm";
case wgpu::TextureFormat::ETC2RGBA8UnormSrgb: return "ETC2RGBA8UnormSrgb";
case wgpu::TextureFormat::EACR11Unorm: return "EACR11Unorm";
case wgpu::TextureFormat::EACR11Snorm: return "EACR11Snorm";
case wgpu::TextureFormat::EACRG11Unorm: return "EACRG11Unorm";
case wgpu::TextureFormat::EACRG11Snorm: return "EACRG11Snorm";
case wgpu::TextureFormat::ASTC4x4Unorm: return "ASTC4x4Unorm";
case wgpu::TextureFormat::ASTC4x4UnormSrgb: return "ASTC4x4UnormSrgb";
case wgpu::TextureFormat::ASTC5x4Unorm: return "ASTC5x4Unorm";
case wgpu::TextureFormat::ASTC5x4UnormSrgb: return "ASTC5x4UnormSrgb";
case wgpu::TextureFormat::ASTC5x5Unorm: return "ASTC5x5Unorm";
case wgpu::TextureFormat::ASTC5x5UnormSrgb: return "ASTC5x5UnormSrgb";
case wgpu::TextureFormat::ASTC6x5Unorm: return "ASTC6x5Unorm";
case wgpu::TextureFormat::ASTC6x5UnormSrgb: return "ASTC6x5UnormSrgb";
case wgpu::TextureFormat::ASTC6x6Unorm: return "ASTC6x6Unorm";
case wgpu::TextureFormat::ASTC6x6UnormSrgb: return "ASTC6x6UnormSrgb";
case wgpu::TextureFormat::ASTC8x5Unorm: return "ASTC8x5Unorm";
case wgpu::TextureFormat::ASTC8x5UnormSrgb: return "ASTC8x5UnormSrgb";
case wgpu::TextureFormat::ASTC8x6Unorm: return "ASTC8x6Unorm";
case wgpu::TextureFormat::ASTC8x6UnormSrgb: return "ASTC8x6UnormSrgb";
case wgpu::TextureFormat::ASTC8x8Unorm: return "ASTC8x8Unorm";
case wgpu::TextureFormat::ASTC8x8UnormSrgb: return "ASTC8x8UnormSrgb";
case wgpu::TextureFormat::ASTC10x5Unorm: return "ASTC10x5Unorm";
case wgpu::TextureFormat::ASTC10x5UnormSrgb: return "ASTC10x5UnormSrgb";
case wgpu::TextureFormat::ASTC10x6Unorm: return "ASTC10x6Unorm";
case wgpu::TextureFormat::ASTC10x6UnormSrgb: return "ASTC10x6UnormSrgb";
case wgpu::TextureFormat::ASTC10x8Unorm: return "ASTC10x8Unorm";
case wgpu::TextureFormat::ASTC10x8UnormSrgb: return "ASTC10x8UnormSrgb";
case wgpu::TextureFormat::ASTC10x10Unorm: return "ASTC10x10Unorm";
case wgpu::TextureFormat::ASTC10x10UnormSrgb: return "ASTC10x10UnormSrgb";
case wgpu::TextureFormat::ASTC12x10Unorm: return "ASTC12x10Unorm";
case wgpu::TextureFormat::ASTC12x10UnormSrgb: return "ASTC12x10UnormSrgb";
case wgpu::TextureFormat::ASTC12x12Unorm: return "ASTC12x12Unorm";
case wgpu::TextureFormat::ASTC12x12UnormSrgb: return "ASTC12x12UnormSrgb";
case wgpu::TextureFormat::R16Unorm: return "R16Unorm";
case wgpu::TextureFormat::RG16Unorm: return "RG16Unorm";
case wgpu::TextureFormat::RGBA16Unorm: return "RGBA16Unorm";
case wgpu::TextureFormat::R16Snorm: return "R16Snorm";
case wgpu::TextureFormat::RG16Snorm: return "RG16Snorm";
case wgpu::TextureFormat::RGBA16Snorm: return "RGBA16Snorm";
case wgpu::TextureFormat::R8BG8Biplanar420Unorm: return "R8BG8Biplanar420Unorm";
case wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm: return "R10X6BG10X6Biplanar420Unorm";
case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm: return "R8BG8A8Triplanar420Unorm";
case wgpu::TextureFormat::R8BG8Biplanar422Unorm: return "R8BG8Biplanar422Unorm";
case wgpu::TextureFormat::R8BG8Biplanar444Unorm: return "R8BG8Biplanar444Unorm";
case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm: return "R10X6BG10X6Biplanar422Unorm";
case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm: return "R10X6BG10X6Biplanar444Unorm";
case wgpu::TextureFormat::External: return "External";
}
}
[[nodiscard]] constexpr std::string_view filamentShaderStageToString(const ShaderStage stage) {
switch (stage) {
case ShaderStage::VERTEX: return "vertex";
@@ -42,7 +42,7 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
if (capabilities.formatCount > 0 && capabilities.formats != nullptr) {
std::for_each(capabilities.formats, capabilities.formats + capabilities.formatCount,
[](wgpu::TextureFormat const format) {
FWGPU_LOGI << " " << webGPUTextureFormatToString(format);
FWGPU_LOGI << " " << webGPUPrintableToString(format);
});
}
FWGPU_LOGI << " surface present modes (" << capabilities.presentModeCount << "):";
@@ -66,7 +66,7 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config,
wgpu::TextureFormat depthFormat) {
FWGPU_LOGI << "WebGPU surface configuration:";
FWGPU_LOGI << " surface format: " << webGPUTextureFormatToString(config.format);
FWGPU_LOGI << " surface format: " << webGPUPrintableToString(config.format);
FWGPU_LOGI << " surface usage: " << webGPUPrintableToString(config.usage);
FWGPU_LOGI << " surface view formats (" << config.viewFormatCount << "):";
if (config.viewFormatCount > 0 && config.viewFormats != nullptr) {
@@ -79,7 +79,7 @@ void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config,
FWGPU_LOGI << " surface width: " << config.width;
FWGPU_LOGI << " surface height: " << config.height;
FWGPU_LOGI << " surface present mode: " << webGPUPrintableToString(config.presentMode);
FWGPU_LOGI << "WebGPU selected depth format: " << webGPUTextureFormatToString(depthFormat);
FWGPU_LOGI << "WebGPU selected depth format: " << webGPUPrintableToString(depthFormat);
}
#endif// FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
@@ -110,6 +110,7 @@ void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config,
return wgpu::TextureFormat::Depth24PlusStencil8;
}
} else {
// other options: Depth16Unorm or Depth24Plus
return wgpu::TextureFormat::Depth32Float;
}
}
@@ -216,9 +217,10 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
}
[[nodiscard]] wgpu::TextureView createDepthTextureView(wgpu::Texture const& depthTexture,
wgpu::TextureFormat const& depthFormat, bool const needStencil) {
wgpu::TextureFormat depthFormat, bool needStencil) {
wgpu::TextureViewDescriptor descriptor{
.label = "depth_texture_view",
.format = depthFormat,
.dimension = wgpu::TextureViewDimension::e2D,
.baseMipLevel = 0,
.mipLevelCount = 1,
@@ -229,16 +231,6 @@ 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";
+15 -122
View File
@@ -17,8 +17,6 @@
#include "WebGPUTexture.h"
#include "WebGPUConstants.h"
#include "WebGPURenderPassMipmapGenerator.h"
#include "WebGPUStrings.h"
#include "DriverBase.h"
#include "private/backend/BackendUtils.h"
@@ -142,13 +140,10 @@ namespace {
* @param samples How many samples to use for MSAA
* @param needsComputeStorageSupport if we need to use this texture as storage binding in something
* like a compute shader
* @param needsRenderAttachmentSupport if we need to use this texture as a render pass attachment
* in something like a render pass blit (e.g. mipmap generation)
* @return The appropriate texture usage flags for the underlying texture
*/
[[nodiscard]] wgpu::TextureUsage fToWGPUTextureUsage(TextureUsage const& fUsage,
const uint8_t samples, const bool needsComputeStorageSupport,
const bool needsRenderAttachmentSupport) {
const uint8_t samples, const bool needsComputeStorageSupport) {
wgpu::TextureUsage retUsage = wgpu::TextureUsage::None;
// if needsComputeStorageSupport we need to read and write to the texture in a shader and thus
@@ -169,16 +164,6 @@ namespace {
if (needsComputeStorageSupport) {
retUsage |= wgpu::TextureUsage::StorageBinding;
}
if (needsRenderAttachmentSupport) {
retUsage |= wgpu::TextureUsage::RenderAttachment;
}
if (any(TextureUsage::BLIT_SRC & fUsage)) {
retUsage |= wgpu::TextureUsage::RenderAttachment;
}
if (any(TextureUsage::BLIT_DST & fUsage)) {
retUsage |= wgpu::TextureUsage::RenderAttachment;
retUsage |= wgpu::TextureUsage::TextureBinding;
}
// WGPU Render attachment covers either color or stencil situation dependant
// NOTE: Depth attachment isn't used this way in Vulkan but logically maps to WGPU docs. If
// issues, investigate here.
@@ -273,26 +258,6 @@ 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) {
@@ -330,17 +295,14 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
wgpu::Device const& device) noexcept
: HwTexture{ samplerType, levels, samples, width, height, depth, format, usage },
mViewFormat{ fToWGPUTextureFormat(format) },
mMipmapGenerationStrategy{ determineMipmapGenerationStrategy(mViewFormat, samplerType,
samples, levels) },
mWebGPUFormat{ mMipmapGenerationStrategy == MipmapGenerationStrategy::SPD_COMPUTE_PASS
mSupportsMultipleMipLevels{ levels > 1 &&
supportsMultipleMipLevelsViaStorageBinding(mViewFormat) },
mWebGPUFormat{ mSupportsMultipleMipLevels
? storageBindingCompatibleFormatForViewFormat(mViewFormat)
: mViewFormat },
mAspect{ fToWGPUTextureViewAspect(usage, format) },
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples,
mMipmapGenerationStrategy == MipmapGenerationStrategy::SPD_COMPUTE_PASS,
mMipmapGenerationStrategy == MipmapGenerationStrategy::RENDER_PASS) },
mViewUsage{ fToWGPUTextureUsage(usage, samples, false, false) },
mDimension{toWebGPUTextureViewDimension(samplerType)},
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples, mSupportsMultipleMipLevels) },
mViewUsage{ fToWGPUTextureUsage(usage, samples, false) },
mBlockWidth{ filament::backend::getBlockWidth(format) },
mBlockHeight{ filament::backend::getBlockHeight(format) },
mDefaultMipLevel{ 0 },
@@ -390,7 +352,7 @@ WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
: HwTexture{ src->target, levelCount, src->samples, src->width, src->height, src->depth,
src->format, src->usage },
mViewFormat{ src->mViewFormat },
mMipmapGenerationStrategy{ src->mMipmapGenerationStrategy },
mSupportsMultipleMipLevels{ src->mSupportsMultipleMipLevels },
mWebGPUFormat{ src->mWebGPUFormat },
mAspect{ src->mAspect },
mWebGPUUsage{ src->mWebGPUUsage },
@@ -402,21 +364,7 @@ WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
mDefaultMipLevel{ baseLevel },
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;
}
src->target) } {}
bool WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(const wgpu::TextureFormat format) {
return storageBindingCompatibleFormatForViewFormat(format) != wgpu::TextureFormat::Undefined;
@@ -431,68 +379,6 @@ 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;
@@ -667,6 +553,13 @@ wgpu::TextureFormat WebGPUTexture::fToWGPUTextureFormat(TextureFormat const& fFo
wgpu::TextureView WebGPUTexture::makeTextureView(const uint8_t& baseLevel,
const uint8_t& levelCount, const uint32_t& baseArrayLayer, const uint32_t& arrayLayerCount,
const SamplerType samplerType) const noexcept {
#if FWGPU_ENABLED(FWGPU_DEBUG_VALIDATION)
if (baseLevel > 0 && !mSupportsMultipleMipLevels) {
FWGPU_LOGW << "Trying to make a texture view into a level ("
<< static_cast<uint32_t>(baseLevel)
<< ") for which we cannot generate mip levels.";
}
#endif
const wgpu::TextureViewDescriptor textureViewDescriptor{
.label = getUserTextureViewLabel(target),
.format = mViewFormat,
+3 -60
View File
@@ -26,28 +26,8 @@
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;
@@ -61,49 +41,17 @@ public:
[[nodiscard]] wgpu::Texture const& getTexture() const { return mTexture; }
[[nodiscard]] wgpu::Texture const& getMsaaSidecarTexture(uint8_t sampleCount) const;
[[nodiscard]] wgpu::TextureView const& getDefaultTextureView() const {
return mDefaultTextureView;
}
[[nodiscard]] wgpu::TextureView getOrMakeTextureView(uint8_t mipLevel, uint32_t arrayLayer);
[[nodiscard]] wgpu::TextureViewDimension getViewDimension() const { return mDimension; }
[[nodiscard]] wgpu::TextureFormat getViewFormat() const { return mViewFormat; }
[[nodiscard]] uint32_t getArrayLayerCount() const { return mArrayLayerCount; }
[[nodiscard]] 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]] bool supportsMultipleMipLevels() const { return mSupportsMultipleMipLevels; }
[[nodiscard]] static wgpu::TextureFormat fToWGPUTextureFormat(
filament::backend::TextureFormat const& fFormat);
@@ -123,7 +71,8 @@ private:
// certain formats (e.g. compute shaders), but we need a view to that format elsewhere
// (e.g. another shader or render target)
wgpu::TextureFormat mViewFormat = wgpu::TextureFormat::Undefined;
MipmapGenerationStrategy mMipmapGenerationStrategy = MipmapGenerationStrategy::NONE;
// this is true IFF levels > 1 AND we can actually generate mipmaps for the texture
bool mSupportsMultipleMipLevels = false;
// 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;
@@ -131,7 +80,6 @@ private:
// usage is inherited from HwTexture. This naming is to distinguish it from Filament's usage
wgpu::TextureUsage mWebGPUUsage = wgpu::TextureUsage::None;
wgpu::TextureUsage mViewUsage = wgpu::TextureUsage::None;
wgpu::TextureViewDimension mDimension = wgpu::TextureViewDimension::Undefined;
size_t mBlockWidth = 0;
size_t mBlockHeight = 0;
uint32_t mArrayLayerCount = 1;
@@ -139,11 +87,6 @@ private:
uint32_t mDefaultMipLevel = 0;
uint32_t mDefaultBaseArrayLayer = 0;
wgpu::TextureView mDefaultTextureView = nullptr;
// At the time of writing this, WebGPU only supported 4 samples in a multi-sampled texture.
// If that has changed, then consider updating the implementation to have a map of msaa textures
// by sampleCount or something like that.
// For now that complexity and cost is not warranted due to WebGPU's restrictions.
wgpu::Texture mMsaaSidecarTexture = nullptr;
[[nodiscard]] wgpu::TextureView makeTextureView(const uint8_t& baseLevel,
const uint8_t& levelCount, const uint32_t& baseArrayLayer,
@@ -56,8 +56,10 @@ namespace {
// The SPD Algorithm can make use of up to 12 storage texture attachments
constexpr uint32_t MAX_MIPMAP_STORAGE_TEXTURES_PER_STAGE = 12u;
constexpr std::array REQUIRED_FEATURES = {
wgpu::FeatureName::TransientAttachments,
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,
// 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
@@ -486,8 +488,7 @@ struct AdapterDetailsHash final {
for (size_t i = 0; i < futures.size(); i++) {
wgpu::RequestAdapterOptions const& options = requests[i];
wgpu::Future& future = futures[i];
wgpu::WaitStatus status =
instance.WaitAny(future, FILAMENT_WEBGPU_REQUEST_ADAPTER_TIMEOUT_NANOSECONDS);
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 "
<< adapterOptionsToString(options);
@@ -659,7 +660,7 @@ wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
assert_invariant(status == wgpu::RequestDeviceStatus::Success);
device = readyDevice;
}),
FILAMENT_WEBGPU_REQUEST_DEVICE_TIMEOUT_NANOSECONDS);
REQUEST_DEVICE_TIMEOUT_NANOSECONDS);
FILAMENT_CHECK_POSTCONDITION(status != wgpu::WaitStatus::TimedOut)
<< "Failed to request a WebGPU device due to a timeout.";
FILAMENT_CHECK_POSTCONDITION(status != wgpu::WaitStatus::Error)
@@ -26,6 +26,7 @@
#include <vector>
// Platform specific includes and defines
#include <Cocoa/Cocoa.h>
#import <QuartzCore/CAMetalLayer.h>
/**
+4 -9
View File
@@ -120,11 +120,8 @@ public:
}
/**
* Transform a Box by a linear transform and a translation.
*
* @param m a linear transform matrix
* @param box the box to transform
* @return the bounding box of the transformed box
* @deprecated Use transform() instead
* @see transform()
*/
friend Box rigidTransform(Box const& box, const math::mat4f& m) noexcept {
return transform(m.upperLeft(), m[3].xyz, box);
@@ -238,10 +235,8 @@ struct UTILS_PUBLIC Aabb {
}
/**
* Applies an affine transformation to the AABB.
*
* @param m the affine transformation to apply
* @return the bounding box of the transformed box
* @deprecated Use transform() instead
* @see transform()
*/
Aabb transform(const math::mat4f& m) const noexcept {
return transform(m.upperLeft(), m[3].xyz, *this);
-6
View File
@@ -190,7 +190,6 @@ 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
@@ -411,11 +410,6 @@ 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;
};
+5 -16
View File
@@ -339,24 +339,13 @@ public:
static math::float4 getColorEstimate(const math::float3 sh[UTILS_NONNULL 9],
math::float3 direction) noexcept;
/**
* Helper to estimate the direction of the dominant light in the environment represented by
* spherical harmonics.
* Spherical harmonics must be set in the Builder or the result is undefined.
* @see getDirectionEstimate(const math::float3)
* @see Builder::irradiance(uint8_t, math::float3 const*)
* @see Builder::radiance(uint8_t, math::float3 const*)
*/
/** @deprecated use static versions instead */
UTILS_DEPRECATED
math::float3 getDirectionEstimate() const noexcept;
/**
* Helper to estimate the color and relative intensity of the environment represented by
* spherical harmonics in a given direction.
* Spherical harmonics must be set in the Builder or the result is undefined.
* @see getColorEstimate(const math::float3, math::float3)
* @see Builder::irradiance(uint8_t, math::float3 const*)
* @see Builder::radiance(uint8_t, math::float3 const*)
*/
/** @deprecated use static versions instead */
UTILS_DEPRECATED
math::float4 getColorEstimate(math::float3 direction) const noexcept;
protected:
+1 -2
View File
@@ -57,8 +57,7 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI {
public:
using CullingMode = backend::CullingMode;
// ReSharper disable once CppRedundantQualifier
using TransparencyMode = filament::TransparencyMode;
using TransparencyMode = TransparencyMode;
using DepthFunc = backend::SamplerCompareFunc;
using StencilCompareFunc = backend::SamplerCompareFunc;
using StencilOperation = backend::StencilOperation;
-6
View File
@@ -481,7 +481,6 @@ 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)) {
@@ -504,10 +503,6 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
return false;
}
if (!unflattener.read(&fieldUnfilterable)) {
return false;
}
if (!unflattener.read(&fieldMultisample)) {
return false;
}
@@ -517,7 +512,6 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
SamplerInterfaceBlock::Type(fieldType),
SamplerInterfaceBlock::Format(fieldFormat),
SamplerInterfaceBlock::Precision(fieldPrecision),
fieldUnfilterable,
fieldMultisample);
}
+18 -11
View File
@@ -2989,7 +2989,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscale(FrameGraph& fg, bool
}
if (dsrOptions.quality == QualityLevel::LOW) {
return upscaleBilinear(fg, dsrOptions, input, vp, outDesc, filter);
return upscaleBilinear(fg, translucent, 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,
FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph& fg, bool translucent,
DynamicResolutionOptions dsrOptions, FrameGraphId<FrameGraphTexture> const input,
filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
SamplerMagFilter filter) noexcept {
@@ -3016,10 +3016,18 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
.attachments = { .color = { data.output } },
.clearFlags = TargetBufferFlags::DEPTH });
},
[this, vp, filter](FrameGraphResources const& resources,
[this, vp, translucent, 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);
@@ -3047,6 +3055,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
auto out = resources.getRenderPassInfo();
auto pipeline = getPipelineState(material.getMaterial(mEngine));
if (translucent) {
enableTranslucentBlending(pipeline);
}
renderFullScreenQuad(out, pipeline, driver);
});
@@ -3054,8 +3065,7 @@ 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,
false /* translucent=false, because dst buffer is allocated */);
output = rcas(fg, dsrOptions.sharpness, output, outDesc, translucent);
}
// we rely on automatic culling of unused render passes
@@ -3433,6 +3443,9 @@ 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);
@@ -3450,9 +3463,6 @@ 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);
@@ -3504,9 +3514,6 @@ 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,
FrameGraphId<FrameGraphTexture> upscaleBilinear(FrameGraph& fg, bool translucent,
DynamicResolutionOptions dsrOptions, FrameGraphId<FrameGraphTexture> input,
Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
backend::SamplerMagFilter filter) noexcept;
+4 -1
View File
@@ -82,6 +82,8 @@ RenderPassBuilder& RenderPassBuilder::customCommand(
RenderPass RenderPassBuilder::build(FEngine const& engine, DriverApi& driver) const {
assert_invariant(mRenderableSoa);
assert_invariant(mScissorViewport.width <= std::numeric_limits<int32_t>::max());
assert_invariant(mScissorViewport.height <= std::numeric_limits<int32_t>::max());
return RenderPass{ engine, driver, *this };
}
@@ -105,7 +107,8 @@ void RenderPass::DescriptorSetHandleDeleter::operator()(
RenderPass::RenderPass(FEngine const& engine, DriverApi& driver,
RenderPassBuilder const& builder) noexcept
: mRenderableSoa(*builder.mRenderableSoa),
mColorPassDescriptorSet(builder.mColorPassDescriptorSet) {
mColorPassDescriptorSet(builder.mColorPassDescriptorSet),
mScissorViewport(builder.mScissorViewport) {
// compute the number of commands we need
updateSummedPrimitiveCounts(
+9 -7
View File
@@ -306,12 +306,6 @@ public:
// allocated commands ARE NOT freed, they're owned by the Arena
~RenderPass() noexcept;
// Specifies the viewport for the scissor rectangle, that is, the final scissor rect is
// offset by the viewport's left-top and clipped to the viewport's width/height.
void setScissorViewport(backend::Viewport const viewport) noexcept {
mScissorViewport = viewport;
}
Command const* begin() const noexcept { return mCommandBegin; }
Command const* end() const noexcept { return mCommandEnd; }
bool empty() const noexcept { return begin() == end(); }
@@ -467,7 +461,7 @@ private:
FScene::RenderableSoa const& mRenderableSoa;
ColorPassDescriptorSet const* const mColorPassDescriptorSet;
backend::Viewport mScissorViewport{ 0, 0, INT32_MAX, INT32_MAX };
backend::Viewport const mScissorViewport{ 0, 0, INT32_MAX, INT32_MAX };
Command const* /* const */ mCommandBegin = nullptr; // Pointer to the first command
Command const* /* const */ mCommandEnd = nullptr; // Pointer to one past the last command
mutable BufferObjectSharedHandle mInstancedUboHandle; // ubo for instanced primitives
@@ -482,6 +476,7 @@ class RenderPassBuilder {
RenderPass::Arena& mArena;
RenderPass::CommandTypeFlags mCommandTypeFlags{};
backend::Viewport mScissorViewport{ 0, 0, INT32_MAX, INT32_MAX };
FScene::RenderableSoa const* mRenderableSoa = nullptr;
utils::Range<uint32_t> mVisibleRenderables{};
math::float3 mCameraPosition{};
@@ -512,6 +507,13 @@ public:
return *this;
}
// Specifies the viewport for the scissor rectangle, that is, the final scissor rect is
// offset by the viewport's left-top and clipped to the viewport's width/height.
RenderPassBuilder& scissorViewport(backend::Viewport const viewport) noexcept {
mScissorViewport = viewport;
return *this;
}
// specifies the geometry to generate commands for
RenderPassBuilder& geometry(
FScene::RenderableSoa const& soa, utils::Range<uint32_t> const vr) noexcept {
+67 -73
View File
@@ -40,6 +40,7 @@
#include <utils/Panic.h>
#include <algorithm>
#include <optional>
#include <utility>
#include <stddef.h>
@@ -269,94 +270,87 @@ RendererUtils::ColorPassOutput RendererUtils::colorPass(
};
}
RenderPass::Command const* RendererUtils::getFirstRefractionCommand(
RenderPass const& pass) noexcept {
// find the first refractive object in channel 2
RenderPass::Command const* const refraction = std::partition_point(pass.begin(), pass.end(),
[](auto const& command) {
constexpr uint64_t mask = RenderPass::CHANNEL_MASK | RenderPass::PASS_MASK;
constexpr uint64_t channel = uint64_t(RenderableManager::Builder::DEFAULT_CHANNEL) << RenderPass::CHANNEL_SHIFT;
constexpr uint64_t value = channel | uint64_t(RenderPass::Pass::REFRACT);
return (command.key & mask) < value;
});
const bool hasScreenSpaceRefraction =
(refraction->key & RenderPass::PASS_MASK) == uint64_t(RenderPass::Pass::REFRACT);
return hasScreenSpaceRefraction ? refraction : nullptr;
}
RendererUtils::ColorPassOutput RendererUtils::refractionPass(
std::optional<RendererUtils::ColorPassOutput> RendererUtils::refractionPass(
FrameGraph& fg, FEngine& engine, FView const& view,
ColorPassInput colorPassInput,
ColorPassConfig config,
PostProcessManager::ScreenSpaceRefConfig const& ssrConfig,
PostProcessManager::ColorGradingConfig const colorGradingConfig,
RenderPass const& pass, RenderPass::Command const* const firstRefractionCommand) noexcept {
RenderPass const& pass) noexcept {
assert_invariant(firstRefractionCommand);
RenderPass::Command const* const refraction = firstRefractionCommand;
// find the first refractive object in channel 2
RenderPass::Command const* const refraction = std::partition_point(pass.begin(), pass.end(),
[](auto const& command) {
constexpr uint64_t mask = RenderPass::CHANNEL_MASK | RenderPass::PASS_MASK;
constexpr uint64_t channel = uint64_t(RenderableManager::Builder::DEFAULT_CHANNEL) << RenderPass::CHANNEL_SHIFT;
constexpr uint64_t value = channel | uint64_t(RenderPass::Pass::REFRACT);
return (command.key & mask) < value;
});
const bool hasScreenSpaceRefraction =
(refraction->key & RenderPass::PASS_MASK) == uint64_t(RenderPass::Pass::REFRACT);
// if there wasn't any refractive object, just skip everything below.
assert_invariant(!colorPassInput.linearColor);
assert_invariant(!colorPassInput.depth);
config.hasScreenSpaceReflectionsOrRefractions = true;
if (UTILS_UNLIKELY(hasScreenSpaceRefraction)) {
assert_invariant(!colorPassInput.linearColor);
assert_invariant(!colorPassInput.depth);
config.hasScreenSpaceReflectionsOrRefractions = true;
PostProcessManager& ppm = engine.getPostProcessManager();
auto const opaquePassOutput = colorPass(fg,
"Color Pass (opaque)", engine, view, colorPassInput, {
// When rendering the opaques, we need to conserve the sample buffer,
// so create a config that specifies the sample count.
.width = config.physicalViewport.width,
.height = config.physicalViewport.height,
.samples = config.msaa,
.format = config.hdrFormat
},
config, { .asSubpass = false, .customResolve = false },
pass.getExecutor(pass.begin(), refraction));
PostProcessManager& ppm = engine.getPostProcessManager();
auto const opaquePassOutput = colorPass(fg,
"Color Pass (opaque)", engine, view, colorPassInput, {
// When rendering the opaques, we need to conserve the sample buffer,
// so create a config that specifies the sample count.
.width = config.physicalViewport.width,
.height = config.physicalViewport.height,
.samples = config.msaa,
.format = config.hdrFormat
},
config, { .asSubpass = false, .customResolve = false },
pass.getExecutor(pass.begin(), refraction));
// Generate the mipmap chain
// Note: we can run some post-processing effects while the "color pass" descriptor set
// in bound because only the descriptor 0 (frame uniforms) matters, and it's
// present in both.
PostProcessManager::generateMipmapSSR(ppm, fg,
opaquePassOutput.linearColor,
ssrConfig.refraction,
true, ssrConfig);
// Generate the mipmap chain
// Note: we can run some post-processing effects while the "color pass" descriptor set
// in bound because only the descriptor 0 (frame uniforms) matters, and it's
// present in both.
PostProcessManager::generateMipmapSSR(ppm, fg,
opaquePassOutput.linearColor,
ssrConfig.refraction,
true, ssrConfig);
// Now we're doing the refraction pass proper.
// This uses the same framebuffer (color and depth) used by the opaque pass.
// For this reason, the `colorBufferDesc` parameter of colorPass() below is only used for
// the width and height.
colorPassInput.linearColor = opaquePassOutput.linearColor;
colorPassInput.depth = opaquePassOutput.depth;
// Now we're doing the refraction pass proper.
// This uses the same framebuffer (color and depth) used by the opaque pass.
// For this reason, the `colorBufferDesc` parameter of colorPass() below is only used for
// the width and height.
colorPassInput.linearColor = opaquePassOutput.linearColor;
colorPassInput.depth = opaquePassOutput.depth;
// Since we're reusing the existing target we don't want to clear any of its buffer.
// Important: if this target ended up being an imported target, then the clearFlags
// specified here wouldn't apply (the clearFlags of the imported target take precedence),
// and we'd end up clearing the opaque pass. This scenario never happens because it is
// prevented in Renderer.cpp's final blit.
config.clearFlags = TargetBufferFlags::NONE;
auto transparentPassOutput = colorPass(fg, "Color Pass (transparent)",
engine, view, colorPassInput, {
.width = config.physicalViewport.width,
.height = config.physicalViewport.height },
config, colorGradingConfig,
pass.getExecutor(refraction, pass.end()));
// Since we're reusing the existing target we don't want to clear any of its buffer.
// Important: if this target ended up being an imported target, then the clearFlags
// specified here wouldn't apply (the clearFlags of the imported target take precedence),
// and we'd end up clearing the opaque pass. This scenario never happens because it is
// prevented in Renderer.cpp's final blit.
config.clearFlags = TargetBufferFlags::NONE;
auto transparentPassOutput = colorPass(fg, "Color Pass (transparent)",
engine, view, colorPassInput, {
.width = config.physicalViewport.width,
.height = config.physicalViewport.height },
config, colorGradingConfig,
pass.getExecutor(refraction, pass.end()));
if (config.msaa > 1 && !colorGradingConfig.asSubpass) {
// We need to do a resolve here because later passes (such as color grading or DoF) will
// need to sample from 'output'. However, because we have MSAA, we know we're not
// sampleable. And this is because in the SSR case, we had to use a renderbuffer to
// conserve the multi-sample buffer.
transparentPassOutput.linearColor = ppm.resolve(fg, "Resolved Color Buffer",
transparentPassOutput.linearColor, { .levels = 1 });
if (config.msaa > 1 && !colorGradingConfig.asSubpass) {
// We need to do a resolve here because later passes (such as color grading or DoF) will
// need to sample from 'output'. However, because we have MSAA, we know we're not
// sampleable. And this is because in the SSR case, we had to use a renderbuffer to
// conserve the multi-sample buffer.
transparentPassOutput.linearColor = ppm.resolve(fg, "Resolved Color Buffer",
transparentPassOutput.linearColor, { .levels = 1 });
}
return transparentPassOutput;
}
return transparentPassOutput;
return std::nullopt;
}
UTILS_NOINLINE
+6 -9
View File
@@ -26,18 +26,17 @@
#include <filament/Viewport.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <backend/PixelBufferDescriptor.h>
#include <math/vec2.h>
#include <math/vec4.h>
#include <stdint.h>
namespace filament {
#include <optional>
#include <utility>
namespace backend {
class PixelBufferDescriptor;
}
namespace filament {
class FRenderTarget;
class FrameGraph;
@@ -100,20 +99,18 @@ public:
PostProcessManager::ColorGradingConfig colorGradingConfig,
RenderPass::Executor passExecutor) noexcept;
static ColorPassOutput refractionPass(
static std::optional<ColorPassOutput> refractionPass(
FrameGraph& fg, FEngine& engine, FView const& view,
ColorPassInput colorPassInput,
ColorPassConfig config,
PostProcessManager::ScreenSpaceRefConfig const& ssrConfig,
PostProcessManager::ColorGradingConfig colorGradingConfig,
RenderPass const& pass, RenderPass::Command const* firstRefractionCommand) noexcept;
RenderPass const& pass) noexcept;
static void readPixels(backend::DriverApi& driver,
backend::Handle<backend::HwRenderTarget> renderTargetHandle,
uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
backend::PixelBufferDescriptor&& buffer);
static RenderPass::Command const* getFirstRefractionCommand(RenderPass const& pass) noexcept;
};
} // namespace filament
+2 -4
View File
@@ -141,7 +141,6 @@ 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);
@@ -762,7 +761,6 @@ 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);
@@ -824,9 +822,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;
}
+4 -19
View File
@@ -98,14 +98,6 @@ 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;
@@ -723,14 +715,10 @@ public:
bool use_shadow_atlas = false;
} shadows;
struct {
// 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;
// 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;
} debug;
} engine;
struct {
@@ -771,9 +759,6 @@ 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 {
+18 -31
View File
@@ -1125,6 +1125,16 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
// --------------------------------------------------------------------------------------------
// Color passes
// this makes the viewport relative to xvp
// FIXME: we should use 'vp' when rendering directly into the swapchain, but that's hard to
// know at this point. This will usually be the case when post-process is disabled.
// FIXME: we probably should take the dynamic scaling into account too
// if MSAA is enabled, we end-up rendering in an intermediate buffer. This is the only case where
// "!hasPostProcess" doesn't guarantee rendering into the swapchain.
const bool useIntermediateBuffer = hasPostProcess || msaaOptions.enabled ||
(isRenderingMultiview && engine.debug.stereo.combine_multiview_images);
passBuilder.scissorViewport(useIntermediateBuffer ? xvp : vp);
// This one doesn't need to be a FrameGraph pass because it always happens by construction
// (i.e. it won't be culled, unless everything is culled), so no need to complexify things.
passBuilder.variant(variant);
@@ -1164,35 +1174,8 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
passBuilder.renderFlags(renderFlags);
}
// create the pass, which generates all its commands (this is a heavy operation)
RenderPass const pass{ passBuilder.build(engine, driver) };
// now that we have the commands we can figure out if we have refraction commands
auto* const firstRefractionCommand = [&view](RenderPass const& pass) {
RenderPass::Command const* p = nullptr;
if (UTILS_UNLIKELY(view.isScreenSpaceRefractionEnabled() && !pass.empty())) {
p = RendererUtils::getFirstRefractionCommand(pass);
}
return p;
}(pass);
hasScreenSpaceRefraction = firstRefractionCommand != nullptr;
// this makes the viewport relative to xvp
// FIXME: we should use 'vp' when rendering directly into the swapchain, but that's hard to
// know at this point. This will usually be the case when post-process is disabled.
// FIXME: we probably should take the dynamic scaling into account too
// if MSAA is enabled, we end-up rendering in an intermediate buffer. This is the only case where
// "!hasPostProcess" doesn't guarantee rendering into the swapchain.
const bool useIntermediateBuffer = hasPostProcess || msaaOptions.enabled ||
ssReflectionsOptions.enabled || hasScreenSpaceRefraction ||
(isRenderingMultiview && engine.debug.stereo.
combine_multiview_images);
// this is slightly ugly, but conceptually `pass` is const; it's just that we can't set
// the scissor viewport during construction
const_cast<RenderPass&>(pass).setScissorViewport(useIntermediateBuffer ? xvp : vp);
FrameGraphTexture::Descriptor colorBufferDesc = {
.width = config.physicalViewport.width,
.height = config.physicalViewport.height,
@@ -1237,17 +1220,21 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
},
colorBufferDesc, config, colorGradingConfigForColor, pass.getExecutor());
if (UTILS_UNLIKELY(hasScreenSpaceRefraction)) {
if (view.isScreenSpaceRefractionEnabled() && !pass.empty()) {
// This cancels the colorPass() call above if refraction is active.
// The color pass + refraction + color-grading as subpass if needed
colorPassOutput = RendererUtils::refractionPass(fg, mEngine, view, {
auto const output = RendererUtils::refractionPass(fg, mEngine, view, {
.shadows = blackboard.get<FrameGraphTexture>("shadows"),
.ssao = blackboard.get<FrameGraphTexture>("ssao"),
.ssr = ssrConfig.ssr,
.structure = structure
},
config, ssrConfig, colorGradingConfigForColor,
pass, firstRefractionCommand);
config, ssrConfig, colorGradingConfigForColor, pass);
hasScreenSpaceRefraction = output.has_value();
if (hasScreenSpaceRefraction) {
colorPassOutput = output.value();
}
}
if (colorGradingConfig.customResolve) {
+15 -34
View File
@@ -386,9 +386,8 @@ 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)
@@ -429,46 +428,28 @@ 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::computeDataSize(p.format, p.type, 1, 1, 1);
size_t const bpp = PBD::computePixelSize(p.format, p.type);
size_t const bpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment);
size_t const bpl = bpr * height; // TODO: PBD should have a "layer stride"
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);
// TODO: PBD should have a p.depth (# layers to skip)
/* 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)
FILAMENT_CHECK_PRECONDITION(
bpp * (pbdWidth - p.left) * (pbdHeight - p.top) * (pbdDepth - 0) >=
bpp * (width - xoffset) * (height - yoffset) * (depth - zoffset))
<< "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,
+5 -10
View File
@@ -162,7 +162,8 @@ 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) {
LOG(WARNING) << "bufferCount cannot be more than " << DOCUMENTED_MAX_VERTEX_BUFFER_COUNT;
utils::slog.w << "bufferCount cannot be more than " << DOCUMENTED_MAX_VERTEX_BUFFER_COUNT
<< utils::io::endl;
}
// Next we check if any unused buffer slots have been allocated. This helps prevent errors
@@ -177,15 +178,9 @@ VertexBuffer* VertexBuffer::Builder::build(Engine& engine) {
<< "attribute " << j << " offset=" << attributes[j].offset
<< " 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";
}
FILAMENT_CHECK_PRECONDITION((attributes[j].stride & 0x3u) == 0)
<< "attribute " << j << " stride=" << attributes[j].stride
<< " is not multiple of 4";
if (engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0) {
FILAMENT_CHECK_PRECONDITION(!(attributes[j].flags & Attribute::FLAG_INTEGER_TARGET))
+1 -2
View File
@@ -8,8 +8,7 @@ material {
{
type : sampler2d,
name : depth,
precision: high,
unfilterable: true
precision: high
},
{
type : float,
+1 -2
View File
@@ -4,8 +4,7 @@ material {
{
type : sampler2d,
name : depth,
precision: medium,
unfilterable: true
precision: medium
},
{
type : float4,
@@ -9,8 +9,7 @@ material {
{
type : sampler2d,
name : depth,
precision: high,
unfilterable: true
precision: high
},
{
type : float4,
@@ -78,3 +77,4 @@ fragment {
postProcess.color = color;
}
}
+1 -2
View File
@@ -9,8 +9,7 @@ material {
{
type : sampler2d,
name : depth,
precision: medium,
unfilterable: true
precision: medium
},
{
type : float2,
+1 -2
View File
@@ -9,8 +9,7 @@ material {
{
type : sampler2d,
name : depth,
precision: medium,
unfilterable: true
precision: medium
},
{
type : float2,
+1 -2
View File
@@ -5,8 +5,7 @@ material {
type : sampler2d,
name : depth,
precision: high,
multisample : true,
unfilterable: true
multisample : true
}
],
outputs : [
+1 -2
View File
@@ -4,8 +4,7 @@ material {
{
type : sampler2d,
name : depth,
precision: high,
unfilterable: true
precision: high
},
{
type : mat4,
@@ -4,8 +4,7 @@ material {
{
type : sampler2d,
name : depth,
precision: high,
unfilterable: true
precision: high
},
{
type : mat4,
+1 -2
View File
@@ -4,8 +4,7 @@ material {
{
type : sampler2d,
name : depth,
precision: high,
unfilterable: true
precision: high
}
],
variables : [
+1 -2
View File
@@ -4,8 +4,7 @@ material {
{
type : sampler2d,
name : depth,
precision: high,
unfilterable: true
precision: high
},
{
type : mat4,
@@ -4,8 +4,7 @@ material {
{
type : sampler2d,
name : depth,
precision: high,
unfilterable: true
precision: high
},
{
type : mat4,
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.62.0"
spec.version = "1.61.1"
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.62.0/filament-v1.62.0-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.61.1/filament-v1.61.1-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 = 62;
static constexpr size_t MATERIAL_VERSION = 61;
/**
* Supported shading models
@@ -61,7 +61,6 @@ 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
};
@@ -84,7 +83,6 @@ 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
@@ -97,8 +95,7 @@ public:
// Add a sampler
Builder& add(std::string_view samplerName, Binding binding, Type type, Format format,
Precision precision = Precision::MEDIUM, bool unfilterable = false,
bool multisample = false,
Precision precision = Precision::MEDIUM, 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, DescriptorFlags::UNFILTERABLE },
{ DescriptorType::SAMPLER_2D_ARRAY_FLOAT, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_POSITIONS },
{ 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 unfilterable,
bool multisample, ShaderStageFlags stages) noexcept {
Binding binding, Type type, Format format, Precision precision, bool multisample,
ShaderStageFlags stages) noexcept {
mEntries.push_back({
{ samplerName.data(), samplerName.size() }, // name
{}, // uniform name
binding, type, format, precision, unfilterable, multisample, stages });
{ samplerName.data(), samplerName.size() }, // name
{ }, // uniform name
binding, type, format, precision, multisample, stages });
return *this;
}
@@ -65,9 +65,8 @@ 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.unfilterable, e.multisample,
e.stages);
for (auto& e : list) {
add(e.name, e.binding, e.type, e.format, e.precision, e.multisample, e.stages);
}
return *this;
}
+7 -31
View File
@@ -27,7 +27,6 @@
#include <backend/DriverEnums.h>
#include <backend/TargetBufferInfo.h>
#include <mutex>
#include <utils/BitmaskEnum.h>
#include <utils/bitset.h>
#include <utils/compiler.h>
@@ -324,8 +323,8 @@ public:
*/
MaterialBuilder& parameter(const char* name, SamplerType samplerType,
SamplerFormat format = SamplerFormat::FLOAT,
ParameterPrecision precision = ParameterPrecision::DEFAULT, bool unfilterable = false,
bool multisample = false, const char* transformName = "",
ParameterPrecision precision = ParameterPrecision::DEFAULT, bool multisample = false,
const char* transformName = "",
ShaderStageFlags stages = ShaderStageFlags::ALL_SHADER_STAGE_FLAGS);
MaterialBuilder& buffer(filament::BufferInterfaceBlock bib);
@@ -659,39 +658,17 @@ public:
// Sampler
Parameter(const char* paramName, SamplerType t, SamplerFormat f, ParameterPrecision p,
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) {}
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) { }
// Uniform
Parameter(const char* paramName, UniformType t, size_t typeSize, ParameterPrecision p)
: name(paramName),
size(typeSize),
uniformType(t),
precision(p),
format{ 0 },
unfilterable(false),
multisample(false),
parameterType(UNIFORM) {}
: name(paramName), size(typeSize), uniformType(t), precision(p), parameterType(UNIFORM) { }
// Subpass
Parameter(const char* paramName, SubpassType t, SamplerFormat f, ParameterPrecision p)
: name(paramName),
size(1),
precision(p),
subpassType(t),
format(f),
unfilterable(false),
multisample(false),
parameterType(SUBPASS) {}
: name(paramName), size(1), precision(p), subpassType(t), format(f), parameterType(SUBPASS) { }
utils::CString name;
size_t size;
@@ -700,7 +677,6 @@ public:
SamplerType samplerType;
SubpassType subpassType;
SamplerFormat format;
bool unfilterable;
bool multisample;
utils::CString transformName;
ShaderStageFlags stages;
+10 -12
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 unfilterable, bool multisample,
SamplerFormat format, ParameterPrecision precision, 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, unfilterable,
multisample, transformName, stages };
mParameters[mParameterCount++] = { name, samplerType, format, precision, multisample, transformName, stages };
return *this;
}
@@ -639,12 +639,10 @@ 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.unfilterable, param.multisample,
param.stages);
param.format, param.precision, 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()) {

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