Compare commits
64
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edb77fe6a9 | ||
|
|
954d1021c7 | ||
|
|
bf598071a3 | ||
|
|
5cbc2321c7 | ||
|
|
6bee5fb56f | ||
|
|
eb0fcc065b | ||
|
|
117535364b | ||
|
|
b15ae15f39 | ||
|
|
74445904f2 | ||
|
|
678ad4ff98 | ||
|
|
b6d7ed2b35 | ||
|
|
725e33763a | ||
|
|
f24146c169 | ||
|
|
67288d483f | ||
|
|
c80c504a2a | ||
|
|
1f8cacdd70 | ||
|
|
a3ae9431f1 | ||
|
|
3d0653604f | ||
|
|
42a0a41be0 | ||
|
|
468d9cccb7 | ||
|
|
6cfd058e3d | ||
|
|
8234549cb9 | ||
|
|
c84d04254e | ||
|
|
573a51c430 | ||
|
|
b4d26bd32b | ||
|
|
5227ea36ce | ||
|
|
6f29ff988c | ||
|
|
3424222d6e | ||
|
|
a9ab9b6cd4 | ||
|
|
5d11dd653a | ||
|
|
7d1cb3ee85 | ||
|
|
8304cd9c32 | ||
|
|
6d1c337363 | ||
|
|
e59036c731 | ||
|
|
dd7d78b2a6 | ||
|
|
f096c4d8de | ||
|
|
da5ea821a8 | ||
|
|
1b944a6582 | ||
|
|
318a9002e7 | ||
|
|
eb5d9ac15d | ||
|
|
5cae0ee082 | ||
|
|
75d641ae8a | ||
|
|
c2c744b06d | ||
|
|
1e246d1332 | ||
|
|
7d242341f2 | ||
|
|
e625c7024c | ||
|
|
192a61a06b | ||
|
|
6a93e3a765 | ||
|
|
e1fb1391f9 | ||
|
|
a3922ac3a9 | ||
|
|
5f1112d85e | ||
|
|
09545690bf | ||
|
|
3ce752c47b | ||
|
|
1f9f3ddb91 | ||
|
|
8e94688fbf | ||
|
|
438ee40f40 | ||
|
|
7f22d733d7 | ||
|
|
77d77f0a5c | ||
|
|
0aabd3dd6a | ||
|
|
72a54b30f9 | ||
|
|
00b2ab7ac4 | ||
|
|
39e15d67fc | ||
|
|
921dbddad9 | ||
|
|
6406d4602c |
@@ -54,7 +54,6 @@ jobs:
|
||||
build-android:
|
||||
name: build-android
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -73,7 +72,6 @@ jobs:
|
||||
build-ios:
|
||||
name: build-iOS
|
||||
runs-on: macos-14-xlarge
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -89,7 +87,6 @@ jobs:
|
||||
build-web:
|
||||
name: build-web
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -107,11 +104,12 @@ jobs:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- id: get_commit_msg
|
||||
uses: ./.github/actions/get-commit-msg
|
||||
- name: Check for manual edits to /docs
|
||||
run: |
|
||||
echo "${{ github.event.pull_request.head.sha }} -- ${{ github.event.pull_request.head.sha }}"
|
||||
# disable for now
|
||||
# bash docs_src/build/presubmit_check.sh ${{ github.event.pull_request.head.sha }}
|
||||
COMMIT_ID=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | head -n 1 | sed "s/commit //g")
|
||||
bash docs_src/build/presubmit_check.sh ${COMMIT_ID}
|
||||
|
||||
test-renderdiff:
|
||||
name: test-renderdiff
|
||||
@@ -138,7 +136,7 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: presubmit-renderdiff-result
|
||||
path: ./out/renderdiff_tests
|
||||
path: ./out/renderdiff
|
||||
|
||||
validate-wgsl-webgpu:
|
||||
name: validate-wgsl-webgpu
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.61.2'
|
||||
implementation 'com.google.android.filament:filament-android:1.62.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.61.2'
|
||||
pod 'Filament', '~> 1.62.0'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -7,6 +7,15 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.62.1
|
||||
|
||||
- Add new shader define `VARIANT_DEPTH`, defined when a material is compiled for depth variants
|
||||
(e.g., shadows) [**Requires recompiling materials**]
|
||||
|
||||
## v1.62.0
|
||||
|
||||
- Add new `unfilterable` field to Filament Material's `sampler` [⚠️ **New Material Version**]
|
||||
|
||||
## v1.61.2
|
||||
|
||||
- samples: samples now have a CLI to select backend api
|
||||
|
||||
@@ -556,7 +556,8 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
|
||||
jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge,
|
||||
jboolean disableHandleUseAfterFreeCheck,
|
||||
jint preferredShaderLanguage,
|
||||
jboolean forceGLES2Context, jboolean assertNativeWindowIsValid) {
|
||||
jboolean forceGLES2Context, jboolean assertNativeWindowIsValid,
|
||||
jint gpuContextPriority) {
|
||||
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
|
||||
Engine::Config config = {
|
||||
.commandBufferSizeMB = (uint32_t) commandBufferSizeMB,
|
||||
@@ -574,6 +575,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
|
||||
.preferredShaderLanguage = (Engine::Config::ShaderLanguage) preferredShaderLanguage,
|
||||
.forceGLES2Context = (bool) forceGLES2Context,
|
||||
.assertNativeWindowIsValid = (bool) assertNativeWindowIsValid,
|
||||
.gpuContextPriority = (Engine::GpuContextPriority) gpuContextPriority,
|
||||
};
|
||||
builder->config(&config);
|
||||
}
|
||||
|
||||
@@ -175,6 +175,36 @@ public class Engine {
|
||||
MULTIVIEW,
|
||||
};
|
||||
|
||||
/**
|
||||
* This controls the priority level for GPU work scheduling, which helps prioritize the
|
||||
* submitted GPU work and enables preemption.
|
||||
*/
|
||||
public enum GpuContextPriority {
|
||||
/**
|
||||
* Backend default GPU context priority (typically MEDIUM)
|
||||
*/
|
||||
DEFAULT,
|
||||
/**
|
||||
* For non-interactive, deferrable workloads. This should not interfere with standard
|
||||
* applications.
|
||||
*/
|
||||
LOW,
|
||||
/**
|
||||
* The default priority level for standard applications.
|
||||
*/
|
||||
MEDIUM,
|
||||
/**
|
||||
* For high-priority, latency-sensitive workloads that are more important than standard
|
||||
* applications.
|
||||
*/
|
||||
HIGH,
|
||||
/**
|
||||
* The highest priority, intended for system-critical, real-time applications where missing
|
||||
* deadlines is unacceptable (e.g., VR/AR compositors or other system-critical tasks).
|
||||
*/
|
||||
REALTIME,
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs <code>Engine</code> objects using a builder pattern.
|
||||
*/
|
||||
@@ -233,7 +263,8 @@ public class Engine {
|
||||
config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge,
|
||||
config.disableHandleUseAfterFreeCheck,
|
||||
config.preferredShaderLanguage.ordinal(),
|
||||
config.forceGLES2Context, config.assertNativeWindowIsValid);
|
||||
config.forceGLES2Context, config.assertNativeWindowIsValid,
|
||||
config.gpuContextPriority.ordinal());
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -489,6 +520,11 @@ public class Engine {
|
||||
* @Deprecated use "backend.opengl.assert_native_window_is_valid" feature flag instead
|
||||
*/
|
||||
public boolean assertNativeWindowIsValid = false;
|
||||
|
||||
/**
|
||||
* GPU context priority level. Controls GPU work scheduling and preemption.
|
||||
*/
|
||||
public GpuContextPriority gpuContextPriority = GpuContextPriority.DEFAULT;
|
||||
}
|
||||
|
||||
private Engine(long nativeEngine, Config config) {
|
||||
@@ -1492,7 +1528,8 @@ public class Engine {
|
||||
long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge,
|
||||
boolean disableHandleUseAfterFreeCheck,
|
||||
int preferredShaderLanguage,
|
||||
boolean forceGLES2Context, boolean assertNativeWindowIsValid);
|
||||
boolean forceGLES2Context, boolean assertNativeWindowIsValid,
|
||||
int gpuContextPriority);
|
||||
private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal);
|
||||
private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext);
|
||||
private static native void nSetBuilderPaused(long nativeBuilder, boolean paused);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.61.2
|
||||
VERSION_NAME=1.62.0
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
BIN
Binary file not shown.
+2
-1
@@ -1,6 +1,7 @@
|
||||
#Wed Nov 17 10:40:18 PST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
Vendored
+179
-115
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -17,67 +17,99 @@
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
@@ -87,9 +119,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -98,88 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
Vendored
+19
-16
@@ -14,7 +14,7 @@
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,7 +25,8 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
Binary file not shown.
+19
-1
@@ -18,6 +18,7 @@ package com.google.android.filament.gltf
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
@@ -50,6 +51,7 @@ class MainActivity : Activity() {
|
||||
// Load the library for the utility layer, which in turn loads gltfio and the Filament core.
|
||||
init { Utils.init() }
|
||||
private const val TAG = "gltf-viewer"
|
||||
private const val STATIC_MODEL_TAG = "use-static-model"
|
||||
}
|
||||
|
||||
private lateinit var surfaceView: SurfaceView
|
||||
@@ -69,6 +71,7 @@ class MainActivity : Activity() {
|
||||
private var loadStartTime = 0L
|
||||
private var loadStartFence: Fence? = null
|
||||
private val viewerContent = AutomationEngine.ViewerContent()
|
||||
private var useStaticModel = false
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -83,6 +86,12 @@ class MainActivity : Activity() {
|
||||
doubleTapDetector = GestureDetector(applicationContext, doubleTapListener)
|
||||
singleTapDetector = GestureDetector(applicationContext, singleTapListener)
|
||||
|
||||
val intent: Intent = intent
|
||||
val bundle: Bundle? = intent.extras
|
||||
bundle?.let {
|
||||
useStaticModel = it.getBoolean(STATIC_MODEL_TAG, false)
|
||||
}
|
||||
|
||||
modelViewer = ModelViewer(surfaceView)
|
||||
viewerContent.view = modelViewer.view
|
||||
viewerContent.sunlight = modelViewer.light
|
||||
@@ -141,7 +150,16 @@ class MainActivity : Activity() {
|
||||
}
|
||||
|
||||
private fun createDefaultRenderables() {
|
||||
val buffer = assets.open("models/scene.gltf").use { input ->
|
||||
// Sometimes it's useful to set to the default model to something static. You can enable
|
||||
// the static model by launching the app from adb, as in
|
||||
// `adb shell am start -n com.google.android.filament.gltf/.MainActivity --ez "use-static-model" true`
|
||||
val modelPath = if (useStaticModel) {
|
||||
"models/helmet.glb"
|
||||
} else {
|
||||
"models/scene.gltf"
|
||||
}
|
||||
|
||||
val buffer = assets.open(modelPath).use { input ->
|
||||
val bytes = ByteArray(input.available())
|
||||
input.read(bytes)
|
||||
ByteBuffer.wrap(bytes)
|
||||
|
||||
+7
-6
@@ -204,9 +204,10 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -409,9 +410,9 @@
|
||||
"integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
||||
@@ -1027,8 +1027,11 @@ samplerCubemap | Cubemap texture
|
||||
[Table [materialParamsTypes]: Material parameter types]
|
||||
|
||||
Samplers
|
||||
: Sampler types can also specify a `format` which can be either `int` or `float` (defaults to
|
||||
`float`).
|
||||
: Sampler types can have the following fields:
|
||||
- `format` : which can be either `int` or `float` (defaults to `float`).
|
||||
- `multisample` : a boolean to indicate whether the sampler is meant for multisampling (defaults to `false`)
|
||||
- `unfilterable` : a boolean to indicate whether the sampling is not filtered (defaults to `false`)
|
||||
|
||||
|
||||
Arrays
|
||||
: A parameter can define an array of values by appending `[size]` after the type name, where
|
||||
|
||||
@@ -30,11 +30,6 @@ from utils import execute, ArgParseImpl
|
||||
CUR_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT_DIR = os.path.join(CUR_DIR, '../../')
|
||||
|
||||
def get_last_commit_hash():
|
||||
res, ret = execute('git rev-parse HEAD', cwd=ROOT_DIR)
|
||||
assert res == 0, 'Failed to get the last commit hash'
|
||||
return ret.strip()
|
||||
|
||||
def get_edited_files(commit_hash):
|
||||
INSERT = '#####?????'
|
||||
res, ret = execute(f'git show --name-only --pretty=%b{INSERT} {commit_hash}')
|
||||
|
||||
@@ -39,13 +39,13 @@ function download_mdbook() {
|
||||
fi
|
||||
|
||||
local CHECK_UNAME="
|
||||
import sys;
|
||||
parts=[a.lower() for a in sys.stdin.read().strip().split(' ')];
|
||||
import sys
|
||||
parts=[a.lower() for a in sys.stdin.read().strip().split(' ')]
|
||||
def get_type():
|
||||
if 'darwin' in parts:
|
||||
if 'x86_64' in parts:
|
||||
return ${DARWIN_X86}
|
||||
elif 'aarch' in parts:
|
||||
elif 'aarch' in parts or 'arm64' in parts:
|
||||
return ${DARWIN_ARM}
|
||||
elif 'linux' in parts:
|
||||
if 'x86_64' in parts:
|
||||
@@ -63,11 +63,12 @@ print(get_type())
|
||||
echo "*** Need to install Rust ***"
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
fi
|
||||
source "${HOME}/.cargo/env"
|
||||
if ! (command -v cargo >/dev/null 2>&1); then
|
||||
echo "*** Still cannot find `cargo` ***"
|
||||
echo "*** Still cannot find cargo ***"
|
||||
exit 1
|
||||
fi
|
||||
cargo install mdbook
|
||||
cargo install --force mdbook
|
||||
else
|
||||
# Download prebuilts from github
|
||||
mkdir -p ${HOME}/.cargo/bin
|
||||
|
||||
@@ -13,7 +13,7 @@ This document is part of the [Filament project](https://github.com/google/filame
|
||||
## Authors
|
||||
|
||||
- [Romain Guy](https://github.com/romainguy), [@romainguy](https://twitter.com/romainguy)
|
||||
- [Mathias Agopian](https://github.com/pixelflinger), [@darthmoosious](https://twitter.com/darthmoosious)
|
||||
- [Mathias Agopian](https://github.com/pixelflinger), [@pixelflinger](https://bsky.app/profile/pixelflinger.bsky.social)
|
||||
|
||||
# Overview
|
||||
|
||||
@@ -78,26 +78,27 @@ in table [standardProperties].
|
||||
Property | Definition
|
||||
-----------------------:|:---------------------
|
||||
**baseColor** | Diffuse albedo for non-metallic surfaces, and specular color for metallic surfaces
|
||||
**metallic** | Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1)
|
||||
**roughness** | Perceived smoothness (1.0) or roughness (0.0) of a surface. Smooth surfaces exhibit sharp reflections
|
||||
**metallic** | Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1)
|
||||
**reflectance** | Fresnel reflectance at normal incidence for dielectric surfaces. This directly controls the strength of the reflections
|
||||
**sheenColor** | Strength of the sheen layer
|
||||
**sheenRoughness** | Perceived smoothness or roughness of the sheen layer
|
||||
**ambientOcclusion** | Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0
|
||||
**clearCoat** | Strength of the clear coat layer
|
||||
**clearCoatRoughness** | Perceived smoothness or roughness of the clear coat layer
|
||||
**clearCoatNormal** | A detail normal used to perturb the clear coat layer using _bump mapping_ (_normal mapping_)
|
||||
**anisotropy** | Amount of anisotropy in either the tangent or bitangent direction
|
||||
**anisotropyDirection** | Local surface direction in tangent space
|
||||
**ambientOcclusion** | Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0
|
||||
**normal** | A detail normal used to perturb the surface using _bump mapping_ (_normal mapping_)
|
||||
**bentNormal** | A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality
|
||||
**clearCoatNormal** | A detail normal used to perturb the clear coat layer using _bump mapping_ (_normal mapping_)
|
||||
**emissive** | Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass
|
||||
**postLightingColor** | Additional color that can be blended with the result of the lighting computations. See `postLightingBlending`
|
||||
**ior** | Index of refraction, either for refractive objects or as an alternative to reflectance
|
||||
**transmission** | Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is
|
||||
**absorption** | Absorption factor for refractive objects
|
||||
**microThickness** | Thickness of the thin layer of refractive objects
|
||||
**thickness** | Thickness of the solid volume of refractive objects
|
||||
**sheenColor** | Strength of the sheen layer
|
||||
**sheenRoughness** | Perceived smoothness or roughness of the sheen layer
|
||||
**emissive** | Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass
|
||||
**normal** | A detail normal used to perturb the surface using _bump mapping_ (_normal mapping_)
|
||||
**postLightingColor** | Additional color that can be blended with the result of the lighting computations. See `postLightingBlending`
|
||||
**absorption** | Absorption factor for refractive objects
|
||||
**transmission** | Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is
|
||||
**ior** | Index of refraction, either for refractive objects or as an alternative to reflectance
|
||||
**microThickness** | Thickness of the thin layer of refractive objects
|
||||
**bentNormal** | A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality
|
||||
**shadowStrength** | Strength factor between 0 and 1 for all shadows received by this material
|
||||
[Table [standardProperties]: Properties of the standard model]
|
||||
|
||||
The type and range of each property is described in table [standardPropertiesTypes].
|
||||
@@ -991,10 +992,13 @@ Type
|
||||
|
||||
Value
|
||||
: Each entry is an object with the properties `name` and `type`, both of `string` type. The
|
||||
name must be a valid GLSL identifier. Entries also have an optional `precision`, which can be
|
||||
name must be a valid GLSL identifier. Entries have an optional `precision`, which can be
|
||||
one of `default` (best precision for the platform, typically `high` on desktop, `medium` on
|
||||
mobile), `low`, `medium`, `high`. The type must be one of the types described in
|
||||
table [materialParamsTypes].
|
||||
table [materialParamsTypes]. For Android external textures, entries also have an optional
|
||||
transformName parameter to specify the name of the material parameter that will be
|
||||
used to expose the transform matrix associated with the external sampler. In iOS and Vulkan,
|
||||
this will always be identity.
|
||||
|
||||
Type | Description
|
||||
:----------------------|:---------------------------------
|
||||
@@ -1023,12 +1027,8 @@ samplerCubemap | Cubemap texture
|
||||
[Table [materialParamsTypes]: Material parameter types]
|
||||
|
||||
Samplers
|
||||
: Sampler types can specify additional options:
|
||||
|
||||
- `format`: either `int` or `float` (defaults to `float`).
|
||||
- `stages`: array of strings containing the list of shader stages this
|
||||
sampler can be accessed from. Each entry must be either `vertex` or
|
||||
`fragment` (defaults to both).
|
||||
: Sampler types can also specify a `format` which can be either `int` or `float` (defaults to
|
||||
`float`).
|
||||
|
||||
Arrays
|
||||
: A parameter can define an array of values by appending `[size]` after the type name, where
|
||||
@@ -1273,6 +1273,9 @@ Description
|
||||
when selecting any shading model that is not `unlit`. See the shader sections of this document
|
||||
for more information on how to access these attributes from the shaders.
|
||||
|
||||
!!! Note: Interaction with custom variables
|
||||
When the `color` attribute is specified, only four custom variables are available instead of five.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
|
||||
material {
|
||||
parameters : [
|
||||
@@ -1303,7 +1306,7 @@ Type
|
||||
: array of `string`
|
||||
|
||||
Value
|
||||
: Up to 4 strings, each must be a valid GLSL identifier.
|
||||
: Up to 5 strings, each must be a valid GLSL identifier.
|
||||
|
||||
Description
|
||||
: Defines custom interpolants (or variables) that are output by the material's vertex shader.
|
||||
@@ -1319,6 +1322,10 @@ Description
|
||||
particular if `default` is specified the default precision is used is the fragment shader
|
||||
(`mediump`) and in the vertex shader (`highp`).
|
||||
|
||||
!!! Warning: Interaction with required attributes
|
||||
If the `color` attribute is specified in the `required` list, then only four variables can be used
|
||||
instead of five.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
|
||||
material {
|
||||
name : Skybox,
|
||||
|
||||
@@ -146,7 +146,7 @@ This document is part of the <a href="https://github.com/google/filament">Filame
|
||||
<ul>
|
||||
<li class="minus"><a href="https://github.com/romainguy">Romain Guy</a>, <a href="https://twitter.com/romainguy">@romainguy</a>
|
||||
</li>
|
||||
<li class="minus"><a href="https://github.com/pixelflinger">Mathias Agopian</a>, <a href="https://twitter.com/darthmoosious">@darthmoosious</a></li></ul>
|
||||
<li class="minus"><a href="https://github.com/pixelflinger">Mathias Agopian</a>, <a href="https://bsky.app/profile/pixelflinger.bsky.social">@pixelflinger</a></li></ul>
|
||||
</p>
|
||||
<a class="target" name="overview"> </a><a class="target" name="overview"> </a><a class="target" name="toc2"> </a><h1>Overview</h1>
|
||||
<p>
|
||||
@@ -217,26 +217,27 @@ in <a href="#table_standardproperties" target="_self">table 1</a>.
|
||||
<div class='table'>
|
||||
<a class="target" name="table_standardproperties"> </a><table class="table longtable"><tr><th style="text-align:right"> Property </th><th style="text-align:left"> Definition </th></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">baseColor</strong> </td><td style="text-align:left"> Diffuse albedo for non-metallic surfaces, and specular color for metallic surfaces </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">metallic</strong> </td><td style="text-align:left"> Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1) </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">roughness</strong> </td><td style="text-align:left"> Perceived smoothness (1.0) or roughness (0.0) of a surface. Smooth surfaces exhibit sharp reflections </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">metallic</strong> </td><td style="text-align:left"> Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1) </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">reflectance</strong> </td><td style="text-align:left"> Fresnel reflectance at normal incidence for dielectric surfaces. This directly controls the strength of the reflections </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">sheenColor</strong> </td><td style="text-align:left"> Strength of the sheen layer </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">sheenRoughness</strong> </td><td style="text-align:left"> Perceived smoothness or roughness of the sheen layer </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">ambientOcclusion</strong> </td><td style="text-align:left"> Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0 </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">clearCoat</strong> </td><td style="text-align:left"> Strength of the clear coat layer </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">clearCoatRoughness</strong> </td><td style="text-align:left"> Perceived smoothness or roughness of the clear coat layer </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">clearCoatNormal</strong> </td><td style="text-align:left"> A detail normal used to perturb the clear coat layer using <em class="underscore">bump mapping</em> (<em class="underscore">normal mapping</em>) </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">anisotropy</strong> </td><td style="text-align:left"> Amount of anisotropy in either the tangent or bitangent direction </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">anisotropyDirection</strong> </td><td style="text-align:left"> Local surface direction in tangent space </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">ambientOcclusion</strong> </td><td style="text-align:left"> Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0 </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">normal</strong> </td><td style="text-align:left"> A detail normal used to perturb the surface using <em class="underscore">bump mapping</em> (<em class="underscore">normal mapping</em>) </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">bentNormal</strong> </td><td style="text-align:left"> A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">clearCoatNormal</strong> </td><td style="text-align:left"> A detail normal used to perturb the clear coat layer using <em class="underscore">bump mapping</em> (<em class="underscore">normal mapping</em>) </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">emissive</strong> </td><td style="text-align:left"> Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">postLightingColor</strong> </td><td style="text-align:left"> Additional color that can be blended with the result of the lighting computations. See <code>postLightingBlending</code> </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">ior</strong> </td><td style="text-align:left"> Index of refraction, either for refractive objects or as an alternative to reflectance </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">transmission</strong> </td><td style="text-align:left"> Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">absorption</strong> </td><td style="text-align:left"> Absorption factor for refractive objects </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">microThickness</strong> </td><td style="text-align:left"> Thickness of the thin layer of refractive objects </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">thickness</strong> </td><td style="text-align:left"> Thickness of the solid volume of refractive objects </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">sheenColor</strong> </td><td style="text-align:left"> Strength of the sheen layer </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">sheenRoughness</strong> </td><td style="text-align:left"> Perceived smoothness or roughness of the sheen layer </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">emissive</strong> </td><td style="text-align:left"> Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">normal</strong> </td><td style="text-align:left"> A detail normal used to perturb the surface using <em class="underscore">bump mapping</em> (<em class="underscore">normal mapping</em>) </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">postLightingColor</strong> </td><td style="text-align:left"> Additional color that can be blended with the result of the lighting computations. See <code>postLightingBlending</code> </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">absorption</strong> </td><td style="text-align:left"> Absorption factor for refractive objects </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">transmission</strong> </td><td style="text-align:left"> Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">ior</strong> </td><td style="text-align:left"> Index of refraction, either for refractive objects or as an alternative to reflectance </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">microThickness</strong> </td><td style="text-align:left"> Thickness of the thin layer of refractive objects </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">bentNormal</strong> </td><td style="text-align:left"> A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">shadowStrength</strong> </td><td style="text-align:left"> Strength factor between 0 and 1 for all shadows received by this material </td></tr>
|
||||
</table><center><div class="tablecaption"><b style="font-style:normal;">Table 1:</b> Properties of the standard model</div></center></div>
|
||||
</p><p>
|
||||
The type and range of each property is described in <a href="#table_standardpropertiestypes" target="_self">table 2</a>.
|
||||
@@ -1097,10 +1098,13 @@ non-shader data.
|
||||
|
||||
<dl><dt>Type</dt><dd><p> array of parameter objects
|
||||
</p></dd><dt>Value</dt><dd><p> Each entry is an object with the properties <code>name</code> and <code>type</code>, both of <code>string</code> type. The
|
||||
name must be a valid GLSL identifier. Entries also have an optional <code>precision</code>, which can be
|
||||
name must be a valid GLSL identifier. Entries have an optional <code>precision</code>, which can be
|
||||
one of <code>default</code> (best precision for the platform, typically <code>high</code> on desktop, <code>medium</code> on
|
||||
mobile), <code>low</code>, <code>medium</code>, <code>high</code>. The type must be one of the types described in
|
||||
<a href="#table_materialparamstypes" target="_self">table 14</a>.
|
||||
<a href="#table_materialparamstypes" target="_self">table 14</a>. For Android external textures, entries also have an optional
|
||||
transformName parameter to specify the name of the material parameter that will be
|
||||
used to expose the transform matrix associated with the external sampler. In iOS and Vulkan,
|
||||
this will always be identity.
|
||||
</p></dd></dl><div class='table'>
|
||||
<a class="target" name="table_materialparamstypes"> </a><table class="table longtable"><tr><th style="text-align:left"> Type </th><th style="text-align:left"> Description </th></tr>
|
||||
<tr><td style="text-align:left"> bool </td><td style="text-align:left"> Single boolean </td></tr>
|
||||
@@ -1322,7 +1326,10 @@ non-shader data.
|
||||
required and does not need to be specified. The <code>tangents</code> attribute is automatically required
|
||||
when selecting any shading model that is not <code>unlit</code>. See the shader sections of this document
|
||||
for more information on how to access these attributes from the shaders.
|
||||
</p></dd></dl></p><pre class="listing tilde"><code><span class="line"></span>material <span class="hljs-punctuation">{</span>
|
||||
</p></dd></dl><div class="admonition note"><div class="admonition-title"> Interaction with custom variables</div>
|
||||
</p><p>
|
||||
When the <code>color</code> attribute is specified, only four custom variables are available instead of five.</div>
|
||||
</p><pre class="listing tilde"><code><span class="line"></span>material <span class="hljs-punctuation">{</span>
|
||||
<span class="line"></span> parameters <span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
|
||||
<span class="line"></span> <span class="hljs-punctuation">{</span>
|
||||
<span class="line"></span> type <span class="hljs-punctuation">:</span> sampler2d<span class="hljs-punctuation">,</span>
|
||||
@@ -1347,7 +1354,7 @@ non-shader data.
|
||||
<p>
|
||||
|
||||
<dl><dt>Type</dt><dd><p> array of <code>string</code>
|
||||
</p></dd><dt>Value</dt><dd><p> Up to 4 strings, each must be a valid GLSL identifier.
|
||||
</p></dd><dt>Value</dt><dd><p> Up to 5 strings, each must be a valid GLSL identifier.
|
||||
</p></dd><dt>Description</dt><dd><p> Defines custom interpolants (or variables) that are output by the material's vertex shader.
|
||||
Each entry of the array defines the name of an interpolant. The full name in the fragment
|
||||
shader is the name of the interpolant with the <code>variable_</code> prefix. For instance, if you
|
||||
@@ -1360,7 +1367,11 @@ non-shader data.
|
||||
In this case the specified precision is used as-is in both fragment and vertex stages, in
|
||||
particular if <code>default</code> is specified the default precision is used is the fragment shader
|
||||
(<code>mediump</code>) and in the vertex shader (<code>highp</code>).
|
||||
</p></dd></dl></p><pre class="listing tilde"><code><span class="line"></span>material <span class="hljs-punctuation">{</span>
|
||||
</p></dd></dl><div class="admonition warning"><div class="admonition-title"> Interaction with required attributes</div>
|
||||
</p><p>
|
||||
If the <code>color</code> attribute is specified in the <code>required</code> list, then only four variables can be used
|
||||
instead of five.</div>
|
||||
</p><pre class="listing tilde"><code><span class="line"></span>material <span class="hljs-punctuation">{</span>
|
||||
<span class="line"></span> name <span class="hljs-punctuation">:</span> Skybox<span class="hljs-punctuation">,</span>
|
||||
<span class="line"></span> parameters <span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
|
||||
<span class="line"></span> <span class="hljs-punctuation">{</span>
|
||||
|
||||
@@ -257,6 +257,7 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/WebGPUPlatform.h
|
||||
src/webgpu/platform/WebGPUPlatform.cpp
|
||||
src/webgpu/SpdMipmapGenerator/SpdMipmapGenerator.cpp
|
||||
src/webgpu/WebGPUBufferBase.cpp
|
||||
src/webgpu/WebGPUBufferBase.h
|
||||
src/webgpu/WebGPUBufferObject.cpp
|
||||
@@ -268,12 +269,18 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
src/webgpu/WebGPUDescriptorSetLayout.h
|
||||
src/webgpu/WebGPUDriver.cpp
|
||||
src/webgpu/WebGPUDriver.h
|
||||
src/webgpu/WebGPUFence.cpp
|
||||
src/webgpu/WebGPUFence.h
|
||||
src/webgpu/WebGPUIndexBuffer.cpp
|
||||
src/webgpu/WebGPUIndexBuffer.h
|
||||
src/webgpu/WebGPUMsaaTextureResolver.cpp
|
||||
src/webgpu/WebGPUMsaaTextureResolver.h
|
||||
src/webgpu/WebGPUPipelineCreation.cpp
|
||||
src/webgpu/WebGPUPipelineCreation.h
|
||||
src/webgpu/WebGPUProgram.cpp
|
||||
src/webgpu/WebGPUProgram.h
|
||||
src/webgpu/WebGPURenderPassMipmapGenerator.cpp
|
||||
src/webgpu/WebGPURenderPassMipmapGenerator.h
|
||||
src/webgpu/WebGPURenderPrimitive.h
|
||||
src/webgpu/WebGPURenderTarget.cpp
|
||||
src/webgpu/WebGPURenderTarget.h
|
||||
@@ -502,11 +509,20 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
target_compile_definitions(${TARGET} PRIVATE $<$<BOOL:${FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING}>:FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING>)
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Linker flags
|
||||
# ==================================================================================================
|
||||
|
||||
target_link_libraries(${TARGET} PRIVATE
|
||||
${OSMESA_LINKER_FLAGS}
|
||||
$<$<AND:$<PLATFORM_ID:Linux>,$<CONFIG:Release>>:${LINUX_LINKER_OPTIMIZATION_FLAGS}>
|
||||
)
|
||||
|
||||
if (LINUX AND FILAMENT_SUPPORTS_WEBGPU)
|
||||
# Unclear why this needs to be explicit while other linux libs do not, but it is necessary for
|
||||
# WebGPU to build on linux.
|
||||
target_link_libraries(${TARGET} PRIVATE -lxcb)
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Installation
|
||||
# ==================================================================================================
|
||||
|
||||
@@ -474,7 +474,12 @@ constexpr std::string_view to_string(DescriptorType type) noexcept {
|
||||
|
||||
enum class DescriptorFlags : uint8_t {
|
||||
NONE = 0x00,
|
||||
DYNAMIC_OFFSET = 0x01
|
||||
|
||||
// Indicate a UNIFORM_BUFFER will have dynamic offsets.
|
||||
DYNAMIC_OFFSET = 0x01,
|
||||
|
||||
// To indicate a texture/sampler type should be unfiltered.
|
||||
UNFILTERABLE = 0x02,
|
||||
};
|
||||
|
||||
using descriptor_set_t = uint8_t;
|
||||
@@ -495,18 +500,12 @@ struct DescriptorSetLayoutBinding {
|
||||
DescriptorFlags flags = DescriptorFlags::NONE;
|
||||
uint16_t count = 0;
|
||||
|
||||
// TODO: uncomment when needed. Note that this class is used as hash key. We need to ensure
|
||||
// no uninitialized padding bytes.
|
||||
// uint8_t externalSamplerDataIndex = EXTERNAL_SAMPLER_DATA_INDEX_UNUSED;
|
||||
|
||||
friend bool operator==(DescriptorSetLayoutBinding const& lhs,
|
||||
DescriptorSetLayoutBinding const& rhs) noexcept {
|
||||
return lhs.type == rhs.type &&
|
||||
lhs.flags == rhs.flags &&
|
||||
lhs.count == rhs.count &&
|
||||
lhs.stageFlags == rhs.stageFlags;
|
||||
// lhs.stageFlags == rhs.stageFlags &&
|
||||
// lhs.externalSamplerDataIndex == rhs.externalSamplerDataIndex;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1254,26 +1253,6 @@ enum class SamplerCompareFunc : uint8_t {
|
||||
N //!< Never. The depth / stencil test always fails.
|
||||
};
|
||||
|
||||
//! this API is copied from (and only applies to) the Vulkan spec.
|
||||
//! These specify YUV to RGB conversions.
|
||||
enum class SamplerYcbcrModelConversion : uint8_t {
|
||||
RGB_IDENTITY = 0,
|
||||
YCBCR_IDENTITY = 1,
|
||||
YCBCR_709 = 2,
|
||||
YCBCR_601 = 3,
|
||||
YCBCR_2020 = 4,
|
||||
};
|
||||
|
||||
enum class SamplerYcbcrRange : uint8_t {
|
||||
ITU_FULL = 0,
|
||||
ITU_NARROW = 1,
|
||||
};
|
||||
|
||||
enum class ChromaLocation : uint8_t {
|
||||
COSITED_EVEN = 0,
|
||||
MIDPOINT = 1,
|
||||
};
|
||||
|
||||
//! Sampler parameters
|
||||
struct SamplerParams { // NOLINT
|
||||
SamplerMagFilter filterMag : 1; //!< magnification filter (NEAREST)
|
||||
@@ -1342,94 +1321,9 @@ static_assert(sizeof(SamplerParams) == 4);
|
||||
static_assert(sizeof(SamplerParams) <= sizeof(uint64_t),
|
||||
"SamplerParams must be no more than 64 bits");
|
||||
|
||||
//! Sampler parameters
|
||||
struct SamplerYcbcrConversion {// NOLINT
|
||||
SamplerYcbcrModelConversion ycbcrModel : 4;
|
||||
TextureSwizzle r : 4;
|
||||
TextureSwizzle g : 4;
|
||||
TextureSwizzle b : 4;
|
||||
TextureSwizzle a : 4;
|
||||
SamplerYcbcrRange ycbcrRange : 1;
|
||||
ChromaLocation xChromaOffset : 1;
|
||||
ChromaLocation yChromaOffset : 1;
|
||||
SamplerMagFilter chromaFilter : 1;
|
||||
uint8_t padding;
|
||||
|
||||
struct Hasher {
|
||||
size_t operator()(const SamplerYcbcrConversion p) const noexcept {
|
||||
// we don't use std::hash<> here, so we don't have to include <functional>
|
||||
return *reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&p));
|
||||
}
|
||||
};
|
||||
|
||||
struct EqualTo {
|
||||
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
|
||||
assert_invariant(lhs.padding == 0);
|
||||
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
|
||||
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
|
||||
return *pLhs == *pRhs;
|
||||
}
|
||||
};
|
||||
|
||||
struct LessThan {
|
||||
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
|
||||
assert_invariant(lhs.padding == 0);
|
||||
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
|
||||
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
|
||||
return *pLhs < *pRhs;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
friend bool operator == (SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs)
|
||||
noexcept {
|
||||
return SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
|
||||
}
|
||||
friend bool operator != (SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs)
|
||||
noexcept {
|
||||
return !SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
|
||||
}
|
||||
friend bool operator < (SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs)
|
||||
noexcept {
|
||||
return SamplerYcbcrConversion::LessThan{}(lhs, rhs);
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(SamplerYcbcrConversion) == 4);
|
||||
|
||||
static_assert(sizeof(SamplerYcbcrConversion) <= sizeof(uint64_t),
|
||||
"SamplerYcbcrConversion must be no more than 64 bits");
|
||||
|
||||
struct ExternalSamplerDatum {
|
||||
ExternalSamplerDatum(SamplerYcbcrConversion ycbcr, SamplerParams spm, uint32_t extFmt)
|
||||
: YcbcrConversion(ycbcr),
|
||||
samplerParams(spm),
|
||||
externalFormat(extFmt) {}
|
||||
bool operator==(ExternalSamplerDatum const& rhs) const {
|
||||
return (YcbcrConversion == rhs.YcbcrConversion && samplerParams == rhs.samplerParams &&
|
||||
externalFormat == rhs.externalFormat);
|
||||
}
|
||||
struct EqualTo {
|
||||
bool operator()(const ExternalSamplerDatum& lhs,
|
||||
const ExternalSamplerDatum& rhs) const noexcept {
|
||||
return (lhs.YcbcrConversion == rhs.YcbcrConversion &&
|
||||
lhs.samplerParams == rhs.samplerParams &&
|
||||
lhs.externalFormat == rhs.externalFormat);
|
||||
}
|
||||
};
|
||||
SamplerYcbcrConversion YcbcrConversion;
|
||||
SamplerParams samplerParams;
|
||||
uint32_t externalFormat;
|
||||
};
|
||||
// No implicit padding allowed due to it being a hash key.
|
||||
static_assert(sizeof(ExternalSamplerDatum) == 12);
|
||||
|
||||
struct DescriptorSetLayout {
|
||||
std::variant<utils::StaticString, utils::CString, std::monostate> label;
|
||||
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
|
||||
|
||||
// TODO: uncomment when needed
|
||||
// utils::FixedCapacityVector<ExternalSamplerDatum> externalSamplerData;
|
||||
};
|
||||
|
||||
//! blending equation function
|
||||
|
||||
@@ -107,6 +107,36 @@ public:
|
||||
MULTIVIEW,
|
||||
};
|
||||
|
||||
/**
|
||||
* This controls the priority level for GPU work scheduling, which helps prioritize the
|
||||
* submitted GPU work and enables preemption.
|
||||
*/
|
||||
enum class GpuContextPriority : uint8_t {
|
||||
/**
|
||||
* Backend default GPU context priority (typically MEDIUM)
|
||||
*/
|
||||
DEFAULT,
|
||||
/**
|
||||
* For non-interactive, deferrable workloads. This should not interfere with standard
|
||||
* applications.
|
||||
*/
|
||||
LOW,
|
||||
/**
|
||||
* The default priority level for standard applications.
|
||||
*/
|
||||
MEDIUM,
|
||||
/**
|
||||
* For high-priority, latency-sensitive workloads that are more important than standard
|
||||
* applications.
|
||||
*/
|
||||
HIGH,
|
||||
/**
|
||||
* The highest priority, intended for system-critical, real-time applications where missing
|
||||
* deadlines is unacceptable (e.g., VR/AR compositors or other system-critical tasks).
|
||||
*/
|
||||
REALTIME,
|
||||
};
|
||||
|
||||
struct DriverConfig {
|
||||
/**
|
||||
* Size of handle arena in bytes. Setting to 0 indicates default value is to be used.
|
||||
@@ -156,6 +186,13 @@ public:
|
||||
* - PlatformMetal
|
||||
*/
|
||||
bool metalDisablePanicOnDrawableFailure = false;
|
||||
|
||||
/**
|
||||
* GPU context priority level. Controls GPU work scheduling and preemption.
|
||||
* This is only supported for:
|
||||
* - PlatformEGL
|
||||
*/
|
||||
GpuContextPriority gpuContextPriority = GpuContextPriority::DEFAULT;
|
||||
};
|
||||
|
||||
Platform() noexcept;
|
||||
|
||||
@@ -0,0 +1,764 @@
|
||||
/** @file DriverAPI.dox
|
||||
* @brief External documentation for the backend::Driver API.
|
||||
*
|
||||
* This file contains Doxygen documentation for the functions declared in DriverAPI.inc.
|
||||
* This is used to keep the documentation separate from the macro-heavy header file.
|
||||
*/
|
||||
|
||||
// General lifecycle and frame management
|
||||
|
||||
/** @fn Driver::tick()
|
||||
* @brief Called periodically by the system to perform maintenance tasks.
|
||||
*/
|
||||
|
||||
/** @fn Driver::beginFrame(int64_t monotonic_clock_ns, int64_t refreshIntervalNs, uint32_t frameId)
|
||||
* @brief Signals the beginning of a new frame to the driver.
|
||||
* @param monotonic_clock_ns The current monotonic clock time in nanoseconds.
|
||||
* @param refreshIntervalNs The display's refresh interval in nanoseconds.
|
||||
* @param frameId A unique identifier for the new frame.
|
||||
*/
|
||||
|
||||
/** @fn Driver::setFrameScheduledCallback(backend::SwapChainHandle sch, backend::CallbackHandler* handler, backend::FrameScheduledCallback&& callback, uint64_t flags)
|
||||
* @brief Schedules a callback to be executed when the frame associated with the given swap chain has been scheduled for presentation.
|
||||
* @param sch The handle of the swap chain.
|
||||
* @param handler The callback handler that will execute the callback.
|
||||
* @param callback The function to be called.
|
||||
* @param flags Flags to control the callback behavior.
|
||||
*/
|
||||
|
||||
/** @fn Driver::setFrameCompletedCallback(backend::SwapChainHandle sch, backend::CallbackHandler* handler, utils::Invocable<void(void)>&& callback)
|
||||
* @brief Sets a callback to be executed when rendering to the given swap chain is complete for the current frame.
|
||||
* @param sch The handle of the swap chain.
|
||||
* @param handler The callback handler that will execute the callback.
|
||||
* @param callback The function to be called upon frame completion.
|
||||
*/
|
||||
|
||||
/** @fn Driver::setPresentationTime(int64_t monotonic_clock_ns)
|
||||
* @brief Informs the driver of the intended presentation time for the current frame.
|
||||
* @param monotonic_clock_ns The desired presentation time in nanoseconds, based on the monotonic clock.
|
||||
*/
|
||||
|
||||
/** @fn Driver::endFrame(uint32_t frameId)
|
||||
* @brief Signals the end of a frame to the driver.
|
||||
* @param frameId The unique identifier of the frame that is ending.
|
||||
*/
|
||||
|
||||
/** @fn Driver::flush()
|
||||
* @brief Submits all pending commands to the GPU for execution, without waiting for them to complete.
|
||||
*/
|
||||
|
||||
/** @fn Driver::finish()
|
||||
* @brief Submits all pending commands and waits for the GPU to finish executing them.
|
||||
*/
|
||||
|
||||
/** @fn Driver::resetState()
|
||||
* @brief Resets any cached or tracked driver state, forcing the driver to re-evaluate and set all states.
|
||||
*/
|
||||
|
||||
/** @fn Driver::setDebugTag(backend::HandleBase::HandleId handleId, utils::CString tag)
|
||||
* @brief Associates a user-provided string tag with a driver handle for debugging purposes.
|
||||
* @param handleId The ID of the handle to tag.
|
||||
* @param tag The debug string to associate with the handle.
|
||||
*/
|
||||
|
||||
// Resource creation
|
||||
|
||||
/** @fn backend::VertexBufferInfoHandle Driver::createVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount, backend::AttributeArray attributes)
|
||||
* @brief Creates a VertexBufferInfo object which describes the layout of a vertex buffer.
|
||||
* @param bufferCount The number of buffer objects in the vertex buffer.
|
||||
* @param attributeCount The number of attributes in the vertex buffer.
|
||||
* @param attributes An array describing each vertex attribute.
|
||||
* @return A handle to the new VertexBufferInfo object.
|
||||
*/
|
||||
|
||||
/** @fn backend::VertexBufferHandle Driver::createVertexBuffer(uint32_t vertexCount, backend::VertexBufferInfoHandle vbih)
|
||||
* @brief Creates a vertex buffer with a specified vertex count and layout.
|
||||
* @param vertexCount The number of vertices in the buffer.
|
||||
* @param vbih A handle to a VertexBufferInfo object describing the vertex layout.
|
||||
* @return A handle to the new vertex buffer.
|
||||
*/
|
||||
|
||||
/** @fn backend::IndexBufferHandle Driver::createIndexBuffer(backend::ElementType elementType, uint32_t indexCount, backend::BufferUsage usage)
|
||||
* @brief Creates an index buffer for indexed drawing.
|
||||
* @param elementType The data type of an index (e.g., USHORT, UINT).
|
||||
* @param indexCount The number of indices in the buffer.
|
||||
* @param usage The expected usage pattern of the buffer (e.g., STATIC, DYNAMIC).
|
||||
* @return A handle to the new index buffer.
|
||||
*/
|
||||
|
||||
/** @fn backend::BufferObjectHandle Driver::createBufferObject(uint32_t byteCount, backend::BufferObjectBinding bindingType, backend::BufferUsage usage)
|
||||
* @brief Creates a generic buffer object for storing arbitrary data on the GPU.
|
||||
* @param byteCount The size of the buffer in bytes.
|
||||
* @param bindingType The purpose of the buffer object (e.g., VERTEX, UNIFORM).
|
||||
* @param usage The expected usage pattern of the buffer.
|
||||
* @return A handle to the new buffer object.
|
||||
*/
|
||||
|
||||
/** @fn backend::TextureHandle Driver::createTexture(backend::SamplerType target, uint8_t levels, backend::TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, backend::TextureUsage usage)
|
||||
* @brief Creates a new texture object.
|
||||
* @param target The type of the texture (e.g. 2D, 3D, CUBEMAP).
|
||||
* @param levels The number of mipmap levels.
|
||||
* @param format The internal format of the texture's pixels.
|
||||
* @param samples The number of samples for multisampling (1 for non-MSAA).
|
||||
* @param width The width of the texture in texels.
|
||||
* @param height The height of the texture in texels.
|
||||
* @param depth The depth of the texture in texels (for 3D or array textures).
|
||||
* @param usage A bitmask specifying the intended usage of the texture.
|
||||
* @return A handle to the new texture.
|
||||
*/
|
||||
|
||||
/** @fn backend::TextureHandle Driver::createTextureView(backend::TextureHandle texture, uint8_t baseLevel, uint8_t levelCount)
|
||||
* @brief Creates a new texture view from an existing texture, allowing access to a subset of its mipmap levels.
|
||||
* @param texture The handle of the original texture.
|
||||
* @param baseLevel The first mipmap level to include in the view.
|
||||
* @param levelCount The number of mipmap levels to include in the view.
|
||||
* @return A handle to the new texture view.
|
||||
*/
|
||||
|
||||
/** @fn backend::TextureHandle Driver::createTextureViewSwizzle(backend::TextureHandle texture, backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b, backend::TextureSwizzle a)
|
||||
* @brief Creates a new texture view with remapped color channels (swizzling).
|
||||
* @param texture The handle of the original texture.
|
||||
* @param r The swizzle operation for the red channel.
|
||||
* @param g The swizzle operation for the green channel.
|
||||
* @param b The swizzle operation for the blue channel.
|
||||
* @param a The swizzle operation for the alpha channel.
|
||||
* @return A handle to the new texture view.
|
||||
*/
|
||||
|
||||
/** @fn backend::TextureHandle Driver::createTextureExternalImage2(backend::SamplerType target, backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, backend::Platform::ExternalImageHandleRef image)
|
||||
* @brief Creates a texture from a platform-specific external image handle.
|
||||
* @param target The type of the texture.
|
||||
* @param format The format of the texture's pixels.
|
||||
* @param width The width of the texture.
|
||||
* @param height The height of the texture.
|
||||
* @param usage The expected usage of the texture.
|
||||
* @param image A reference to the platform-specific external image.
|
||||
* @return A handle to the new texture.
|
||||
*/
|
||||
|
||||
/** @fn backend::TextureHandle Driver::createTextureExternalImage(backend::SamplerType target, backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, void* image)
|
||||
* @brief Creates a texture from a platform-specific external image pointer.
|
||||
* @param target The type of the texture.
|
||||
* @param format The format of the texture's pixels.
|
||||
* @param width The width of the texture.
|
||||
* @param height The height of the texture.
|
||||
* @param usage The expected usage of the texture.
|
||||
* @param image A pointer to the external image data.
|
||||
* @return A handle to the new texture.
|
||||
*/
|
||||
|
||||
/** @fn backend::TextureHandle Driver::createTextureExternalImagePlane(backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, void* image, uint32_t plane)
|
||||
* @brief Creates a texture from a single plane of a multi-planar external image.
|
||||
* @param format The format of the texture's pixels.
|
||||
* @param width The width of the texture.
|
||||
* @param height The height of the texture.
|
||||
* @param usage The expected usage of the texture.
|
||||
* @param image A pointer to the external image data.
|
||||
* @param plane The index of the plane to create the texture from.
|
||||
* @return A handle to the new texture.
|
||||
*/
|
||||
|
||||
/** @fn backend::TextureHandle Driver::importTexture(intptr_t id, backend::SamplerType target, uint8_t levels, backend::TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, backend::TextureUsage usage)
|
||||
* @brief Imports an existing backend-native texture into Filament.
|
||||
* @param id The backend-specific texture ID or handle.
|
||||
* @param target The type of the texture.
|
||||
* @param levels The number of mipmap levels.
|
||||
* @param format The format of the texture's pixels.
|
||||
* @param samples The number of samples for multisampling.
|
||||
* @param width The width of the texture.
|
||||
* @param height The height of the texture.
|
||||
* @param depth The depth of the texture.
|
||||
* @param usage The expected usage of the texture.
|
||||
* @return A handle to the imported texture.
|
||||
*/
|
||||
|
||||
/** @fn backend::RenderPrimitiveHandle Driver::createRenderPrimitive(backend::VertexBufferHandle vbh, backend::IndexBufferHandle ibh, backend::PrimitiveType pt)
|
||||
* @brief Creates a render primitive, which represents a piece of geometry to be rendered.
|
||||
* @param vbh A handle to a vertex buffer.
|
||||
* @param ibh A handle to an index buffer.
|
||||
* @param pt The type of primitive to render (e.g., TRIANGLES, LINES).
|
||||
* @return A handle to the new render primitive.
|
||||
*/
|
||||
|
||||
/** @fn backend::ProgramHandle Driver::createProgram(backend::Program&& program)
|
||||
* @brief Creates a shader program from a backend-specific program object.
|
||||
* @param program A Program object containing the shader code and metadata.
|
||||
* @return A handle to the new program.
|
||||
*/
|
||||
|
||||
/** @fn backend::RenderTargetHandle Driver::createDefaultRenderTarget()
|
||||
* @brief Creates a render target that represents the default framebuffer (e.g., the screen).
|
||||
* @return A handle to the default render target.
|
||||
*/
|
||||
|
||||
/** @fn backend::RenderTargetHandle Driver::createRenderTarget(backend::TargetBufferFlags targetBufferFlags, uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount, backend::MRT color, backend::TargetBufferInfo depth, backend::TargetBufferInfo stencil)
|
||||
* @brief Creates an offscreen render target.
|
||||
* @param targetBufferFlags A bitmask specifying which buffers (color, depth, stencil) are included.
|
||||
* @param width The width of the render target in pixels.
|
||||
* @param height The height of the render target in pixels.
|
||||
* @param samples The number of samples for multisampling.
|
||||
* @param layerCount The number of layers for layered rendering.
|
||||
* @param color An array of color attachments.
|
||||
* @param depth The depth attachment.
|
||||
* @param stencil The stencil attachment.
|
||||
* @return A handle to the new render target.
|
||||
*/
|
||||
|
||||
/** @fn backend::FenceHandle Driver::createFence()
|
||||
* @brief Creates a fence for synchronization between the CPU and GPU.
|
||||
* @return A handle to the new fence.
|
||||
*/
|
||||
|
||||
/** @fn backend::SwapChainHandle Driver::createSwapChain(void* nativeWindow, uint64_t flags)
|
||||
* @brief Creates a swap chain for a native window, used for presenting rendered frames.
|
||||
* @param nativeWindow A platform-specific pointer to the native window.
|
||||
* @param flags Configuration flags for the swap chain.
|
||||
* @return A handle to the new swap chain.
|
||||
*/
|
||||
|
||||
/** @fn backend::SwapChainHandle Driver::createSwapChainHeadless(uint32_t width, uint32_t height, uint64_t flags)
|
||||
* @brief Creates a headless swap chain for offscreen rendering without a native window.
|
||||
* @param width The width of the swap chain.
|
||||
* @param height The height of the swap chain.
|
||||
* @param flags Configuration flags for the swap chain.
|
||||
* @return A handle to the new swap chain.
|
||||
*/
|
||||
|
||||
/** @fn backend::TimerQueryHandle Driver::createTimerQuery()
|
||||
* @brief Creates a timer query object for measuring GPU execution time.
|
||||
* @return A handle to the new timer query.
|
||||
*/
|
||||
|
||||
/** @fn backend::DescriptorSetLayoutHandle Driver::createDescriptorSetLayout(backend::DescriptorSetLayout&& info)
|
||||
* @brief Creates a descriptor set layout, which defines the layout of bindings in a descriptor set.
|
||||
* @param info The layout information for the descriptor set.
|
||||
* @return A handle to the new descriptor set layout.
|
||||
*/
|
||||
|
||||
/** @fn backend::DescriptorSetHandle Driver::createDescriptorSet(backend::DescriptorSetLayoutHandle dslh)
|
||||
* @brief Creates a descriptor set based on a given layout.
|
||||
* @param dslh A handle to the descriptor set layout.
|
||||
* @return A handle to the new descriptor set.
|
||||
*/
|
||||
|
||||
/** @fn Driver::updateDescriptorSetBuffer(backend::DescriptorSetHandle dsh, backend::descriptor_binding_t binding, backend::BufferObjectHandle boh, uint32_t offset, uint32_t size)
|
||||
* @brief Updates a buffer descriptor within a descriptor set.
|
||||
* @param dsh The descriptor set to update.
|
||||
* @param binding The binding point of the descriptor.
|
||||
* @param boh The handle of the buffer object to bind.
|
||||
* @param offset The offset into the buffer.
|
||||
* @param size The size of the buffer region to bind.
|
||||
*/
|
||||
|
||||
/** @fn Driver::updateDescriptorSetTexture(backend::DescriptorSetHandle dsh, backend::descriptor_binding_t binding, backend::TextureHandle th, SamplerParams params)
|
||||
* @brief Updates a texture descriptor within a descriptor set.
|
||||
* @param dsh The descriptor set to update.
|
||||
* @param binding The binding point of the descriptor.
|
||||
* @param th The handle of the texture to bind.
|
||||
* @param params The sampler parameters for the texture.
|
||||
*/
|
||||
|
||||
/** @fn Driver::bindDescriptorSet(backend::DescriptorSetHandle dsh, backend::descriptor_set_t set, backend::DescriptorSetOffsetArray&& offsets)
|
||||
* @brief Binds a descriptor set to the current pipeline.
|
||||
* @param dsh The descriptor set to bind.
|
||||
* @param set The index of the descriptor set to bind.
|
||||
* @param offsets An array of dynamic offsets for the descriptor set.
|
||||
*/
|
||||
|
||||
// Resource destruction
|
||||
|
||||
/** @fn Driver::destroyVertexBuffer(backend::VertexBufferHandle vbh)
|
||||
* @brief Destroys a vertex buffer and releases its resources.
|
||||
* @param vbh The handle of the vertex buffer to destroy.
|
||||
*/
|
||||
|
||||
/** @fn Driver::destroyVertexBufferInfo(backend::VertexBufferInfoHandle vbih)
|
||||
* @brief Destroys a VertexBufferInfo object.
|
||||
* @param vbih The handle of the VertexBufferInfo object to destroy.
|
||||
*/
|
||||
|
||||
/** @fn Driver::destroyIndexBuffer(backend::IndexBufferHandle ibh)
|
||||
* @brief Destroys an index buffer and releases its resources.
|
||||
* @param ibh The handle of the index buffer to destroy.
|
||||
*/
|
||||
|
||||
/** @fn Driver::destroyBufferObject(backend::BufferObjectHandle boh)
|
||||
* @brief Destroys a buffer object and releases its resources.
|
||||
* @param boh The handle of the buffer object to destroy.
|
||||
*/
|
||||
|
||||
/** @fn Driver::destroyRenderPrimitive(backend::RenderPrimitiveHandle rph)
|
||||
* @brief Destroys a render primitive.
|
||||
* @param rph The handle of the render primitive to destroy.
|
||||
*/
|
||||
|
||||
/** @fn Driver::destroyProgram(backend::ProgramHandle ph)
|
||||
* @brief Destroys a shader program.
|
||||
* @param ph The handle of the program to destroy.
|
||||
*/
|
||||
|
||||
/** @fn Driver::destroyTexture(backend::TextureHandle th)
|
||||
* @brief Destroys a texture and releases its resources.
|
||||
* @param th The handle of the texture to destroy.
|
||||
*/
|
||||
|
||||
/** @fn Driver::destroyRenderTarget(backend::RenderTargetHandle rth)
|
||||
* @brief Destroys a render target.
|
||||
* @param rth The handle of the render target to destroy.
|
||||
*/
|
||||
|
||||
/** @fn Driver::destroySwapChain(backend::SwapChainHandle sch)
|
||||
* @brief Destroys a swap chain.
|
||||
* @param sch The handle of the swap chain to destroy.
|
||||
*/
|
||||
|
||||
/** @fn Driver::destroyStream(backend::StreamHandle sh)
|
||||
* @brief Destroys a stream.
|
||||
* @param sh The handle of the stream to destroy.
|
||||
*/
|
||||
|
||||
/** @fn Driver::destroyTimerQuery(backend::TimerQueryHandle tqh)
|
||||
* @brief Destroys a timer query object.
|
||||
* @param tqh The handle of the timer query to destroy.
|
||||
*/
|
||||
|
||||
/** @fn Driver::destroyFence(backend::FenceHandle fh)
|
||||
* @brief Destroys a fence.
|
||||
* @param fh The handle of the fence to destroy.
|
||||
*/
|
||||
|
||||
/** @fn Driver::destroyDescriptorSetLayout(backend::DescriptorSetLayoutHandle dslh)
|
||||
* @brief Destroys a descriptor set layout.
|
||||
* @param dslh The handle of the descriptor set layout to destroy.
|
||||
*/
|
||||
|
||||
/** @fn Driver::destroyDescriptorSet(backend::DescriptorSetHandle dsh)
|
||||
* @brief Destroys a descriptor set.
|
||||
* @param dsh The handle of the descriptor set to destroy.
|
||||
*/
|
||||
|
||||
// Synchronous APIs
|
||||
|
||||
/** @fn void Driver::terminate()
|
||||
* @brief Terminates the driver and releases all associated resources. This is a synchronous operation.
|
||||
*/
|
||||
|
||||
/** @fn backend::StreamHandle Driver::createStreamNative(void* stream)
|
||||
* @brief Creates a stream from a native stream object (e.g., a SurfaceTexture on Android).
|
||||
* @param stream A pointer to the native stream object.
|
||||
* @return A handle to the new stream.
|
||||
*/
|
||||
|
||||
/** @fn backend::StreamHandle Driver::createStreamAcquired()
|
||||
* @brief Creates a stream that will be populated with images acquired via `setAcquiredImage`.
|
||||
* @return A handle to the new stream.
|
||||
*/
|
||||
|
||||
/** @fn void Driver::setAcquiredImage(backend::StreamHandle stream, void* image, const math::mat3f& transform, backend::CallbackHandler* handler, backend::StreamCallback cb, void* userData)
|
||||
* @brief Provides an image to an acquired stream, with a callback for when the image is no longer in use.
|
||||
* @param stream The handle of the stream.
|
||||
* @param image A pointer to the image data.
|
||||
* @param transform The transformation matrix for the image.
|
||||
* @param handler The callback handler.
|
||||
* @param cb The callback function to be invoked when the image is released.
|
||||
* @param userData User data for the callback.
|
||||
*/
|
||||
|
||||
/** @fn void Driver::setStreamDimensions(backend::StreamHandle stream, uint32_t width, uint32_t height)
|
||||
* @brief Sets the dimensions of a stream, which may be necessary for certain stream types.
|
||||
* @param stream The handle of the stream.
|
||||
* @param width The new width of the stream.
|
||||
* @param height The new height of the stream.
|
||||
*/
|
||||
|
||||
/** @fn int64_t Driver::getStreamTimestamp(backend::StreamHandle stream)
|
||||
* @brief Gets the timestamp of the last frame from a stream, if available.
|
||||
* @param stream The handle of the stream.
|
||||
* @return The timestamp in nanoseconds, or 0 if not available.
|
||||
*/
|
||||
|
||||
/** @fn void Driver::updateStreams(backend::DriverApi* driver)
|
||||
* @brief Updates all active streams, typically called once per frame.
|
||||
* @param driver The driver API pointer.
|
||||
*/
|
||||
|
||||
/** @fn backend::FenceStatus Driver::getFenceStatus(backend::FenceHandle fh)
|
||||
* @brief Gets the current status of a fence.
|
||||
* @param fh The handle of the fence.
|
||||
* @return The status of the fence (e.g., SIGNALED, TIMEOUT_EXPIRED).
|
||||
*/
|
||||
|
||||
/** @fn bool Driver::isTextureFormatSupported(backend::TextureFormat format)
|
||||
* @brief Checks if a specific texture format is supported by the driver.
|
||||
* @param format The texture format to check.
|
||||
* @return True if the format is supported, false otherwise.
|
||||
*/
|
||||
|
||||
/** @fn bool Driver::isTextureSwizzleSupported()
|
||||
* @brief Checks if texture channel swizzling is supported by the driver.
|
||||
* @return True if supported, false otherwise.
|
||||
*/
|
||||
|
||||
/** @fn bool Driver::isTextureFormatMipmappable(backend::TextureFormat format)
|
||||
* @brief Checks if a texture format can have mipmaps automatically generated.
|
||||
* @param format The texture format to check.
|
||||
* @return True if the format is mipmappable, false otherwise.
|
||||
*/
|
||||
|
||||
/** @fn bool Driver::isRenderTargetFormatSupported(backend::TextureFormat format)
|
||||
* @brief Checks if a texture format is supported for use as a render target attachment.
|
||||
* @param format The texture format to check.
|
||||
* @return True if the format is supported, false otherwise.
|
||||
*/
|
||||
|
||||
/** @fn bool Driver::isFrameBufferFetchSupported()
|
||||
* @brief Checks if framebuffer fetch (reading from the framebuffer in a shader) is supported.
|
||||
* @return True if supported, false otherwise.
|
||||
*/
|
||||
|
||||
/** @fn bool Driver::isFrameBufferFetchMultiSampleSupported()
|
||||
* @brief Checks if multisampled framebuffer fetch is supported.
|
||||
* @return True if supported, false otherwise.
|
||||
*/
|
||||
|
||||
/** @fn bool Driver::isFrameTimeSupported()
|
||||
* @brief Checks if frame time queries are supported for performance measurement.
|
||||
* @return True if supported, false otherwise.
|
||||
*/
|
||||
|
||||
/** @fn bool Driver::isAutoDepthResolveSupported()
|
||||
* @brief Checks if automatic resolution of multisampled depth buffers is supported.
|
||||
* @return True if supported, false otherwise.
|
||||
*/
|
||||
|
||||
/** @fn bool Driver::isSRGBSwapChainSupported()
|
||||
* @brief Checks if sRGB swap chains are supported for correct color space handling.
|
||||
* @return True if supported, false otherwise.
|
||||
*/
|
||||
|
||||
/** @fn bool Driver::isProtectedContentSupported()
|
||||
* @brief Checks if rendering protected content (e.g., for DRM) is supported.
|
||||
* @return True if supported, false otherwise.
|
||||
*/
|
||||
|
||||
/** @fn bool Driver::isStereoSupported()
|
||||
* @brief Checks if stereoscopic rendering is supported.
|
||||
* @return True if supported, false otherwise.
|
||||
*/
|
||||
|
||||
/** @fn bool Driver::isParallelShaderCompileSupported()
|
||||
* @brief Checks if the driver can compile shaders in parallel for improved performance.
|
||||
* @return True if supported, false otherwise.
|
||||
*/
|
||||
|
||||
/** @fn bool Driver::isDepthStencilResolveSupported()
|
||||
* @brief Checks if resolving multisampled depth/stencil buffers is supported.
|
||||
* @return True if supported, false otherwise.
|
||||
*/
|
||||
|
||||
/** @fn bool Driver::isDepthStencilBlitSupported(backend::TextureFormat format)
|
||||
* @brief Checks if blitting (copying) depth/stencil data is supported for a given format.
|
||||
* @param format The texture format.
|
||||
* @return True if supported, false otherwise.
|
||||
*/
|
||||
|
||||
/** @fn bool Driver::isProtectedTexturesSupported()
|
||||
* @brief Checks if creating protected textures is supported.
|
||||
* @return True if supported, false otherwise.
|
||||
*/
|
||||
|
||||
/** @fn bool Driver::isDepthClampSupported()
|
||||
* @brief Checks if depth clamping is supported by the hardware.
|
||||
* @return True if supported, false otherwise.
|
||||
*/
|
||||
|
||||
/** @fn uint8_t Driver::getMaxDrawBuffers()
|
||||
* @brief Gets the maximum number of simultaneous draw buffers (Multiple Render Targets).
|
||||
* @return The maximum number of draw buffers.
|
||||
*/
|
||||
|
||||
/** @fn size_t Driver::getMaxUniformBufferSize()
|
||||
* @brief Gets the maximum size of a uniform buffer in bytes.
|
||||
* @return The maximum size in bytes.
|
||||
*/
|
||||
|
||||
/** @fn size_t Driver::getMaxTextureSize(backend::SamplerType target)
|
||||
* @brief Gets the maximum texture dimension (width, height, or depth) for a given target type.
|
||||
* @param target The texture target type.
|
||||
* @return The maximum size in texels.
|
||||
*/
|
||||
|
||||
/** @fn size_t Driver::getMaxArrayTextureLayers()
|
||||
* @brief Gets the maximum number of layers in an array texture.
|
||||
* @return The maximum number of layers.
|
||||
*/
|
||||
|
||||
/** @fn math::float2 Driver::getClipSpaceParams()
|
||||
* @brief Gets the clip space parameters for the current backend.
|
||||
* @return A float2 containing clip space parameters.
|
||||
*/
|
||||
|
||||
/** @fn void Driver::setupExternalImage2(backend::Platform::ExternalImageHandleRef image)
|
||||
* @brief Performs any necessary setup for an external image before it can be used.
|
||||
* @param image A reference to the external image.
|
||||
*/
|
||||
|
||||
/** @fn void Driver::setupExternalImage(void* image)
|
||||
* @brief Performs any necessary setup for an external image before it can be used.
|
||||
* @param image A pointer to the external image.
|
||||
*/
|
||||
|
||||
/** @fn backend::TimerQueryResult Driver::getTimerQueryValue(backend::TimerQueryHandle query, uint64_t* elapsedTime)
|
||||
* @brief Gets the result of a timer query, providing the elapsed GPU time.
|
||||
* @param query The timer query handle.
|
||||
* @param elapsedTime A pointer to store the elapsed time in nanoseconds.
|
||||
* @return The result of the query (e.g., AVAILABLE, NOT_READY).
|
||||
*/
|
||||
|
||||
/** @fn bool Driver::isWorkaroundNeeded(backend::Workaround workaround)
|
||||
* @brief Checks if a specific driver or hardware workaround is needed.
|
||||
* @param workaround The workaround to check.
|
||||
* @return True if the workaround is needed, false otherwise.
|
||||
*/
|
||||
|
||||
/** @fn backend::FeatureLevel Driver::getFeatureLevel()
|
||||
* @brief Gets the feature level supported by the driver.
|
||||
* @return The driver's feature level.
|
||||
*/
|
||||
|
||||
// Resource updates
|
||||
|
||||
/** @fn Driver::setVertexBufferObject(backend::VertexBufferHandle vbh, uint32_t index, backend::BufferObjectHandle bufferObject)
|
||||
* @brief Associates a buffer object with a specific buffer slot in a vertex buffer.
|
||||
* @param vbh The handle of the vertex buffer.
|
||||
* @param index The index of the buffer slot to set.
|
||||
* @param bufferObject The handle of the buffer object to associate.
|
||||
*/
|
||||
|
||||
/** @fn Driver::updateIndexBuffer(backend::IndexBufferHandle ibh, backend::BufferDescriptor&& data, uint32_t byteOffset)
|
||||
* @brief Updates the data of an index buffer.
|
||||
* @param ibh The handle of the index buffer.
|
||||
* @param data The new data to upload.
|
||||
* @param byteOffset The offset in bytes into the buffer to start writing to.
|
||||
*/
|
||||
|
||||
/** @fn Driver::updateBufferObject(backend::BufferObjectHandle boh, backend::BufferDescriptor&& data, uint32_t byteOffset)
|
||||
* @brief Updates the data of a buffer object.
|
||||
* @param boh The handle of the buffer object.
|
||||
* @param data The new data to upload.
|
||||
* @param byteOffset The offset in bytes into the buffer to start writing to.
|
||||
*/
|
||||
|
||||
/** @fn Driver::registerBufferObjectStreams(backend::BufferObjectHandle boh, backend::BufferObjectStreamDescriptor&& streams)
|
||||
* @brief Registers external streams with a buffer object for efficient data transfer.
|
||||
* @param boh The handle of the buffer object.
|
||||
* @param streams The stream descriptors.
|
||||
*/
|
||||
|
||||
/** @fn Driver::updateBufferObjectUnsynchronized(backend::BufferObjectHandle boh, backend::BufferDescriptor&& data, uint32_t byteOffset)
|
||||
* @brief Updates a buffer object without synchronization, for use when manual synchronization is handled.
|
||||
* @param boh The handle of the buffer object.
|
||||
* @param data The new data to upload.
|
||||
* @param byteOffset The offset in bytes into the buffer to start writing to.
|
||||
*/
|
||||
|
||||
/** @fn Driver::resetBufferObject(backend::BufferObjectHandle boh)
|
||||
* @brief Resets a buffer object, potentially discarding its contents.
|
||||
* @param boh The handle of the buffer object.
|
||||
*/
|
||||
|
||||
/** @fn Driver::update3DImage(backend::TextureHandle th, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth, backend::PixelBufferDescriptor&& data)
|
||||
* @brief Updates a sub-region of a 3D texture.
|
||||
* @param th The handle of the texture.
|
||||
* @param level The mipmap level to update.
|
||||
* @param xoffset The x offset of the sub-region.
|
||||
* @param yoffset The y offset of the sub-region.
|
||||
* @param zoffset The z offset of the sub-region.
|
||||
* @param width The width of the sub-region.
|
||||
* @param height The height of the sub-region.
|
||||
* @param depth The depth of the sub-region.
|
||||
* @param data The pixel data to upload.
|
||||
*/
|
||||
|
||||
/** @fn Driver::generateMipmaps(backend::TextureHandle th)
|
||||
* @brief Generates mipmaps for a texture automatically.
|
||||
* @param th The handle of the texture.
|
||||
*/
|
||||
|
||||
/** @fn Driver::setExternalStream(backend::TextureHandle th, backend::StreamHandle sh)
|
||||
* @brief Associates an external stream with a texture for video or camera input.
|
||||
* @param th The handle of the texture.
|
||||
* @param sh The handle of the stream.
|
||||
*/
|
||||
|
||||
// Render passes
|
||||
|
||||
/** @fn Driver::beginRenderPass(backend::RenderTargetHandle rth, const backend::RenderPassParams& params)
|
||||
* @brief Begins a new render pass, targeting a specific render target.
|
||||
* @param rth The handle of the render target.
|
||||
* @param params The parameters for the render pass (e.g., clear values, load/store actions).
|
||||
*/
|
||||
|
||||
/** @fn Driver::endRenderPass()
|
||||
* @brief Ends the current render pass.
|
||||
*/
|
||||
|
||||
/** @fn Driver::nextSubpass()
|
||||
* @brief Moves to the next subpass within the current render pass.
|
||||
*/
|
||||
|
||||
// Timer queries
|
||||
|
||||
/** @fn Driver::beginTimerQuery(backend::TimerQueryHandle query)
|
||||
* @brief Begins a timer query to measure GPU time.
|
||||
* @param query The handle of the timer query.
|
||||
*/
|
||||
|
||||
/** @fn Driver::endTimerQuery(backend::TimerQueryHandle query)
|
||||
* @brief Ends a timer query.
|
||||
* @param query The handle of the timer query.
|
||||
*/
|
||||
|
||||
/** @fn Driver::compilePrograms(backend::CompilerPriorityQueue priority, backend::CallbackHandler* handler, backend::CallbackHandler::Callback callback, void* user)
|
||||
* @brief Compiles a queue of pending shader programs.
|
||||
* @param priority The priority queue to use for compilation.
|
||||
* @param handler The callback handler.
|
||||
* @param callback The callback function to be invoked upon completion.
|
||||
* @param user User data for the callback.
|
||||
*/
|
||||
|
||||
// Swap chain
|
||||
|
||||
/** @fn Driver::makeCurrent(backend::SwapChainHandle schDraw, backend::SwapChainHandle schRead)
|
||||
* @brief Makes a swap chain current for drawing and reading.
|
||||
* @param schDraw The draw swap chain.
|
||||
* @param schRead The read swap chain.
|
||||
*/
|
||||
|
||||
/** @fn Driver::commit(backend::SwapChainHandle sch)
|
||||
* @brief Commits the back buffer of a swap chain, making it visible.
|
||||
* @param sch The handle of the swap chain.
|
||||
*/
|
||||
|
||||
// Rendering state
|
||||
|
||||
/** @fn Driver::setPushConstant(backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant value)
|
||||
* @brief Sets a push constant value for a specific shader stage.
|
||||
* @param stage The shader stage to set the constant for.
|
||||
* @param index The index of the push constant.
|
||||
* @param value The value to set.
|
||||
*/
|
||||
|
||||
/** @fn Driver::insertEventMarker(const char* string)
|
||||
* @brief Inserts a debug event marker into the command stream.
|
||||
* @param string The marker string.
|
||||
*/
|
||||
|
||||
/** @fn Driver::pushGroupMarker(const char* string)
|
||||
* @brief Pushes a debug group marker onto the command stream stack.
|
||||
* @param string The marker string.
|
||||
*/
|
||||
|
||||
/** @fn Driver::popGroupMarker()
|
||||
* @brief Pops a debug group marker from the command stream stack.
|
||||
*/
|
||||
|
||||
/** @fn Driver::startCapture()
|
||||
* @brief Starts a graphics capture session.
|
||||
*/
|
||||
|
||||
/** @fn Driver::stopCapture()
|
||||
* @brief Stops a graphics capture session.
|
||||
*/
|
||||
|
||||
// Read-back
|
||||
|
||||
/** @fn Driver::readPixels(backend::RenderTargetHandle src, uint32_t x, uint32_t y, uint32_t width, uint32_t height, backend::PixelBufferDescriptor&& data)
|
||||
* @brief Reads a block of pixels from a render target into a client-side buffer.
|
||||
* @param src The source render target.
|
||||
* @param x The x coordinate of the region to read.
|
||||
* @param y The y coordinate of the region to read.
|
||||
* @param width The width of the region to read.
|
||||
* @param height The height of the region to read.
|
||||
* @param data The buffer to store the pixel data.
|
||||
*/
|
||||
|
||||
/** @fn Driver::readBufferSubData(backend::BufferObjectHandle src, uint32_t offset, uint32_t size, backend::BufferDescriptor&& data)
|
||||
* @brief Reads a sub-region of a buffer object into a client-side buffer.
|
||||
* @param src The source buffer object.
|
||||
* @param offset The offset to start reading from.
|
||||
* @param size The number of bytes to read.
|
||||
* @param data The buffer to store the data.
|
||||
*/
|
||||
|
||||
// Rendering
|
||||
|
||||
/** @fn Driver::blitDEPRECATED(backend::TargetBufferFlags buffers, backend::RenderTargetHandle dst, backend::Viewport dstRect, backend::RenderTargetHandle src, backend::Viewport srcRect, backend::SamplerMagFilter filter)
|
||||
* @brief Blits (copies) a region from one render target to another (deprecated).
|
||||
* @param buffers The buffers to blit (e.g., color, depth).
|
||||
* @param dst The destination render target.
|
||||
* @param dstRect The destination rectangle.
|
||||
* @param src The source render target.
|
||||
* @param srcRect The source rectangle.
|
||||
* @param filter The filter to use for scaling.
|
||||
*/
|
||||
|
||||
/** @fn Driver::resolve(backend::TextureHandle dst, uint8_t dstLevel, uint8_t dstLayer, backend::TextureHandle src, uint8_t srcLevel, uint8_t srcLayer)
|
||||
* @brief Resolves a multisampled texture into a non-multisampled texture.
|
||||
* @param dst The destination texture.
|
||||
* @param dstLevel The destination mipmap level.
|
||||
* @param dstLayer The destination layer.
|
||||
* @param src The source multisampled texture.
|
||||
* @param srcLevel The source mipmap level.
|
||||
* @param srcLayer The source layer.
|
||||
*/
|
||||
|
||||
/** @fn Driver::blit(backend::TextureHandle dst, uint8_t dstLevel, uint8_t dstLayer, math::uint2 dstOrigin, backend::TextureHandle src, uint8_t srcLevel, uint8_t srcLayer, math::uint2 srcOrigin, math::uint2 size)
|
||||
* @brief Blits (copies) a region from one texture to another.
|
||||
* @param dst The destination texture.
|
||||
* @param dstLevel The destination mipmap level.
|
||||
* @param dstLayer The destination layer.
|
||||
* @param dstOrigin The origin of the destination region.
|
||||
* @param src The source texture.
|
||||
* @param srcLevel The source mipmap level.
|
||||
* @param srcLayer The source layer.
|
||||
* @param srcOrigin The origin of the source region.
|
||||
* @param size The size of the region to blit.
|
||||
*/
|
||||
|
||||
/** @fn Driver::bindPipeline(const backend::PipelineState& state)
|
||||
* @brief Binds a pipeline state object, including shaders and render states.
|
||||
* @param state The pipeline state to bind.
|
||||
*/
|
||||
|
||||
/** @fn Driver::bindRenderPrimitive(backend::RenderPrimitiveHandle rph)
|
||||
* @brief Binds a render primitive for subsequent draw calls.
|
||||
* @param rph The handle of the render primitive.
|
||||
*/
|
||||
|
||||
/** @fn Driver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount)
|
||||
* @brief Draws a render primitive using the currently bound pipeline and primitive.
|
||||
* @param indexOffset The offset into the index buffer.
|
||||
* @param indexCount The number of indices to draw.
|
||||
* @param instanceCount The number of instances to draw.
|
||||
*/
|
||||
|
||||
/** @fn Driver::draw(backend::PipelineState state, backend::RenderPrimitiveHandle rph, uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount)
|
||||
* @brief A combined call to bind a pipeline, bind a primitive, and draw.
|
||||
* @param state The pipeline state to bind.
|
||||
* @param rph The render primitive to bind.
|
||||
* @param indexOffset The offset into the index buffer.
|
||||
* @param indexCount The number of indices to draw.
|
||||
* @param instanceCount The number of instances to draw.
|
||||
*/
|
||||
|
||||
/** @fn Driver::dispatchCompute(backend::ProgramHandle program, math::uint3 workGroupCount)
|
||||
* @brief Dispatches a compute shader.
|
||||
* @param program The handle of the compute program.
|
||||
* @param workGroupCount The number of work groups to dispatch in each dimension.
|
||||
*/
|
||||
|
||||
/** @fn Driver::scissor(Viewport scissor)
|
||||
* @brief Sets the scissor rectangle for clipping.
|
||||
* @param scissor The scissor rectangle.
|
||||
*/
|
||||
@@ -116,7 +116,7 @@ public:
|
||||
* Destroys the object D at Handle<B> and construct a new D in its place
|
||||
* e.g.:
|
||||
* Handle<ConcreteTexture> h = allocateAndConstruct(w, h);
|
||||
* ConcreteTexture* p = reconstruct(h, w, h);
|
||||
* ConcreteTexture* p = destroyAndConstruct(h, w, h);
|
||||
*/
|
||||
template<typename D, typename B, typename ... ARGS>
|
||||
std::enable_if_t<std::is_base_of_v<B, D>, D>*
|
||||
|
||||
@@ -66,7 +66,7 @@ static std::string to_string(int const i) { return std::to_string(i); }
|
||||
static std::string to_string(float const f) { return "float(" + std::to_string(f) + ")"; }
|
||||
|
||||
static void logCompilationError(ShaderStage shaderType, const char* name, GLuint shaderId,
|
||||
CString const& sourceCode) noexcept;
|
||||
Program::ShaderBlob const& sourceCode) noexcept;
|
||||
static void logProgramLinkError(char const* name, GLuint program) noexcept;
|
||||
|
||||
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext const& context, char* source,
|
||||
@@ -88,7 +88,7 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||
ShaderCompilerService& compiler;
|
||||
CString const& name;
|
||||
FixedCapacityVector<std::pair<CString, uint8_t>> attributes;
|
||||
shaders_source_t shaderSourceCode;
|
||||
Program::ShaderSource shaderSourceCode;
|
||||
void* user = nullptr;
|
||||
struct {
|
||||
shaders_t shaders{};
|
||||
@@ -612,7 +612,7 @@ void ShaderCompilerService::executeTickOps() noexcept {
|
||||
#ifndef NDEBUG
|
||||
// for debugging we return the original shader source (without the modifications we
|
||||
// made here), otherwise the line numbers wouldn't match.
|
||||
token->shaderSourceCode[i] = { shader_src, shader_len };
|
||||
token->shaderSourceCode[i] = std::move(shader);
|
||||
#endif
|
||||
token->gl.shaders[i] = shaderId;
|
||||
}
|
||||
@@ -758,7 +758,8 @@ void ShaderCompilerService::executeTickOps() noexcept {
|
||||
|
||||
UTILS_NOINLINE
|
||||
/* static */ void logCompilationError(ShaderStage shaderType, const char* name,
|
||||
GLuint const shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
|
||||
GLuint const shaderId,
|
||||
UTILS_UNUSED_IN_RELEASE Program::ShaderBlob const& sourceCode) noexcept {
|
||||
|
||||
{ // scope for the temporary string storage
|
||||
auto to_string = [](ShaderStage type) -> const char* {
|
||||
@@ -785,7 +786,8 @@ UTILS_NOINLINE
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
std::string_view const shader{ sourceCode.data(), sourceCode.size() };
|
||||
std::string_view const shader{ reinterpret_cast<const char*>(sourceCode.data()),
|
||||
sourceCode.size() };
|
||||
size_t lc = 1;
|
||||
size_t start = 0;
|
||||
std::string line;
|
||||
|
||||
@@ -58,7 +58,6 @@ class ShaderCompilerService {
|
||||
public:
|
||||
using program_token_t = std::shared_ptr<OpenGLProgramToken>;
|
||||
using shaders_t = std::array<GLuint, Program::SHADER_TYPE_COUNT>;
|
||||
using shaders_source_t = std::array<utils::CString, Program::SHADER_TYPE_COUNT>;
|
||||
|
||||
explicit ShaderCompilerService(OpenGLDriver& driver);
|
||||
|
||||
|
||||
@@ -209,10 +209,42 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& drive
|
||||
}
|
||||
#endif
|
||||
|
||||
// Configure GPU context priority level for scheduling and preemption
|
||||
if (driverConfig.gpuContextPriority != Platform::GpuContextPriority::DEFAULT) {
|
||||
if (extensions.has("EGL_IMG_context_priority")) {
|
||||
EGLint priorityLevel = EGL_CONTEXT_PRIORITY_MEDIUM_IMG;
|
||||
const char* priorityName;
|
||||
switch (driverConfig.gpuContextPriority) {
|
||||
case Platform::GpuContextPriority::DEFAULT:
|
||||
assert_invariant(false);
|
||||
break;
|
||||
case Platform::GpuContextPriority::LOW:
|
||||
priorityLevel = EGL_CONTEXT_PRIORITY_LOW_IMG;
|
||||
priorityName = "LOW";
|
||||
break;
|
||||
case Platform::GpuContextPriority::MEDIUM:
|
||||
priorityLevel = EGL_CONTEXT_PRIORITY_MEDIUM_IMG;
|
||||
priorityName = "MEDIUM";
|
||||
break;
|
||||
case Platform::GpuContextPriority::HIGH:
|
||||
priorityLevel = EGL_CONTEXT_PRIORITY_HIGH_IMG;
|
||||
priorityName = "HIGH";
|
||||
break;
|
||||
case Platform::GpuContextPriority::REALTIME:
|
||||
priorityLevel = EGL_CONTEXT_PRIORITY_HIGH_IMG;
|
||||
priorityName = "REALTIME(=HIGH)";
|
||||
break;
|
||||
}
|
||||
contextAttribs[EGL_CONTEXT_PRIORITY_LEVEL_IMG] = priorityLevel;
|
||||
LOG(INFO) << "EGL: Enabling GPU context priority: " << priorityName;
|
||||
} else {
|
||||
LOG(WARNING) << "EGL: GPU context priority requested but not supported";
|
||||
}
|
||||
}
|
||||
|
||||
// config use for creating the context
|
||||
EGLConfig eglConfig = EGL_NO_CONFIG_KHR;
|
||||
|
||||
|
||||
if (UTILS_UNLIKELY(!ext.egl.KHR_no_config_context)) {
|
||||
// find a config we can use if we don't have "EGL_KHR_no_config_context" and that we can use
|
||||
// for the dummy pbuffer surface.
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
*/
|
||||
|
||||
#include "VulkanBufferProxy.h"
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanMemory.h"
|
||||
|
||||
#include "VulkanBufferCache.h"
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanContext.h"
|
||||
#include "VulkanMemory.h"
|
||||
|
||||
using namespace bluevk;
|
||||
@@ -36,6 +36,38 @@ VulkanBufferProxy::VulkanBufferProxy(VmaAllocator allocator, VulkanStagePool& st
|
||||
|
||||
void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* cpuData,
|
||||
uint32_t byteOffset, uint32_t numBytes) {
|
||||
// The VulkanBuffer is available if the only object reference is hold by the
|
||||
// `VulkanBufferProxy`. This means that the buffer is not currently referenced by a
|
||||
// `VulkanCommandBuffer`.
|
||||
bool const isAvailable = mBuffer->getCount() == 1;
|
||||
|
||||
if (isAvailable) {
|
||||
// We are up to date with all the update operations, so no need to synchronize with previous
|
||||
// updates.
|
||||
mUpdatedBytes = 0;
|
||||
mUpdatedOffset = 0;
|
||||
}
|
||||
|
||||
// Keep track of the VulkanBuffer usage
|
||||
commands.acquire(mBuffer);
|
||||
|
||||
// Check if we can just memcpy directly to the GPU memory.
|
||||
// This is only allowed for UNIFORMS that are AVAILABLE and the memory is HOST_VISIBLE
|
||||
// (supports memcpy from host). This works regardless if it's a full or partial update of the
|
||||
// buffer.
|
||||
bool const isMemcopyable = mBuffer->getGpuBuffer()->allocationInfo.pMappedData != nullptr;
|
||||
bool const isUniform = getUsage() == VulkanBufferUsage::UNIFORM;
|
||||
bool const useMemcpy =
|
||||
isUniform && isMemcopyable && isAvailable && !FVK_FORCE_STAGING_FOR_BUFFER_UPDATES;
|
||||
if (useMemcpy) {
|
||||
char* dest = static_cast<char*>(mBuffer->getGpuBuffer()->allocationInfo.pMappedData) +
|
||||
byteOffset;
|
||||
memcpy(dest, cpuData, numBytes);
|
||||
vmaFlushAllocation(mAllocator, mBuffer->getGpuBuffer()->vmaAllocation, byteOffset,
|
||||
numBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: this should be stored within the command buffer before going out of
|
||||
// scope, so that the command buffer can manage its lifecycle.
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> stage = mStagePool.acquireStage(numBytes);
|
||||
@@ -128,4 +160,4 @@ VulkanBufferUsage VulkanBufferProxy::getUsage() const noexcept {
|
||||
return mBuffer->getGpuBuffer()->usage;
|
||||
}
|
||||
|
||||
}// namespace filament::backend
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -49,6 +49,6 @@ private:
|
||||
uint32_t mUpdatedBytes = 0;
|
||||
};
|
||||
|
||||
}// namespace filament::backend
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_VULKANBUFFERPROXY_H
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKANBUFFERPROXY_H
|
||||
|
||||
@@ -214,4 +214,8 @@ constexpr static const int FVK_MAX_PIPELINE_AGE = FVK_MAX_COMMAND_BUFFERS;
|
||||
// destroying any unused pipeline object.
|
||||
static_assert(FVK_MAX_PIPELINE_AGE >= FVK_MAX_COMMAND_BUFFERS);
|
||||
|
||||
// Whether the buffer updates always use staging or not. Otherwise its allowed to also use memcpy
|
||||
// for buffer updates. Default is false
|
||||
constexpr static const bool FVK_FORCE_STAGING_FOR_BUFFER_UPDATES = false;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -394,7 +394,13 @@ void VulkanDriver::collectGarbage() {
|
||||
void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
int64_t refreshIntervalNs, uint32_t frameId) {
|
||||
FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
|
||||
// Do nothing.
|
||||
|
||||
// Check if any command have finished and reset all its used resources. The resources
|
||||
// wont be destroyed but their reference count will decreased if the command is already
|
||||
// completed.
|
||||
//
|
||||
// This will let us check if any VulkanBuffer is currently in flight or not.
|
||||
mCommands.gc();
|
||||
|
||||
if (mAppState.hasExternalSamplers()) {
|
||||
mExternalImageManager.onBeginFrame();
|
||||
@@ -1236,7 +1242,8 @@ void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor
|
||||
scheduleDestroy(std::move(p));
|
||||
}
|
||||
|
||||
void VulkanDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh, BufferObjectStreamDescriptor&& streams) {
|
||||
void VulkanDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh,
|
||||
BufferObjectStreamDescriptor&& streams) {
|
||||
// Noop
|
||||
}
|
||||
|
||||
|
||||
@@ -34,16 +34,23 @@ namespace {
|
||||
static constexpr uint32_t MAX_EMPTY_STAGES_TO_RETAIN = 1;
|
||||
constexpr uint32_t STAGE_SIZE = 1048576;
|
||||
|
||||
uint32_t alignValue(uint32_t valueToAlign, uint32_t alignment) {
|
||||
if (alignment == 0) {
|
||||
return valueToAlign;
|
||||
}
|
||||
|
||||
uint32_t remainder = valueToAlign % alignment;
|
||||
return remainder == 0 ? valueToAlign : valueToAlign + (alignment - remainder);
|
||||
}
|
||||
|
||||
}// namespace
|
||||
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStage::acquireSegment(
|
||||
fvkmemory::ResourceManager* resManager, uint32_t numBytes) {
|
||||
auto segment = fvkmemory::resource_ptr<Segment>::construct(
|
||||
resManager, this, numBytes, mCurrentOffset, [this](uint32_t offset) {
|
||||
mSegments.erase(offset);
|
||||
});
|
||||
mSegments.insert({mCurrentOffset, segment.get()});
|
||||
mCurrentOffset += numBytes;
|
||||
fvkmemory::ResourceManager* resManager, uint32_t segmentOffset, uint32_t numBytes) {
|
||||
auto segment = fvkmemory::resource_ptr<Segment>::construct(resManager, this, numBytes,
|
||||
segmentOffset, [this](uint32_t offset) { mSegments.erase(offset); });
|
||||
mSegments.insert({ segmentOffset, segment.get() });
|
||||
mCurrentOffset = segmentOffset + numBytes;
|
||||
return segment;
|
||||
}
|
||||
|
||||
@@ -54,7 +61,8 @@ VulkanStagePool::VulkanStagePool(VmaAllocator allocator, fvkmemory::ResourceMana
|
||||
mCommands(commands),
|
||||
mDeviceLimits(deviceLimits) {}
|
||||
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStagePool::acquireStage(uint32_t numBytes) {
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStagePool::acquireStage(uint32_t numBytes,
|
||||
uint32_t alignment) {
|
||||
// Apply alignment to the byte count to ensure that, when we later flush
|
||||
// data written by the host, we only flush the atoms that we modified, and
|
||||
// no adjacent atoms.
|
||||
@@ -64,17 +72,35 @@ fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStagePool::acquireStage(uint
|
||||
// equal to the requested size.
|
||||
auto iter = mStages.lower_bound(numBytes);
|
||||
|
||||
VulkanStage* pStage;
|
||||
if (iter != mStages.end()) {
|
||||
pStage = iter->second;
|
||||
mStages.erase(iter);
|
||||
} else {
|
||||
// If we have potential buffer candidates, loop through and try to find one
|
||||
// that works with our alignment. Also, set the offset for the buffer.
|
||||
// If we have no buffer, create a new one.
|
||||
VulkanStage* pStage = nullptr;
|
||||
uint32_t segmentOffset = 0;
|
||||
while (iter != mStages.end()) {
|
||||
segmentOffset = alignValue(iter->second->currentOffset(), alignment);
|
||||
|
||||
// Check for overflow + if there's space.
|
||||
if (segmentOffset >= iter->second->currentOffset() &&
|
||||
iter->second->capacity() - numBytes >= segmentOffset) {
|
||||
// This buffer has enough space.
|
||||
pStage = iter->second;
|
||||
mStages.erase(iter);
|
||||
break;
|
||||
}
|
||||
|
||||
// This buffer wasn't suitable. Check the next larger one.
|
||||
++iter;
|
||||
}
|
||||
|
||||
if (!pStage) {
|
||||
pStage = allocateNewStage(std::max(numBytes, STAGE_SIZE));
|
||||
}
|
||||
|
||||
// Note: this allocation updates `currentOffset` and `segments` within
|
||||
// the parent stage. When destroyed, it will update `segments`.
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> pSegment = pStage->acquireSegment(mResManager, numBytes);
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> pSegment =
|
||||
pStage->acquireSegment(mResManager, segmentOffset, numBytes);
|
||||
|
||||
// Update the stage's metadata, and reinsert it with the remaining segment
|
||||
// capacity.
|
||||
@@ -86,12 +112,7 @@ fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStagePool::acquireStage(uint
|
||||
|
||||
uint32_t VulkanStagePool::alignToNonCoherentAtomSize(uint32_t bytes) {
|
||||
VkDeviceSize alignment = mDeviceLimits->nonCoherentAtomSize;
|
||||
if (alignment == 0) {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
uint32_t remainder = bytes % alignment;
|
||||
return remainder == 0 ? bytes : bytes + (alignment - remainder);
|
||||
return alignValue(bytes, alignment);
|
||||
}
|
||||
|
||||
VulkanStage* VulkanStagePool::allocateNewStage(uint32_t capacity) {
|
||||
|
||||
@@ -105,8 +105,11 @@ public:
|
||||
// Marks a region of the block as "in-use", and provides information about
|
||||
// the allocated region to the caller. Note: this assumes that numBytes
|
||||
// is aligned to the physical device's nonCoherentAtomSize.
|
||||
// Takes the offset to use as the segment's start offset. It is assumed
|
||||
// that this is greater than the segment's current offset, and it is
|
||||
// undefined behavior if not.
|
||||
fvkmemory::resource_ptr<Segment> acquireSegment(fvkmemory::ResourceManager* resManager,
|
||||
uint32_t numBytes);
|
||||
uint32_t segmentOffset, uint32_t numBytes);
|
||||
|
||||
private:
|
||||
const VmaAllocation mMemory;
|
||||
@@ -144,7 +147,10 @@ public:
|
||||
// buffers so that we have less objects around that we have to keep track
|
||||
// of.
|
||||
// This function is NOT thread-safe.
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> acquireStage(uint32_t numBytes);
|
||||
// numBytes - the number of bytes required by this segment.
|
||||
// alignment - the multiple to align the buffer offset to
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> acquireStage(uint32_t numBytes,
|
||||
uint32_t alignment = 0);
|
||||
|
||||
// Images have VK_IMAGE_LAYOUT_GENERAL and must not be transitioned to any other layout
|
||||
VulkanStageImage const* acquireImage(PixelDataFormat format, PixelDataType type,
|
||||
|
||||
@@ -227,7 +227,7 @@ void adjustedMemcpy(void* mapped, PixelBufferDescriptor const& p, size_t width,
|
||||
|
||||
// Slow path of copying row by row
|
||||
assert_invariant(pbdStride >= width);
|
||||
if (UTILS_UNLIKELY(p.left > 0 || p.top > 0 || pbdStride > width)) {
|
||||
if (UTILS_UNLIKELY(pixelSize > 0 && (p.left > 0 || p.top > 0 || pbdStride > width))) {
|
||||
size_t const pbdRowSize =
|
||||
PixelBufferDescriptor::computeDataSize(p.format, p.type, pbdStride, 1, p.alignment);
|
||||
size_t const pbdHeight = p.size / pixelSize / pbdStride / depth;
|
||||
@@ -248,7 +248,9 @@ void adjustedMemcpy(void* mapped, PixelBufferDescriptor const& p, size_t width,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
size_t const writeSize = pixelSize * (width * height * depth);
|
||||
// Note: if pixelSize = 0, we're dealing with a compressed format; we
|
||||
// should fall back to the buffer's size.
|
||||
size_t const writeSize = pixelSize > 0 ? pixelSize * (width * height * depth) : p.size;
|
||||
memcpy(mapped, buf, writeSize);
|
||||
}
|
||||
}
|
||||
@@ -517,13 +519,17 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
|
||||
// Otherwise, use vkCmdCopyBufferToImage.
|
||||
size_t const bpp =
|
||||
PixelBufferDescriptor::computeDataSize(hostData->format, hostData->type, 1, 1, 1);
|
||||
size_t const writeSize = width * height * depth * bpp;
|
||||
// Note: if bpp = 0, we're dealing with a compressed format; we should fall
|
||||
// back to the buffer's size.
|
||||
size_t const writeSize = bpp > 0 ? width * height * depth * bpp : hostData->size;
|
||||
|
||||
// Note: the following stageSegment must be stored within the command buffer
|
||||
// before going out of scope, to ensure proper bookkeeping within the
|
||||
// staging buffer pool.
|
||||
uint8_t alignment =
|
||||
fvkutils::getTexelBlockSize(fvkutils::getVkFormat(hostData->format, hostData->type));
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> stageSegment =
|
||||
mState->mStagePool.acquireStage(writeSize);
|
||||
mState->mStagePool.acquireStage(writeSize, alignment);
|
||||
assert_invariant(stageSegment->memory());
|
||||
adjustedMemcpy(stageSegment->mapping(), *hostData, width, height, depth);
|
||||
vmaFlushAllocation(mState->mAllocator, stageSegment->memory(), stageSegment->offset(),
|
||||
@@ -591,7 +597,9 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& data, uint3
|
||||
uint32_t height, uint32_t depth, uint32_t miplevel) {
|
||||
// Otherwise, use vkCmdCopyBufferToImage.
|
||||
size_t const bpp = PixelBufferDescriptor::computeDataSize(data.format, data.type, 1, 1, 1);
|
||||
size_t const writeSize = width * height * depth * bpp;
|
||||
// Note: if bpp = 0, we're dealing with a compressed format; we should fall
|
||||
// back to the buffer's size.
|
||||
size_t const writeSize = bpp > 0 ? width * height * depth * bpp : data.size;
|
||||
|
||||
void* mapped = nullptr;
|
||||
VulkanStageImage const* stage
|
||||
|
||||
@@ -29,6 +29,8 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using namespace fvkutils;
|
||||
|
||||
VulkanYcbcrConversionCache::VulkanYcbcrConversionCache(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
|
||||
|
||||
#include "utils/Definitions.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/Hash.h>
|
||||
@@ -30,7 +32,7 @@ namespace filament::backend {
|
||||
class VulkanYcbcrConversionCache {
|
||||
public:
|
||||
struct Params {
|
||||
SamplerYcbcrConversion conversion = {}; // 4
|
||||
fvkutils::SamplerYcbcrConversion conversion = {}; // 4
|
||||
VkFormat format; // 4
|
||||
uint64_t externalFormat = 0; // 8
|
||||
};
|
||||
@@ -45,16 +47,15 @@ private:
|
||||
|
||||
struct ConversionEqualTo {
|
||||
bool operator()(Params lhs, Params rhs) const noexcept {
|
||||
SamplerYcbcrConversion::EqualTo equal;
|
||||
fvkutils::SamplerYcbcrConversion::EqualTo equal;
|
||||
return equal(lhs.conversion, rhs.conversion) &&
|
||||
lhs.externalFormat == rhs.externalFormat &&
|
||||
lhs.format == rhs.format;
|
||||
lhs.externalFormat == rhs.externalFormat && lhs.format == rhs.format;
|
||||
}
|
||||
};
|
||||
using ConversionHashFn = utils::hash::MurmurHashFn<Params>;
|
||||
tsl::robin_map<Params, VkSamplerYcbcrConversion, ConversionHashFn, ConversionEqualTo> mCache;
|
||||
};
|
||||
|
||||
}// namespace filament::backend
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
|
||||
|
||||
@@ -71,6 +71,8 @@ struct Resource {
|
||||
restype(ResourceType::UNDEFINED_TYPE),
|
||||
mHandleConsideredDestroyed(false) {}
|
||||
|
||||
uint32_t getCount() const { return mCount; }
|
||||
|
||||
private:
|
||||
inline void inc() noexcept {
|
||||
mCount++;
|
||||
|
||||
@@ -832,6 +832,9 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
context.mPhysicalDeviceFeatures.features.shaderClipDistance = VK_FALSE;
|
||||
}
|
||||
|
||||
// TODO: Add support of VK_KHR_global_priority with `driverConfig.gpuContextPriority`
|
||||
// in VulkanPlatform::createDriver.
|
||||
|
||||
ExtensionSet deviceExts;
|
||||
// If using a shared context, we do not assume any extensions.
|
||||
if (!mImpl->mSharedContext) {
|
||||
|
||||
@@ -233,6 +233,212 @@ VkFormat getVkFormat(TextureFormat format) {
|
||||
}
|
||||
}
|
||||
|
||||
// As per VUID-vkCmdCopyBufferToImage-dstImage-07975 and
|
||||
// VUID-vkCmdCopyBufferToImage-dstImage-07978, these provide the texel block
|
||||
// sizes for each format (as confirmed using the table at
|
||||
// https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#formats-compatibility-classes).
|
||||
// We have not listed values for multi-plane formats, as we do not support them
|
||||
// unless they're externally provided.
|
||||
uint8_t getTexelBlockSize(VkFormat format) {
|
||||
switch (format) {
|
||||
// 8-bit formats.
|
||||
case VK_FORMAT_R8_UNORM:
|
||||
case VK_FORMAT_R8_SNORM:
|
||||
case VK_FORMAT_R8_USCALED:
|
||||
case VK_FORMAT_R8_SSCALED:
|
||||
case VK_FORMAT_R8_UINT:
|
||||
case VK_FORMAT_R8_SINT:
|
||||
case VK_FORMAT_R8_SRGB:
|
||||
return 1;
|
||||
|
||||
// 16-bit formats.
|
||||
case VK_FORMAT_R16_UNORM:
|
||||
case VK_FORMAT_R16_SNORM:
|
||||
case VK_FORMAT_R16_USCALED:
|
||||
case VK_FORMAT_R16_SSCALED:
|
||||
case VK_FORMAT_R16_UINT:
|
||||
case VK_FORMAT_R16_SINT:
|
||||
case VK_FORMAT_R16_SFLOAT:
|
||||
case VK_FORMAT_R8G8_UNORM:
|
||||
case VK_FORMAT_R8G8_SNORM:
|
||||
case VK_FORMAT_R8G8_USCALED:
|
||||
case VK_FORMAT_R8G8_SSCALED:
|
||||
case VK_FORMAT_R8G8_UINT:
|
||||
case VK_FORMAT_R8G8_SINT:
|
||||
case VK_FORMAT_R8G8_SRGB:
|
||||
case VK_FORMAT_R5G6B5_UNORM_PACK16:
|
||||
case VK_FORMAT_R5G5B5A1_UNORM_PACK16:
|
||||
case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
|
||||
return 2;
|
||||
|
||||
// 24-bit formats.
|
||||
case VK_FORMAT_R8G8B8_UNORM:
|
||||
case VK_FORMAT_R8G8B8_SNORM:
|
||||
case VK_FORMAT_R8G8B8_USCALED:
|
||||
case VK_FORMAT_R8G8B8_SSCALED:
|
||||
case VK_FORMAT_R8G8B8_UINT:
|
||||
case VK_FORMAT_R8G8B8_SINT:
|
||||
case VK_FORMAT_R8G8B8_SRGB:
|
||||
case VK_FORMAT_B8G8R8_UNORM:
|
||||
case VK_FORMAT_B8G8R8_SNORM:
|
||||
case VK_FORMAT_B8G8R8_USCALED:
|
||||
case VK_FORMAT_B8G8R8_SSCALED:
|
||||
case VK_FORMAT_B8G8R8_UINT:
|
||||
case VK_FORMAT_B8G8R8_SINT:
|
||||
case VK_FORMAT_B8G8R8_SRGB:
|
||||
return 3;
|
||||
|
||||
// 32-bit formats.
|
||||
case VK_FORMAT_R32_UINT:
|
||||
case VK_FORMAT_R32_SINT:
|
||||
case VK_FORMAT_R32_SFLOAT:
|
||||
case VK_FORMAT_R16G16_UNORM:
|
||||
case VK_FORMAT_R16G16_SNORM:
|
||||
case VK_FORMAT_R16G16_USCALED:
|
||||
case VK_FORMAT_R16G16_SSCALED:
|
||||
case VK_FORMAT_R16G16_UINT:
|
||||
case VK_FORMAT_R16G16_SINT:
|
||||
case VK_FORMAT_R16G16_SFLOAT:
|
||||
case VK_FORMAT_R8G8B8A8_UNORM:
|
||||
case VK_FORMAT_R8G8B8A8_SNORM:
|
||||
case VK_FORMAT_R8G8B8A8_USCALED:
|
||||
case VK_FORMAT_R8G8B8A8_SSCALED:
|
||||
case VK_FORMAT_R8G8B8A8_UINT:
|
||||
case VK_FORMAT_R8G8B8A8_SINT:
|
||||
case VK_FORMAT_R8G8B8A8_SRGB:
|
||||
case VK_FORMAT_B8G8R8A8_UNORM:
|
||||
case VK_FORMAT_B8G8R8A8_SNORM:
|
||||
case VK_FORMAT_B8G8R8A8_USCALED:
|
||||
case VK_FORMAT_B8G8R8A8_SSCALED:
|
||||
case VK_FORMAT_B8G8R8A8_UINT:
|
||||
case VK_FORMAT_B8G8R8A8_SINT:
|
||||
case VK_FORMAT_B8G8R8A8_SRGB:
|
||||
case VK_FORMAT_A8B8G8R8_UNORM_PACK32:
|
||||
case VK_FORMAT_A8B8G8R8_SNORM_PACK32:
|
||||
case VK_FORMAT_A8B8G8R8_USCALED_PACK32:
|
||||
case VK_FORMAT_A8B8G8R8_SSCALED_PACK32:
|
||||
case VK_FORMAT_A8B8G8R8_UINT_PACK32:
|
||||
case VK_FORMAT_A8B8G8R8_SINT_PACK32:
|
||||
case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
|
||||
case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
|
||||
// Depth and stencil formats.
|
||||
case VK_FORMAT_S8_UINT:
|
||||
case VK_FORMAT_D16_UNORM:
|
||||
case VK_FORMAT_X8_D24_UNORM_PACK32:
|
||||
case VK_FORMAT_D32_SFLOAT:
|
||||
case VK_FORMAT_D24_UNORM_S8_UINT:
|
||||
case VK_FORMAT_D32_SFLOAT_S8_UINT:
|
||||
case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
|
||||
case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
|
||||
return 4;
|
||||
|
||||
// 48-bit formats.
|
||||
case VK_FORMAT_R16G16B16_UNORM:
|
||||
case VK_FORMAT_R16G16B16_SNORM:
|
||||
case VK_FORMAT_R16G16B16_USCALED:
|
||||
case VK_FORMAT_R16G16B16_SSCALED:
|
||||
case VK_FORMAT_R16G16B16_UINT:
|
||||
case VK_FORMAT_R16G16B16_SINT:
|
||||
case VK_FORMAT_R16G16B16_SFLOAT:
|
||||
return 6;
|
||||
|
||||
// 64-bit formats.
|
||||
case VK_FORMAT_R32G32_UINT:
|
||||
case VK_FORMAT_R32G32_SINT:
|
||||
case VK_FORMAT_R32G32_SFLOAT:
|
||||
case VK_FORMAT_R16G16B16A16_UNORM:
|
||||
case VK_FORMAT_R16G16B16A16_SNORM:
|
||||
case VK_FORMAT_R16G16B16A16_USCALED:
|
||||
case VK_FORMAT_R16G16B16A16_SSCALED:
|
||||
case VK_FORMAT_R16G16B16A16_UINT:
|
||||
case VK_FORMAT_R16G16B16A16_SINT:
|
||||
case VK_FORMAT_R16G16B16A16_SFLOAT:
|
||||
// Compressed formats.
|
||||
case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC4_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC4_SNORM_BLOCK:
|
||||
case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
|
||||
case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
|
||||
case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
|
||||
case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
|
||||
case VK_FORMAT_EAC_R11_UNORM_BLOCK:
|
||||
case VK_FORMAT_EAC_R11_SNORM_BLOCK:
|
||||
return 8;
|
||||
|
||||
// 96-bit formats.
|
||||
case VK_FORMAT_R32G32B32_UINT:
|
||||
case VK_FORMAT_R32G32B32_SINT:
|
||||
case VK_FORMAT_R32G32B32_SFLOAT:
|
||||
return 12;
|
||||
|
||||
// 128-bit formats.
|
||||
case VK_FORMAT_R32G32B32A32_UINT:
|
||||
case VK_FORMAT_R32G32B32A32_SINT:
|
||||
case VK_FORMAT_R32G32B32A32_SFLOAT:
|
||||
// Compressed formats.
|
||||
case VK_FORMAT_BC2_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC2_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC3_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC3_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC5_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC5_SNORM_BLOCK:
|
||||
case VK_FORMAT_BC6H_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_BC6H_UFLOAT_BLOCK:
|
||||
case VK_FORMAT_BC7_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC7_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_4x4_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x4_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x5_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x5_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x6_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x5_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x6_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x8_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x5_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x6_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x8_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x10_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x10_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x12_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_4x4_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x4_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x5_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x5_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x6_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x5_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x6_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x8_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x5_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x6_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x8_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x10_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x10_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x12_SRGB_BLOCK:
|
||||
case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
|
||||
case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
|
||||
case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
|
||||
case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
|
||||
return 16;
|
||||
|
||||
case VK_FORMAT_UNDEFINED:
|
||||
// In cases where we've explicitly already determined that the
|
||||
// format is not supported, let the rest of the system handle
|
||||
// that explicitly. Return '1' for no alignment.
|
||||
return 1;
|
||||
|
||||
default:
|
||||
// This is the case where there's a format that we added support
|
||||
// for in getVkFormat that we have not added support for in this
|
||||
// function, and should be treated as an error.
|
||||
assert_invariant(false && "Unknown data type, conversion is not supported.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
VkFormat getVkFormat(PixelDataFormat format, PixelDataType type) {
|
||||
if (type == PixelDataType::USHORT_565) return VK_FORMAT_R5G6B5_UNORM_PACK16;
|
||||
if (type == PixelDataType::UINT_2_10_10_10_REV) return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKAN_UTILS_CONVERSION_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKAN_UTILS_CONVERSION_H
|
||||
|
||||
#include "Definitions.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <private/backend/BackendUtils.h> // for getFormatSize()
|
||||
@@ -47,6 +49,9 @@ VkFormat getVkFormatLinear(VkFormat format);
|
||||
// than a driver-level Texture format, and can account for a specified byte alignment.
|
||||
uint32_t getBytesPerPixel(TextureFormat format);
|
||||
|
||||
// Required for aligning textures in staging buffers.
|
||||
uint8_t getTexelBlockSize(VkFormat format);
|
||||
|
||||
VkCompareOp getCompareOp(SamplerCompareFunc func);
|
||||
VkBlendFactor getBlendFactor(BlendFunction mode);
|
||||
VkCullModeFlags getCullMode(CullingMode mode);
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKAN_UTILS_DEFINITIONS_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKAN_UTILS_DEFINITIONS_H
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
@@ -379,6 +381,81 @@ static constexpr uint8_t getFragmentStageShift() noexcept {
|
||||
// We have at most 4 descriptor sets. This is to indicate which ones are active.
|
||||
using DescriptorSetMask = utils::bitset8;
|
||||
|
||||
//! this API is copied from (and only applies to) the Vulkan spec.
|
||||
//! These specify YUV to RGB conversions.
|
||||
enum class SamplerYcbcrModelConversion : uint8_t {
|
||||
RGB_IDENTITY = 0,
|
||||
YCBCR_IDENTITY = 1,
|
||||
YCBCR_709 = 2,
|
||||
YCBCR_601 = 3,
|
||||
YCBCR_2020 = 4,
|
||||
};
|
||||
|
||||
enum class SamplerYcbcrRange : uint8_t {
|
||||
ITU_FULL = 0,
|
||||
ITU_NARROW = 1,
|
||||
};
|
||||
|
||||
enum class ChromaLocation : uint8_t {
|
||||
COSITED_EVEN = 0,
|
||||
MIDPOINT = 1,
|
||||
};
|
||||
|
||||
//! Sampler parameters
|
||||
struct SamplerYcbcrConversion { // NOLINT
|
||||
SamplerYcbcrModelConversion ycbcrModel : 4;
|
||||
TextureSwizzle r : 4;
|
||||
TextureSwizzle g : 4;
|
||||
TextureSwizzle b : 4;
|
||||
TextureSwizzle a : 4;
|
||||
SamplerYcbcrRange ycbcrRange : 1;
|
||||
ChromaLocation xChromaOffset : 1;
|
||||
ChromaLocation yChromaOffset : 1;
|
||||
SamplerMagFilter chromaFilter : 1;
|
||||
uint8_t padding;
|
||||
|
||||
struct Hasher {
|
||||
size_t operator()(const SamplerYcbcrConversion p) const noexcept {
|
||||
// we don't use std::hash<> here, so we don't have to include <functional>
|
||||
return *reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&p));
|
||||
}
|
||||
};
|
||||
|
||||
struct EqualTo {
|
||||
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
|
||||
assert_invariant(lhs.padding == 0);
|
||||
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
|
||||
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
|
||||
return *pLhs == *pRhs;
|
||||
}
|
||||
};
|
||||
|
||||
struct LessThan {
|
||||
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
|
||||
assert_invariant(lhs.padding == 0);
|
||||
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
|
||||
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
|
||||
return *pLhs < *pRhs;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
friend bool operator==(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) noexcept {
|
||||
return SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
|
||||
}
|
||||
friend bool operator!=(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) noexcept {
|
||||
return !SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
|
||||
}
|
||||
friend bool operator<(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) noexcept {
|
||||
return SamplerYcbcrConversion::LessThan{}(lhs, rhs);
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(SamplerYcbcrConversion) == 4);
|
||||
|
||||
static_assert(sizeof(SamplerYcbcrConversion) <= sizeof(uint64_t),
|
||||
"SamplerYcbcrConversion must be no more than 64 bits");
|
||||
|
||||
} // namespace filament::backend::fvkutils
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKAN_UTILS_DEFINITIONS_H
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
WebGPU does not include a trivial way to create mipmaps, and thus we must provide a render pass or compute pass to generate them.
|
||||
|
||||
This directory is the result of porting https://github.com/JolifantoBambla/webgpu-spd to C++, which itself is a WebGPU port of https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/blob/main/docs/samples/single-pass-downsampler.md.
|
||||
|
||||
The first version of our port is primarily from a Gemini "Convert this to C++" request, which worked as an MVP with very few changes. Future commits will likely alter this version significantly, but it is being merged in this early state to facilitate rapid iteration.
|
||||
@@ -0,0 +1,956 @@
|
||||
#include "SpdMipmapGenerator.h"
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
// C++ port of https://github.com/JolifantoBambla/webgpu-spd for early experiments
|
||||
namespace spd {
|
||||
|
||||
// Helper to convert enums to strings for map keys or shader code
|
||||
const char* to_string(wgpu::TextureFormat format) {
|
||||
switch (format) {
|
||||
case wgpu::TextureFormat::RGBA8Unorm:
|
||||
return "rgba8unorm";
|
||||
case wgpu::TextureFormat::BGRA8Unorm:
|
||||
return "bgra8unorm";
|
||||
case wgpu::TextureFormat::R32Float:
|
||||
return "r32float";
|
||||
case wgpu::TextureFormat::RG32Float:
|
||||
return "rg32float";
|
||||
case wgpu::TextureFormat::RGBA32Float:
|
||||
return "rgba32float";
|
||||
case wgpu::TextureFormat::R16Float:
|
||||
return "r16float";
|
||||
case wgpu::TextureFormat::RG16Float:
|
||||
return "rg16float";
|
||||
case wgpu::TextureFormat::RGBA16Float:
|
||||
return "rgba16float";
|
||||
// Add other formats as needed
|
||||
default:
|
||||
return "rgba8unorm";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string MipmapGenerator::GetFilterCode(SPDFilter filter) {
|
||||
switch (filter) {
|
||||
case SPDFilter::Min:
|
||||
return R"(
|
||||
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
|
||||
return min(min(v0, v1), min(v2, v3));
|
||||
}
|
||||
)";
|
||||
case SPDFilter::Max:
|
||||
return R"(
|
||||
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
|
||||
return max(max(v0, v1), max(v2, v3));
|
||||
}
|
||||
)";
|
||||
case SPDFilter::MinMax:
|
||||
return R"(
|
||||
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
|
||||
let max4 = max(max(v0.xy, v1.xy), max(v2.xy, v3.xy));
|
||||
return vec4<SPDScalar>(min(min(v0.x, v1.x), min(v2.x, v3.x)), max(max4.x, max4.y), 0.0, 0.0);
|
||||
}
|
||||
)";
|
||||
case SPDFilter::Average:
|
||||
default:
|
||||
return R"(
|
||||
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
|
||||
return (v0 + v1 + v2 + v3) * 0.25;
|
||||
}
|
||||
)";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MipmapGenerator::MipmapGenerator(const wgpu::Device& device)
|
||||
: m_device(device) {
|
||||
wgpu::BindGroupLayoutEntry bglEntry{};
|
||||
bglEntry.binding = 0;
|
||||
bglEntry.visibility = wgpu::ShaderStage::Compute;
|
||||
bglEntry.buffer.type = wgpu::BufferBindingType::Uniform;
|
||||
bglEntry.buffer.minBindingSize = 16;
|
||||
|
||||
wgpu::BindGroupLayoutDescriptor bglDesc{};
|
||||
bglDesc.entryCount = 1;
|
||||
bglDesc.entries = &bglEntry;
|
||||
m_internalResourcesBindGroupLayout =
|
||||
m_device.CreateBindGroupLayout(&bglDesc); // Basic BGL for <= 6 mips
|
||||
|
||||
wgpu::Limits deviceLimits;
|
||||
m_device.GetLimits(&deviceLimits);
|
||||
// Max number of *output* mips we can generate in a single pass.
|
||||
// This is device.maxStorageTexturesPerShaderStage - 1 (for the source texture binding),
|
||||
// further capped at 11 output mips (which means 12 total texture bindings: 1 source + 11
|
||||
// outputs). WebGPU minspec for maxStorageTexturesPerShaderStage is 4, so
|
||||
// deviceLimits.maxStorageTexturesPerShaderStage >= 4. Thus, m_maxMipsPerPass will be at least
|
||||
// std::min(4u - 1, 11u) = 3.
|
||||
m_maxMipsPerPass =
|
||||
std::min(static_cast<uint32_t>(deviceLimits.maxStorageTexturesPerShaderStage) - 1, 11u);
|
||||
m_maxArrayLayers = deviceLimits.maxStorageTexturesPerShaderStage;
|
||||
|
||||
|
||||
// If we can generate more than 6 mips in a pass, we need an advanced BGL
|
||||
// that includes atomic counter and mid-mip buffer for levels > 5.
|
||||
if (m_maxMipsPerPass > 6) {
|
||||
std::vector<wgpu::BindGroupLayoutEntry> entriesAdvanced(3);
|
||||
// Entry 0: Uniform buffer (DownsamplePassMeta)
|
||||
entriesAdvanced[0].binding = 0;
|
||||
entriesAdvanced[0].visibility = wgpu::ShaderStage::Compute;
|
||||
entriesAdvanced[0].buffer.type = wgpu::BufferBindingType::Uniform;
|
||||
entriesAdvanced[0].buffer.minBindingSize = 16; // sizeof(DownsamplePassMeta) effectively
|
||||
|
||||
// Entry 1: Storage buffer (spd_global_counter)
|
||||
entriesAdvanced[1].binding = 1;
|
||||
entriesAdvanced[1].visibility = wgpu::ShaderStage::Compute;
|
||||
entriesAdvanced[1].buffer.type = wgpu::BufferBindingType::Storage;
|
||||
entriesAdvanced[1].buffer.minBindingSize = 4; // sizeof(atomic<u32>)
|
||||
|
||||
// Entry 2: Storage buffer (mip_dst_6_buffer)
|
||||
entriesAdvanced[2].binding = 2;
|
||||
entriesAdvanced[2].visibility = wgpu::ShaderStage::Compute;
|
||||
entriesAdvanced[2].buffer.type = wgpu::BufferBindingType::Storage;
|
||||
// Size for array<array<array<vec4<f32>, 64>, 64>> which is 16 (bytes for vec4f32) * 64 * 64
|
||||
// * arrayLayers. The shader defines it as [slice][uv.y][uv.x], so 64x64 per slice.
|
||||
// minBindingSize here is for one slice as a baseline. Actual buffer will be larger for
|
||||
// multiple array layers.
|
||||
entriesAdvanced[2].buffer.minBindingSize = 16 * 64 * 64;
|
||||
|
||||
wgpu::BindGroupLayoutDescriptor bglDescAdvanced{};
|
||||
bglDescAdvanced.label = "SPD Internal BGL (Advanced)";
|
||||
bglDescAdvanced.entryCount = static_cast<uint32_t>(entriesAdvanced.size());
|
||||
bglDescAdvanced.entries = entriesAdvanced.data();
|
||||
m_internalResourcesBindGroupLayout_Advanced =
|
||||
m_device.CreateBindGroupLayout(&bglDescAdvanced);
|
||||
}
|
||||
}
|
||||
|
||||
void MipmapGenerator::PreparePipelines(wgpu::TextureFormat format, SPDFilter filter,
|
||||
bool halfPrecision) {
|
||||
SPDScalarType scalarType = SanitizeScalarType(format, halfPrecision);
|
||||
// Prepare for a reasonable number of mips, up to m_maxMipsPerPass
|
||||
// The loop should go from 1 up to and including m_maxMipsPerPass.
|
||||
// If m_maxMipsPerPass is 0 (e.g. maxStorageTexturesPerShaderStage <=1 ), this loop won't run.
|
||||
for (uint32_t i = 1; i <= m_maxMipsPerPass; ++i) {
|
||||
GetOrCreatePipeline({ format, scalarType, filter, i });
|
||||
}
|
||||
}
|
||||
|
||||
SPDScalarType MipmapGenerator::SanitizeScalarType(wgpu::TextureFormat format, bool halfPrecision) {
|
||||
std::string formatStr = to_string(format);
|
||||
std::transform(formatStr.begin(), formatStr.end(), formatStr.begin(), ::tolower);
|
||||
|
||||
SPDScalarType texelType = SPDScalarType::F32; // Default to F32
|
||||
if (formatStr.find("sint") != std::string::npos) {
|
||||
texelType = SPDScalarType::I32;
|
||||
} else if (formatStr.find("uint") != std::string::npos) {
|
||||
texelType = SPDScalarType::U32;
|
||||
}
|
||||
|
||||
if (halfPrecision && texelType == SPDScalarType::F32) {
|
||||
if (m_device.HasFeature(wgpu::FeatureName::ShaderF16)) {
|
||||
return SPDScalarType::F16;
|
||||
} else {
|
||||
// Optional: Log a warning here that halfPrecision was requested for a float format
|
||||
// but the device does not support ShaderF16, so F32 will be used.
|
||||
}
|
||||
} else if (halfPrecision && texelType != SPDScalarType::F32) {
|
||||
// Optional: Log a warning here that halfPrecision was requested for a non-float format.
|
||||
}
|
||||
return texelType;
|
||||
}
|
||||
|
||||
SPDPipeline& MipmapGenerator::GetOrCreatePipeline(const PipelineCacheKey& key) {
|
||||
uint32_t numMipsForPipeline = std::min(key.numMips, m_maxMipsPerPass);
|
||||
if (m_pipelines.find(key) == m_pipelines.end()) {
|
||||
// Create the pipeline
|
||||
SPDPipeline spdPipeline;
|
||||
|
||||
std::vector<wgpu::BindGroupLayoutEntry> mipsBglEntries;
|
||||
// Bindings are 0 for source, 1 to numMipsForPipeline for destinations
|
||||
for (uint32_t i = 0; i <= numMipsForPipeline; ++i) {
|
||||
wgpu::BindGroupLayoutEntry entry{};
|
||||
entry.binding = i;
|
||||
entry.visibility = wgpu::ShaderStage::Compute;
|
||||
if (i == 0) {
|
||||
entry.texture.sampleType = (key.scalarType == SPDScalarType::I32)
|
||||
? wgpu::TextureSampleType::Sint
|
||||
: (key.scalarType == SPDScalarType::U32)
|
||||
? wgpu::TextureSampleType::Uint
|
||||
: wgpu::TextureSampleType::UnfilterableFloat;
|
||||
entry.texture.viewDimension = wgpu::TextureViewDimension::e2DArray;
|
||||
} else {
|
||||
entry.storageTexture.access = wgpu::StorageTextureAccess::WriteOnly;
|
||||
entry.storageTexture.format = key.format;
|
||||
entry.storageTexture.viewDimension = wgpu::TextureViewDimension::e2DArray;
|
||||
}
|
||||
mipsBglEntries.push_back(entry);
|
||||
}
|
||||
|
||||
wgpu::BindGroupLayoutDescriptor mipsBglDesc{};
|
||||
mipsBglDesc.entryCount = mipsBglEntries.size();
|
||||
mipsBglDesc.entries = mipsBglEntries.data();
|
||||
spdPipeline.mipsBindGroupLayout = m_device.CreateBindGroupLayout(&mipsBglDesc);
|
||||
|
||||
std::string shaderCode = MakeShaderCode(key.format, GetFilterCode(key.filter),
|
||||
numMipsForPipeline, key.scalarType);
|
||||
wgpu::ShaderModuleWGSLDescriptor wgslDesc{};
|
||||
wgslDesc.code = shaderCode.c_str();
|
||||
|
||||
wgpu::ShaderModuleDescriptor shaderModuleDesc{};
|
||||
shaderModuleDesc.nextInChain = &wgslDesc;
|
||||
wgpu::ShaderModule shaderModule = m_device.CreateShaderModule(&shaderModuleDesc);
|
||||
|
||||
wgpu::BindGroupLayout chosenInternalBGL =
|
||||
(numMipsForPipeline > 6 && m_internalResourcesBindGroupLayout_Advanced)
|
||||
? m_internalResourcesBindGroupLayout_Advanced
|
||||
: m_internalResourcesBindGroupLayout;
|
||||
|
||||
wgpu::BindGroupLayout bgls[] = { spdPipeline.mipsBindGroupLayout, chosenInternalBGL };
|
||||
wgpu::PipelineLayoutDescriptor layoutDesc{};
|
||||
layoutDesc.bindGroupLayoutCount = 2;
|
||||
layoutDesc.bindGroupLayouts = bgls;
|
||||
|
||||
wgpu::ComputePipelineDescriptor pipelineDesc{};
|
||||
pipelineDesc.layout = m_device.CreatePipelineLayout(&layoutDesc);
|
||||
pipelineDesc.compute.module = shaderModule;
|
||||
pipelineDesc.compute.entryPoint = "downsample";
|
||||
|
||||
spdPipeline.pipeline = m_device.CreateComputePipeline(&pipelineDesc);
|
||||
m_pipelines[key] = std::move(spdPipeline);
|
||||
}
|
||||
return m_pipelines[key];
|
||||
}
|
||||
|
||||
|
||||
void MipmapGenerator::Generate(wgpu::CommandEncoder& commandEncoder, wgpu::Texture srcTexture,
|
||||
const SPDPassConfig& config) {
|
||||
if (config.numMips <= 1 || config.numMips <= config.sourceMipLevel + 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_maxMipsPerPass == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
PreparePipelines(srcTexture.GetFormat(), config.filter, config.halfPrecision);
|
||||
|
||||
uint32_t totalSrcArrayLayers = srcTexture.GetDepthOrArrayLayers();
|
||||
|
||||
// Loop over array layer chunks
|
||||
for (uint32_t currentBaseArrayLayer = 0; currentBaseArrayLayer < totalSrcArrayLayers;
|
||||
currentBaseArrayLayer += m_maxArrayLayers) {
|
||||
|
||||
uint32_t arrayLayersThisPass =
|
||||
std::min(m_maxArrayLayers, totalSrcArrayLayers - currentBaseArrayLayer);
|
||||
|
||||
uint32_t mipsToGenerateCount = config.numMips - (config.sourceMipLevel + 1);
|
||||
uint32_t currentSourceMipLevel = config.sourceMipLevel;
|
||||
|
||||
while (mipsToGenerateCount > 0) {
|
||||
uint32_t mipsThisPass = std::min(mipsToGenerateCount, m_maxMipsPerPass);
|
||||
|
||||
SPDPassConfig passSpecificConfig = {};
|
||||
passSpecificConfig.filter = config.filter;
|
||||
passSpecificConfig.halfPrecision = config.halfPrecision;
|
||||
passSpecificConfig.targetTexture =
|
||||
config.targetTexture ? config.targetTexture : srcTexture;
|
||||
passSpecificConfig.numMips = mipsThisPass;
|
||||
passSpecificConfig.sourceMipLevel = currentSourceMipLevel;
|
||||
|
||||
// Pass baseArrayLayer and numArrayLayers directly to generatePass
|
||||
generatePass(commandEncoder, srcTexture, passSpecificConfig, currentBaseArrayLayer,
|
||||
arrayLayersThisPass);
|
||||
|
||||
mipsToGenerateCount -= mipsThisPass;
|
||||
currentSourceMipLevel += mipsThisPass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MipmapGenerator::generatePass(wgpu::CommandEncoder& commandEncoder, wgpu::Texture srcTexture,
|
||||
const SPDPassConfig& passConfig, uint32_t baseArrayLayer, uint32_t numArrayLayers) {
|
||||
uint32_t width = srcTexture.GetWidth() >> passConfig.sourceMipLevel;
|
||||
uint32_t height = srcTexture.GetHeight() >> passConfig.sourceMipLevel;
|
||||
|
||||
uint32_t arrayLayerCountForThisPass = numArrayLayers;
|
||||
|
||||
wgpu::Texture target = passConfig.targetTexture ? passConfig.targetTexture : srcTexture;
|
||||
uint32_t numMipsThisPass = passConfig.numMips;
|
||||
|
||||
if (numMipsThisPass == 0) return;
|
||||
|
||||
SPDScalarType scalarType = SanitizeScalarType(srcTexture.GetFormat(), passConfig.halfPrecision);
|
||||
SPDPipeline& spdPipeline = GetOrCreatePipeline(
|
||||
{ target.GetFormat(), scalarType, passConfig.filter, numMipsThisPass });
|
||||
|
||||
// --- Create Bind Group 0 (Mips) ---
|
||||
std::vector<wgpu::BindGroupEntry> mipEntries;
|
||||
|
||||
wgpu::TextureViewDescriptor srcViewDesc{};
|
||||
srcViewDesc.dimension = wgpu::TextureViewDimension::e2DArray;
|
||||
srcViewDesc.baseMipLevel = passConfig.sourceMipLevel;
|
||||
srcViewDesc.mipLevelCount = 1;
|
||||
srcViewDesc.baseArrayLayer = baseArrayLayer;
|
||||
srcViewDesc.arrayLayerCount = arrayLayerCountForThisPass;
|
||||
|
||||
wgpu::BindGroupEntry srcEntry{};
|
||||
srcEntry.binding = 0;
|
||||
srcEntry.textureView = srcTexture.CreateView(&srcViewDesc);
|
||||
mipEntries.push_back(srcEntry);
|
||||
|
||||
for (uint32_t i = 0; i < numMipsThisPass; ++i) {
|
||||
wgpu::TextureViewDescriptor dstViewDesc{};
|
||||
dstViewDesc.dimension = wgpu::TextureViewDimension::e2DArray;
|
||||
dstViewDesc.baseMipLevel = passConfig.sourceMipLevel + i + 1;
|
||||
dstViewDesc.mipLevelCount = 1;
|
||||
dstViewDesc.baseArrayLayer = baseArrayLayer;
|
||||
dstViewDesc.arrayLayerCount = arrayLayerCountForThisPass;
|
||||
|
||||
wgpu::BindGroupEntry dstEntry{};
|
||||
dstEntry.binding = i + 1;
|
||||
dstEntry.textureView = target.CreateView(&dstViewDesc);
|
||||
mipEntries.push_back(dstEntry);
|
||||
}
|
||||
|
||||
wgpu::BindGroupDescriptor mipBindGroupDesc{};
|
||||
mipBindGroupDesc.layout = spdPipeline.mipsBindGroupLayout;
|
||||
mipBindGroupDesc.entryCount = mipEntries.size();
|
||||
mipBindGroupDesc.entries = mipEntries.data();
|
||||
wgpu::BindGroup mipsBindGroup = m_device.CreateBindGroup(&mipBindGroupDesc);
|
||||
|
||||
// --- Create Bind Group 1 (Internal Resources) ---
|
||||
uint32_t widthDiv64 = (width + 63) / 64;
|
||||
uint32_t heightDiv64 = (height + 63) / 64;
|
||||
uint32_t numWorkGroups = widthDiv64 * heightDiv64;
|
||||
|
||||
struct DownsamplePassMeta {
|
||||
uint32_t work_group_offset[2] = { 0, 0 };
|
||||
uint32_t num_work_groups;
|
||||
uint32_t mips;
|
||||
uint32_t padding[12];
|
||||
} meta;
|
||||
meta.num_work_groups = numWorkGroups;
|
||||
meta.mips = numMipsThisPass;
|
||||
|
||||
wgpu::BufferDescriptor metaBufferDesc{};
|
||||
metaBufferDesc.size = sizeof(DownsamplePassMeta);
|
||||
metaBufferDesc.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst;
|
||||
wgpu::Buffer metaBuffer = m_device.CreateBuffer(&metaBufferDesc);
|
||||
m_device.GetQueue().WriteBuffer(metaBuffer, 0, &meta, sizeof(meta));
|
||||
|
||||
wgpu::BindGroup internalBindGroup;
|
||||
wgpu::BindGroupLayout chosenInternalBGL =
|
||||
(numMipsThisPass > 6 && m_internalResourcesBindGroupLayout_Advanced)
|
||||
? m_internalResourcesBindGroupLayout_Advanced
|
||||
: m_internalResourcesBindGroupLayout;
|
||||
|
||||
if (numMipsThisPass > 6 && m_internalResourcesBindGroupLayout_Advanced) {
|
||||
wgpu::BufferDescriptor atomicCounterBufferDesc{};
|
||||
atomicCounterBufferDesc.size = sizeof(uint32_t) * arrayLayerCountForThisPass;
|
||||
atomicCounterBufferDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst;
|
||||
wgpu::Buffer atomicCounterBuffer = m_device.CreateBuffer(&atomicCounterBufferDesc);
|
||||
|
||||
commandEncoder.ClearBuffer(atomicCounterBuffer, 0, WGPU_WHOLE_SIZE);
|
||||
|
||||
wgpu::BufferDescriptor midMipBufferDesc{};
|
||||
midMipBufferDesc.size = 64 * 64 * 16 * arrayLayerCountForThisPass;
|
||||
midMipBufferDesc.usage = wgpu::BufferUsage::Storage;
|
||||
wgpu::Buffer midMipBuffer = m_device.CreateBuffer(&midMipBufferDesc);
|
||||
|
||||
std::vector<wgpu::BindGroupEntry> internalEntries(3);
|
||||
internalEntries[0].binding = 0;
|
||||
internalEntries[0].buffer = metaBuffer;
|
||||
internalEntries[0].size = sizeof(DownsamplePassMeta);
|
||||
|
||||
internalEntries[1].binding = 1;
|
||||
internalEntries[1].buffer = atomicCounterBuffer;
|
||||
internalEntries[1].size = atomicCounterBuffer.GetSize();
|
||||
|
||||
internalEntries[2].binding = 2;
|
||||
internalEntries[2].buffer = midMipBuffer;
|
||||
internalEntries[2].size = midMipBuffer.GetSize();
|
||||
|
||||
wgpu::BindGroupDescriptor internalBindGroupDesc{};
|
||||
internalBindGroupDesc.layout = chosenInternalBGL;
|
||||
internalBindGroupDesc.entryCount = static_cast<uint32_t>(internalEntries.size());
|
||||
internalBindGroupDesc.entries = internalEntries.data();
|
||||
internalBindGroup = m_device.CreateBindGroup(&internalBindGroupDesc);
|
||||
} else {
|
||||
wgpu::BindGroupEntry metaEntry{};
|
||||
metaEntry.binding = 0;
|
||||
metaEntry.buffer = metaBuffer;
|
||||
metaEntry.size = sizeof(DownsamplePassMeta);
|
||||
|
||||
wgpu::BindGroupDescriptor internalBindGroupDesc{};
|
||||
internalBindGroupDesc.layout = chosenInternalBGL;
|
||||
internalBindGroupDesc.entryCount = 1;
|
||||
internalBindGroupDesc.entries = &metaEntry;
|
||||
internalBindGroup = m_device.CreateBindGroup(&internalBindGroupDesc);
|
||||
}
|
||||
|
||||
// --- Dispatch ---
|
||||
wgpu::ComputePassEncoder pass = commandEncoder.BeginComputePass();
|
||||
pass.SetPipeline(spdPipeline.pipeline);
|
||||
pass.SetBindGroup(0, mipsBindGroup);
|
||||
pass.SetBindGroup(1, internalBindGroup);
|
||||
pass.DispatchWorkgroups(widthDiv64, heightDiv64, arrayLayerCountForThisPass);
|
||||
pass.End();
|
||||
}
|
||||
// Main shader generation logic
|
||||
|
||||
// Helper function to check if a string is in a vector of strings
|
||||
bool includes(const std::vector<std::string>& vec, const std::string& str) {
|
||||
for (const auto& s: vec) {
|
||||
if (s == str) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string MakeShaderCode(wgpu::TextureFormat outputFormat, const std::string& filterOp,
|
||||
unsigned int numMips, // Assuming a default value for numMips
|
||||
SPDScalarType scalarType) { // Default scalarType
|
||||
std::stringstream ss;
|
||||
|
||||
std::string texelType;
|
||||
if (scalarType == SPDScalarType::I32) {
|
||||
texelType = "i32";
|
||||
} else if (scalarType == SPDScalarType::U32) {
|
||||
texelType = "u32";
|
||||
} else {
|
||||
texelType = "f32";
|
||||
}
|
||||
|
||||
bool useF16 = (scalarType == SPDScalarType::F16);
|
||||
|
||||
std::string filterCode = filterOp;
|
||||
if (filterOp == SPD_FILTER_AVERAGE && !includes({ "f32", "f16" }, texelType)) {
|
||||
// Replace "* 0.25" with "/ 4"
|
||||
size_t pos = filterCode.find("* 0.25");
|
||||
if (pos != std::string::npos) {
|
||||
filterCode.replace(pos, std::string("* 0.25").length(), "/ 4");
|
||||
}
|
||||
}
|
||||
|
||||
// Generate mipsBindings
|
||||
std::string mipsBindings;
|
||||
for (unsigned int i = 0; i < numMips; ++i) {
|
||||
mipsBindings += "@group(0) @binding(" + std::to_string(i + 1) + ") var dst_mip_" +
|
||||
std::to_string(i + 1) + ": texture_storage_2d_array<" +
|
||||
to_string(outputFormat) + ", write>;\n";
|
||||
}
|
||||
|
||||
// Generate mipsAccessorBody
|
||||
std::string mipsAccessorBody;
|
||||
for (unsigned int i = 0; i < numMips; ++i) {
|
||||
if (i == 5 && numMips > 6) {
|
||||
mipsAccessorBody += " else if mip == 6 {\n";
|
||||
mipsAccessorBody += " textureStore(dst_mip_6, uv, slice, " +
|
||||
(useF16 ? "vec4<" + texelType + ">(value)" : "value") + ");\n";
|
||||
mipsAccessorBody += " mip_dst_6_buffer[slice][uv.y][uv.x] = value;\n";
|
||||
mipsAccessorBody += " }";
|
||||
} else {
|
||||
if (i != 0) {
|
||||
mipsAccessorBody += " else ";
|
||||
}
|
||||
mipsAccessorBody += "if mip == " + std::to_string(i + 1) + " {\n";
|
||||
mipsAccessorBody += " textureStore(dst_mip_" + std::to_string(i + 1) +
|
||||
", uv, slice, " +
|
||||
(useF16 ? "vec4<" + texelType + ">(value)" : "value") + ");\n";
|
||||
mipsAccessorBody += " }";
|
||||
}
|
||||
}
|
||||
|
||||
std::string mipsAccessor =
|
||||
"fn store_dst_mip(value: vec4<SPDScalar>, uv: vec2<u32>, slice: u32, mip: u32) {\n" +
|
||||
mipsAccessorBody + "\n}";
|
||||
std::string midMipAccessor = "return mip_dst_6_buffer[slice][uv.y][uv.x];";
|
||||
|
||||
// Start building the final shader code string
|
||||
ss << R"(
|
||||
// This file is part of the FidelityFX SDK.
|
||||
//
|
||||
// Copyright (C) 2023 Advanced Micro Devices, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files(the “Software”), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions :
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
// AUTHORS COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
|
||||
// Definitions --------------------------------------------------------------------------------------------------------
|
||||
|
||||
)";
|
||||
if (useF16) {
|
||||
ss << "enable f16;\n";
|
||||
}
|
||||
ss << "alias SPDScalar = " << texelType
|
||||
<< ";\n\n"; // Using texelType here, assuming SPDScalar maps to it.
|
||||
|
||||
ss << R"(
|
||||
// Helpers ------------------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A helper function performing a remap 64x1 to 8x8 remapping which is necessary for 2D wave reductions.
|
||||
* * The 64-wide lane indices to 8x8 remapping is performed as follows:
|
||||
* 00 01 08 09 10 11 18 19
|
||||
* 02 03 0a 0b 12 13 1a 1b
|
||||
* 04 05 0c 0d 14 15 1c 1d
|
||||
* 06 07 0e 0f 16 17 1e 1f
|
||||
* 20 21 28 29 30 31 38 39
|
||||
* 22 23 2a 2b 32 33 3a 3b
|
||||
* 24 25 2c 2d 34 35 3c 3d
|
||||
* 26 27 2e 2f 36 37 3e 3f
|
||||
* * @param a: The input 1D coordinate to remap.
|
||||
*
|
||||
* @returns The remapped 2D coordinates.
|
||||
*/
|
||||
fn remap_for_wave_reduction(a: u32) -> vec2<u32> {
|
||||
return vec2<u32>(
|
||||
insertBits(extractBits(a, 2u, 3u), a, 0u, 1u),
|
||||
insertBits(extractBits(a, 3u, 3u), extractBits(a, 1u, 2u), 0u, 2u)
|
||||
);
|
||||
}
|
||||
|
||||
fn map_to_xy(local_invocation_index: u32) -> vec2<u32> {
|
||||
let sub_xy: vec2<u32> = remap_for_wave_reduction(local_invocation_index % 64);
|
||||
return vec2<u32>(
|
||||
sub_xy.x + 8 * ((local_invocation_index >> 6) % 2),
|
||||
sub_xy.y + 8 * ((local_invocation_index >> 7))
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute a linear value from a SRGB value.
|
||||
* * @param value: The value to convert to linear from SRGB.
|
||||
* * @returns A value in SRGB space.
|
||||
*/
|
||||
/*
|
||||
fn srgb_to_linear(value: SPDScalar) -> SPDScalar {
|
||||
let j = vec3<SPDScalar>(0.0031308 * 12.92, 12.92, 1.0 / 2.4);
|
||||
let k = vec2<SPDScalar>(1.055, -0.055);
|
||||
return clamp(j.x, value * j.y, pow(value, j.z) * k.x + k.y);
|
||||
}
|
||||
*/
|
||||
|
||||
// Resources & Accessors -----------------------------------------------------------------------------------------------
|
||||
struct DownsamplePassMeta {
|
||||
work_group_offset: vec2<u32>,
|
||||
num_work_groups: u32,
|
||||
mips: u32,
|
||||
}
|
||||
|
||||
// In the original version dst_mip_i is an image2Darray [SPD_MAX_MIP_LEVELS+1], i.e., 12+1, but WGSL doesn't support arrays of textures yet
|
||||
// Also these are read_write because for mips 7-13, the workgroup reads from mip level 6 - since most formats don't support read_write access in WGSL yet, we use a single read_write buffer in such cases instead
|
||||
@group(0) @binding(0) var src_mip_0: texture_2d_array<)"
|
||||
<< texelType << R"(>;
|
||||
)" << mipsBindings
|
||||
<< R"(
|
||||
|
||||
@group(1) @binding(0) var<uniform> downsample_pass_meta : DownsamplePassMeta;
|
||||
@group(1) @binding(1) var<storage, read_write> spd_global_counter: array<atomic<u32>>;
|
||||
@group(1) @binding(2) var<storage, read_write> mip_dst_6_buffer: array<array<array<vec4<f32>, 64>, 64>>;
|
||||
|
||||
fn get_mips() -> u32 {
|
||||
return downsample_pass_meta.mips;
|
||||
}
|
||||
|
||||
fn get_num_work_groups() -> u32 {
|
||||
return downsample_pass_meta.num_work_groups;
|
||||
}
|
||||
|
||||
fn get_work_group_offset() -> vec2<u32> {
|
||||
return downsample_pass_meta.work_group_offset;
|
||||
}
|
||||
|
||||
fn load_src_image(uv: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
|
||||
return vec4<SPDScalar>(textureLoad(src_mip_0, uv, slice, 0));
|
||||
}
|
||||
|
||||
fn load_mid_mip_image(uv: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
|
||||
)";
|
||||
if (numMips > 6) {
|
||||
ss << midMipAccessor;
|
||||
} else {
|
||||
ss << "return vec4<SPDScalar>();";
|
||||
}
|
||||
ss << R"(
|
||||
}
|
||||
|
||||
)" << mipsAccessor
|
||||
<< R"(
|
||||
|
||||
// Workgroup -----------------------------------------------------------------------------------------------------------
|
||||
|
||||
var<workgroup> spd_intermediate: array<array<vec4<SPDScalar>, 16>, 16>;
|
||||
var<workgroup> spd_counter: atomic<u32>;
|
||||
|
||||
fn spd_increase_atomic_counter(slice: u32) {
|
||||
atomicStore(&spd_counter, atomicAdd(&spd_global_counter[slice], 1));
|
||||
}
|
||||
|
||||
fn spd_get_atomic_counter() -> u32 {
|
||||
return atomicLoad(&spd_counter);
|
||||
}
|
||||
|
||||
fn spd_reset_atomic_counter(slice: u32) {
|
||||
atomicStore(&spd_global_counter[slice], 0);
|
||||
}
|
||||
|
||||
// Cotnrol flow --------------------------------------------------------------------------------------------------------
|
||||
|
||||
fn spd_barrier() {
|
||||
// in glsl this does: groupMemoryBarrier(); barrier();
|
||||
workgroupBarrier();
|
||||
}
|
||||
|
||||
// Only last active workgroup should proceed
|
||||
fn spd_exit_workgroup(num_work_groups: u32, local_invocation_index: u32, slice: u32) -> bool {
|
||||
// global atomic counter
|
||||
if (local_invocation_index == 0) {
|
||||
spd_increase_atomic_counter(slice);
|
||||
}
|
||||
spd_barrier();
|
||||
return spd_get_atomic_counter() != (num_work_groups - 1);
|
||||
}
|
||||
|
||||
// Pixel access --------------------------------------------------------------------------------------------------------
|
||||
|
||||
)" << filterCode
|
||||
<< R"(
|
||||
|
||||
fn spd_store(pix: vec2<u32>, out_value: vec4<SPDScalar>, mip: u32, slice: u32) {
|
||||
store_dst_mip(out_value, pix, slice, mip + 1);
|
||||
}
|
||||
|
||||
fn spd_load_intermediate(x: u32, y: u32) -> vec4<SPDScalar> {
|
||||
return spd_intermediate[x][y];
|
||||
}
|
||||
|
||||
fn spd_store_intermediate(x: u32, y: u32, value: vec4<SPDScalar>) {
|
||||
spd_intermediate[x][y] = value;
|
||||
}
|
||||
|
||||
fn spd_reduce_intermediate(i0: vec2<u32>, i1: vec2<u32>, i2: vec2<u32>, i3: vec2<u32>) -> vec4<SPDScalar> {
|
||||
let v0 = spd_load_intermediate(i0.x, i0.y);
|
||||
let v1 = spd_load_intermediate(i1.x, i1.y);
|
||||
let v2 = spd_load_intermediate(i2.x, i2.y);
|
||||
let v3 = spd_load_intermediate(i3.x, i3.y);
|
||||
return spd_reduce_4(v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
fn spd_reduce_load_4(base: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
|
||||
let v0 = load_src_image(base + vec2<u32>(0, 0), slice);
|
||||
let v1 = load_src_image(base + vec2<u32>(0, 1), slice);
|
||||
let v2 = load_src_image(base + vec2<u32>(1, 0), slice);
|
||||
let v3 = load_src_image(base + vec2<u32>(1, 1), slice);
|
||||
return spd_reduce_4(v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
fn spd_reduce_load_mid_mip_4(base: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
|
||||
let v0 = load_mid_mip_image(base + vec2<u32>(0, 0), slice);
|
||||
let v1 = load_mid_mip_image(base + vec2<u32>(0, 1), slice);
|
||||
let v2 = load_mid_mip_image(base + vec2<u32>(1, 0), slice);
|
||||
let v3 = load_mid_mip_image(base + vec2<u32>(1, 1), slice);
|
||||
return spd_reduce_4(v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
// Main logic ---------------------------------------------------------------------------------------------------------
|
||||
|
||||
fn spd_downsample_mips_0_1(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
|
||||
var v: array<vec4<SPDScalar>, 4>;
|
||||
|
||||
let workgroup64 = workgroup_id.xy * 64;
|
||||
let workgroup32 = workgroup_id.xy * 32;
|
||||
let workgroup16 = workgroup_id.xy * 16;
|
||||
|
||||
var tex = workgroup64 + vec2<u32>(x * 2, y * 2);
|
||||
var pix = workgroup32 + vec2<u32>(x, y);
|
||||
v[0] = spd_reduce_load_4(tex, slice);
|
||||
spd_store(pix, v[0], 0, slice);
|
||||
|
||||
tex = workgroup64 + vec2<u32>(x * 2 + 32, y * 2);
|
||||
pix = workgroup32 + vec2<u32>(x + 16, y);
|
||||
v[1] = spd_reduce_load_4(tex, slice);
|
||||
spd_store(pix, v[1], 0, slice);
|
||||
|
||||
tex = workgroup64 + vec2<u32>(x * 2, y * 2 + 32);
|
||||
pix = workgroup32 + vec2<u32>(x, y + 16);
|
||||
v[2] = spd_reduce_load_4(tex, slice);
|
||||
spd_store(pix, v[2], 0, slice);
|
||||
|
||||
tex = workgroup64 + vec2<u32>(x * 2 + 32, y * 2 + 32);
|
||||
pix = workgroup32 + vec2<u32>(x + 16, y + 16);
|
||||
v[3] = spd_reduce_load_4(tex, slice);
|
||||
spd_store(pix, v[3], 0, slice);
|
||||
|
||||
if mip <= 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0u; i < 4u; i++) {
|
||||
spd_store_intermediate(x, y, v[i]);
|
||||
spd_barrier();
|
||||
if local_invocation_index < 64 {
|
||||
v[i] = spd_reduce_intermediate(
|
||||
vec2<u32>(x * 2 + 0, y * 2 + 0),
|
||||
vec2<u32>(x * 2 + 1, y * 2 + 0),
|
||||
vec2<u32>(x * 2 + 0, y * 2 + 1),
|
||||
vec2<u32>(x * 2 + 1, y * 2 + 1)
|
||||
);
|
||||
spd_store(workgroup16 + vec2<u32>(x + (i % 2) * 8, y + (i / 2) * 8), v[i], 1, slice);
|
||||
}
|
||||
spd_barrier();
|
||||
}
|
||||
|
||||
if local_invocation_index < 64 {
|
||||
spd_store_intermediate(x + 0, y + 0, v[0]);
|
||||
spd_store_intermediate(x + 8, y + 0, v[1]);
|
||||
spd_store_intermediate(x + 0, y + 8, v[2]);
|
||||
spd_store_intermediate(x + 8, y + 8, v[3]);
|
||||
}
|
||||
}
|
||||
|
||||
fn spd_downsample_mip_2(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
|
||||
if local_invocation_index < 64u {
|
||||
let v = spd_reduce_intermediate(
|
||||
vec2<u32>(x * 2 + 0, y * 2 + 0),
|
||||
vec2<u32>(x * 2 + 1, y * 2 + 0),
|
||||
vec2<u32>(x * 2 + 0, y * 2 + 1),
|
||||
vec2<u32>(x * 2 + 1, y * 2 + 1)
|
||||
);
|
||||
spd_store(workgroup_id.xy * 8 + vec2<u32>(x, y), v, mip, slice);
|
||||
// store to LDS, try to reduce bank conflicts
|
||||
// x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
|
||||
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
// 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 x
|
||||
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
// x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
|
||||
// ...
|
||||
// x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
|
||||
spd_store_intermediate(x * 2 + y % 2, y * 2, v);
|
||||
}
|
||||
}
|
||||
|
||||
fn spd_downsample_mip_3(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
|
||||
if local_invocation_index < 16u {
|
||||
// x 0 x 0
|
||||
// 0 0 0 0
|
||||
// 0 x 0 x
|
||||
// 0 0 0 0
|
||||
let v = spd_reduce_intermediate(
|
||||
vec2<u32>(x * 4 + 0 + 0, y * 4 + 0),
|
||||
vec2<u32>(x * 4 + 2 + 0, y * 4 + 0),
|
||||
vec2<u32>(x * 4 + 0 + 1, y * 4 + 2),
|
||||
vec2<u32>(x * 4 + 2 + 1, y * 4 + 2)
|
||||
);
|
||||
spd_store(workgroup_id.xy * 4 + vec2<u32>(x, y), v, mip, slice);
|
||||
// store to LDS
|
||||
// x 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0
|
||||
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
// 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0 0
|
||||
// ...
|
||||
// 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0
|
||||
// ...
|
||||
// 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x
|
||||
// ...
|
||||
spd_store_intermediate(x * 4 + y, y * 4, v);
|
||||
}
|
||||
}
|
||||
|
||||
fn spd_downsample_mip_4(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
|
||||
if local_invocation_index < 4u {
|
||||
// x 0 0 0 x 0 0 0
|
||||
// ...
|
||||
// 0 x 0 0 0 x 0 0
|
||||
let v = spd_reduce_intermediate(
|
||||
vec2<u32>(x * 8 + 0 + 0 + y * 2, y * 8 + 0),
|
||||
vec2<u32>(x * 8 + 4 + 0 + y * 2, y * 8 + 0),
|
||||
vec2<u32>(x * 8 + 0 + 1 + y * 2, y * 8 + 4),
|
||||
vec2<u32>(x * 8 + 4 + 1 + y * 2, y * 8 + 4)
|
||||
);
|
||||
spd_store(workgroup_id.xy * 2 + vec2<u32>(x, y), v, mip, slice);
|
||||
// store to LDS
|
||||
// x x x x 0 ...
|
||||
// 0 ...
|
||||
spd_store_intermediate(x + y * 2, 0, v);
|
||||
}
|
||||
}
|
||||
|
||||
fn spd_downsample_mip_5(workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
|
||||
if local_invocation_index < 1u {
|
||||
// x x x x 0 ...
|
||||
// 0 ...
|
||||
let v = spd_reduce_intermediate(vec2<u32>(0, 0), vec2<u32>(1, 0), vec2<u32>(2, 0), vec2<u32>(3, 0));
|
||||
spd_store(workgroup_id.xy, v, mip, slice);
|
||||
}
|
||||
}
|
||||
|
||||
fn spd_downsample_next_four(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, base_mip: u32, mips: u32, slice: u32) {
|
||||
if mips <= base_mip {
|
||||
return;
|
||||
}
|
||||
spd_barrier();
|
||||
spd_downsample_mip_2(x, y, workgroup_id, local_invocation_index, base_mip, slice);
|
||||
|
||||
if mips <= base_mip + 1 {
|
||||
return;
|
||||
}
|
||||
spd_barrier();
|
||||
spd_downsample_mip_3(x, y, workgroup_id, local_invocation_index, base_mip + 1, slice);
|
||||
|
||||
if mips <= base_mip + 2 {
|
||||
return;
|
||||
}
|
||||
spd_barrier();
|
||||
spd_downsample_mip_4(x, y, workgroup_id, local_invocation_index, base_mip + 2, slice);
|
||||
|
||||
if mips <= base_mip + 3 {
|
||||
return;
|
||||
}
|
||||
spd_barrier();
|
||||
spd_downsample_mip_5(workgroup_id, local_invocation_index, base_mip + 3, slice);
|
||||
}
|
||||
|
||||
fn spd_downsample_last_four(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, base_mip: u32, mips: u32, slice: u32, exit: bool) {
|
||||
if mips <= base_mip {
|
||||
return;
|
||||
}
|
||||
spd_barrier();
|
||||
if !exit {
|
||||
spd_downsample_mip_2(x, y, workgroup_id, local_invocation_index, base_mip, slice);
|
||||
}
|
||||
|
||||
if mips <= base_mip + 1 {
|
||||
return;
|
||||
}
|
||||
spd_barrier();
|
||||
if !exit {
|
||||
spd_downsample_mip_3(x, y, workgroup_id, local_invocation_index, base_mip + 1, slice);
|
||||
}
|
||||
|
||||
if mips <= base_mip + 2 {
|
||||
return;
|
||||
}
|
||||
spd_barrier();
|
||||
if !exit {
|
||||
spd_downsample_mip_4(x, y, workgroup_id, local_invocation_index, base_mip + 2, slice);
|
||||
}
|
||||
|
||||
if mips <= base_mip + 3 {
|
||||
return;
|
||||
}
|
||||
spd_barrier();
|
||||
if !exit {
|
||||
spd_downsample_mip_5(workgroup_id, local_invocation_index, base_mip + 3, slice);
|
||||
}
|
||||
}
|
||||
|
||||
fn spd_downsample_mips_6_7(x: u32, y: u32, mips: u32, slice: u32) {
|
||||
var tex = vec2<u32>(x * 4 + 0, y * 4 + 0);
|
||||
var pix = vec2<u32>(x * 2 + 0, y * 2 + 0);
|
||||
let v0 = spd_reduce_load_mid_mip_4(tex, slice);
|
||||
spd_store(pix, v0, 6, slice);
|
||||
|
||||
tex = vec2<u32>(x * 4 + 2, y * 4 + 0);
|
||||
pix = vec2<u32>(x * 2 + 1, y * 2 + 0);
|
||||
let v1 = spd_reduce_load_mid_mip_4(tex, slice);
|
||||
spd_store(pix, v1, 6, slice);
|
||||
|
||||
tex = vec2<u32>(x * 4 + 0, y * 4 + 2);
|
||||
pix = vec2<u32>(x * 2 + 0, y * 2 + 1);
|
||||
let v2 = spd_reduce_load_mid_mip_4(tex, slice);
|
||||
spd_store(pix, v2, 6, slice);
|
||||
|
||||
tex = vec2<u32>(x * 4 + 2, y * 4 + 2);
|
||||
pix = vec2<u32>(x * 2 + 1, y * 2 + 1);
|
||||
let v3 = spd_reduce_load_mid_mip_4(tex, slice);
|
||||
spd_store(pix, v3, 6, slice);
|
||||
|
||||
if mips <= 7 {
|
||||
return;
|
||||
}
|
||||
// no barrier needed, working on values only from the same thread
|
||||
|
||||
let v = spd_reduce_4(v0, v1, v2, v3);
|
||||
spd_store(vec2<u32>(x, y), v, 7, slice);
|
||||
spd_store_intermediate(x, y, v);
|
||||
}
|
||||
|
||||
fn spd_downsample_last_6(x: u32, y: u32, local_invocation_index: u32, mips: u32, num_work_groups: u32, slice: u32) {
|
||||
if mips <= 6 {
|
||||
return;
|
||||
}
|
||||
|
||||
// increase the global atomic counter for the given slice and check if it's the last remaining thread group:
|
||||
// terminate if not, continue if yes.
|
||||
let exit = spd_exit_workgroup(num_work_groups, local_invocation_index, slice);
|
||||
|
||||
// can't exit directly because subsequent barrier calls break uniform control flow...
|
||||
if !exit {
|
||||
// reset the global atomic counter back to 0 for the next spd dispatch
|
||||
spd_reset_atomic_counter(slice);
|
||||
|
||||
// After mip 5 there is only a single workgroup left that downsamples the remaining up to 64x64 texels.
|
||||
// compute MIP level 6 and 7
|
||||
spd_downsample_mips_6_7(x, y, mips, slice);
|
||||
}
|
||||
|
||||
// compute MIP level 8, 9, 10, 11
|
||||
spd_downsample_last_four(x, y, vec2<u32>(0, 0), local_invocation_index, 8, mips, slice, exit);
|
||||
}
|
||||
|
||||
/// Downsamples a 64x64 tile based on the work group id.
|
||||
/// If after downsampling it's the last active thread group, computes the remaining MIP levels.
|
||||
///
|
||||
/// @param [in] workGroupID index of the work group / thread group
|
||||
/// @param [in] localInvocationIndex index of the thread within the thread group in 1D
|
||||
/// @param [in] mips the number of total MIP levels to compute for the input texture
|
||||
/// @param [in] numWorkGroups the total number of dispatched work groups / thread groups for this slice
|
||||
/// @param [in] slice the slice of the input texture
|
||||
fn spd_downsample(workgroup_id: vec2<u32>, local_invocation_index: u32, mips: u32, num_work_groups: u32, slice: u32) {
|
||||
let xy = map_to_xy(local_invocation_index);
|
||||
spd_downsample_mips_0_1(xy.x, xy.y, workgroup_id, local_invocation_index, mips, slice);
|
||||
spd_downsample_next_four(xy.x, xy.y, workgroup_id, local_invocation_index, 2, mips, slice);
|
||||
)";
|
||||
if (numMips > 6) {
|
||||
ss << " spd_downsample_last_6(xy.x, xy.y, local_invocation_index, mips, "
|
||||
"num_work_groups, slice);\n";
|
||||
}
|
||||
ss << R"(}
|
||||
|
||||
// Entry points -------------------------------------------------------------------------------------------------------
|
||||
|
||||
@compute
|
||||
@workgroup_size(256, 1, 1)
|
||||
fn downsample(@builtin(local_invocation_index) local_invocation_index: u32, @builtin(workgroup_id) workgroup_id: vec3<u32>) {
|
||||
spd_downsample(
|
||||
workgroup_id.xy + get_work_group_offset(),
|
||||
local_invocation_index,
|
||||
get_mips(),
|
||||
get_num_work_groups(),
|
||||
workgroup_id.z
|
||||
);
|
||||
}
|
||||
)";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
|
||||
} // namespace spd
|
||||
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tsl/robin_map.h>
|
||||
#include <vector>
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
// C++ port of https://github.com/JolifantoBambla/webgpu-spd for early experiments
|
||||
namespace spd {
|
||||
|
||||
// Enum for selecting the downsampling filter.
|
||||
enum class SPDFilter { Average, Min, Max, MinMax };
|
||||
|
||||
// Enum for shader scalar types.
|
||||
enum class SPDScalarType { F32, F16, I32, U32 };
|
||||
|
||||
// Configuration for a single mipmap generation pass.
|
||||
struct SPDPassConfig {
|
||||
SPDFilter filter = SPDFilter::Average;
|
||||
wgpu::Texture targetTexture = nullptr;
|
||||
uint32_t numMips = 0; // For the public API, this is total desired mips. Internally, mips for
|
||||
// the current pass.
|
||||
bool halfPrecision = false;
|
||||
uint32_t sourceMipLevel = 0;
|
||||
};
|
||||
|
||||
// Holds a pipeline and its corresponding bind group layout.
|
||||
struct SPDPipeline {
|
||||
wgpu::BindGroupLayout mipsBindGroupLayout = nullptr;
|
||||
wgpu::ComputePipeline pipeline = nullptr;
|
||||
};
|
||||
|
||||
// Manages pipeline creation, caching, and execution for mipmap generation.
|
||||
class MipmapGenerator {
|
||||
public:
|
||||
MipmapGenerator(const wgpu::Device& device);
|
||||
|
||||
// Pre-creates pipelines for specified formats and filters.
|
||||
void PreparePipelines(wgpu::TextureFormat format, SPDFilter filter, bool halfPrecision = false);
|
||||
|
||||
// Generates a compute pass for creating mipmaps.
|
||||
void Generate(wgpu::CommandEncoder& commandEncoder, wgpu::Texture srcTexture,
|
||||
const SPDPassConfig& config);
|
||||
|
||||
private:
|
||||
void generatePass(wgpu::CommandEncoder& commandEncoder, wgpu::Texture srcTexture,
|
||||
const SPDPassConfig& passConfig, uint32_t baseArrayLayer,
|
||||
uint32_t numArrayLayers); // Internal pass config might differ slightly if needed
|
||||
|
||||
wgpu::Device m_device;
|
||||
wgpu::BindGroupLayout m_internalResourcesBindGroupLayout;
|
||||
wgpu::BindGroupLayout m_internalResourcesBindGroupLayout_Advanced = nullptr;
|
||||
|
||||
// Maximum number of mips that can be generated in a single pass.
|
||||
uint32_t m_maxMipsPerPass;
|
||||
uint32_t m_maxArrayLayers;
|
||||
|
||||
// Key for the pipeline cache.
|
||||
struct PipelineCacheKey {
|
||||
wgpu::TextureFormat format;
|
||||
SPDScalarType scalarType;
|
||||
SPDFilter filter;
|
||||
uint32_t numMips;
|
||||
|
||||
bool operator==(const PipelineCacheKey& other) const {
|
||||
return format == other.format && scalarType == other.scalarType &&
|
||||
filter == other.filter && numMips == other.numMips;
|
||||
}
|
||||
};
|
||||
|
||||
// Hash function for PipelineCacheKey.
|
||||
struct PipelineCacheKeyHash {
|
||||
std::size_t operator()(const PipelineCacheKey& key) const {
|
||||
std::size_t h1 = std::hash<wgpu::TextureFormat>()(key.format);
|
||||
std::size_t h2 = std::hash<SPDScalarType>()(key.scalarType);
|
||||
std::size_t h3 = std::hash<SPDFilter>()(key.filter);
|
||||
std::size_t h4 = std::hash<uint32_t>()(key.numMips);
|
||||
// A simple way to combine hashes.
|
||||
return h1 ^ (h2 << 1) ^ (h3 << 2) ^ (h4 << 3);
|
||||
}
|
||||
};
|
||||
|
||||
// Cached pipelines.
|
||||
tsl::robin_map<PipelineCacheKey, SPDPipeline, PipelineCacheKeyHash> m_pipelines;
|
||||
|
||||
// Helper methods
|
||||
SPDPipeline& GetOrCreatePipeline(const PipelineCacheKey& key);
|
||||
SPDScalarType SanitizeScalarType(wgpu::TextureFormat format, bool halfPrecision);
|
||||
std::string GetFilterCode(SPDFilter filter);
|
||||
};
|
||||
// Assuming SPD_FILTER_AVERAGE is a string constant
|
||||
const std::string SPD_FILTER_AVERAGE = "value * 0.25"; // Original filter operation
|
||||
|
||||
// Generates the WGSL shader code dynamically.
|
||||
std::string MakeShaderCode(wgpu::TextureFormat outputFormat,
|
||||
const std::string& filterOp = SPD_FILTER_AVERAGE, uint32_t numMips = 0,
|
||||
SPDScalarType scalarType = SPDScalarType::F32);
|
||||
|
||||
} // namespace spd
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Lukas Herzberger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -117,10 +117,10 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
|
||||
samplerEntryInfo.binding = samplerEntry.binding;
|
||||
samplerEntry.visibility = wEntry.visibility;
|
||||
wEntry.texture.multisampled = isMultiSampledTypeDescriptor(fEntry.type);
|
||||
// TODO: Set once we have the filtering values
|
||||
if (isDepthDescriptor(fEntry.type)) {
|
||||
samplerEntry.sampler.type = wgpu::SamplerBindingType::Comparison;
|
||||
} else if (isIntDescriptor(fEntry.type)) {
|
||||
} else if (isIntDescriptor(fEntry.type) ||
|
||||
any(fEntry.flags & DescriptorFlags::UNFILTERABLE)) {
|
||||
samplerEntry.sampler.type = wgpu::SamplerBindingType::NonFiltering;
|
||||
} else {
|
||||
samplerEntry.sampler.type = wgpu::SamplerBindingType::Filtering;
|
||||
@@ -151,8 +151,11 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
|
||||
if (isDepthDescriptor(fEntry.type)) {
|
||||
wEntry.texture.sampleType = wgpu::TextureSampleType::Depth;
|
||||
} else if (isFloatDescriptor(fEntry.type)) {
|
||||
// TODO: Set once we have the filtering values
|
||||
wEntry.texture.sampleType = wgpu::TextureSampleType::Float;
|
||||
if (any(fEntry.flags & DescriptorFlags::UNFILTERABLE)) {
|
||||
wEntry.texture.sampleType = wgpu::TextureSampleType::UnfilterableFloat;
|
||||
} else {
|
||||
wEntry.texture.sampleType = wgpu::TextureSampleType::Float;
|
||||
}
|
||||
} else if (isIntDescriptor(fEntry.type)) {
|
||||
wEntry.texture.sampleType = wgpu::TextureSampleType::Sint;
|
||||
} else if (isUnsignedIntDescriptor(fEntry.type)) {
|
||||
|
||||
@@ -18,11 +18,13 @@
|
||||
#include "WebGPUBufferObject.h"
|
||||
#include "WebGPUDescriptorSet.h"
|
||||
#include "WebGPUDescriptorSetLayout.h"
|
||||
#include "WebGPUFence.h"
|
||||
#include "WebGPUIndexBuffer.h"
|
||||
#include "WebGPUPipelineCreation.h"
|
||||
#include "WebGPUProgram.h"
|
||||
#include "WebGPURenderPrimitive.h"
|
||||
#include "WebGPURenderTarget.h"
|
||||
#include "WebGPUStrings.h"
|
||||
#include "WebGPUSwapChain.h"
|
||||
#include "WebGPUTexture.h"
|
||||
#include "WebGPUVertexBuffer.h"
|
||||
@@ -41,7 +43,7 @@
|
||||
#include <utils/CString.h>
|
||||
#include <utils/Hash.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
@@ -53,6 +55,8 @@
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
Driver* WebGPUDriver::create(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
@@ -64,13 +68,15 @@ Driver* WebGPUDriver::create(WebGPUPlatform& platform, const Platform::DriverCon
|
||||
|
||||
WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
|
||||
const Platform::DriverConfig& driverConfig) noexcept
|
||||
: mPlatform(platform),
|
||||
mHandleAllocator("Handles", driverConfig.handleArenaSize,
|
||||
driverConfig.disableHandleUseAfterFreeCheck, driverConfig.disableHeapHandleTags) {
|
||||
mAdapter = mPlatform.requestAdapter(nullptr);
|
||||
mDevice = mPlatform.requestDevice(mAdapter);
|
||||
: mPlatform{ platform },
|
||||
mAdapter{ mPlatform.requestAdapter(nullptr) },
|
||||
mDevice{ mPlatform.requestDevice(mAdapter) },
|
||||
mQueue{ mDevice.GetQueue() },
|
||||
mRenderPassMipmapGenerator{ mDevice },
|
||||
mSpdComputePassMipmapGenerator{ mDevice },
|
||||
mHandleAllocator{ "Handles", driverConfig.handleArenaSize,
|
||||
driverConfig.disableHandleUseAfterFreeCheck, driverConfig.disableHeapHandleTags } {
|
||||
mDevice.GetLimits(&mDeviceLimits);
|
||||
mQueue = mDevice.GetQueue();
|
||||
}
|
||||
|
||||
WebGPUDriver::~WebGPUDriver() noexcept = default;
|
||||
@@ -100,6 +106,7 @@ void WebGPUDriver::terminate() {
|
||||
|
||||
void WebGPUDriver::tick(int) {
|
||||
mDevice.Tick();
|
||||
mAdapter.GetInstance().ProcessEvents();
|
||||
}
|
||||
|
||||
void WebGPUDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
@@ -261,7 +268,9 @@ Handle<HwProgram> WebGPUDriver::createProgramS() noexcept {
|
||||
}
|
||||
|
||||
Handle<HwFence> WebGPUDriver::createFenceS() noexcept {
|
||||
return Handle<HwFence>((Handle<HwFence>::HandleId) mNextFakeHandle++);
|
||||
// The handle must be constructed here, as a synchronous call to get the status
|
||||
// might happen before createFenceR is executed.
|
||||
return allocAndConstructHandle<WebGPUFence, HwFence>();
|
||||
}
|
||||
|
||||
Handle<HwTimerQuery> WebGPUDriver::createTimerQueryS() noexcept {
|
||||
@@ -334,9 +343,6 @@ void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
|
||||
mDevice, flags);
|
||||
assert_invariant(mSwapChain);
|
||||
|
||||
FWGPU_LOGW << "WebGPU support is highly experimental, in development, and tested for only a "
|
||||
"small set of simple samples (e.g. hellotriangle and texturedquad), thus issues "
|
||||
"are likely to be encountered at this stage.";
|
||||
#if !FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) && !defined(NDEBUG)
|
||||
char printSystemHex[16];
|
||||
snprintf(printSystemHex, sizeof(printSystemHex), "%#x", FWGPU_PRINT_SYSTEM);
|
||||
@@ -448,15 +454,23 @@ void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> renderTarge
|
||||
}
|
||||
|
||||
void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> renderTargetHandle,
|
||||
const TargetBufferFlags targets, const uint32_t width, const uint32_t height,
|
||||
const TargetBufferFlags targetFlags, const uint32_t width, const uint32_t height,
|
||||
const uint8_t samples, const uint8_t layerCount, const MRT color,
|
||||
const TargetBufferInfo depth, const TargetBufferInfo stencil) {
|
||||
constructHandle<WebGPURenderTarget>(renderTargetHandle, width, height, samples, layerCount,
|
||||
color, depth, stencil);
|
||||
constructHandle<WebGPURenderTarget>(
|
||||
renderTargetHandle, width, height, samples, layerCount, color, depth, stencil,
|
||||
targetFlags,
|
||||
[&](const Handle<HwTexture> textureHandle) {
|
||||
return handleCast<WebGPUTexture>(textureHandle);
|
||||
},
|
||||
mDevice);
|
||||
}
|
||||
|
||||
void WebGPUDriver::createFenceR(Handle<HwFence> fh, int) {
|
||||
//todo
|
||||
void WebGPUDriver::createFenceR(Handle<HwFence> fenceHandle, const int /* dummy */) {
|
||||
// the handle was already constructed in createFenceS
|
||||
const auto fence = handleCast<WebGPUFence>(fenceHandle);
|
||||
assert_invariant(mQueue);
|
||||
fence->addMarkerToQueueState(mQueue);
|
||||
}
|
||||
|
||||
void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {}
|
||||
@@ -509,13 +523,18 @@ void WebGPUDriver::updateStreams(CommandStream* driver) {
|
||||
//todo
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyFence(Handle<HwFence> fh) {
|
||||
//todo
|
||||
void WebGPUDriver::destroyFence(Handle<HwFence> fenceHandle) {
|
||||
if (fenceHandle) {
|
||||
destructHandle<WebGPUFence>(fenceHandle);
|
||||
}
|
||||
}
|
||||
|
||||
FenceStatus WebGPUDriver::getFenceStatus(Handle<HwFence> fh) {
|
||||
//todo
|
||||
return FenceStatus::CONDITION_SATISFIED;
|
||||
FenceStatus WebGPUDriver::getFenceStatus(Handle<HwFence> fenceHandle) {
|
||||
const auto fence = handleCast<WebGPUFence>(fenceHandle);
|
||||
if (!fence) {
|
||||
return FenceStatus::ERROR;
|
||||
}
|
||||
return fence->getStatus();
|
||||
}
|
||||
|
||||
// We create all textures using VK_IMAGE_TILING_OPTIMAL, so our definition of "supported" is that
|
||||
@@ -529,8 +548,8 @@ bool WebGPUDriver::isTextureSwizzleSupported() {
|
||||
}
|
||||
|
||||
bool WebGPUDriver::isTextureFormatMipmappable(const TextureFormat format) {
|
||||
//todo
|
||||
return true;
|
||||
return WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(
|
||||
WebGPUTexture::fToWGPUTextureFormat(format));
|
||||
}
|
||||
|
||||
bool WebGPUDriver::isRenderTargetFormatSupported(const TextureFormat format) {
|
||||
@@ -605,15 +624,28 @@ uint8_t WebGPUDriver::getMaxDrawBuffers() {
|
||||
}
|
||||
|
||||
size_t WebGPUDriver::getMaxUniformBufferSize() {
|
||||
return 16384u;
|
||||
return mDeviceLimits.maxUniformBufferBindingSize;
|
||||
}
|
||||
|
||||
size_t WebGPUDriver::getMaxTextureSize(const SamplerType target) {
|
||||
return 2048u;
|
||||
size_t result = 2048u;
|
||||
switch (target) {
|
||||
case SamplerType::SAMPLER_2D:
|
||||
case SamplerType::SAMPLER_2D_ARRAY:
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
result = mDeviceLimits.maxTextureDimension2D;
|
||||
break;
|
||||
case SamplerType::SAMPLER_3D:
|
||||
result = mDeviceLimits.maxTextureDimension3D;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t WebGPUDriver::getMaxArrayTextureLayers() {
|
||||
return 256u;
|
||||
return mDeviceLimits.maxTextureArrayLayers;
|
||||
}
|
||||
|
||||
void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
|
||||
@@ -692,27 +724,6 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
|
||||
|
||||
const uint8_t* dataBuff = static_cast<const uint8_t*>(data->buffer);
|
||||
size_t dataSize = data->size;
|
||||
std::unique_ptr<uint8_t[]> paddedBuffer;
|
||||
|
||||
if (bytesPerRow % 256 != 0) {
|
||||
uint32_t padding = 256 - (bytesPerRow % 256);
|
||||
uint32_t paddedBytesPerRow = bytesPerRow + padding;
|
||||
|
||||
size_t paddedBufferSize = static_cast<size_t>(paddedBytesPerRow) * height * depth;
|
||||
paddedBuffer = std::make_unique<uint8_t[]>(paddedBufferSize);
|
||||
uint8_t* dest = paddedBuffer.get();
|
||||
|
||||
for (uint32_t z = 0; z < depth; ++z) {
|
||||
for (uint32_t y = 0; y < height; ++y) {
|
||||
std::memcpy(dest, dataBuff, bytesPerRow);
|
||||
dest += paddedBytesPerRow;
|
||||
dataBuff += bytesPerRow;
|
||||
}
|
||||
}
|
||||
dataBuff = paddedBuffer.get();
|
||||
dataSize = paddedBufferSize;
|
||||
bytesPerRow = paddedBytesPerRow;
|
||||
}
|
||||
|
||||
auto layout = wgpu::TexelCopyBufferLayout{ .bytesPerRow = bytesPerRow, .rowsPerImage = height };
|
||||
|
||||
@@ -738,58 +749,62 @@ void WebGPUDriver::setExternalStream(Handle<HwTexture> textureHandle,
|
||||
}
|
||||
|
||||
void WebGPUDriver::generateMipmaps(Handle<HwTexture> textureHandle) {
|
||||
if (!mCommandEncoder) {
|
||||
mMipQueue.push_back(textureHandle);
|
||||
return;
|
||||
}
|
||||
auto texture = handleCast<WebGPUTexture>(textureHandle);
|
||||
assert_invariant(texture);
|
||||
wgpu::Texture wgpuTexture = texture->getTexture();
|
||||
assert_invariant(wgpuTexture);
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(wgpuTexture.GetUsage() & wgpu::TextureUsage::CopySrc)
|
||||
<< "Texture intended for mipmap generation (as source) must have CopySrc usage.";
|
||||
FILAMENT_CHECK_PRECONDITION(wgpuTexture.GetUsage() & wgpu::TextureUsage::CopyDst)
|
||||
<< "Texture intended for mipmap generation (as destination) must have CopyDst usage.";
|
||||
|
||||
uint32_t mipLevelCount = wgpuTexture.GetMipLevelCount();
|
||||
if (mipLevelCount <= 1) {
|
||||
return;
|
||||
const uint32_t totalMipLevels = wgpuTexture.GetMipLevelCount();
|
||||
if (totalMipLevels <= 1) {
|
||||
return; // nothing to do
|
||||
}
|
||||
|
||||
uint32_t width = wgpuTexture.GetWidth();
|
||||
uint32_t height = wgpuTexture.GetHeight();
|
||||
// For 3D textures, depth is > 1. For 2D/Cube/Array, effectively 1 for mip-level copies.
|
||||
uint32_t depth =
|
||||
(texture->target == SamplerType::SAMPLER_3D) ? wgpuTexture.GetDepthOrArrayLayers() : 1;
|
||||
const auto usage = wgpuTexture.GetUsage();
|
||||
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::TextureBinding)
|
||||
<< "Texture for mipmap generation must have TextureBinding usage.";
|
||||
|
||||
for (uint32_t mipLevel = 0; mipLevel < mipLevelCount - 1; ++mipLevel) {
|
||||
wgpu::TexelCopyTextureInfo sourceCopyInfo{
|
||||
.texture = wgpuTexture,
|
||||
.mipLevel = mipLevel,
|
||||
.aspect = texture->getAspect(),
|
||||
};
|
||||
switch (texture->getMipmapGenerationStrategy()) {
|
||||
case WebGPUTexture::MipmapGenerationStrategy::RENDER_PASS:
|
||||
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::RenderAttachment)
|
||||
<< "Texture for mipmap generation via render pass blit must have "
|
||||
"RenderAttachment usage.";
|
||||
mRenderPassMipmapGenerator.generateMipmaps(mQueue, texture->getTexture());
|
||||
break;
|
||||
case WebGPUTexture::MipmapGenerationStrategy::NONE:
|
||||
FWGPU_LOGW << "Trying to generate mipmaps for a texture we already know we cannot do "
|
||||
"such generation. Texture sample type "
|
||||
<< to_string(texture->target) << " WebGPU texture format "
|
||||
<< webGPUTextureFormatToString(texture->getViewFormat()) << " samples "
|
||||
<< static_cast<uint32_t>(texture->samples)
|
||||
<< ". Going to try using the SPD_COMPUTE_PASS approach.";
|
||||
UTILS_FALLTHROUGH;
|
||||
case WebGPUTexture::MipmapGenerationStrategy::SPD_COMPUTE_PASS:
|
||||
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::StorageBinding)
|
||||
<< "Texture for mipmap generation must have StorageBinding usage "
|
||||
"(as a compute pass strategy was selected for use). "
|
||||
"supportsMultipleMipLevels for WebGPU texture format "
|
||||
<< webGPUTextureFormatToString(wgpuTexture.GetFormat()) << " is "
|
||||
<< WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(
|
||||
wgpuTexture.GetFormat());
|
||||
// We will record all passes into a single command encoder.
|
||||
wgpu::CommandEncoderDescriptor encoderDesc = {};
|
||||
encoderDesc.label = "Mipmap Command Encoder";
|
||||
wgpu::CommandEncoder encoder = mDevice.CreateCommandEncoder(&encoderDesc);
|
||||
|
||||
wgpu::TexelCopyTextureInfo destinationCopyInfo{
|
||||
.texture = wgpuTexture,
|
||||
.mipLevel = mipLevel + 1,
|
||||
.aspect = texture->getAspect(),
|
||||
};
|
||||
spd::SPDPassConfig spdConfig = { .filter = spd::SPDFilter::Average,
|
||||
.targetTexture = wgpuTexture,
|
||||
.numMips = totalMipLevels,
|
||||
.halfPrecision = false,
|
||||
.sourceMipLevel = 0 };
|
||||
|
||||
uint32_t dstWidth = std::max(1u, width >> 1);
|
||||
uint32_t dstHeight = std::max(1u, height >> 1);
|
||||
uint32_t dstDepth = std::max(1u, depth >> 1);
|
||||
mSpdComputePassMipmapGenerator.Generate(encoder, wgpuTexture, spdConfig);
|
||||
|
||||
wgpu::Extent3D copySize{ .width = dstWidth,
|
||||
.height = dstHeight,
|
||||
.depthOrArrayLayers = (texture->target == SamplerType::SAMPLER_3D)
|
||||
? dstDepth
|
||||
: texture->getArrayLayerCount() };
|
||||
mCommandEncoder.CopyTextureToTexture(&sourceCopyInfo, &destinationCopyInfo, ©Size);
|
||||
|
||||
width = dstWidth;
|
||||
height = dstHeight;
|
||||
depth = dstDepth;
|
||||
// Finish the encoder and submit all the passes at once.
|
||||
wgpu::CommandBufferDescriptor cmdBufferDesc = {};
|
||||
cmdBufferDesc.label = "Mipmap Command Buffer";
|
||||
wgpu::CommandBuffer commandBuffer = encoder.Finish(&cmdBufferDesc);
|
||||
mQueue.Submit(1, &commandBuffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -809,12 +824,16 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
|
||||
wgpu::TextureView defaultDepthStencilView = nullptr;
|
||||
wgpu::TextureFormat defaultDepthStencilFormat = wgpu::TextureFormat::Undefined;
|
||||
|
||||
const bool msaaSidecarsRequired{ renderTarget->getSamples() > 1 &&
|
||||
renderTarget->getSampleCountPerAttachment() <= 1 };
|
||||
|
||||
std::array<wgpu::TextureView, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> customColorViews{};
|
||||
std::array<wgpu::TextureView, customColorViews.size()> customColorMsaaSidecarViews{};
|
||||
uint32_t customColorViewCount = 0;
|
||||
wgpu::TextureView customDepthView = nullptr;
|
||||
wgpu::TextureFormat customDepthFormat = wgpu::TextureFormat::Undefined;
|
||||
wgpu::TextureFormat customStencilFormat = wgpu::TextureFormat::Undefined;
|
||||
wgpu::TextureView customStencilView = nullptr;
|
||||
|
||||
wgpu::TextureView customDepthStencilView = nullptr;
|
||||
wgpu::TextureView customDepthStencilMsaaSidecarTextureView = nullptr;
|
||||
wgpu::TextureFormat customDepthStencilFormat = wgpu::TextureFormat::Undefined;
|
||||
|
||||
mCurrentRenderTarget = renderTarget;
|
||||
if (renderTarget->isDefaultRenderTarget()) {
|
||||
@@ -822,6 +841,20 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
|
||||
defaultColorView = mTextureView;
|
||||
defaultDepthStencilView = mSwapChain->getDepthTextureView();
|
||||
defaultDepthStencilFormat = mSwapChain->getDepthFormat();
|
||||
|
||||
if (any(renderTarget->getTargetFlags() & TargetBufferFlags::STENCIL) &&
|
||||
!(hasStencil(defaultDepthStencilFormat))) {
|
||||
FILAMENT_CHECK_POSTCONDITION(false)
|
||||
<< "Default render target requested stencil, but swap chain's depth format "
|
||||
<< (uint32_t) defaultDepthStencilFormat << " does not have a stencil aspect.";
|
||||
}
|
||||
|
||||
if (any(renderTarget->getTargetFlags() & TargetBufferFlags::DEPTH) &&
|
||||
!(hasDepth(defaultDepthStencilFormat))) {
|
||||
FILAMENT_CHECK_POSTCONDITION(false)
|
||||
<< "Default render target requested depth, but swap chain's depth format "
|
||||
<< (uint32_t) defaultDepthStencilFormat << " does not have a depth aspect.";
|
||||
}
|
||||
} else {
|
||||
// Resolve views for custom render target
|
||||
const auto& colorInfos = renderTarget->getColorAttachmentInfos();
|
||||
@@ -836,41 +869,78 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
|
||||
<< ".";
|
||||
const uint8_t mipLevel = colorInfos[i].level;
|
||||
const uint32_t arrayLayer = colorInfos[i].layer;
|
||||
customColorViews[customColorViewCount++] =
|
||||
customColorViews[customColorViewCount] =
|
||||
colorTexture->getOrMakeTextureView(mipLevel, arrayLayer);
|
||||
if (msaaSidecarsRequired) {
|
||||
const wgpu::TextureView msaaSidecarView{
|
||||
colorTexture->makeMsaaSidecarTextureViewIfTextureSidecarExists(
|
||||
renderTarget->getSamples(), mipLevel, arrayLayer)
|
||||
};
|
||||
FILAMENT_CHECK_POSTCONDITION(msaaSidecarView)
|
||||
<< "Could not get a required MSAA sidecar texture view for color "
|
||||
<< customColorViewCount << "?";
|
||||
customColorMsaaSidecarViews[customColorViewCount] = msaaSidecarView;
|
||||
}
|
||||
customColorViewCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto& depthInfo = renderTarget->getDepthAttachmentInfo();
|
||||
if (depthInfo.handle) {
|
||||
FILAMENT_CHECK_POSTCONDITION(depthInfo.layer < renderTarget->getLayerCount())
|
||||
<< "Depth attachment requests layer " << depthInfo.layer
|
||||
<< "but render target has only " << renderTarget->getLayerCount() << ".";
|
||||
auto depthTexture = handleCast<WebGPUTexture>(depthInfo.handle);
|
||||
if (depthTexture) {
|
||||
const uint8_t depthMipLevel = depthInfo.level;
|
||||
const uint32_t depthArrayLayer = depthInfo.layer;
|
||||
customDepthView =
|
||||
depthTexture->getOrMakeTextureView(depthMipLevel, depthArrayLayer);
|
||||
customDepthFormat = depthTexture->getWebGPUFormat();
|
||||
}
|
||||
const auto& stencilInfo = renderTarget->getStencilAttachmentInfo();
|
||||
|
||||
Handle<HwTexture> depthStencilSourceHandle = {};
|
||||
uint8_t depthStencilMipLevel = 0;
|
||||
uint32_t depthStencilArrayLayer = 0;
|
||||
|
||||
if (depthInfo.handle && stencilInfo.handle) {
|
||||
auto const depthTexture{handleCast<WebGPUTexture>(depthInfo.handle)->getTexture()};
|
||||
auto const stencilTexture{handleCast<WebGPUTexture>(stencilInfo.handle)->getTexture()};
|
||||
FILAMENT_CHECK_POSTCONDITION(depthTexture.Get() == stencilTexture.Get())
|
||||
<< "Filament must reference the same resource if both depth and stencil "
|
||||
"textures are present";
|
||||
}
|
||||
|
||||
const auto& stencilInfo = renderTarget->getStencilAttachmentInfo();
|
||||
if (stencilInfo.handle) {
|
||||
// If depth and stencil use the same texture handle, this will re-cast but that's fine.
|
||||
auto stencilTexture = handleCast<WebGPUTexture>(stencilInfo.handle);
|
||||
if (stencilTexture) {
|
||||
FILAMENT_CHECK_POSTCONDITION(stencilInfo.layer < renderTarget->getLayerCount())
|
||||
<< "Stencil attachment requests layer " << stencilInfo.layer
|
||||
<< " but render target has only " << renderTarget->getLayerCount()
|
||||
<< " layers.";
|
||||
const uint8_t stencilMipLevel = stencilInfo.level;
|
||||
const uint32_t stencilArrayLayer = stencilInfo.layer;
|
||||
customStencilView =
|
||||
stencilTexture->getOrMakeTextureView(stencilMipLevel, stencilArrayLayer);
|
||||
customStencilFormat = stencilTexture->getWebGPUFormat();
|
||||
if (depthInfo.handle) {
|
||||
depthStencilSourceHandle = depthInfo.handle;
|
||||
depthStencilMipLevel = depthInfo.level;
|
||||
depthStencilArrayLayer = depthInfo.layer;
|
||||
} else if (stencilInfo.handle) {
|
||||
depthStencilSourceHandle = stencilInfo.handle;
|
||||
depthStencilMipLevel = stencilInfo.level;
|
||||
depthStencilArrayLayer = stencilInfo.layer;
|
||||
}
|
||||
|
||||
if (depthStencilSourceHandle) {
|
||||
auto dsTexture = handleCast<WebGPUTexture>(depthStencilSourceHandle);
|
||||
if (dsTexture) {
|
||||
customDepthStencilView = dsTexture->getOrMakeTextureView(depthStencilMipLevel,
|
||||
depthStencilArrayLayer);
|
||||
if (msaaSidecarsRequired) {
|
||||
customDepthStencilMsaaSidecarTextureView =
|
||||
dsTexture->makeMsaaSidecarTextureViewIfTextureSidecarExists(
|
||||
renderTarget->getSamples(), depthStencilMipLevel,
|
||||
depthStencilArrayLayer);
|
||||
FILAMENT_CHECK_POSTCONDITION(customDepthStencilMsaaSidecarTextureView)
|
||||
<< "Could not get a required MSAA sidecar texture view for "
|
||||
"depth/stencil?";
|
||||
}
|
||||
customDepthStencilFormat = dsTexture->getViewFormat();
|
||||
|
||||
if (any(renderTarget->getTargetFlags() & TargetBufferFlags::STENCIL) &&
|
||||
!(hasStencil(customDepthStencilFormat))) {
|
||||
FILAMENT_CHECK_POSTCONDITION(false)
|
||||
<< "Custom render target requested stencil, but the provided texture"
|
||||
"format number"
|
||||
<< (uint32_t) customDepthStencilFormat
|
||||
<< " does not have a stencil aspect.";
|
||||
}
|
||||
if (any(renderTarget->getTargetFlags() & TargetBufferFlags::DEPTH) &&
|
||||
!(hasDepth(customDepthStencilFormat))) {
|
||||
FILAMENT_CHECK_POSTCONDITION(false) << "Custom render target requested depth, "
|
||||
"but the provided texture format number"
|
||||
<< (uint32_t) customDepthStencilFormat
|
||||
<< " does not have a depth aspect.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -879,13 +949,11 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
|
||||
params,
|
||||
defaultColorView,
|
||||
defaultDepthStencilView,
|
||||
defaultDepthStencilFormat,
|
||||
customColorViews.data(),
|
||||
customColorMsaaSidecarViews.data(),
|
||||
customColorViewCount,
|
||||
customDepthView,
|
||||
customStencilView,
|
||||
customDepthFormat,
|
||||
customStencilFormat);
|
||||
customDepthStencilView,
|
||||
customDepthStencilMsaaSidecarTextureView);
|
||||
|
||||
mRenderPassEncoder = mCommandEncoder.BeginRenderPass(&renderPassDescriptor);
|
||||
|
||||
@@ -920,16 +988,26 @@ void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
|
||||
wgpu::Extent2D surfaceSize = mPlatform.getSurfaceExtent(mNativeWindow);
|
||||
mTextureView = mSwapChain->getCurrentSurfaceTextureView(surfaceSize);
|
||||
assert_invariant(mTextureView);
|
||||
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = {
|
||||
.label = "command_encoder"
|
||||
};
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
||||
if (!mMipQueue.empty()) {
|
||||
for (auto& handle: mMipQueue) {
|
||||
generateMipmaps(handle);
|
||||
|
||||
assert_invariant(mDefaultRenderTarget);
|
||||
|
||||
wgpu::TextureFormat depthFormat = mSwapChain->getDepthFormat();
|
||||
TargetBufferFlags newTargetFlags = filament::backend::TargetBufferFlags::NONE;
|
||||
|
||||
//Assuming Color always present in default render target.
|
||||
newTargetFlags |= filament::backend::TargetBufferFlags::COLOR;
|
||||
if (depthFormat != wgpu::TextureFormat::Undefined) {
|
||||
if (hasDepth(depthFormat)) {
|
||||
newTargetFlags |= filament::backend::TargetBufferFlags::DEPTH;
|
||||
}
|
||||
if (hasStencil(depthFormat)) {
|
||||
newTargetFlags |= filament::backend::TargetBufferFlags::STENCIL;
|
||||
}
|
||||
mMipQueue.clear();
|
||||
}
|
||||
mDefaultRenderTarget->setTargetFlags(newTargetFlags);
|
||||
|
||||
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = { .label = "frame_command_encoder" };
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
||||
assert_invariant(mCommandEncoder);
|
||||
}
|
||||
|
||||
@@ -941,6 +1019,22 @@ void WebGPUDriver::commit(Handle<HwSwapChain> sch) {
|
||||
assert_invariant(mCommandBuffer);
|
||||
mCommandEncoder = nullptr;
|
||||
mQueue.Submit(1, &mCommandBuffer);
|
||||
|
||||
static bool firstRender = true;
|
||||
// For the first frame rendered, we need to make sure the work is done before presenting or we
|
||||
// get a purple flash
|
||||
if (firstRender) {
|
||||
auto f = mQueue.OnSubmittedWorkDone(wgpu::CallbackMode::WaitAnyOnly,
|
||||
[=](wgpu::QueueWorkDoneStatus) {});
|
||||
const wgpu::Instance instance = mAdapter.GetInstance();
|
||||
auto wStatus = instance.WaitAny(f,
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count());
|
||||
if (wStatus != wgpu::WaitStatus::Success) {
|
||||
FWGPU_LOGW << "Waiting for first frame work to finish resulted in an error"
|
||||
<< static_cast<uint32_t>(wStatus);
|
||||
}
|
||||
firstRender = false;
|
||||
}
|
||||
mCommandBuffer = nullptr;
|
||||
mTextureView = nullptr;
|
||||
assert_invariant(mSwapChain);
|
||||
@@ -993,7 +1087,33 @@ void WebGPUDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
void WebGPUDriver::resolve(Handle<HwTexture> destinationTextureHandle, const uint8_t sourceLevel,
|
||||
const uint8_t sourceLayer, Handle<HwTexture> sourceTextureHandle,
|
||||
const uint8_t destinationLevel, const uint8_t destinationLayer) {
|
||||
// todo
|
||||
FILAMENT_CHECK_PRECONDITION(mCommandEncoder)
|
||||
<< "Resolve assumes there is a valid command encoder to piggyback on.";
|
||||
FILAMENT_CHECK_PRECONDITION(mRenderPassEncoder == nullptr)
|
||||
<< "Resolve cannot be called during an existing render pass";
|
||||
const auto sourceTexture{ handleCast<WebGPUTexture>(sourceTextureHandle) };
|
||||
const auto destinationTexture{ handleCast<WebGPUTexture>(destinationTextureHandle) };
|
||||
assert_invariant(sourceTexture);
|
||||
assert_invariant(destinationTexture);
|
||||
const WebGPUMsaaTextureResolver::ResolveRequest request{
|
||||
.commandEncoder = mCommandEncoder,
|
||||
.viewFormat = sourceTexture->getViewFormat(),
|
||||
.source = {
|
||||
.texture = sourceTexture->getTexture(),
|
||||
.viewDimension = sourceTexture->getViewDimension(),
|
||||
.mipLevel = sourceLevel,
|
||||
.layer = sourceLayer,
|
||||
.aspect = sourceTexture->getAspect(),
|
||||
},
|
||||
.destination = {
|
||||
.texture = destinationTexture->getTexture(),
|
||||
.viewDimension = destinationTexture->getViewDimension(),
|
||||
.mipLevel = destinationLevel,
|
||||
.layer = destinationLayer,
|
||||
.aspect = destinationTexture->getAspect(),
|
||||
},
|
||||
};
|
||||
mMsaaTextureResolver.resolve(request);
|
||||
}
|
||||
|
||||
void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_t sourceLevel,
|
||||
@@ -1003,13 +1123,24 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
|
||||
// todo
|
||||
}
|
||||
|
||||
void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
size_t WebGPUDriver::computePipelineKey(PipelineState const& pipelineState,
|
||||
WebGPURenderTarget const* const renderTarget) const {
|
||||
// TODO Investigate implications of this hash more closely. Vulkan has a whole class
|
||||
// VulkanPipelineCache to handle this, may be missing nuance
|
||||
static auto pipleineStateHasher = utils::hash::MurmurHashFn<filament::backend::PipelineState>();
|
||||
auto hash = pipleineStateHasher(pipelineState);
|
||||
if (mPipelineMap.find(hash) != mPipelineMap.end()) {
|
||||
mRenderPassEncoder.SetPipeline(mPipelineMap[hash]);
|
||||
// VulkanPipelineCache to handle this, may be missing nuance
|
||||
static const auto pipelineStateHasher{
|
||||
utils::hash::MurmurHashFn<filament::backend::PipelineState>()
|
||||
};
|
||||
const std::hash<uint32_t> intHasher{};
|
||||
const std::hash<WebGPURenderTarget const*> addressHasher{};
|
||||
const size_t pipelineStateHash{ intHasher(pipelineStateHasher(pipelineState)) };
|
||||
const size_t renderTargetHash{ addressHasher(renderTarget) };
|
||||
return utils::hash::combine(pipelineStateHash, renderTargetHash);
|
||||
}
|
||||
|
||||
void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
auto pipelineKey{ computePipelineKey(pipelineState, mCurrentRenderTarget) };
|
||||
if (mPipelineMap.find(pipelineKey) != mPipelineMap.end()) {
|
||||
mRenderPassEncoder.SetPipeline(mPipelineMap[pipelineKey]);
|
||||
return;
|
||||
}
|
||||
const auto program = handleCast<WebGPUProgram>(pipelineState.program);
|
||||
@@ -1047,14 +1178,15 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
assert_invariant(vertexBufferInfo);
|
||||
|
||||
std::vector<wgpu::TextureFormat> pipelineColorFormats;
|
||||
wgpu::TextureFormat pipelineDepthFormat = wgpu::TextureFormat::Undefined;
|
||||
wgpu::TextureFormat pipelineDepthStencilFormat = wgpu::TextureFormat::Undefined;
|
||||
uint8_t pipelineSamples = 1;
|
||||
bool const requestedDepth = any(mCurrentRenderTarget->getTargetFlags() & TargetBufferFlags::DEPTH);
|
||||
bool const requestedStencil = any(mCurrentRenderTarget->getTargetFlags() & TargetBufferFlags::STENCIL);
|
||||
pipelineSamples = mCurrentRenderTarget->getSamples();
|
||||
|
||||
if (mCurrentRenderTarget->isDefaultRenderTarget()) {
|
||||
pipelineColorFormats.push_back(mSwapChain->getColorFormat());
|
||||
pipelineDepthFormat = mSwapChain->getDepthFormat();
|
||||
pipelineSamples =
|
||||
mCurrentRenderTarget->getSamples();// Default RT should have samples (usually 1)
|
||||
pipelineDepthStencilFormat = mSwapChain->getDepthFormat();
|
||||
} else {
|
||||
const auto& mrtColorAttachments = mCurrentRenderTarget->getColorAttachmentInfos();
|
||||
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; ++i) {
|
||||
@@ -1068,21 +1200,19 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
|
||||
const auto& depthInfo = mCurrentRenderTarget->getDepthAttachmentInfo();
|
||||
const auto& stencilInfo = mCurrentRenderTarget->getStencilAttachmentInfo();
|
||||
Handle<HwTexture> depthStencilHandle = {};
|
||||
if (depthInfo.handle) {
|
||||
FILAMENT_CHECK_POSTCONDITION(!stencilInfo.handle)
|
||||
<< "depth and stencil attachments cannot both be provided for WebGPU";
|
||||
const auto depthTexture = handleCast<WebGPUTexture>(depthInfo.handle);
|
||||
if (depthTexture) pipelineDepthFormat = depthTexture->getTexture().GetFormat();
|
||||
} else {
|
||||
if (stencilInfo.handle) {
|
||||
const auto stencilTexture = handleCast<WebGPUTexture>(stencilInfo.handle);
|
||||
// Assuming combined depth/stencil format if only stencil is present
|
||||
if (stencilTexture) {
|
||||
pipelineDepthFormat = stencilTexture->getTexture().GetFormat();
|
||||
}
|
||||
depthStencilHandle = depthInfo.handle;
|
||||
} else if (stencilInfo.handle) {
|
||||
depthStencilHandle = stencilInfo.handle;
|
||||
}
|
||||
|
||||
if (depthStencilHandle) {
|
||||
const auto dsTexture = handleCast<WebGPUTexture>(depthStencilHandle);
|
||||
if (dsTexture) {
|
||||
pipelineDepthStencilFormat = dsTexture->getTexture().GetFormat();
|
||||
}
|
||||
}
|
||||
pipelineSamples = mCurrentRenderTarget->getSamples();
|
||||
}
|
||||
|
||||
// TODO: We expected this to be a sane check, however it complains when running shadowtest.
|
||||
@@ -1094,9 +1224,9 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
wgpu::RenderPipeline pipeline = createWebGPURenderPipeline(mDevice, *program, *vertexBufferInfo,
|
||||
layout, pipelineState.rasterState, pipelineState.stencilState,
|
||||
pipelineState.polygonOffset, pipelineState.primitiveType, pipelineColorFormats,
|
||||
pipelineDepthFormat, pipelineSamples);
|
||||
pipelineDepthStencilFormat, pipelineSamples, requestedDepth, requestedStencil);
|
||||
assert_invariant(pipeline);
|
||||
mPipelineMap[hash] = pipeline;
|
||||
mPipelineMap[pipelineKey] = pipeline;
|
||||
mRenderPassEncoder.SetPipeline(pipeline);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include "WebGPURenderTarget.h"
|
||||
#include "webgpu/WebGPUConstants.h"
|
||||
#include "webgpu/WebGPUMsaaTextureResolver.h"
|
||||
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
|
||||
#include "DriverBase.h"
|
||||
@@ -29,6 +31,8 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include "SpdMipmapGenerator/SpdMipmapGenerator.h"
|
||||
#include <tsl/robin_map.h>
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -53,7 +57,7 @@ public:
|
||||
[[nodiscard]] static Driver* create(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept;
|
||||
|
||||
private:
|
||||
explicit WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept;
|
||||
WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept;
|
||||
[[nodiscard]] ShaderModel getShaderModel() const noexcept final;
|
||||
[[nodiscard]] ShaderLanguage getShaderLanguage() const noexcept final;
|
||||
[[nodiscard]] wgpu::Sampler makeSampler(SamplerParams const& params);
|
||||
@@ -70,14 +74,16 @@ private:
|
||||
WebGPUSwapChain* mSwapChain = nullptr;
|
||||
uint64_t mNextFakeHandle = 1;
|
||||
wgpu::CommandEncoder mCommandEncoder = nullptr;
|
||||
std::vector<Handle<HwTexture>> mMipQueue;
|
||||
wgpu::TextureView mTextureView = nullptr;
|
||||
wgpu::RenderPassEncoder mRenderPassEncoder = nullptr;
|
||||
wgpu::CommandBuffer mCommandBuffer = nullptr;
|
||||
WebGPURenderTarget* mDefaultRenderTarget = nullptr;
|
||||
WebGPURenderTarget* mCurrentRenderTarget = nullptr;
|
||||
WebGPURenderPassMipmapGenerator mRenderPassMipmapGenerator;
|
||||
spd::MipmapGenerator mSpdComputePassMipmapGenerator;
|
||||
WebGPUMsaaTextureResolver mMsaaTextureResolver{};
|
||||
|
||||
tsl::robin_map<uint32_t, wgpu::RenderPipeline> mPipelineMap;
|
||||
tsl::robin_map<size_t, wgpu::RenderPipeline> mPipelineMap;
|
||||
|
||||
struct DescriptorSetBindingInfo{
|
||||
wgpu::BindGroup bindGroup;
|
||||
@@ -85,6 +91,9 @@ private:
|
||||
backend::DescriptorSetOffsetArray offsets;
|
||||
};
|
||||
std::array<DescriptorSetBindingInfo,MAX_DESCRIPTOR_SET_COUNT> mCurrentDescriptorSets;
|
||||
|
||||
[[nodiscard]] size_t computePipelineKey(PipelineState const&, WebGPURenderTarget const*) const;
|
||||
|
||||
/*
|
||||
* Driver interface
|
||||
*/
|
||||
@@ -119,6 +128,11 @@ private:
|
||||
return mHandleAllocator.construct<D>(handle, std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
template<typename D, typename B, typename... ARGS>
|
||||
Handle<B> allocAndConstructHandle(ARGS&&... args) {
|
||||
return mHandleAllocator.allocateAndConstruct<D>(std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
template<typename D, typename B>
|
||||
D* handleCast(Handle<B> handle) noexcept {
|
||||
return mHandleAllocator.handle_cast<D*>(handle);
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "WebGPUFence.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
FenceStatus WebGPUFence::getStatus() { return mStatus.load(); }
|
||||
|
||||
void WebGPUFence::addMarkerToQueueState(wgpu::Queue const& queue) {
|
||||
queue.OnSubmittedWorkDone(
|
||||
wgpu::CallbackMode::AllowSpontaneous,
|
||||
[](const wgpu::QueueWorkDoneStatus status,
|
||||
std::atomic<FenceStatus>* const fenceStatus) {
|
||||
if (!fenceStatus) {
|
||||
return;
|
||||
}
|
||||
switch (status) {
|
||||
case wgpu::QueueWorkDoneStatus::Success:
|
||||
fenceStatus->store(FenceStatus::CONDITION_SATISFIED);
|
||||
break;
|
||||
case wgpu::QueueWorkDoneStatus::CallbackCancelled:
|
||||
case wgpu::QueueWorkDoneStatus::Error:
|
||||
fenceStatus->store(FenceStatus::ERROR);
|
||||
break;
|
||||
}
|
||||
},
|
||||
&mStatus);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_WEBGPUFENCE_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUFENCE_H
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace wgpu {
|
||||
class Queue;
|
||||
} // namespace wgpu
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPUFence final : public HwFence {
|
||||
public:
|
||||
[[nodiscard]] FenceStatus getStatus();
|
||||
|
||||
void addMarkerToQueueState(wgpu::Queue const&);
|
||||
|
||||
private:
|
||||
std::atomic<FenceStatus> mStatus{ FenceStatus::TIMEOUT_EXPIRED };
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_WEBGPUFENCE_H
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "WebGPUMsaaTextureResolver.h"
|
||||
|
||||
#include "WebGPUTexture.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
void resolveColorTextures(wgpu::CommandEncoder const& commandEncoder,
|
||||
wgpu::TextureView const& sourceTextureView,
|
||||
wgpu::TextureView const& destinationTextureView) {
|
||||
const wgpu::RenderPassColorAttachment colorAttachment{
|
||||
.view = sourceTextureView,
|
||||
.depthSlice = wgpu::kDepthSliceUndefined, // being explicit for consistent behavior
|
||||
.resolveTarget = destinationTextureView,
|
||||
.loadOp = wgpu::LoadOp::Load,
|
||||
.storeOp = wgpu::StoreOp::Store,
|
||||
.clearValue = {}, // being explicit for consistent behavior
|
||||
};
|
||||
const wgpu::RenderPassDescriptor renderPassDescriptor{
|
||||
.label = "resolve_render_pass",
|
||||
.colorAttachmentCount = 1,
|
||||
.colorAttachments = &colorAttachment,
|
||||
.depthStencilAttachment = nullptr, // being explicit for consistent behavior
|
||||
.occlusionQuerySet = nullptr, // being explicit for consistent behavior
|
||||
.timestampWrites = nullptr, // being explicit for consistent behavior
|
||||
};
|
||||
const wgpu::RenderPassEncoder renderPassEncoder{ commandEncoder.BeginRenderPass(
|
||||
&renderPassDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(renderPassEncoder)
|
||||
<< "Failed to create wgpu::RenderPassEncoder for WebGPUDriver::resolve";
|
||||
renderPassEncoder.End(); // only the implicit resolve is happening in the pass
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void WebGPUMsaaTextureResolver::resolve(ResolveRequest const& request) {
|
||||
ResolveRequest::TextureInfo const& source{ request.source };
|
||||
ResolveRequest::TextureInfo const& destination{ request.destination };
|
||||
FILAMENT_CHECK_PRECONDITION(destination.texture.GetWidth() == source.texture.GetWidth() &&
|
||||
destination.texture.GetHeight() == source.texture.GetHeight())
|
||||
<< "invalid resolve: source and destination sizes don't match";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
source.texture.GetSampleCount() > 1 && destination.texture.GetSampleCount() == 1)
|
||||
<< "invalid resolve: source.samples=" << source.texture.GetSampleCount()
|
||||
<< ", destination.samples=" << destination.texture.GetSampleCount();
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(source.texture.GetFormat() == destination.texture.GetFormat())
|
||||
<< "source and destination texture format don't match";
|
||||
const wgpu::TextureFormat format{ source.texture.GetFormat() };
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!hasDepth(format)) << "can't resolve depth formats";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!hasStencil(format)) << "can't resolve stencil formats";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(source.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
|
||||
<< "source texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
destination.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
|
||||
<< "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(destination.texture.GetUsage() & wgpu::TextureUsage::TextureBinding)
|
||||
<< "destination texture usage doesn't have wgpu::TextureUsage::TextureBinding";
|
||||
|
||||
const wgpu::TextureViewDescriptor sourceTextureViewDescriptor{
|
||||
.label = "resolve_source_texture_view",
|
||||
.format = request.viewFormat,
|
||||
.dimension = source.viewDimension,
|
||||
.baseMipLevel = source.mipLevel,
|
||||
.mipLevelCount = 1,
|
||||
.baseArrayLayer = source.layer,
|
||||
.arrayLayerCount = 1,
|
||||
.aspect = source.aspect,
|
||||
.usage = source.texture.GetUsage(),
|
||||
};
|
||||
const wgpu::TextureView sourceTextureView{ source.texture.CreateView(
|
||||
&sourceTextureViewDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(sourceTextureView)
|
||||
<< "Failed to create wgpu::TextureView sourceTextureView";
|
||||
const wgpu::TextureViewDescriptor destinationTextureViewDescriptor{
|
||||
.label = "resolve_destination_texture_view",
|
||||
.format = request.viewFormat,
|
||||
.dimension = destination.viewDimension,
|
||||
.baseMipLevel = destination.mipLevel,
|
||||
.mipLevelCount = 1,
|
||||
.baseArrayLayer = destination.layer,
|
||||
.arrayLayerCount = 1,
|
||||
.aspect = destination.aspect,
|
||||
.usage = destination.texture.GetUsage(),
|
||||
};
|
||||
const wgpu::TextureView destinationTextureView{ destination.texture.CreateView(
|
||||
&destinationTextureViewDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(destinationTextureView)
|
||||
<< "Failed to create wgpu::TextureView destinationTextureView.";
|
||||
|
||||
if (hasDepth(format)) {
|
||||
PANIC_PRECONDITION("DEPTH RESOLVE NOT IMPLEMENTED YET");
|
||||
} else {
|
||||
resolveColorTextures(request.commandEncoder, sourceTextureView, destinationTextureView);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_WEBGPUMSAATEXTURERESOLVER_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUMSAATEXTURERESOLVER_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace wgpu {
|
||||
class CommandEncoder;
|
||||
class Texture;
|
||||
enum class TextureAspect : uint32_t;
|
||||
enum class TextureFormat : uint32_t;
|
||||
enum class TextureViewDimension : uint32_t;
|
||||
} // namespace wgpu
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPUTexture;
|
||||
|
||||
class WebGPUMsaaTextureResolver final {
|
||||
public:
|
||||
struct ResolveRequest final {
|
||||
struct TextureInfo final {
|
||||
wgpu::Texture const& texture;
|
||||
wgpu::TextureViewDimension viewDimension;
|
||||
uint8_t mipLevel;
|
||||
uint8_t layer;
|
||||
wgpu::TextureAspect aspect;
|
||||
};
|
||||
wgpu::CommandEncoder const& commandEncoder;
|
||||
wgpu::TextureFormat viewFormat;
|
||||
TextureInfo source;
|
||||
TextureInfo destination;
|
||||
};
|
||||
|
||||
void resolve(ResolveRequest const&);
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_WEBGPUMSAATEXTURERESOLVER_H
|
||||
@@ -169,39 +169,52 @@ wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device,
|
||||
wgpu::PipelineLayout const& layout, RasterState const& rasterState,
|
||||
StencilState const& stencilState, PolygonOffset const& polygonOffset,
|
||||
const PrimitiveType primitiveType, std::vector<wgpu::TextureFormat> const& colorFormats,
|
||||
const wgpu::TextureFormat depthFormat, const uint8_t samplesCount) {
|
||||
wgpu::TextureFormat const& depthStencilFormat, const uint8_t samplesCount, const bool requestedDepth, const bool requestedStencil) {
|
||||
assert_invariant(program.vertexShaderModule);
|
||||
wgpu::DepthStencilState depthStencilState{};
|
||||
if (depthFormat != wgpu::TextureFormat::Undefined) {
|
||||
depthStencilState.format = depthFormat;
|
||||
depthStencilState.depthWriteEnabled = rasterState.depthWrite;
|
||||
depthStencilState.depthCompare = toWebGPU(rasterState.depthFunc);
|
||||
depthStencilState.stencilFront = {
|
||||
.compare = toWebGPU(stencilState.front.stencilFunc),
|
||||
.failOp = toWebGPU(stencilState.front.stencilOpStencilFail),
|
||||
.depthFailOp = toWebGPU(stencilState.front.stencilOpDepthFail),
|
||||
.passOp = toWebGPU(stencilState.front.stencilOpDepthStencilPass),
|
||||
};
|
||||
depthStencilState.stencilBack = {
|
||||
.compare = toWebGPU(stencilState.back.stencilFunc),
|
||||
.failOp = toWebGPU(stencilState.back.stencilOpStencilFail),
|
||||
.depthFailOp = toWebGPU(stencilState.back.stencilOpDepthFail),
|
||||
.passOp = toWebGPU(stencilState.back.stencilOpDepthStencilPass),
|
||||
};
|
||||
depthStencilState.stencilReadMask =
|
||||
stencilState.front.readMask; // Use front face's comparison mask for read mask
|
||||
depthStencilState.stencilWriteMask = stencilState.stencilWrite ? 0xFFFFFFFF : 0u;
|
||||
depthStencilState.depthBias = static_cast<int32_t>(polygonOffset.constant);
|
||||
depthStencilState.depthBiasSlopeScale = polygonOffset.slope;
|
||||
depthStencilState.depthBiasClamp = 0.0f;
|
||||
const bool depthOrStencilRequested = (requestedDepth || requestedStencil);
|
||||
|
||||
if (!hasStencilAspect(depthFormat)) {
|
||||
depthStencilState.stencilFront.compare = wgpu::CompareFunction::Always;
|
||||
if (depthOrStencilRequested) {
|
||||
FILAMENT_CHECK_PRECONDITION(depthStencilFormat != wgpu::TextureFormat::Undefined)
|
||||
<< "Depth or Stencil requested for pipeline, but depthStencilFormat is "
|
||||
"wgpu::TextureFormat::Undefined.";
|
||||
depthStencilState.format = depthStencilFormat;
|
||||
|
||||
if (requestedDepth) {
|
||||
depthStencilState.depthWriteEnabled = rasterState.depthWrite;
|
||||
depthStencilState.depthCompare = toWebGPU(rasterState.depthFunc);
|
||||
depthStencilState.depthBias = static_cast<int32_t>(polygonOffset.constant);
|
||||
depthStencilState.depthBiasSlopeScale = polygonOffset.slope;
|
||||
depthStencilState.depthBiasClamp = 0.0f;
|
||||
} else {
|
||||
depthStencilState.depthWriteEnabled = false;
|
||||
depthStencilState.depthCompare = wgpu::CompareFunction::Undefined;
|
||||
depthStencilState.depthBias = 0;
|
||||
depthStencilState.depthBiasSlopeScale = 0.0f;
|
||||
depthStencilState.depthBiasClamp = 0.0f;
|
||||
}
|
||||
|
||||
if (hasStencilAspect(depthStencilFormat) && requestedStencil) {
|
||||
depthStencilState.stencilFront = {
|
||||
.compare = toWebGPU(stencilState.front.stencilFunc),
|
||||
.failOp = toWebGPU(stencilState.front.stencilOpStencilFail),
|
||||
.depthFailOp = toWebGPU(stencilState.front.stencilOpDepthFail),
|
||||
.passOp = toWebGPU(stencilState.front.stencilOpDepthStencilPass),
|
||||
};
|
||||
depthStencilState.stencilBack = {
|
||||
.compare = toWebGPU(stencilState.back.stencilFunc),
|
||||
.failOp = toWebGPU(stencilState.back.stencilOpStencilFail),
|
||||
.depthFailOp = toWebGPU(stencilState.back.stencilOpDepthFail),
|
||||
.passOp = toWebGPU(stencilState.back.stencilOpDepthStencilPass),
|
||||
};
|
||||
depthStencilState.stencilReadMask = stencilState.front.readMask;
|
||||
depthStencilState.stencilWriteMask = stencilState.stencilWrite ? stencilState.front.writeMask : 0u;
|
||||
} else {
|
||||
depthStencilState.stencilFront.compare = wgpu::CompareFunction::Undefined;
|
||||
depthStencilState.stencilFront.failOp = wgpu::StencilOperation::Keep;
|
||||
depthStencilState.stencilFront.depthFailOp = wgpu::StencilOperation::Keep;
|
||||
depthStencilState.stencilFront.passOp = wgpu::StencilOperation::Keep;
|
||||
depthStencilState.stencilBack =
|
||||
depthStencilState.stencilFront; // Keep back and front consistent
|
||||
depthStencilState.stencilBack = depthStencilState.stencilFront;
|
||||
depthStencilState.stencilReadMask = 0;
|
||||
depthStencilState.stencilWriteMask = 0;
|
||||
}
|
||||
@@ -245,11 +258,11 @@ wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device,
|
||||
.unclippedDepth = !rasterState.depthClamp &&
|
||||
device.HasFeature(wgpu::FeatureName::DepthClipControl)
|
||||
},
|
||||
.depthStencil = depthFormat != wgpu::TextureFormat::Undefined ? &depthStencilState: nullptr,
|
||||
.depthStencil = depthOrStencilRequested ? &depthStencilState: nullptr,
|
||||
.multisample = {
|
||||
.count = samplesCount,
|
||||
.mask = 0xFFFFFFFF,
|
||||
.alphaToCoverageEnabled = rasterState.alphaToCoverage
|
||||
.alphaToCoverageEnabled = (samplesCount > 1) && rasterState.alphaToCoverage
|
||||
},
|
||||
.fragment = nullptr // will add below if fragment module is included
|
||||
};
|
||||
|
||||
@@ -40,8 +40,8 @@ class WebGPUProgram;
|
||||
[[nodiscard]] wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const&,
|
||||
WebGPUProgram const&, WebGPUVertexBufferInfo const&, wgpu::PipelineLayout const&,
|
||||
RasterState const&, StencilState const&, PolygonOffset const&, PrimitiveType primitiveType,
|
||||
std::vector<wgpu::TextureFormat> const& colorFormats, wgpu::TextureFormat depthFormat,
|
||||
uint8_t samplesCount);
|
||||
std::vector<wgpu::TextureFormat> const& colorFormats, wgpu::TextureFormat const& depthStencilFormat,
|
||||
uint8_t samplesCount, bool requestedDepth, bool requestedStencil);
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
|
||||
@@ -0,0 +1,552 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "WebGPURenderPassMipmapGenerator.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
// The shaders expect a single-sampled 2D texture
|
||||
// with a format compatible with scalar sample format f32
|
||||
// (this is checked with the getCompatibilityFor function)
|
||||
constexpr uint32_t TEXTURE_BIND_GROUP_INDEX{ 0 };
|
||||
constexpr size_t TEXTURE_BIND_GROUP_ENTRY_SIZE{ 2 }; // sampler and texture
|
||||
constexpr uint32_t SAMPLER_BINDING_INDEX{ 0 };
|
||||
constexpr uint32_t TEXTURE_BINDING_INDEX{ 1 };
|
||||
constexpr std::string_view VERTEX_SHADER_ENTRY_POINT{ "vertexShaderMain" };
|
||||
constexpr std::string_view FRAGMENT_SHADER_ENTRY_POINT{ "fragmentShaderMain" };
|
||||
// Note: would be nice to generate this constexpr string from the constexpr variables above
|
||||
// to avoid them getting out of sync while still having the compile/build time optimization
|
||||
constexpr std::string_view SHADER_SOURCE{ R"(
|
||||
@group(0) @binding(0) var previousMipLevelSampler: sampler;
|
||||
@group(0) @binding(1) var previousMipLevelTexture: texture_2d<f32>;
|
||||
|
||||
struct VertexShaderOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) textureCoordinate: vec2<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vertexShaderMain(@builtin(vertex_index) vertexIndex: u32) -> VertexShaderOutput {
|
||||
let fullScreenTriangleVertices = array<vec2<f32>, 3>(
|
||||
vec2<f32>(-1.0, -1.0),
|
||||
vec2<f32>(3.0, -1.0),
|
||||
vec2<f32>(-1.0, 3.0)
|
||||
);
|
||||
var out: VertexShaderOutput;
|
||||
let vertex = fullScreenTriangleVertices[vertexIndex];
|
||||
out.position = vec4<f32>(vertex * 2.0 - 1.0, 0.0, 1.0);
|
||||
out.textureCoordinate = vec2<f32>(vertex.x, 1.0 - vertex.y);
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragmentShaderMain(in: VertexShaderOutput) -> @location(0) vec4<f32> {
|
||||
return textureSample(previousMipLevelTexture,
|
||||
previousMipLevelSampler,
|
||||
in.textureCoordinate);
|
||||
}
|
||||
)" };
|
||||
|
||||
[[nodiscard]] wgpu::Sampler createPreviousMipLevelSampler(wgpu::Device const& device) {
|
||||
const wgpu::SamplerDescriptor descriptor{
|
||||
.label = "previous_mip_level_sampler",
|
||||
.addressModeU = wgpu::AddressMode::ClampToEdge,
|
||||
.addressModeV = wgpu::AddressMode::ClampToEdge,
|
||||
.addressModeW = wgpu::AddressMode::Undefined, // only 2D textures supported for now
|
||||
.magFilter = wgpu::FilterMode::Linear,
|
||||
.minFilter = wgpu::FilterMode::Linear,
|
||||
.mipmapFilter = wgpu::MipmapFilterMode::Nearest, // unused for this purpose
|
||||
.lodMinClamp = 0.0f, // should not matter, just being consistently defined
|
||||
.lodMaxClamp = 32.0f, // should not matter, just being consistently defined
|
||||
.compare = wgpu::CompareFunction::Undefined, // should not matter, just being consistently
|
||||
// defined
|
||||
.maxAnisotropy = 1, // should not matter, just being consistently defined
|
||||
};
|
||||
const wgpu::Sampler sampler{ device.CreateSampler(&descriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create previous mip level sampler?";
|
||||
return sampler;
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::ShaderModule createShaderModule(wgpu::Device const& device) {
|
||||
wgpu::ShaderModuleWGSLDescriptor wgslDescriptor{};
|
||||
wgslDescriptor.code = SHADER_SOURCE.data();
|
||||
const wgpu::ShaderModuleDescriptor shaderModuleDescriptor{
|
||||
.nextInChain = &wgslDescriptor,
|
||||
.label = "render_pass_mipmap_generation_shaders",
|
||||
};
|
||||
const wgpu::ShaderModule shaderModule{ device.CreateShaderModule(&shaderModuleDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(shaderModule)
|
||||
<< "Failed to create shader module for render pass mipmap generation?";
|
||||
return shaderModule;
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::BindGroupLayout createTextureBindGroupLayout(wgpu::Device const& device) {
|
||||
const wgpu::BindGroupLayoutEntry bindGroupLayoutEntries[TEXTURE_BIND_GROUP_ENTRY_SIZE] {
|
||||
{
|
||||
.binding = SAMPLER_BINDING_INDEX,
|
||||
.visibility = wgpu::ShaderStage::Fragment,
|
||||
.sampler = {
|
||||
.type = wgpu::SamplerBindingType::Filtering, // only hardware accelerated filtering
|
||||
// supported
|
||||
},
|
||||
},
|
||||
{
|
||||
.binding = TEXTURE_BINDING_INDEX,
|
||||
.visibility = wgpu::ShaderStage::Fragment,
|
||||
.texture = {
|
||||
.sampleType = wgpu::TextureSampleType::Float, // only F32 scalar sample
|
||||
// type supported for now
|
||||
.viewDimension = wgpu::TextureViewDimension::e2D, // only 2D supported for now
|
||||
.multisampled = false, // MSAA textures not supported for now
|
||||
},
|
||||
},
|
||||
};
|
||||
const wgpu::BindGroupLayoutDescriptor textureBindGroupLayoutDescriptor{
|
||||
.label = "render_pass_mipmap_generation_texture_bind_group_layout",
|
||||
.entryCount = TEXTURE_BIND_GROUP_ENTRY_SIZE,
|
||||
.entries = bindGroupLayoutEntries,
|
||||
};
|
||||
const wgpu::BindGroupLayout textureBindGroupLayout{ device.CreateBindGroupLayout(
|
||||
&textureBindGroupLayoutDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(textureBindGroupLayout)
|
||||
<< "Failed to create texture bind group layout for render pass mipmap generation?";
|
||||
return textureBindGroupLayout;
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::PipelineLayout createPipelineLayout(wgpu::Device const& device,
|
||||
wgpu::BindGroupLayout const& textureBindGroupLayout) {
|
||||
const wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor{
|
||||
.label = "render_pass_mipmap_generation_pipeline_layout",
|
||||
.bindGroupLayoutCount = 1,
|
||||
.bindGroupLayouts = &textureBindGroupLayout,
|
||||
};
|
||||
const wgpu::PipelineLayout pipelineLayout{ device.CreatePipelineLayout(
|
||||
&pipelineLayoutDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipelineLayout)
|
||||
<< "Failed to create pipeline layout for render pass mipmap generation?";
|
||||
return pipelineLayout;
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::RenderPipeline createPipeline(wgpu::Device const& device,
|
||||
wgpu::ShaderModule const& shaderModule, wgpu::PipelineLayout const& pipelineLayout,
|
||||
const wgpu::TextureFormat textureFormat) {
|
||||
const wgpu::ColorTargetState colorTargetState{ .format = textureFormat };
|
||||
const wgpu::FragmentState fragmentState{
|
||||
.module = shaderModule,
|
||||
.entryPoint = FRAGMENT_SHADER_ENTRY_POINT,
|
||||
.constantCount = 0, // should not matter, just being consistently defined
|
||||
.constants = nullptr, // should not matter, just being consistently defined
|
||||
.targetCount = 1,
|
||||
.targets = &colorTargetState,
|
||||
};
|
||||
const wgpu::RenderPipelineDescriptor pipelineDescriptor{
|
||||
.label = "render_pass_mipmap_generation_pipeline",
|
||||
.layout = pipelineLayout,
|
||||
.vertex = {
|
||||
.module = shaderModule,
|
||||
.entryPoint = VERTEX_SHADER_ENTRY_POINT,
|
||||
.constantCount = 0, // should not matter, just being consistently defined
|
||||
.constants = nullptr, // should not matter, just being consistently defined
|
||||
.bufferCount = 0, // using hardcoded vertices in the shader for
|
||||
// performance and simplicity
|
||||
.buffers = nullptr,
|
||||
},
|
||||
.primitive = {
|
||||
.topology = wgpu::PrimitiveTopology::TriangleList,
|
||||
.stripIndexFormat = wgpu::IndexFormat::Undefined, // should not matter
|
||||
// (not using a strip topology),
|
||||
// just being consistently defined
|
||||
.frontFace = wgpu::FrontFace::Undefined, // should not matter (no cull mode),
|
||||
// just being consistently defined
|
||||
.cullMode = wgpu::CullMode::None,
|
||||
.unclippedDepth = false, // should not matter, just being consistently defined
|
||||
},
|
||||
.depthStencil = nullptr, // not applicable, but explicitly set here for
|
||||
// consistent definition
|
||||
.multisample = {
|
||||
.count = 1, // no multi-sampling
|
||||
.mask = 0xFFFFFFFF,
|
||||
.alphaToCoverageEnabled = false,
|
||||
},
|
||||
.fragment = &fragmentState,
|
||||
};
|
||||
const wgpu::RenderPipeline pipeline{ device.CreateRenderPipeline(&pipelineDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create pipeline for render pass mipmap generation?";
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WebGPURenderPassMipmapGenerator::WebGPURenderPassMipmapGenerator(wgpu::Device const& device)
|
||||
: mDevice{ device },
|
||||
mPreviousMipLevelSampler{ createPreviousMipLevelSampler(mDevice) },
|
||||
mShaderModule{ createShaderModule(mDevice) },
|
||||
mTextureBindGroupLayout{ createTextureBindGroupLayout(mDevice) },
|
||||
mPipelineLayout{ createPipelineLayout(mDevice, mTextureBindGroupLayout) } {}
|
||||
|
||||
WebGPURenderPassMipmapGenerator::FormatCompatibility
|
||||
WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat format,
|
||||
const wgpu::TextureDimension dimension, const uint32_t sampleCount) {
|
||||
// check that the format is compatible (currently expects that the scalar sample type is
|
||||
// f32)...
|
||||
switch (format) {
|
||||
case wgpu::TextureFormat::Depth16Unorm:
|
||||
case wgpu::TextureFormat::Depth24Plus:
|
||||
case wgpu::TextureFormat::Depth24PlusStencil8:
|
||||
case wgpu::TextureFormat::Depth32Float:
|
||||
case wgpu::TextureFormat::Depth32FloatStencil8:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "A depth texture format requires special sampler treatment and does "
|
||||
"not "
|
||||
"generally support linear filtering needed for render pass based "
|
||||
"mipmap "
|
||||
"generation, thus this texture is not supported, as it is a kind of "
|
||||
"depth texture.",
|
||||
};
|
||||
case wgpu::TextureFormat::Undefined:
|
||||
case wgpu::TextureFormat::External:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "Undefined or External textures are not supported for render pass "
|
||||
"based "
|
||||
"mipmap generation",
|
||||
};
|
||||
default:
|
||||
const ScalarSampleType scalarSampleType{ getScalarSampleTypeFrom(format) };
|
||||
switch (scalarSampleType) {
|
||||
case ScalarSampleType::U32:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "The provided texture format requires an unsigned integer "
|
||||
"sampler, which does not natively support linear filtering, "
|
||||
"which is needed for render pass based mipmap generation.",
|
||||
};
|
||||
case ScalarSampleType::I32:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "The provided texture format requires a signed integer "
|
||||
"sampler, which does not natively support linear filtering, "
|
||||
"which is needed for render pass based mipmap generation.",
|
||||
};
|
||||
case ScalarSampleType::F32:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// check that the dimensionality is compatible (currently expects 2D textures)...
|
||||
switch (dimension) {
|
||||
case wgpu::TextureDimension::Undefined:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "Texture has an undefined texture dimension (expected 2D), thus it is "
|
||||
"not "
|
||||
"supported by the render pass based mipmap generation implementation.",
|
||||
};
|
||||
case wgpu::TextureDimension::e1D:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "Texture has a 1D texture dimension (expected 2D), thus it is not "
|
||||
"current supported by the render pass based mipmap generation "
|
||||
"implementation (but could be extended to later if needed).",
|
||||
};
|
||||
case wgpu::TextureDimension::e3D:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "Texture has a 3D texture dimension (expected 2D), thus it is not "
|
||||
"current supported by the render pass based mipmap generation "
|
||||
"implementation (but could be extended to later if REALLY needed).",
|
||||
};
|
||||
case wgpu::TextureDimension::e2D:
|
||||
break;
|
||||
}
|
||||
// check that the texture is single-sampled (for now)...
|
||||
if (sampleCount > 1) {
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "Multi-sampled textures are not currently supported with the render pass "
|
||||
"based mipmap generation implementation (but could be extended to later if "
|
||||
"REALLY needed. The idea would be to resolve the texture to a single "
|
||||
"sampled "
|
||||
"texture before generating single-sample mipmaps, but that may still not "
|
||||
"necessarily be what is desired.).",
|
||||
};
|
||||
}
|
||||
return { .compatible = true, .reason = "" };
|
||||
}
|
||||
|
||||
constexpr WebGPURenderPassMipmapGenerator::ScalarSampleType
|
||||
WebGPURenderPassMipmapGenerator::getScalarSampleTypeFrom(const wgpu::TextureFormat format) {
|
||||
switch (format) {
|
||||
case wgpu::TextureFormat::R8Uint:
|
||||
case wgpu::TextureFormat::R16Uint:
|
||||
case wgpu::TextureFormat::R32Uint:
|
||||
case wgpu::TextureFormat::Stencil8:
|
||||
case wgpu::TextureFormat::RG8Uint:
|
||||
case wgpu::TextureFormat::RG16Uint:
|
||||
case wgpu::TextureFormat::RGBA8Uint:
|
||||
case wgpu::TextureFormat::RGB10A2Uint:
|
||||
case wgpu::TextureFormat::RG32Uint:
|
||||
case wgpu::TextureFormat::RGBA16Uint:
|
||||
case wgpu::TextureFormat::RGBA32Uint:
|
||||
return ScalarSampleType::U32;
|
||||
case wgpu::TextureFormat::R8Sint:
|
||||
case wgpu::TextureFormat::R16Sint:
|
||||
case wgpu::TextureFormat::R32Sint:
|
||||
case wgpu::TextureFormat::RG8Sint:
|
||||
case wgpu::TextureFormat::RG16Sint:
|
||||
case wgpu::TextureFormat::RGBA8Sint:
|
||||
case wgpu::TextureFormat::RG32Sint:
|
||||
case wgpu::TextureFormat::RGBA16Sint:
|
||||
case wgpu::TextureFormat::RGBA32Sint:
|
||||
return ScalarSampleType::I32;
|
||||
case wgpu::TextureFormat::R8Snorm:
|
||||
case wgpu::TextureFormat::R8Unorm:
|
||||
case wgpu::TextureFormat::R16Float:
|
||||
case wgpu::TextureFormat::RG16Float:
|
||||
case wgpu::TextureFormat::RG8Unorm:
|
||||
case wgpu::TextureFormat::RG8Snorm:
|
||||
case wgpu::TextureFormat::R32Float:
|
||||
case wgpu::TextureFormat::RGBA8Unorm:
|
||||
case wgpu::TextureFormat::RGBA8UnormSrgb:
|
||||
case wgpu::TextureFormat::RGBA8Snorm:
|
||||
case wgpu::TextureFormat::BGRA8Unorm:
|
||||
case wgpu::TextureFormat::BGRA8UnormSrgb:
|
||||
case wgpu::TextureFormat::RGB10A2Unorm:
|
||||
case wgpu::TextureFormat::RG11B10Ufloat:
|
||||
case wgpu::TextureFormat::RGB9E5Ufloat:
|
||||
case wgpu::TextureFormat::RG32Float:
|
||||
case wgpu::TextureFormat::RGBA16Float:
|
||||
case wgpu::TextureFormat::RGBA32Float:
|
||||
case wgpu::TextureFormat::BC1RGBAUnorm:
|
||||
case wgpu::TextureFormat::BC1RGBAUnormSrgb:
|
||||
case wgpu::TextureFormat::BC2RGBAUnorm:
|
||||
case wgpu::TextureFormat::BC2RGBAUnormSrgb:
|
||||
case wgpu::TextureFormat::BC3RGBAUnorm:
|
||||
case wgpu::TextureFormat::BC3RGBAUnormSrgb:
|
||||
case wgpu::TextureFormat::BC4RUnorm:
|
||||
case wgpu::TextureFormat::BC4RSnorm:
|
||||
case wgpu::TextureFormat::BC5RGUnorm:
|
||||
case wgpu::TextureFormat::BC5RGSnorm:
|
||||
case wgpu::TextureFormat::BC6HRGBUfloat:
|
||||
case wgpu::TextureFormat::BC6HRGBFloat:
|
||||
case wgpu::TextureFormat::BC7RGBAUnorm:
|
||||
case wgpu::TextureFormat::BC7RGBAUnormSrgb:
|
||||
case wgpu::TextureFormat::ETC2RGB8Unorm:
|
||||
case wgpu::TextureFormat::ETC2RGB8UnormSrgb:
|
||||
case wgpu::TextureFormat::ETC2RGB8A1Unorm:
|
||||
case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb:
|
||||
case wgpu::TextureFormat::ETC2RGBA8Unorm:
|
||||
case wgpu::TextureFormat::ETC2RGBA8UnormSrgb:
|
||||
case wgpu::TextureFormat::EACR11Unorm:
|
||||
case wgpu::TextureFormat::EACR11Snorm:
|
||||
case wgpu::TextureFormat::EACRG11Unorm:
|
||||
case wgpu::TextureFormat::EACRG11Snorm:
|
||||
case wgpu::TextureFormat::ASTC4x4Unorm:
|
||||
case wgpu::TextureFormat::ASTC4x4UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC5x4Unorm:
|
||||
case wgpu::TextureFormat::ASTC5x4UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC5x5Unorm:
|
||||
case wgpu::TextureFormat::ASTC5x5UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC6x5Unorm:
|
||||
case wgpu::TextureFormat::ASTC6x5UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC6x6Unorm:
|
||||
case wgpu::TextureFormat::ASTC6x6UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC8x5Unorm:
|
||||
case wgpu::TextureFormat::ASTC8x5UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC8x6Unorm:
|
||||
case wgpu::TextureFormat::ASTC8x6UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC8x8Unorm:
|
||||
case wgpu::TextureFormat::ASTC8x8UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC10x5Unorm:
|
||||
case wgpu::TextureFormat::ASTC10x5UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC10x6Unorm:
|
||||
case wgpu::TextureFormat::ASTC10x6UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC10x8Unorm:
|
||||
case wgpu::TextureFormat::ASTC10x8UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC10x10Unorm:
|
||||
case wgpu::TextureFormat::ASTC10x10UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC12x10Unorm:
|
||||
case wgpu::TextureFormat::ASTC12x10UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC12x12Unorm:
|
||||
case wgpu::TextureFormat::ASTC12x12UnormSrgb:
|
||||
case wgpu::TextureFormat::R16Unorm:
|
||||
case wgpu::TextureFormat::RG16Unorm:
|
||||
case wgpu::TextureFormat::RGBA16Unorm:
|
||||
case wgpu::TextureFormat::R16Snorm:
|
||||
case wgpu::TextureFormat::RG16Snorm:
|
||||
case wgpu::TextureFormat::RGBA16Snorm:
|
||||
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
|
||||
case wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm:
|
||||
case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm:
|
||||
case wgpu::TextureFormat::R8BG8Biplanar422Unorm:
|
||||
case wgpu::TextureFormat::R8BG8Biplanar444Unorm:
|
||||
case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm:
|
||||
case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm:
|
||||
return ScalarSampleType::F32;
|
||||
case wgpu::TextureFormat::Depth16Unorm:
|
||||
case wgpu::TextureFormat::Depth24Plus:
|
||||
case wgpu::TextureFormat::Depth24PlusStencil8:
|
||||
case wgpu::TextureFormat::Depth32Float:
|
||||
case wgpu::TextureFormat::Depth32FloatStencil8:
|
||||
PANIC_POSTCONDITION("A depth texture format requires special sampler treatment and "
|
||||
"does not generally support linear filtering (needed for mipmap "
|
||||
"generation). Thus no applicable scalar sample type for %d",
|
||||
format);
|
||||
break;
|
||||
case wgpu::TextureFormat::Undefined:
|
||||
case wgpu::TextureFormat::External:
|
||||
PANIC_POSTCONDITION("No scalar sample type for texture format %d", format);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WebGPURenderPassMipmapGenerator::generateMipmaps(wgpu::Queue const& queue,
|
||||
wgpu::Texture const& texture) {
|
||||
const uint32_t mipLevelCount{ texture.GetMipLevelCount() };
|
||||
if (mipLevelCount < 2) {
|
||||
return; // nothing to do
|
||||
}
|
||||
wgpu::RenderPipeline const& pipeline{ getOrCreatePipelineFor(texture.GetFormat()) };
|
||||
const wgpu::CommandEncoderDescriptor commandEncoderDescriptor{
|
||||
.label = "mipmap_generation_render_pass_cmd_encoder",
|
||||
};
|
||||
const wgpu::CommandEncoder commandEncoder{ mDevice.CreateCommandEncoder(
|
||||
&commandEncoderDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(commandEncoder)
|
||||
<< "Failed to create command encoder for layer for render pass mipmap generation?";
|
||||
const uint32_t layerCount{ texture.GetDepthOrArrayLayers() };
|
||||
for (uint32_t layer = 0; layer < layerCount; layer++) {
|
||||
for (uint32_t mipLevel = 1; mipLevel < mipLevelCount; mipLevel++) {
|
||||
generateMipmap(commandEncoder, texture, pipeline, layer, mipLevel);
|
||||
}
|
||||
}
|
||||
// submit the command buffer...
|
||||
const wgpu::CommandBufferDescriptor commandBufferDescriptor{
|
||||
.label = "mipmap_generation_render_pass_cmd_buffer",
|
||||
};
|
||||
const wgpu::CommandBuffer commandBuffer{ commandEncoder.Finish(&commandBufferDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(commandBuffer)
|
||||
<< "Failed to create command buffer for layer for render pass mipmap generation?";
|
||||
queue.Submit(1, &commandBuffer);
|
||||
}
|
||||
|
||||
void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const& commandEncoder,
|
||||
const wgpu::Texture& texture, const wgpu::RenderPipeline& pipeline, const uint32_t layer,
|
||||
const uint32_t mipLevel) {
|
||||
// create texture views for this pass...
|
||||
const wgpu::TextureFormat format{ texture.GetFormat() };
|
||||
// only 2D textures supported at this time...
|
||||
const wgpu::TextureViewDimension dimension{ wgpu::TextureViewDimension::e2D };
|
||||
const wgpu::TextureViewDescriptor sourceViewDescriptor{
|
||||
.label = "previous_mip_level_texture_view",
|
||||
.format = format,
|
||||
.dimension = dimension,
|
||||
.baseMipLevel = mipLevel - 1,
|
||||
.mipLevelCount = 1,
|
||||
.baseArrayLayer = layer,
|
||||
.arrayLayerCount = 1,
|
||||
.aspect = wgpu::TextureAspect::All,
|
||||
.usage = wgpu::TextureUsage::TextureBinding,
|
||||
};
|
||||
const wgpu::TextureView sourceView{ texture.CreateView(&sourceViewDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create source texture view for layer " << layer << " and mip level "
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
const wgpu::TextureViewDescriptor destinationViewDescriptor{
|
||||
.label = "next_mip_level_texture_view",
|
||||
.format = format,
|
||||
.dimension = dimension,
|
||||
.baseMipLevel = mipLevel,
|
||||
.mipLevelCount = 1,
|
||||
.baseArrayLayer = layer,
|
||||
.arrayLayerCount = 1,
|
||||
.aspect = wgpu::TextureAspect::All,
|
||||
.usage = wgpu::TextureUsage::RenderAttachment,
|
||||
};
|
||||
const wgpu::TextureView destinationView{ texture.CreateView(&destinationViewDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create destination texture view for layer " << layer << " and mip level "
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
// create the render pass...
|
||||
const wgpu::BindGroupEntry textureBindGroupEntries[TEXTURE_BIND_GROUP_ENTRY_SIZE]{
|
||||
{
|
||||
.binding = SAMPLER_BINDING_INDEX,
|
||||
.sampler = mPreviousMipLevelSampler,
|
||||
},
|
||||
{
|
||||
.binding = TEXTURE_BINDING_INDEX,
|
||||
.textureView = sourceView,
|
||||
},
|
||||
};
|
||||
const wgpu::BindGroupDescriptor textureBindGroupDescriptor{
|
||||
.label = "render_pass_mipmap_generation_texture_bind_group",
|
||||
.layout = mTextureBindGroupLayout,
|
||||
.entryCount = TEXTURE_BIND_GROUP_ENTRY_SIZE,
|
||||
.entries = textureBindGroupEntries,
|
||||
};
|
||||
const wgpu::BindGroup textureBindGroup{ mDevice.CreateBindGroup(&textureBindGroupDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create texture bind group for layer " << layer << " and mip level "
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
const wgpu::RenderPassColorAttachment colorAttachment{
|
||||
.view = destinationView,
|
||||
.depthSlice = wgpu::kDepthSliceUndefined, // not applicable
|
||||
.resolveTarget = nullptr, // not applicable
|
||||
.loadOp = wgpu::LoadOp::Clear,
|
||||
.storeOp = wgpu::StoreOp::Store,
|
||||
.clearValue = { .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 },
|
||||
};
|
||||
const wgpu::RenderPassDescriptor renderPassDescriptor{
|
||||
.label = "mipmap_generation_render_pass",
|
||||
.colorAttachmentCount = 1,
|
||||
.colorAttachments = &colorAttachment,
|
||||
.depthStencilAttachment = nullptr, // not applicable
|
||||
.occlusionQuerySet = nullptr, // not applicable
|
||||
.timestampWrites = nullptr, // not applicable
|
||||
};
|
||||
const wgpu::RenderPassEncoder renderPassEncoder{ commandEncoder.BeginRenderPass(
|
||||
&renderPassDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(renderPassEncoder)
|
||||
<< "Failed to create render pass encoder for layer " << layer << " and mip level "
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
renderPassEncoder.SetPipeline(pipeline);
|
||||
renderPassEncoder.SetBindGroup(TEXTURE_BIND_GROUP_INDEX, textureBindGroup);
|
||||
renderPassEncoder.Draw(3); // draw the full-screen triangle
|
||||
// with hard-coded vertices in the shader
|
||||
renderPassEncoder.End();
|
||||
}
|
||||
|
||||
wgpu::RenderPipeline& WebGPURenderPassMipmapGenerator::getOrCreatePipelineFor(
|
||||
const wgpu::TextureFormat textureFormat) {
|
||||
const uint32_t key{ static_cast<uint32_t>(textureFormat) };
|
||||
if (mPipelineByTextureFormat.find(key) == mPipelineByTextureFormat.end()) {
|
||||
mPipelineByTextureFormat[key] =
|
||||
createPipeline(mDevice, mShaderModule, mPipelineLayout, textureFormat);
|
||||
}
|
||||
return mPipelineByTextureFormat[key];
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_WEBGPURENDERPASSMIPMAPGENERATOR_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPURENDERPASSMIPMAPGENERATOR_H
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPURenderPassMipmapGenerator final {
|
||||
public:
|
||||
struct FormatCompatibility final {
|
||||
bool compatible{ false };
|
||||
std::string_view reason;
|
||||
};
|
||||
|
||||
[[nodiscard]] static FormatCompatibility getCompatibilityFor(wgpu::TextureFormat,
|
||||
wgpu::TextureDimension, uint32_t sampleCount);
|
||||
|
||||
explicit WebGPURenderPassMipmapGenerator(wgpu::Device const&);
|
||||
|
||||
void generateMipmaps(wgpu::Queue const&, wgpu::Texture const&);
|
||||
|
||||
private:
|
||||
enum class ScalarSampleType : uint8_t {
|
||||
F32,
|
||||
I32,
|
||||
U32,
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr static ScalarSampleType getScalarSampleTypeFrom(
|
||||
wgpu::TextureFormat format);
|
||||
|
||||
[[nodiscard]] wgpu::RenderPipeline& getOrCreatePipelineFor(wgpu::TextureFormat);
|
||||
|
||||
void generateMipmap(wgpu::CommandEncoder const&, wgpu::Texture const&,
|
||||
wgpu::RenderPipeline const&, uint32_t layer, uint32_t mipLevel);
|
||||
|
||||
wgpu::Device const& mDevice;
|
||||
const wgpu::Sampler mPreviousMipLevelSampler{ nullptr };
|
||||
const wgpu::ShaderModule mShaderModule{ nullptr };
|
||||
const wgpu::BindGroupLayout mTextureBindGroupLayout{ nullptr };
|
||||
const wgpu::PipelineLayout mPipelineLayout{ nullptr };
|
||||
tsl::robin_map<uint32_t, wgpu::RenderPipeline> mPipelineByTextureFormat;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_WEBGPURENDERPASSMIPMAPGENERATOR_H
|
||||
@@ -16,7 +16,11 @@
|
||||
|
||||
#include "WebGPURenderTarget.h"
|
||||
|
||||
#include "WebGPUTexture.h"
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <private/backend/BackendUtils.h>
|
||||
@@ -26,30 +30,120 @@
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Panics if the number of samples in the original attachment textures do not match.
|
||||
* @return The number of samples in the original attachment textures (the number/count in each one).
|
||||
* Returns 0 if there are no attachments.
|
||||
*/
|
||||
[[nodiscard]] uint8_t createMsaaSidecarTextures(const uint8_t renderTargetSampleCount,
|
||||
const TargetBufferFlags targetFlags, MRT const& colorAttachments,
|
||||
TargetBufferInfo const& depthAttachment, TargetBufferInfo const& stencilAttachment,
|
||||
std::function<WebGPUTexture*(const Handle<HwTexture>)> const& getWebGPUTexture,
|
||||
wgpu::Device const& device) {
|
||||
struct Target final {
|
||||
std::string_view name;
|
||||
TargetBufferFlags flag{ TargetBufferFlags::NONE };
|
||||
size_t colorIndex{ 0 };
|
||||
};
|
||||
// assuming 8 colors. hopefully the following static_assert will get tripped if another
|
||||
// color ever gets added
|
||||
static_assert(
|
||||
(TargetBufferFlags::COLOR0 | TargetBufferFlags::COLOR1 | TargetBufferFlags::COLOR2 |
|
||||
TargetBufferFlags::COLOR3 | TargetBufferFlags::COLOR4 |
|
||||
TargetBufferFlags::COLOR5 | TargetBufferFlags::COLOR6 |
|
||||
TargetBufferFlags::COLOR7) == TargetBufferFlags::COLOR_ALL);
|
||||
const static std::array TARGETS{
|
||||
Target{ .name = "COLOR0", .flag = TargetBufferFlags::COLOR0, .colorIndex = 0 },
|
||||
Target{ .name = "COLOR1", .flag = TargetBufferFlags::COLOR1, .colorIndex = 1 },
|
||||
Target{ .name = "COLOR2", .flag = TargetBufferFlags::COLOR2, .colorIndex = 2 },
|
||||
Target{ .name = "COLOR3", .flag = TargetBufferFlags::COLOR3, .colorIndex = 3 },
|
||||
Target{ .name = "COLOR4", .flag = TargetBufferFlags::COLOR4, .colorIndex = 4 },
|
||||
Target{ .name = "COLOR5", .flag = TargetBufferFlags::COLOR5, .colorIndex = 5 },
|
||||
Target{ .name = "COLOR6", .flag = TargetBufferFlags::COLOR6, .colorIndex = 6 },
|
||||
Target{ .name = "COLOR7", .flag = TargetBufferFlags::COLOR7, .colorIndex = 7 },
|
||||
Target{ .name = "DEPTH", .flag = TargetBufferFlags::DEPTH },
|
||||
Target{ .name = "STENCIL", .flag = TargetBufferFlags::STENCIL },
|
||||
};
|
||||
#ifndef NDEBUG
|
||||
for (auto const& target: TARGETS) {
|
||||
assert_invariant(target.colorIndex <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT &&
|
||||
"color index is >= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT, which should "
|
||||
"not be possible and could lead to us indexing into the colorAttachments "
|
||||
"array beyond its bounds.");
|
||||
}
|
||||
#endif
|
||||
bool firstAttachment{ true };
|
||||
uint8_t sampleCountPerAttachment{ 0 };
|
||||
for (auto const& target: TARGETS) {
|
||||
if (any(targetFlags & target.flag)) {
|
||||
const Handle<HwTexture> textureHandle{
|
||||
target.flag == TargetBufferFlags::DEPTH
|
||||
? depthAttachment.handle
|
||||
: (target.flag == TargetBufferFlags::STENCIL
|
||||
? stencilAttachment.handle
|
||||
: colorAttachments[target.colorIndex].handle)
|
||||
};
|
||||
WebGPUTexture* const texture{ getWebGPUTexture(textureHandle) };
|
||||
assert_invariant(texture != nullptr && "target flag indicate the use of an attachment "
|
||||
"for which we do not have a texture?");
|
||||
if (firstAttachment) {
|
||||
sampleCountPerAttachment = texture->samples;
|
||||
firstAttachment = false;
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION(texture->samples == sampleCountPerAttachment)
|
||||
<< target.name << " attachment texture has " << +texture->samples
|
||||
<< " but the other attachment(s) have " << +sampleCountPerAttachment;
|
||||
if (renderTargetSampleCount > 1 && sampleCountPerAttachment == 1) {
|
||||
texture->createMsaaSidecarTextureIfNotAlreadyCreated(renderTargetSampleCount,
|
||||
device);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_invariant(sampleCountPerAttachment);
|
||||
return sampleCountPerAttachment;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WebGPURenderTarget::WebGPURenderTarget(const uint32_t width, const uint32_t height,
|
||||
const uint8_t samples, const uint8_t layerCount, MRT const& colorAttachmentsMRT,
|
||||
Attachment const& depthAttachmentInfo, Attachment const& stencilAttachmentInfo)
|
||||
Attachment const& depthAttachmentInfo, Attachment const& stencilAttachmentInfo,
|
||||
TargetBufferFlags const& targetFlags,
|
||||
std::function<WebGPUTexture*(const Handle<HwTexture>)> const& getWebGPUTexture,
|
||||
wgpu::Device const& device)
|
||||
: HwRenderTarget{ width, height },
|
||||
mDefaultRenderTarget{ false },
|
||||
mTargetFlags{ targetFlags },
|
||||
mSamples{ samples },
|
||||
mLayerCount{ layerCount },
|
||||
mColorAttachments{ colorAttachmentsMRT },
|
||||
mDepthAttachment{ depthAttachmentInfo },
|
||||
mStencilAttachment{ stencilAttachmentInfo } {
|
||||
// TODO consider making this an array
|
||||
mColorAttachmentDescriptors.reserve(MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
|
||||
mStencilAttachment{ stencilAttachmentInfo },
|
||||
mSampleCountPerAttachment{ createMsaaSidecarTextures(mSamples, mTargetFlags,
|
||||
mColorAttachments, mDepthAttachment, mStencilAttachment, getWebGPUTexture, device) } {
|
||||
// TODO consider possibly making this an array (that would avoid a heap allocation, but the
|
||||
// concern is the size limitation on the handle itself)
|
||||
mColorAttachmentDesc.reserve(MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
|
||||
}
|
||||
|
||||
// Default constructor for the default render target
|
||||
WebGPURenderTarget::WebGPURenderTarget()
|
||||
: HwRenderTarget{ 0, 0 },
|
||||
mDefaultRenderTarget{ true },
|
||||
mTargetFlags{ TargetBufferFlags::NONE },
|
||||
mSamples{ 1 },
|
||||
mLayerCount{ 1 } {}
|
||||
mLayerCount{ 1 },
|
||||
mSampleCountPerAttachment{ 1 } {}
|
||||
|
||||
wgpu::LoadOp WebGPURenderTarget::getLoadOperation(RenderPassParams const& params,
|
||||
const TargetBufferFlags bufferToOperateOn) {
|
||||
@@ -57,7 +151,7 @@ wgpu::LoadOp WebGPURenderTarget::getLoadOperation(RenderPassParams const& params
|
||||
return wgpu::LoadOp::Clear;
|
||||
}
|
||||
if (any(params.flags.discardStart & bufferToOperateOn)) {
|
||||
return wgpu::LoadOp::Clear; // Or wgpu::LoadOp::Undefined if clear is not desired on discard
|
||||
return wgpu::LoadOp::Clear;
|
||||
}
|
||||
return wgpu::LoadOp::Load;
|
||||
}
|
||||
@@ -73,128 +167,137 @@ wgpu::StoreOp WebGPURenderTarget::getStoreOperation(RenderPassParams const& para
|
||||
void WebGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor& outDescriptor,
|
||||
RenderPassParams const& params, wgpu::TextureView const& defaultColorTextureView,
|
||||
wgpu::TextureView const& defaultDepthStencilTextureView,
|
||||
wgpu::TextureFormat const& defaultDepthStencilFormat,
|
||||
wgpu::TextureView const* customColorTextureViews, uint32_t customColorTextureViewCount,
|
||||
wgpu::TextureView const& customDepthTextureView,
|
||||
wgpu::TextureView const& customStencilTextureView,
|
||||
const wgpu::TextureFormat customDepthFormat,
|
||||
const wgpu::TextureFormat customStencilFormat) {
|
||||
mColorAttachmentDescriptors.clear();
|
||||
mHasDepthStencilAttachment = false;
|
||||
wgpu::TextureView const* customColorTextureViews,
|
||||
wgpu::TextureView const* customColorMsaaSidecarTextureViews,
|
||||
uint32_t customColorTextureViewCount,
|
||||
wgpu::TextureView const& customDepthStencilTextureView,
|
||||
wgpu::TextureView const& customDepthStencilMsaaSidecarTextureView) {
|
||||
const bool hasMsaaSidecars{ (customColorTextureViewCount > 0 &&
|
||||
customColorMsaaSidecarTextureViews[0]) ||
|
||||
customDepthStencilMsaaSidecarTextureView };
|
||||
// either all the textures have MSAA sidecars or none of them do
|
||||
if (hasMsaaSidecars) {
|
||||
FILAMENT_CHECK_PRECONDITION(std::all_of(customColorMsaaSidecarTextureViews,
|
||||
customColorMsaaSidecarTextureViews + customColorTextureViewCount,
|
||||
[](wgpu::TextureView const& msaaView) { return msaaView != nullptr; }))
|
||||
<< "A color or depth/stencil attachment texture has a MSAA sidecar but at least "
|
||||
"one other color attachment texture does not.";
|
||||
FILAMENT_CHECK_PRECONDITION(customDepthStencilMsaaSidecarTextureView != nullptr)
|
||||
<< "The color attachment texture(s) have MSAA sidecar(s) but the depth/stencil "
|
||||
"texture does not.";
|
||||
} else {
|
||||
FILAMENT_CHECK_PRECONDITION(std::all_of(customColorMsaaSidecarTextureViews,
|
||||
customColorMsaaSidecarTextureViews + customColorTextureViewCount,
|
||||
[](wgpu::TextureView const& msaaView) { return msaaView == nullptr; }))
|
||||
<< "A color or depth/stencil attachment texture does not have a MSAA sidecar but "
|
||||
"at least one color attachment texture does.";
|
||||
FILAMENT_CHECK_PRECONDITION(customDepthStencilMsaaSidecarTextureView == nullptr)
|
||||
<< "Custom color textures for the render target do not have MSAA sidecar(s) but "
|
||||
"the depth/stencil texture does.";
|
||||
}
|
||||
|
||||
mColorAttachmentDesc.clear();
|
||||
|
||||
const bool hasDepth = any(mTargetFlags & TargetBufferFlags::DEPTH);
|
||||
const bool hasStencil = any(mTargetFlags & TargetBufferFlags::STENCIL);
|
||||
mHasDepthStencilAttachment = hasDepth || hasStencil;
|
||||
bool const depthReadOnly = (params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0;
|
||||
|
||||
// Color attachments
|
||||
if (mDefaultRenderTarget) {
|
||||
assert_invariant(defaultColorTextureView);
|
||||
mColorAttachmentDescriptors.push_back({ .view = defaultColorTextureView,
|
||||
mColorAttachmentDesc.push_back({
|
||||
.view = defaultColorTextureView,
|
||||
.resolveTarget = nullptr,
|
||||
.loadOp = WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::COLOR0),
|
||||
.storeOp = WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::COLOR0),
|
||||
.clearValue = { params.clearColor.r, params.clearColor.g, params.clearColor.b,
|
||||
params.clearColor.a } });
|
||||
|
||||
if (defaultDepthStencilTextureView) {
|
||||
mDepthStencilAttachmentDescriptor = {
|
||||
.view = defaultDepthStencilTextureView,
|
||||
.depthLoadOp =
|
||||
WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::DEPTH),
|
||||
.depthStoreOp =
|
||||
WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::DEPTH),
|
||||
.depthClearValue = static_cast<float>(params.clearDepth),
|
||||
.depthReadOnly =
|
||||
(params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0,
|
||||
.stencilLoadOp = wgpu::LoadOp::Undefined,
|
||||
.stencilStoreOp = wgpu::StoreOp::Undefined,
|
||||
.stencilClearValue = params.clearStencil,
|
||||
.stencilReadOnly =
|
||||
(params.readOnlyDepthStencil & RenderPassParams::READONLY_STENCIL) > 0,
|
||||
};
|
||||
|
||||
if (defaultDepthStencilFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
|
||||
defaultDepthStencilFormat == wgpu::TextureFormat::Depth32FloatStencil8 ||
|
||||
defaultDepthStencilFormat == wgpu::TextureFormat::Stencil8) {
|
||||
mDepthStencilAttachmentDescriptor.stencilLoadOp =
|
||||
WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::STENCIL);
|
||||
mDepthStencilAttachmentDescriptor.stencilStoreOp =
|
||||
WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::STENCIL);
|
||||
}
|
||||
mHasDepthStencilAttachment = true;
|
||||
}
|
||||
} else { // Custom Render Target
|
||||
.clearValue = {
|
||||
.r = params.clearColor.r,
|
||||
.g = params.clearColor.g,
|
||||
.b = params.clearColor.b,
|
||||
.a = params.clearColor.a,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
for (uint32_t i = 0; i < customColorTextureViewCount; ++i) {
|
||||
if (customColorTextureViews[i]) {
|
||||
mColorAttachmentDescriptors.push_back({ .view = customColorTextureViews[i],
|
||||
// .resolveTarget = nullptr; // TODO: MSAA resolve for custom RT
|
||||
const wgpu::TextureView msaaSidecar{ customColorMsaaSidecarTextureViews[i] };
|
||||
mColorAttachmentDesc.push_back({
|
||||
.view = hasMsaaSidecars ? msaaSidecar : customColorTextureViews[i],
|
||||
.resolveTarget = hasMsaaSidecars ? customColorTextureViews[i] : nullptr,
|
||||
.loadOp =
|
||||
WebGPURenderTarget::getLoadOperation(params, getTargetBufferFlagsAt(i)),
|
||||
.storeOp = WebGPURenderTarget::getStoreOperation(params,
|
||||
getTargetBufferFlagsAt(i)),
|
||||
.clearValue = { .r = params.clearColor.r,
|
||||
.clearValue = {
|
||||
.r = params.clearColor.r,
|
||||
.g = params.clearColor.g,
|
||||
.b = params.clearColor.b,
|
||||
.a = params.clearColor.a } });
|
||||
.a = params.clearColor.a,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
outDescriptor.colorAttachmentCount = mColorAttachmentDesc.size();
|
||||
outDescriptor.colorAttachments = mColorAttachmentDesc.data();
|
||||
|
||||
// Depth/Stencil Attachments
|
||||
if (mHasDepthStencilAttachment) {
|
||||
wgpu::TextureView depthStencilViewToUse{ nullptr };
|
||||
if (mDefaultRenderTarget) {
|
||||
depthStencilViewToUse = defaultDepthStencilTextureView;
|
||||
assert_invariant(depthStencilViewToUse);
|
||||
} else {
|
||||
if (customDepthStencilTextureView) {
|
||||
depthStencilViewToUse = hasMsaaSidecars ? customDepthStencilMsaaSidecarTextureView
|
||||
: customDepthStencilTextureView;
|
||||
}
|
||||
}
|
||||
|
||||
FILAMENT_CHECK_POSTCONDITION(!(customDepthTextureView && customStencilTextureView))
|
||||
<< "WebGPU CANNOT support separate texture views for depth + stencil. depth + "
|
||||
"stencil needs to be in one texture view";
|
||||
|
||||
const bool hasStencil =
|
||||
customStencilTextureView ||
|
||||
(customDepthFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
|
||||
customDepthFormat == wgpu::TextureFormat::Depth32FloatStencil8);
|
||||
|
||||
const bool hasDepth =
|
||||
customDepthTextureView ||
|
||||
(customStencilFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
|
||||
customDepthFormat == wgpu::TextureFormat::Depth32FloatStencil8);
|
||||
|
||||
if (customDepthTextureView || customStencilTextureView) {
|
||||
assert_invariant((hasDepth || hasStencil) &&
|
||||
"Depth or Texture view without a valid texture format");
|
||||
mDepthStencilAttachmentDescriptor = {};
|
||||
mDepthStencilAttachmentDescriptor.view =
|
||||
customDepthTextureView ? customDepthTextureView : customStencilTextureView;
|
||||
if (depthStencilViewToUse) {
|
||||
mDepthStencilAttachmentDesc = {};
|
||||
mDepthStencilAttachmentDesc.view = depthStencilViewToUse;
|
||||
|
||||
if (hasDepth) {
|
||||
mDepthStencilAttachmentDescriptor.depthLoadOp =
|
||||
WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::DEPTH);
|
||||
mDepthStencilAttachmentDescriptor.depthStoreOp =
|
||||
WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::DEPTH);
|
||||
mDepthStencilAttachmentDescriptor.depthClearValue =
|
||||
static_cast<float>(params.clearDepth);
|
||||
mDepthStencilAttachmentDescriptor.depthReadOnly =
|
||||
(params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0;
|
||||
mDepthStencilAttachmentDesc.depthLoadOp =
|
||||
depthReadOnly ? wgpu::LoadOp::Undefined
|
||||
: getLoadOperation(params, TargetBufferFlags::DEPTH);
|
||||
mDepthStencilAttachmentDesc.depthStoreOp =
|
||||
depthReadOnly ? wgpu::StoreOp::Undefined
|
||||
: getStoreOperation(params, TargetBufferFlags::DEPTH);
|
||||
mDepthStencilAttachmentDesc.depthClearValue = static_cast<float>(params.clearDepth);
|
||||
mDepthStencilAttachmentDesc.depthReadOnly = depthReadOnly;
|
||||
} else {
|
||||
mDepthStencilAttachmentDescriptor.depthLoadOp = wgpu::LoadOp::Undefined;
|
||||
mDepthStencilAttachmentDescriptor.depthStoreOp = wgpu::StoreOp::Undefined;
|
||||
mDepthStencilAttachmentDescriptor.depthReadOnly = true;
|
||||
mDepthStencilAttachmentDesc.depthLoadOp = wgpu::LoadOp::Undefined;
|
||||
mDepthStencilAttachmentDesc.depthStoreOp = wgpu::StoreOp::Undefined;
|
||||
mDepthStencilAttachmentDesc.depthReadOnly = true;
|
||||
}
|
||||
|
||||
if (hasStencil) {
|
||||
mDepthStencilAttachmentDescriptor.stencilLoadOp =
|
||||
WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::STENCIL);
|
||||
mDepthStencilAttachmentDescriptor.stencilStoreOp =
|
||||
WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::STENCIL);
|
||||
mDepthStencilAttachmentDescriptor.stencilClearValue = params.clearStencil;
|
||||
mDepthStencilAttachmentDescriptor.stencilReadOnly =
|
||||
mDepthStencilAttachmentDesc.stencilLoadOp =
|
||||
getLoadOperation(params, TargetBufferFlags::STENCIL);
|
||||
mDepthStencilAttachmentDesc.stencilStoreOp =
|
||||
getStoreOperation(params, TargetBufferFlags::STENCIL);
|
||||
mDepthStencilAttachmentDesc.stencilClearValue = params.clearStencil;
|
||||
mDepthStencilAttachmentDesc.stencilReadOnly =
|
||||
(params.readOnlyDepthStencil & RenderPassParams::READONLY_STENCIL) > 0;
|
||||
} else {
|
||||
mDepthStencilAttachmentDescriptor.stencilLoadOp = wgpu::LoadOp::Undefined;
|
||||
mDepthStencilAttachmentDescriptor.stencilStoreOp = wgpu::StoreOp::Undefined;
|
||||
mDepthStencilAttachmentDescriptor.stencilReadOnly = true;
|
||||
mDepthStencilAttachmentDesc.stencilLoadOp = wgpu::LoadOp::Undefined;
|
||||
mDepthStencilAttachmentDesc.stencilStoreOp = wgpu::StoreOp::Undefined;
|
||||
mDepthStencilAttachmentDesc.stencilReadOnly = true;
|
||||
}
|
||||
mHasDepthStencilAttachment = true;
|
||||
outDescriptor.depthStencilAttachment = &mDepthStencilAttachmentDesc;
|
||||
|
||||
} else {
|
||||
outDescriptor.depthStencilAttachment = nullptr;
|
||||
mDepthStencilAttachmentDesc = {};
|
||||
}
|
||||
|
||||
} else {
|
||||
outDescriptor.depthStencilAttachment = nullptr;
|
||||
mDepthStencilAttachmentDesc = {};
|
||||
}
|
||||
|
||||
outDescriptor.colorAttachmentCount = mColorAttachmentDescriptors.size();
|
||||
outDescriptor.colorAttachments = mColorAttachmentDescriptors.data();
|
||||
outDescriptor.depthStencilAttachment =
|
||||
mHasDepthStencilAttachment ? &mDepthStencilAttachmentDescriptor : nullptr;
|
||||
|
||||
// descriptor.sampleCount was removed from the core spec. If your webgpu.h still has it,
|
||||
// and your Dawn version expects it, you might need to set it here based on this->samples.
|
||||
// e.g., descriptor.sampleCount = this->samples;
|
||||
}
|
||||
|
||||
}// namespace filament::backend
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
|
||||
|
||||
#include "WebGPUTexture.h"
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
@@ -24,6 +26,7 @@
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
@@ -34,44 +37,44 @@ public:
|
||||
|
||||
WebGPURenderTarget(uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount,
|
||||
MRT const& colorAttachments, Attachment const& depthAttachment,
|
||||
Attachment const& stencilAttachment);
|
||||
Attachment const& stencilAttachment, TargetBufferFlags const& targetFlags,
|
||||
std::function<WebGPUTexture*(const Handle<HwTexture>)> const&, wgpu::Device const&);
|
||||
|
||||
// Default constructor for the default render target
|
||||
WebGPURenderTarget();
|
||||
|
||||
// Updated signature: takes resolved views for custom RTs, and default views for default RT
|
||||
void setUpRenderPassAttachments(
|
||||
wgpu::RenderPassDescriptor& outDescriptor,
|
||||
void setUpRenderPassAttachments(wgpu::RenderPassDescriptor& outDescriptor,
|
||||
RenderPassParams const& params,
|
||||
// For default render target:
|
||||
wgpu::TextureView const& defaultColorTextureView,
|
||||
wgpu::TextureView const& defaultDepthStencilTextureView,
|
||||
wgpu::TextureFormat const& defaultDepthStencilFormat,
|
||||
// For custom render targets:
|
||||
wgpu::TextureView const* customColorTextureViews, // Array of views
|
||||
wgpu::TextureView const* customColorTextureViews, // Array of views
|
||||
wgpu::TextureView const* customColorMsaaSidecarTextureViews, // nullptrs if N/A
|
||||
uint32_t customColorTextureViewCount,
|
||||
wgpu::TextureView const& customDepthTextureView,
|
||||
wgpu::TextureView const& customStencilTextureView,
|
||||
wgpu::TextureFormat customDepthFormat,
|
||||
wgpu::TextureFormat customStencilFormat);
|
||||
wgpu::TextureView const& customDepthStencilTextureView,
|
||||
wgpu::TextureView const& customDepthStencilMsaaSidecarTextureView /* nullptr if N/A */);
|
||||
|
||||
[[nodiscard]] bool isDefaultRenderTarget() const { return mDefaultRenderTarget; }
|
||||
[[nodiscard]] uint8_t getSamples() const { return mSamples; }
|
||||
[[nodiscard]] uint8_t getSampleCountPerAttachment() const { return mSampleCountPerAttachment; }
|
||||
[[nodiscard]] uint8_t getLayerCount() const { return mLayerCount; }
|
||||
|
||||
// Accessors for the driver to get stored attachment info
|
||||
[[nodiscard]] MRT const& getColorAttachmentInfos() const { return mColorAttachments; }
|
||||
[[nodiscard]] Attachment const& getDepthAttachmentInfo() const { return mDepthAttachment; }
|
||||
[[nodiscard]] Attachment const& getStencilAttachmentInfo() const { return mStencilAttachment; }
|
||||
|
||||
// Static helpers for load/store operations
|
||||
[[nodiscard]] static wgpu::LoadOp getLoadOperation(RenderPassParams const& params,
|
||||
TargetBufferFlags buffer);
|
||||
[[nodiscard]] static wgpu::StoreOp getStoreOperation(RenderPassParams const& params,
|
||||
TargetBufferFlags buffer);
|
||||
|
||||
[[nodiscard]] TargetBufferFlags getTargetFlags() const { return mTargetFlags; }
|
||||
void setTargetFlags( TargetBufferFlags value) { mTargetFlags = value; }
|
||||
|
||||
private:
|
||||
bool mDefaultRenderTarget = false;
|
||||
TargetBufferFlags mTargetFlags = TargetBufferFlags::NONE;
|
||||
uint8_t mSamples = 1;
|
||||
uint8_t mLayerCount = 1;
|
||||
|
||||
@@ -81,9 +84,11 @@ private:
|
||||
Attachment mDepthAttachment{};
|
||||
Attachment mStencilAttachment{};
|
||||
|
||||
uint8_t mSampleCountPerAttachment = 0;
|
||||
|
||||
// Cached descriptors for the render pass
|
||||
std::vector<wgpu::RenderPassColorAttachment> mColorAttachmentDescriptors;
|
||||
wgpu::RenderPassDepthStencilAttachment mDepthStencilAttachmentDescriptor{};
|
||||
std::vector<wgpu::RenderPassColorAttachment> mColorAttachmentDesc;
|
||||
wgpu::RenderPassDepthStencilAttachment mDepthStencilAttachmentDesc{};
|
||||
bool mHasDepthStencilAttachment = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -21,50 +21,27 @@
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
#include <dawn/webgpu_cpp_print.h>
|
||||
#endif
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <cstdint>
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
#include <sstream>
|
||||
#endif
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
/**
|
||||
* Reusable set of convenience functions for strings -- generally string views, literals, &
|
||||
* streaming -- used in the WebGPU backend
|
||||
* strings -- used in the WebGPU backend
|
||||
*/
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
/**
|
||||
* Convenience template to print WGPU types (which don't support Filament's utils::io::ostream)
|
||||
* @tparam WebGPUPrintable e.g. wgpu::FeatureName type
|
||||
* @param out stream to print to
|
||||
* @param printable the instance to print
|
||||
* @return the stream printed to
|
||||
*
|
||||
* Example usage (because including the template operator override itself does not compile):
|
||||
* #if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
* utils::io::ostream& operator<<(utils::io::ostream& out,
|
||||
* const wgpu::FeatureName featureName) noexcept {
|
||||
* return streamInsertWebGPUPrintable(out, featureName);
|
||||
* }
|
||||
* #endif
|
||||
*/
|
||||
template<typename WebGPUPrintable>
|
||||
utils::io::ostream& streamInsertWebGPUPrintable(utils::io::ostream& out,
|
||||
const WebGPUPrintable printable) {
|
||||
std::stringstream printableStream;
|
||||
printableStream << printable;
|
||||
out << printableStream.str();
|
||||
return out;
|
||||
[[nodiscard]] inline std::string webGPUPrintableToString(const WebGPUPrintable printable) {
|
||||
std::stringstream out;
|
||||
out << printable;
|
||||
return out.str();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -115,46 +92,17 @@ utils::io::ostream& streamInsertWebGPUPrintable(utils::io::ostream& out,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience template to print wgpu::RequestAdapterOptions
|
||||
* @tparam STREAM_TYPE useful namely for streaming to either an utils::io::ostream OR
|
||||
* utils::details::PanicStream, but also for any kind of stream
|
||||
* @param out stream to print to
|
||||
* @param options instance to be printed
|
||||
* @return stream printed to
|
||||
*
|
||||
* Example usage (because including the template operator override itself does not compile):
|
||||
* template<typename STREAM_TYPE>
|
||||
* STREAM_TYPE& operator<<(STREAM_TYPE& out, wgpu::RequestAdapterOptions const& options) noexcept {
|
||||
* return streamInsertRequestAdapterOptions(out, options);
|
||||
* }
|
||||
*/
|
||||
template<typename STREAM_TYPE>
|
||||
STREAM_TYPE& streamInsertRequestAdapterOptions(STREAM_TYPE& out,
|
||||
wgpu::RequestAdapterOptions const& options) noexcept {
|
||||
[[nodiscard]] inline std::string adapterOptionsToString(
|
||||
wgpu::RequestAdapterOptions const& options) {
|
||||
std::stringstream out;
|
||||
out << "power preference " << powerPreferenceToString(options.powerPreference)
|
||||
<< " force fallback adapter " << bool(options.forceFallbackAdapter) << " backend type "
|
||||
<< backendTypeToString(options.backendType);
|
||||
return out;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience template to print wgpu::AdapterInfo
|
||||
* @tparam STREAM_TYPE useful namely for streaming to either an utils::io::ostream OR
|
||||
* utils::details::PanicStream, but also for any kind of stream
|
||||
* @param out stream to print to
|
||||
* @param options instance to be printed
|
||||
* @return stream printed to
|
||||
*
|
||||
* Example usage (because including the template operator override itself does not compile):
|
||||
* template<typename STREAM_TYPE>
|
||||
* STREAM_TYPE& operator<<(STREAM_TYPE& out, wgpu::AdapterInfo const& info) noexcept {
|
||||
* return streamInsertRequestAdapterInfo(out, info);
|
||||
* }
|
||||
*/
|
||||
template<class STREAM_TYPE>
|
||||
STREAM_TYPE& streamInsertRequestAdapterInfo(STREAM_TYPE& out,
|
||||
wgpu::AdapterInfo const& info) noexcept {
|
||||
[[nodiscard]] inline std::string adapterInfoToString(wgpu::AdapterInfo const& info) {
|
||||
std::stringstream out;
|
||||
out << "vendor (" << info.vendorID << ") '" << info.vendor
|
||||
<< "' device (" << info.deviceID << ") '" << info.device
|
||||
<< "' adapter " << adapterTypeToString(info.adapterType)
|
||||
@@ -162,7 +110,7 @@ STREAM_TYPE& streamInsertRequestAdapterInfo(STREAM_TYPE& out,
|
||||
<< " architecture '" << info.architecture
|
||||
<< "' subgroupMinSize " << info.subgroupMinSize
|
||||
<< " subgroupMaxSize " << info.subgroupMaxSize;
|
||||
return out;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::string_view deviceLostReasonToString(
|
||||
@@ -175,7 +123,123 @@ STREAM_TYPE& streamInsertRequestAdapterInfo(STREAM_TYPE& out,
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::string_view filamentShaderStageToString(ShaderStage stage) {
|
||||
[[nodiscard]] constexpr std::string_view webGPUTextureFormatToString(
|
||||
const wgpu::TextureFormat format) {
|
||||
switch (format) {
|
||||
case wgpu::TextureFormat::Undefined: return "Undefined";
|
||||
case wgpu::TextureFormat::R8Unorm: return "R8Unorm";
|
||||
case wgpu::TextureFormat::R8Snorm: return "R8Snorm";
|
||||
case wgpu::TextureFormat::R8Uint: return "R8Uint";
|
||||
case wgpu::TextureFormat::R8Sint: return "R8Sint";
|
||||
case wgpu::TextureFormat::R16Uint: return "R16Uint";
|
||||
case wgpu::TextureFormat::R16Sint: return "R16Sint";
|
||||
case wgpu::TextureFormat::R16Float: return "R16Float";
|
||||
case wgpu::TextureFormat::RG8Unorm: return "RG8Unorm";
|
||||
case wgpu::TextureFormat::RG8Snorm: return "RG8Snorm";
|
||||
case wgpu::TextureFormat::RG8Uint: return "RG8Uint";
|
||||
case wgpu::TextureFormat::RG8Sint: return "RG8Sint";
|
||||
case wgpu::TextureFormat::R32Float: return "R32Float";
|
||||
case wgpu::TextureFormat::R32Uint: return "R32Uint";
|
||||
case wgpu::TextureFormat::R32Sint: return "R32Sint";
|
||||
case wgpu::TextureFormat::RG16Uint: return "RG16Uint";
|
||||
case wgpu::TextureFormat::RG16Sint: return "RG16Sint";
|
||||
case wgpu::TextureFormat::RG16Float: return "RG16Float";
|
||||
case wgpu::TextureFormat::RGBA8Unorm: return "RGBA8Unorm";
|
||||
case wgpu::TextureFormat::RGBA8UnormSrgb: return "RGBA8UnormSrgb";
|
||||
case wgpu::TextureFormat::RGBA8Snorm: return "RGBA8Snorm";
|
||||
case wgpu::TextureFormat::RGBA8Uint: return "RGBA8Uint";
|
||||
case wgpu::TextureFormat::RGBA8Sint: return "RGBA8Sint";
|
||||
case wgpu::TextureFormat::BGRA8Unorm: return "BGRA8Unorm";
|
||||
case wgpu::TextureFormat::BGRA8UnormSrgb: return "BGRA8UnormSrgb";
|
||||
case wgpu::TextureFormat::RGB10A2Uint: return "RGB10A2Uint";
|
||||
case wgpu::TextureFormat::RGB10A2Unorm: return "RGB10A2Unorm";
|
||||
case wgpu::TextureFormat::RG11B10Ufloat: return "RG11B10Ufloat";
|
||||
case wgpu::TextureFormat::RGB9E5Ufloat: return "RGB9E5Ufloat";
|
||||
case wgpu::TextureFormat::RG32Float: return "RG32Float";
|
||||
case wgpu::TextureFormat::RG32Uint: return "RG32Uint";
|
||||
case wgpu::TextureFormat::RG32Sint: return "RG32Sint";
|
||||
case wgpu::TextureFormat::RGBA16Uint: return "RGBA16Uint";
|
||||
case wgpu::TextureFormat::RGBA16Sint: return "RGBA16Sint";
|
||||
case wgpu::TextureFormat::RGBA16Float: return "RGBA16Float";
|
||||
case wgpu::TextureFormat::RGBA32Float: return "RGBA32Float";
|
||||
case wgpu::TextureFormat::RGBA32Uint: return "RGBA32Uint";
|
||||
case wgpu::TextureFormat::RGBA32Sint: return "RGBA32Sint";
|
||||
case wgpu::TextureFormat::Stencil8: return "Stencil8";
|
||||
case wgpu::TextureFormat::Depth16Unorm: return "Depth16Unorm";
|
||||
case wgpu::TextureFormat::Depth24Plus: return "Depth24Plus";
|
||||
case wgpu::TextureFormat::Depth24PlusStencil8: return "Depth24PlusStencil8";
|
||||
case wgpu::TextureFormat::Depth32Float: return "Depth32Float";
|
||||
case wgpu::TextureFormat::Depth32FloatStencil8: return "Depth32FloatStencil8";
|
||||
case wgpu::TextureFormat::BC1RGBAUnorm: return "BC1RGBAUnorm";
|
||||
case wgpu::TextureFormat::BC1RGBAUnormSrgb: return "BC1RGBAUnormSrgb";
|
||||
case wgpu::TextureFormat::BC2RGBAUnorm: return "BC2RGBAUnorm";
|
||||
case wgpu::TextureFormat::BC2RGBAUnormSrgb: return "BC2RGBAUnormSrgb";
|
||||
case wgpu::TextureFormat::BC3RGBAUnorm: return "BC3RGBAUnorm";
|
||||
case wgpu::TextureFormat::BC3RGBAUnormSrgb: return "BC3RGBAUnormSrgb";
|
||||
case wgpu::TextureFormat::BC4RUnorm: return "BC4RUnorm";
|
||||
case wgpu::TextureFormat::BC4RSnorm: return "BC4RSnorm";
|
||||
case wgpu::TextureFormat::BC5RGUnorm: return "BC5RGUnorm";
|
||||
case wgpu::TextureFormat::BC5RGSnorm: return "BC5RGSnorm";
|
||||
case wgpu::TextureFormat::BC6HRGBUfloat: return "BC6HRGBUfloat";
|
||||
case wgpu::TextureFormat::BC6HRGBFloat: return "BC6HRGBFloat";
|
||||
case wgpu::TextureFormat::BC7RGBAUnorm: return "BC7RGBAUnorm";
|
||||
case wgpu::TextureFormat::BC7RGBAUnormSrgb: return "BC7RGBAUnormSrgb";
|
||||
case wgpu::TextureFormat::ETC2RGB8Unorm: return "ETC2RGB8Unorm";
|
||||
case wgpu::TextureFormat::ETC2RGB8UnormSrgb: return "ETC2RGB8UnormSrgb";
|
||||
case wgpu::TextureFormat::ETC2RGB8A1Unorm: return "ETC2RGB8A1Unorm";
|
||||
case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb: return "ETC2RGB8A1UnormSrgb";
|
||||
case wgpu::TextureFormat::ETC2RGBA8Unorm: return "ETC2RGBA8Unorm";
|
||||
case wgpu::TextureFormat::ETC2RGBA8UnormSrgb: return "ETC2RGBA8UnormSrgb";
|
||||
case wgpu::TextureFormat::EACR11Unorm: return "EACR11Unorm";
|
||||
case wgpu::TextureFormat::EACR11Snorm: return "EACR11Snorm";
|
||||
case wgpu::TextureFormat::EACRG11Unorm: return "EACRG11Unorm";
|
||||
case wgpu::TextureFormat::EACRG11Snorm: return "EACRG11Snorm";
|
||||
case wgpu::TextureFormat::ASTC4x4Unorm: return "ASTC4x4Unorm";
|
||||
case wgpu::TextureFormat::ASTC4x4UnormSrgb: return "ASTC4x4UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC5x4Unorm: return "ASTC5x4Unorm";
|
||||
case wgpu::TextureFormat::ASTC5x4UnormSrgb: return "ASTC5x4UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC5x5Unorm: return "ASTC5x5Unorm";
|
||||
case wgpu::TextureFormat::ASTC5x5UnormSrgb: return "ASTC5x5UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC6x5Unorm: return "ASTC6x5Unorm";
|
||||
case wgpu::TextureFormat::ASTC6x5UnormSrgb: return "ASTC6x5UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC6x6Unorm: return "ASTC6x6Unorm";
|
||||
case wgpu::TextureFormat::ASTC6x6UnormSrgb: return "ASTC6x6UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC8x5Unorm: return "ASTC8x5Unorm";
|
||||
case wgpu::TextureFormat::ASTC8x5UnormSrgb: return "ASTC8x5UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC8x6Unorm: return "ASTC8x6Unorm";
|
||||
case wgpu::TextureFormat::ASTC8x6UnormSrgb: return "ASTC8x6UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC8x8Unorm: return "ASTC8x8Unorm";
|
||||
case wgpu::TextureFormat::ASTC8x8UnormSrgb: return "ASTC8x8UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC10x5Unorm: return "ASTC10x5Unorm";
|
||||
case wgpu::TextureFormat::ASTC10x5UnormSrgb: return "ASTC10x5UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC10x6Unorm: return "ASTC10x6Unorm";
|
||||
case wgpu::TextureFormat::ASTC10x6UnormSrgb: return "ASTC10x6UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC10x8Unorm: return "ASTC10x8Unorm";
|
||||
case wgpu::TextureFormat::ASTC10x8UnormSrgb: return "ASTC10x8UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC10x10Unorm: return "ASTC10x10Unorm";
|
||||
case wgpu::TextureFormat::ASTC10x10UnormSrgb: return "ASTC10x10UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC12x10Unorm: return "ASTC12x10Unorm";
|
||||
case wgpu::TextureFormat::ASTC12x10UnormSrgb: return "ASTC12x10UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC12x12Unorm: return "ASTC12x12Unorm";
|
||||
case wgpu::TextureFormat::ASTC12x12UnormSrgb: return "ASTC12x12UnormSrgb";
|
||||
case wgpu::TextureFormat::R16Unorm: return "R16Unorm";
|
||||
case wgpu::TextureFormat::RG16Unorm: return "RG16Unorm";
|
||||
case wgpu::TextureFormat::RGBA16Unorm: return "RGBA16Unorm";
|
||||
case wgpu::TextureFormat::R16Snorm: return "R16Snorm";
|
||||
case wgpu::TextureFormat::RG16Snorm: return "RG16Snorm";
|
||||
case wgpu::TextureFormat::RGBA16Snorm: return "RGBA16Snorm";
|
||||
case wgpu::TextureFormat::R8BG8Biplanar420Unorm: return "R8BG8Biplanar420Unorm";
|
||||
case wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm: return "R10X6BG10X6Biplanar420Unorm";
|
||||
case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm: return "R8BG8A8Triplanar420Unorm";
|
||||
case wgpu::TextureFormat::R8BG8Biplanar422Unorm: return "R8BG8Biplanar422Unorm";
|
||||
case wgpu::TextureFormat::R8BG8Biplanar444Unorm: return "R8BG8Biplanar444Unorm";
|
||||
case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm: return "R10X6BG10X6Biplanar422Unorm";
|
||||
case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm: return "R10X6BG10X6Biplanar444Unorm";
|
||||
case wgpu::TextureFormat::External: return "External";
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::string_view filamentShaderStageToString(const ShaderStage stage) {
|
||||
switch (stage) {
|
||||
case ShaderStage::VERTEX: return "vertex";
|
||||
case ShaderStage::FRAGMENT: return "fragment";
|
||||
|
||||
@@ -24,43 +24,25 @@
|
||||
#include "backend/DriverEnums.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
utils::io::ostream& operator<<(utils::io::ostream& out, const wgpu::TextureFormat format) noexcept {
|
||||
return filament::backend::streamInsertWebGPUPrintable(out, format);
|
||||
}
|
||||
|
||||
utils::io::ostream& operator<<(utils::io::ostream& out,
|
||||
const wgpu::TextureUsage textureUsage) noexcept {
|
||||
return filament::backend::streamInsertWebGPUPrintable(out, textureUsage);
|
||||
}
|
||||
|
||||
utils::io::ostream& operator<<(utils::io::ostream& out,
|
||||
const wgpu::PresentMode presentMode) noexcept {
|
||||
return filament::backend::streamInsertWebGPUPrintable(out, presentMode);
|
||||
}
|
||||
|
||||
utils::io::ostream& operator<<(utils::io::ostream& out,
|
||||
const wgpu::CompositeAlphaMode alphaMode) noexcept {
|
||||
return filament::backend::streamInsertWebGPUPrintable(out, alphaMode);
|
||||
}
|
||||
|
||||
void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabilities) {
|
||||
FWGPU_LOGI << "WebGPU surface capabilities:";
|
||||
FWGPU_LOGI << " surface usages: " << capabilities.usages;
|
||||
FWGPU_LOGI << " surface usages: " << webGPUPrintableToString(capabilities.usages);
|
||||
FWGPU_LOGI << " surface formats (" << capabilities.formatCount << "):";
|
||||
if (capabilities.formatCount > 0 && capabilities.formats != nullptr) {
|
||||
std::for_each(capabilities.formats, capabilities.formats + capabilities.formatCount,
|
||||
[](wgpu::TextureFormat const format) {
|
||||
FWGPU_LOGI << " " << format;
|
||||
FWGPU_LOGI << " " << webGPUTextureFormatToString(format);
|
||||
});
|
||||
}
|
||||
FWGPU_LOGI << " surface present modes (" << capabilities.presentModeCount << "):";
|
||||
@@ -68,7 +50,7 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
|
||||
std::for_each(capabilities.presentModes,
|
||||
capabilities.presentModes + capabilities.presentModeCount,
|
||||
[](wgpu::PresentMode const presentMode) {
|
||||
FWGPU_LOGI << " " << presentMode;
|
||||
FWGPU_LOGI << " " << webGPUPrintableToString(presentMode);
|
||||
});
|
||||
}
|
||||
FWGPU_LOGI << " surface alpha modes (" << capabilities.alphaModeCount << "):";
|
||||
@@ -76,7 +58,7 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
|
||||
std::for_each(capabilities.alphaModes,
|
||||
capabilities.alphaModes + capabilities.alphaModeCount,
|
||||
[](wgpu::CompositeAlphaMode const alphaMode) {
|
||||
FWGPU_LOGI << " " << alphaMode;
|
||||
FWGPU_LOGI << " " << webGPUPrintableToString(alphaMode);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -84,20 +66,20 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
|
||||
void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config,
|
||||
wgpu::TextureFormat depthFormat) {
|
||||
FWGPU_LOGI << "WebGPU surface configuration:";
|
||||
FWGPU_LOGI << " surface format: " << config.format;
|
||||
FWGPU_LOGI << " surface usage: " << config.usage;
|
||||
FWGPU_LOGI << " surface format: " << webGPUTextureFormatToString(config.format);
|
||||
FWGPU_LOGI << " surface usage: " << webGPUPrintableToString(config.usage);
|
||||
FWGPU_LOGI << " surface view formats (" << config.viewFormatCount << "):";
|
||||
if (config.viewFormatCount > 0 && config.viewFormats != nullptr) {
|
||||
std::for_each(config.viewFormats, config.viewFormats + config.viewFormatCount,
|
||||
[](wgpu::TextureFormat const viewFormat) {
|
||||
FWGPU_LOGI << " " << viewFormat;
|
||||
FWGPU_LOGI << " " << webGPUPrintableToString(viewFormat);
|
||||
});
|
||||
}
|
||||
FWGPU_LOGI << " surface alpha mode: " << config.alphaMode;
|
||||
FWGPU_LOGI << " surface alpha mode: " << webGPUPrintableToString(config.alphaMode);
|
||||
FWGPU_LOGI << " surface width: " << config.width;
|
||||
FWGPU_LOGI << " surface height: " << config.height;
|
||||
FWGPU_LOGI << " surface present mode: " << config.presentMode;
|
||||
FWGPU_LOGI << "WebGPU selected depth format: " << depthFormat;
|
||||
FWGPU_LOGI << " surface present mode: " << webGPUPrintableToString(config.presentMode);
|
||||
FWGPU_LOGI << "WebGPU selected depth format: " << webGPUTextureFormatToString(depthFormat);
|
||||
}
|
||||
#endif// FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
|
||||
@@ -128,7 +110,6 @@ void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config,
|
||||
return wgpu::TextureFormat::Depth24PlusStencil8;
|
||||
}
|
||||
} else {
|
||||
// other options: Depth16Unorm or Depth24Plus
|
||||
return wgpu::TextureFormat::Depth32Float;
|
||||
}
|
||||
}
|
||||
@@ -235,10 +216,9 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::TextureView createDepthTextureView(wgpu::Texture const& depthTexture,
|
||||
wgpu::TextureFormat depthFormat, bool needStencil) {
|
||||
wgpu::TextureFormat const& depthFormat, bool const needStencil) {
|
||||
wgpu::TextureViewDescriptor descriptor{
|
||||
.label = "depth_texture_view",
|
||||
.format = depthFormat,
|
||||
.dimension = wgpu::TextureViewDimension::e2D,
|
||||
.baseMipLevel = 0,
|
||||
.mipLevelCount = 1,
|
||||
@@ -249,6 +229,16 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
|
||||
};
|
||||
if (needStencil) {
|
||||
descriptor.aspect = wgpu::TextureAspect::All;
|
||||
descriptor.format = depthFormat;
|
||||
} else {
|
||||
descriptor.aspect = wgpu::TextureAspect::DepthOnly;
|
||||
if (depthFormat == wgpu::TextureFormat::Depth32FloatStencil8) {
|
||||
descriptor.format = wgpu::TextureFormat::Depth32Float;
|
||||
} else if (depthFormat == wgpu::TextureFormat::Depth24PlusStencil8) {
|
||||
descriptor.format = wgpu::TextureFormat::Depth24Plus;
|
||||
} else {
|
||||
descriptor.format = depthFormat;
|
||||
}
|
||||
}
|
||||
wgpu::TextureView depthTextureView = depthTexture.CreateView(&descriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(depthTextureView) << "Failed to create depth texture view";
|
||||
@@ -257,8 +247,6 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
|
||||
|
||||
}// namespace
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize,
|
||||
wgpu::Adapter const& adapter, wgpu::Device const& device, uint64_t flags)
|
||||
: mDevice(device),
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#include "WebGPUTexture.h"
|
||||
|
||||
#include "WebGPUConstants.h"
|
||||
#include "WebGPURenderPassMipmapGenerator.h"
|
||||
#include "WebGPUStrings.h"
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include "private/backend/BackendUtils.h"
|
||||
@@ -25,9 +27,12 @@
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
#include <webgpu/webgpu_cpp_print.h>
|
||||
#include <sstream>
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -59,19 +64,119 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param format texture format to potentially be used for storage binding
|
||||
* @return true if the format is compatible with wgpu::TextureUsage::StorageBinding
|
||||
*/
|
||||
[[nodiscard]] constexpr bool isFormatStorageCompatible(const wgpu::TextureFormat format) {
|
||||
switch (format) {
|
||||
// List of formats that support storage binding
|
||||
case wgpu::TextureFormat::R32Float:
|
||||
case wgpu::TextureFormat::R32Sint:
|
||||
case wgpu::TextureFormat::R32Uint:
|
||||
case wgpu::TextureFormat::RG32Float:
|
||||
case wgpu::TextureFormat::RG32Sint:
|
||||
case wgpu::TextureFormat::RG32Uint:
|
||||
case wgpu::TextureFormat::RGBA16Float:
|
||||
case wgpu::TextureFormat::RGBA16Sint:
|
||||
case wgpu::TextureFormat::RGBA16Uint:
|
||||
case wgpu::TextureFormat::RGBA32Float:
|
||||
case wgpu::TextureFormat::RGBA32Sint:
|
||||
case wgpu::TextureFormat::RGBA32Uint:
|
||||
case wgpu::TextureFormat::RGBA8Unorm:
|
||||
case wgpu::TextureFormat::RGBA8Snorm:
|
||||
case wgpu::TextureFormat::RGBA8Uint:
|
||||
case wgpu::TextureFormat::RGBA8Sint:
|
||||
return true;
|
||||
default:
|
||||
// All other formats, including packed floats (RG11B10Ufloat),
|
||||
// depth/stencil, and sRGB formats do not support storage.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param viewFormat potential view format to a texture
|
||||
* @return an underlying texture format that is storage binding compatible for which this view
|
||||
* texture format can be used. If no such format exists, Undefined is returned.
|
||||
* If the view texture is already storage binding compatible then the view texture itself
|
||||
* is returned.
|
||||
*/
|
||||
[[nodiscard]] constexpr wgpu::TextureFormat storageBindingCompatibleFormatForViewFormat(
|
||||
const wgpu::TextureFormat viewFormat) {
|
||||
switch (viewFormat) {
|
||||
case wgpu::TextureFormat::RGBA8UnormSrgb: return wgpu::TextureFormat::RGBA8Unorm;
|
||||
case wgpu::TextureFormat::BGRA8UnormSrgb: return wgpu::TextureFormat::BGRA8Unorm;
|
||||
case wgpu::TextureFormat::BC1RGBAUnormSrgb: return wgpu::TextureFormat::BC1RGBAUnorm;
|
||||
case wgpu::TextureFormat::BC2RGBAUnormSrgb: return wgpu::TextureFormat::BC2RGBAUnorm;
|
||||
case wgpu::TextureFormat::BC3RGBAUnormSrgb: return wgpu::TextureFormat::BC3RGBAUnorm;
|
||||
case wgpu::TextureFormat::BC7RGBAUnormSrgb: return wgpu::TextureFormat::BC7RGBAUnorm;
|
||||
case wgpu::TextureFormat::ETC2RGB8UnormSrgb: return wgpu::TextureFormat::ETC2RGB8Unorm;
|
||||
case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb: return wgpu::TextureFormat::ETC2RGB8A1Unorm;
|
||||
case wgpu::TextureFormat::ETC2RGBA8UnormSrgb: return wgpu::TextureFormat::ETC2RGBA8Unorm;
|
||||
case wgpu::TextureFormat::ASTC4x4UnormSrgb: return wgpu::TextureFormat::ASTC4x4Unorm;
|
||||
case wgpu::TextureFormat::ASTC5x4UnormSrgb: return wgpu::TextureFormat::ASTC5x4Unorm;
|
||||
case wgpu::TextureFormat::ASTC5x5UnormSrgb: return wgpu::TextureFormat::ASTC5x5Unorm;
|
||||
case wgpu::TextureFormat::ASTC6x5UnormSrgb: return wgpu::TextureFormat::ASTC6x5Unorm;
|
||||
case wgpu::TextureFormat::ASTC6x6UnormSrgb: return wgpu::TextureFormat::ASTC6x6Unorm;
|
||||
case wgpu::TextureFormat::ASTC8x5UnormSrgb: return wgpu::TextureFormat::ASTC8x5Unorm;
|
||||
case wgpu::TextureFormat::ASTC8x6UnormSrgb: return wgpu::TextureFormat::ASTC8x6Unorm;
|
||||
case wgpu::TextureFormat::ASTC8x8UnormSrgb: return wgpu::TextureFormat::ASTC8x8Unorm;
|
||||
case wgpu::TextureFormat::ASTC10x5UnormSrgb: return wgpu::TextureFormat::ASTC10x5Unorm;
|
||||
case wgpu::TextureFormat::ASTC10x6UnormSrgb: return wgpu::TextureFormat::ASTC10x6Unorm;
|
||||
case wgpu::TextureFormat::ASTC10x8UnormSrgb: return wgpu::TextureFormat::ASTC10x8Unorm;
|
||||
case wgpu::TextureFormat::ASTC10x10UnormSrgb: return wgpu::TextureFormat::ASTC10x10Unorm;
|
||||
case wgpu::TextureFormat::ASTC12x10UnormSrgb: return wgpu::TextureFormat::ASTC12x10Unorm;
|
||||
case wgpu::TextureFormat::ASTC12x12UnormSrgb: return wgpu::TextureFormat::ASTC12x12Unorm;
|
||||
default:
|
||||
if (isFormatStorageCompatible(viewFormat)) {
|
||||
return viewFormat; // view format is itself already storage binding compatible
|
||||
} else {
|
||||
return wgpu::TextureFormat::Undefined; // no valid format in this case
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fUsage Filament's requested texture usage
|
||||
* @param samples How many samples to use for MSAA
|
||||
* @param needsComputeStorageSupport if we need to use this texture as storage binding in something
|
||||
* like a compute shader
|
||||
* @param needsRenderAttachmentSupport if we need to use this texture as a render pass attachment
|
||||
* in something like a render pass blit (e.g. mipmap generation)
|
||||
* @return The appropriate texture usage flags for the underlying texture
|
||||
*/
|
||||
[[nodiscard]] wgpu::TextureUsage fToWGPUTextureUsage(TextureUsage const& fUsage,
|
||||
const uint8_t samples) {
|
||||
const uint8_t samples, const bool needsComputeStorageSupport,
|
||||
const bool needsRenderAttachmentSupport) {
|
||||
wgpu::TextureUsage retUsage = wgpu::TextureUsage::None;
|
||||
|
||||
// if needsComputeStorageSupport we need to read and write to the texture in a shader and thus
|
||||
// require CopySrc, CopyDst, TextureBinding, & StorageBinding
|
||||
|
||||
// Basing this mapping off of VulkanTexture.cpp's getUsage func and suggestions from Gemini
|
||||
// TODO Validate assumptions, revisit if issues.
|
||||
if (any(TextureUsage::BLIT_SRC & fUsage)) {
|
||||
if (needsComputeStorageSupport || any(TextureUsage::BLIT_SRC & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::CopySrc;
|
||||
}
|
||||
if (any((TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE) & fUsage)) {
|
||||
if (needsComputeStorageSupport ||
|
||||
any((TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE) & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::CopyDst;
|
||||
}
|
||||
if (any(TextureUsage::SAMPLEABLE & fUsage)) {
|
||||
if (needsComputeStorageSupport || any(TextureUsage::SAMPLEABLE & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::TextureBinding;
|
||||
}
|
||||
if (needsComputeStorageSupport) {
|
||||
retUsage |= wgpu::TextureUsage::StorageBinding;
|
||||
}
|
||||
if (needsRenderAttachmentSupport) {
|
||||
retUsage |= wgpu::TextureUsage::RenderAttachment;
|
||||
}
|
||||
if (any(TextureUsage::BLIT_SRC & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::RenderAttachment;
|
||||
}
|
||||
if (any(TextureUsage::BLIT_DST & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::RenderAttachment;
|
||||
retUsage |= wgpu::TextureUsage::TextureBinding;
|
||||
}
|
||||
// WGPU Render attachment covers either color or stencil situation dependant
|
||||
@@ -100,7 +205,6 @@ namespace {
|
||||
retUsage |= wgpu::TextureUsage::TransientAttachment;
|
||||
}
|
||||
// NOTE: Unused wgpu flags:
|
||||
// StorageBinding
|
||||
// StorageAttachment
|
||||
|
||||
// NOTE: Unused Filament flags:
|
||||
@@ -169,6 +273,26 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] WebGPUTexture::MipmapGenerationStrategy determineMipmapGenerationStrategy(
|
||||
const wgpu::TextureFormat format, const SamplerType samplerType, const uint8_t sampleCount,
|
||||
const uint8_t mipmapLevels) {
|
||||
if (mipmapLevels <= 1) {
|
||||
return WebGPUTexture::MipmapGenerationStrategy::NONE; // no mipmap generation needed
|
||||
}
|
||||
const WebGPURenderPassMipmapGenerator::FormatCompatibility renderPassCompatibility{
|
||||
WebGPURenderPassMipmapGenerator::getCompatibilityFor(format,
|
||||
toWebGPUTextureDimension(samplerType), sampleCount)
|
||||
};
|
||||
if (renderPassCompatibility.compatible) {
|
||||
return WebGPUTexture::MipmapGenerationStrategy::RENDER_PASS;
|
||||
}
|
||||
if (WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(format)) {
|
||||
return WebGPUTexture::MipmapGenerationStrategy::SPD_COMPUTE_PASS;
|
||||
}
|
||||
// we don't have a way to generate mipmaps for this texture, at least at this time
|
||||
return WebGPUTexture::MipmapGenerationStrategy::NONE;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr wgpu::Extent3D toTextureSize(const SamplerType samplerType,
|
||||
const uint32_t width, const uint32_t height, const uint32_t depth) {
|
||||
switch (samplerType) {
|
||||
@@ -205,11 +329,22 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
|
||||
const uint32_t height, const uint32_t depth, const TextureUsage usage,
|
||||
wgpu::Device const& device) noexcept
|
||||
: HwTexture{ samplerType, levels, samples, width, height, depth, format, usage },
|
||||
mWebGPUFormat{ fToWGPUTextureFormat(format) },
|
||||
mViewFormat{ fToWGPUTextureFormat(format) },
|
||||
mMipmapGenerationStrategy{ determineMipmapGenerationStrategy(mViewFormat, samplerType,
|
||||
samples, levels) },
|
||||
mWebGPUFormat{ mMipmapGenerationStrategy == MipmapGenerationStrategy::SPD_COMPUTE_PASS
|
||||
? storageBindingCompatibleFormatForViewFormat(mViewFormat)
|
||||
: mViewFormat },
|
||||
mAspect{ fToWGPUTextureViewAspect(usage, format) },
|
||||
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples) },
|
||||
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples,
|
||||
mMipmapGenerationStrategy == MipmapGenerationStrategy::SPD_COMPUTE_PASS,
|
||||
mMipmapGenerationStrategy == MipmapGenerationStrategy::RENDER_PASS) },
|
||||
mViewUsage{ fToWGPUTextureUsage(usage, samples, false, false) },
|
||||
mDimension{toWebGPUTextureViewDimension(samplerType)},
|
||||
mBlockWidth{ filament::backend::getBlockWidth(format) },
|
||||
mBlockHeight{ filament::backend::getBlockHeight(format) } {
|
||||
mBlockHeight{ filament::backend::getBlockHeight(format) },
|
||||
mDefaultMipLevel{ 0 },
|
||||
mDefaultBaseArrayLayer{ 0 } {
|
||||
assert_invariant(
|
||||
samples == 1 ||
|
||||
samples == 4 &&
|
||||
@@ -225,10 +360,8 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
|
||||
.format = mWebGPUFormat,
|
||||
.mipLevelCount = levels,
|
||||
.sampleCount = samples,
|
||||
// TODO Is this fine? Could do all-the-things, a naive mapping or get something from
|
||||
// Filament
|
||||
.viewFormatCount = 0,
|
||||
.viewFormats = nullptr,
|
||||
.viewFormatCount = 1,
|
||||
.viewFormats = &mViewFormat,
|
||||
};
|
||||
mArrayLayerCount = toArrayLayerCount(samplerType, textureDescriptor.size.depthOrArrayLayers);
|
||||
assert_invariant(textureDescriptor.format != wgpu::TextureFormat::Undefined &&
|
||||
@@ -240,23 +373,54 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
|
||||
mArrayLayerCount, samplerType);
|
||||
FILAMENT_CHECK_POSTCONDITION(mDefaultTextureView)
|
||||
<< "Failed to create default texture view for " << textureDescriptor.label;
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
if (mViewFormat != mWebGPUFormat) {
|
||||
std::stringstream viewFormatStream;
|
||||
viewFormatStream << mViewFormat;
|
||||
std::stringstream textureFormatStream;
|
||||
textureFormatStream << mWebGPUFormat;
|
||||
FWGPU_LOGD << "Texture '" << textureDescriptor.label << "' has view format "
|
||||
<< viewFormatStream.str() << " and texture format " << textureFormatStream.str();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
|
||||
const uint8_t levelCount) noexcept
|
||||
: HwTexture{ src->target, levelCount, src->samples, src->width, src->height, src->depth,
|
||||
src->format, src->usage },
|
||||
mTexture{ src->mTexture },
|
||||
mViewFormat{ src->mViewFormat },
|
||||
mMipmapGenerationStrategy{ src->mMipmapGenerationStrategy },
|
||||
mWebGPUFormat{ src->mWebGPUFormat },
|
||||
mAspect{ src->mAspect },
|
||||
mWebGPUUsage{ src->mWebGPUUsage },
|
||||
mArrayLayerCount{ src->mArrayLayerCount },
|
||||
mViewUsage{ src->mViewUsage },
|
||||
mBlockWidth{ src->mBlockWidth },
|
||||
mBlockHeight{ src->mBlockHeight },
|
||||
mDefaultTextureView{ makeTextureView(baseLevel, levelCount, 0, src->mArrayLayerCount,
|
||||
src->target) },
|
||||
mArrayLayerCount{ src->mArrayLayerCount },
|
||||
mTexture{ src->mTexture },
|
||||
mDefaultMipLevel{ baseLevel },
|
||||
mDefaultBaseArrayLayer{ src->mArrayLayerCount } {}
|
||||
mDefaultBaseArrayLayer{ src->mArrayLayerCount },
|
||||
mDefaultTextureView{ makeTextureView(mDefaultMipLevel, levelCount, 0, mDefaultBaseArrayLayer,
|
||||
src->target) },
|
||||
mMsaaSidecarTexture{ src->mMsaaSidecarTexture } {}
|
||||
|
||||
wgpu::Texture const& WebGPUTexture::getMsaaSidecarTexture(const uint8_t sampleCount) const {
|
||||
if (mMsaaSidecarTexture == nullptr) {
|
||||
return mMsaaSidecarTexture; // nullptr (no such sidecar)
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION(sampleCount == mMsaaSidecarTexture.GetSampleCount())
|
||||
<< "The MSAA sidecar texture has a different sample count ("
|
||||
<< mMsaaSidecarTexture.GetSampleCount() << ") than requested (" << +sampleCount
|
||||
<< "). Note that this restriction was written when WebGPU only supported msaa "
|
||||
"textures with 4 samples. If that has changed, this implementation should be "
|
||||
"updated (e.g. map of sidecar textures by sampleCount or something).";
|
||||
return mMsaaSidecarTexture;
|
||||
}
|
||||
|
||||
bool WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(const wgpu::TextureFormat format) {
|
||||
return storageBindingCompatibleFormatForViewFormat(format) != wgpu::TextureFormat::Undefined;
|
||||
}
|
||||
|
||||
wgpu::TextureView WebGPUTexture::getOrMakeTextureView(const uint8_t mipLevel,
|
||||
const uint32_t arrayLayer) {
|
||||
@@ -267,6 +431,68 @@ wgpu::TextureView WebGPUTexture::getOrMakeTextureView(const uint8_t mipLevel,
|
||||
return makeTextureView(mipLevel, 1, arrayLayer, 1, target);
|
||||
}
|
||||
|
||||
void WebGPUTexture::createMsaaSidecarTextureIfNotAlreadyCreated(const uint8_t samples,
|
||||
wgpu::Device const& device) {
|
||||
FILAMENT_CHECK_PRECONDITION(samples > 1) << "Requesting to create a MSAA sidecar texture for "
|
||||
<< +samples << " samples? Invalid request.";
|
||||
if (mMsaaSidecarTexture) {
|
||||
FILAMENT_CHECK_PRECONDITION(mMsaaSidecarTexture.GetSampleCount() == samples)
|
||||
<< "An MSAA sidecar texture has already been created for this texture, but with a "
|
||||
"different sample count ("
|
||||
<< mMsaaSidecarTexture.GetSampleCount() << ") than requested (" << +samples
|
||||
<< "). Note that this restriction was written when WebGPU only supported msaa "
|
||||
"textures with 4 samples. If that has changed, this implementation should be "
|
||||
"updated (e.g. map of sidecar textures by sampleCount or something).";
|
||||
return; // we already have the sidecar created
|
||||
}
|
||||
const wgpu::TextureDescriptor descriptor{
|
||||
.label = "msaa_sidecar_texture",
|
||||
.usage = mTexture.GetUsage(),
|
||||
.dimension = mTexture.GetDimension(),
|
||||
.size = {
|
||||
.width = mTexture.GetWidth(),
|
||||
.height = mTexture.GetHeight(),
|
||||
.depthOrArrayLayers = mTexture.GetDepthOrArrayLayers(),
|
||||
},
|
||||
.format = mTexture.GetFormat(),
|
||||
.mipLevelCount = mTexture.GetMipLevelCount(),
|
||||
.sampleCount = samples,
|
||||
.viewFormatCount = 1,
|
||||
.viewFormats = &mViewFormat,
|
||||
};
|
||||
mMsaaSidecarTexture = device.CreateTexture(&descriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(mMsaaSidecarTexture) << "Failed to create MSAA sidecar texture";
|
||||
}
|
||||
|
||||
wgpu::TextureView WebGPUTexture::makeMsaaSidecarTextureViewIfTextureSidecarExists(
|
||||
const uint8_t samples, const uint8_t mipLevel, const uint32_t arrayLayer) const {
|
||||
if (mMsaaSidecarTexture == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION(mMsaaSidecarTexture.GetSampleCount() == samples)
|
||||
<< "An MSAA sidecar texture has already been created for this texture, but with a "
|
||||
"different sample count ("
|
||||
<< mMsaaSidecarTexture.GetSampleCount() << ") than requested for view (" << +samples
|
||||
<< "). Note that this restriction was written when WebGPU only supported msaa "
|
||||
"textures with 4 samples. If that has changed, this implementation should be "
|
||||
"updated (e.g. map of sidecar textures by sampleCount or something).";
|
||||
const wgpu::TextureViewDescriptor descriptor{
|
||||
.label = "msaa_sidecar_texture_view",
|
||||
.format = mViewFormat,
|
||||
.dimension = mDimension,
|
||||
.baseMipLevel = mipLevel,
|
||||
.mipLevelCount = 1,
|
||||
.baseArrayLayer = arrayLayer,
|
||||
.arrayLayerCount = 1,
|
||||
.aspect = mAspect,
|
||||
.usage = mViewUsage,
|
||||
};
|
||||
const wgpu::TextureView textureView{ mMsaaSidecarTexture.CreateView(&descriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(mMsaaSidecarTexture)
|
||||
<< "Failed to create MSAA sidecar texture view (" << +samples << " samples)";
|
||||
return textureView;
|
||||
}
|
||||
|
||||
wgpu::TextureFormat WebGPUTexture::fToWGPUTextureFormat(TextureFormat const& fFormat) {
|
||||
switch (fFormat) {
|
||||
case TextureFormat::R8: return wgpu::TextureFormat::R8Unorm;
|
||||
@@ -356,51 +582,43 @@ wgpu::TextureFormat WebGPUTexture::fToWGPUTextureFormat(TextureFormat const& fFo
|
||||
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
|
||||
// and discard the alpha and lower precision.
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB565 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGB9_E5: return wgpu::TextureFormat::RGB9E5Ufloat;
|
||||
case TextureFormat::RGB5_A1:
|
||||
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
|
||||
// and handle the packing/unpacking in shaders.
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB5_A1 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGBA4:
|
||||
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
|
||||
// and handle the packing/unpacking in shaders.
|
||||
FWGPU_LOGW << "Requested Filament texture format RGBA4 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGB8:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB8 but getting "
|
||||
"wgpu::TextureFormat::RGBA8Unorm (no direct sRGB equivalent in wgpu "
|
||||
"without alpha)"
|
||||
<< utils::io::endl;
|
||||
"without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8Unorm;
|
||||
case TextureFormat::SRGB8:
|
||||
FWGPU_LOGW << "Requested Filament texture format SRGB8 but getting "
|
||||
"wgpu::TextureFormat::RGBA8UnormSrgb (no direct sRGB equivalent in wgpu "
|
||||
"without alpha)"
|
||||
<< utils::io::endl;
|
||||
"without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8UnormSrgb;
|
||||
case TextureFormat::RGB8_SNORM:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB8_SNORM but getting "
|
||||
"wgpu::TextureFormat::RGBA8Snorm (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA8Snorm (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8Snorm;
|
||||
case TextureFormat::RGB8UI:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB8UI but getting "
|
||||
"wgpu::TextureFormat::RGBA8Uint (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA8Uint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8Uint;
|
||||
case TextureFormat::RGB8I:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB8I but getting "
|
||||
"wgpu::TextureFormat::RGBA8Sint (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA8Sint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8Sint;
|
||||
case TextureFormat::R11F_G11F_B10F: return wgpu::TextureFormat::RG11B10Ufloat;
|
||||
case TextureFormat::UNUSED: return wgpu::TextureFormat::Undefined;
|
||||
@@ -408,38 +626,32 @@ wgpu::TextureFormat WebGPUTexture::fToWGPUTextureFormat(TextureFormat const& fFo
|
||||
case TextureFormat::RGB16F:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB16F but getting "
|
||||
"wgpu::TextureFormat::RGBA16Float (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA16Float (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA16Float;
|
||||
case TextureFormat::RGB16UI:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB16UI but getting "
|
||||
"wgpu::TextureFormat::RGBA16Uint (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA16Uint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA16Uint;
|
||||
case TextureFormat::RGB16I:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB16I but getting "
|
||||
"wgpu::TextureFormat::RGBA16Sint (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA16Sint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA16Sint;
|
||||
case TextureFormat::RGB32F:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB32F but getting "
|
||||
"wgpu::TextureFormat::RGBA32Float (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA32Float (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA32Float;
|
||||
case TextureFormat::RGB32UI:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB32UI but getting "
|
||||
"wgpu::TextureFormat::RGBA32Uint (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA32Uint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA32Uint;
|
||||
case TextureFormat::RGB32I:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB32I but getting "
|
||||
"wgpu::TextureFormat::RGBA32Sint (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA32Sint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA32Sint;
|
||||
case TextureFormat::DXT1_RGB: return wgpu::TextureFormat::BC1RGBAUnorm;
|
||||
case TextureFormat::DXT1_RGBA: return wgpu::TextureFormat::BC1RGBAUnorm;
|
||||
@@ -457,14 +669,14 @@ wgpu::TextureView WebGPUTexture::makeTextureView(const uint8_t& baseLevel,
|
||||
const SamplerType samplerType) const noexcept {
|
||||
const wgpu::TextureViewDescriptor textureViewDescriptor{
|
||||
.label = getUserTextureViewLabel(target),
|
||||
.format = mWebGPUFormat,
|
||||
.format = mViewFormat,
|
||||
.dimension = toWebGPUTextureViewDimension(samplerType),
|
||||
.baseMipLevel = baseLevel,
|
||||
.mipLevelCount = levelCount,
|
||||
.baseArrayLayer = baseArrayLayer,
|
||||
.arrayLayerCount = arrayLayerCount,
|
||||
.aspect = mAspect,
|
||||
.usage = mWebGPUUsage };
|
||||
.usage = mViewUsage };
|
||||
wgpu::TextureView textureView = mTexture.CreateView(&textureViewDescriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(textureView)
|
||||
<< "Failed to create texture view " << textureViewDescriptor.label;
|
||||
|
||||
@@ -26,8 +26,28 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
[[nodiscard]] constexpr bool hasStencil(const wgpu::TextureFormat textureFormat) {
|
||||
return textureFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
|
||||
textureFormat == wgpu::TextureFormat::Depth32FloatStencil8 ||
|
||||
textureFormat == wgpu::TextureFormat::Stencil8;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool hasDepth(const wgpu::TextureFormat textureFormat) {
|
||||
return textureFormat == wgpu::TextureFormat::Depth16Unorm ||
|
||||
textureFormat == wgpu::TextureFormat::Depth32Float ||
|
||||
textureFormat == wgpu::TextureFormat::Depth24Plus ||
|
||||
textureFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
|
||||
textureFormat == wgpu::TextureFormat::Depth32FloatStencil8;
|
||||
}
|
||||
|
||||
class WebGPUTexture : public HwTexture {
|
||||
public:
|
||||
enum class MipmapGenerationStrategy : uint8_t {
|
||||
RENDER_PASS,
|
||||
SPD_COMPUTE_PASS,
|
||||
NONE,
|
||||
};
|
||||
|
||||
WebGPUTexture(SamplerType, uint8_t levels, TextureFormat, uint8_t samples, uint32_t width,
|
||||
uint32_t height, uint32_t depth, TextureUsage, wgpu::Device const&) noexcept;
|
||||
|
||||
@@ -41,35 +61,89 @@ public:
|
||||
|
||||
[[nodiscard]] wgpu::Texture const& getTexture() const { return mTexture; }
|
||||
|
||||
[[nodiscard]] wgpu::Texture const& getMsaaSidecarTexture(uint8_t sampleCount) const;
|
||||
|
||||
[[nodiscard]] wgpu::TextureView const& getDefaultTextureView() const {
|
||||
return mDefaultTextureView;
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::TextureView getOrMakeTextureView(uint8_t mipLevel, uint32_t arrayLayer);
|
||||
|
||||
[[nodiscard]] wgpu::TextureFormat getWebGPUFormat() const { return mWebGPUFormat; }
|
||||
[[nodiscard]] wgpu::TextureViewDimension getViewDimension() const { return mDimension; }
|
||||
[[nodiscard]] wgpu::TextureFormat getViewFormat() const { return mViewFormat; }
|
||||
|
||||
[[nodiscard]] uint32_t getArrayLayerCount() const { return mArrayLayerCount; }
|
||||
|
||||
[[nodiscard]] MipmapGenerationStrategy getMipmapGenerationStrategy() const {
|
||||
return mMipmapGenerationStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the MSAA sidecar texture if it has not already been created.
|
||||
* If the sidecar already exists with a different number of samples this will panic
|
||||
* (at the time of writing this WebGPU only supports MSAA with 4 samples. If this changes, then
|
||||
* multiple sidecars with different number of samples could be supported if needed, but that
|
||||
* level of complexity is not warranted at this time).
|
||||
* Additionally, if samples is <= 1 this will panic as well, as that is not an MSAA texture.
|
||||
* @param samples The number of samples the texture will have
|
||||
*/
|
||||
void createMsaaSidecarTextureIfNotAlreadyCreated(uint8_t samples, wgpu::Device const&);
|
||||
|
||||
/**
|
||||
* @param samples The number of samples the underlying texture supports
|
||||
* @param mipLevel The mip level into the underyling texture for which this view will reference
|
||||
* (this view will only have one mip level)
|
||||
* @param arrayLayer The layer into the underyling texture for which this view will reference
|
||||
* (this view will only have one layer)
|
||||
* @return A texture view for the MSAA sidecar texture
|
||||
*/
|
||||
wgpu::TextureView makeMsaaSidecarTextureViewIfTextureSidecarExists(uint8_t samples,
|
||||
uint8_t mipLevel, uint32_t arrayLayer) const;
|
||||
|
||||
/**
|
||||
* @return nullptr if a MSAA sidecar texture is not appliable, otherwise a view to one
|
||||
*/
|
||||
[[nodiscard]] wgpu::TextureView makeMsaaSidecarTextureView(wgpu::Texture const&, uint8_t mipLevel, uint32_t arrayLayer) const;
|
||||
|
||||
[[nodiscard]] static wgpu::TextureFormat fToWGPUTextureFormat(
|
||||
filament::backend::TextureFormat const& fFormat);
|
||||
|
||||
/**
|
||||
* @param format a required texture format (can be a view to a different underlying texture
|
||||
* format)
|
||||
* @return true if multiple mip levels can be generated via a compute shader + storage binding
|
||||
* on the texture, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] static bool supportsMultipleMipLevelsViaStorageBinding(
|
||||
wgpu::TextureFormat format);
|
||||
|
||||
private:
|
||||
// CreateTextureR has info for a texture and sampler. Texture Views are needed for binding,
|
||||
// along with a sampler Current plan: Inherit the sampler and Texture to always exist (It is a
|
||||
// ref counted pointer) when making views. View is optional
|
||||
wgpu::Texture mTexture = nullptr;
|
||||
// the texture view's format, which may differ from the underlying texture's format itself,
|
||||
// an example of when this is needed is when certain underlying shaders don't support
|
||||
// certain formats (e.g. compute shaders), but we need a view to that format elsewhere
|
||||
// (e.g. another shader or render target)
|
||||
wgpu::TextureFormat mViewFormat = wgpu::TextureFormat::Undefined;
|
||||
MipmapGenerationStrategy mMipmapGenerationStrategy = MipmapGenerationStrategy::NONE;
|
||||
// format is inherited from HwTexture. This naming is to distinguish it from Filament's format
|
||||
// this is the underlying texture's format, not necessarily the "view" of it... see mViewFormat
|
||||
wgpu::TextureFormat mWebGPUFormat = wgpu::TextureFormat::Undefined;
|
||||
wgpu::TextureAspect mAspect = wgpu::TextureAspect::Undefined;
|
||||
// usage is inherited from HwTexture. This naming is to distinguish it from Filament's usage
|
||||
wgpu::TextureUsage mWebGPUUsage = wgpu::TextureUsage::None;
|
||||
uint32_t mArrayLayerCount = 1;
|
||||
wgpu::TextureUsage mViewUsage = wgpu::TextureUsage::None;
|
||||
wgpu::TextureViewDimension mDimension = wgpu::TextureViewDimension::Undefined;
|
||||
size_t mBlockWidth = 0;
|
||||
size_t mBlockHeight = 0;
|
||||
wgpu::TextureView mDefaultTextureView = nullptr;
|
||||
uint32_t mArrayLayerCount = 1;
|
||||
wgpu::Texture mTexture = nullptr;
|
||||
uint32_t mDefaultMipLevel = 0;
|
||||
uint32_t mDefaultBaseArrayLayer = 0;
|
||||
wgpu::TextureView mDefaultTextureView = nullptr;
|
||||
// At the time of writing this, WebGPU only supported 4 samples in a multi-sampled texture.
|
||||
// If that has changed, then consider updating the implementation to have a map of msaa textures
|
||||
// by sampleCount or something like that.
|
||||
// For now that complexity and cost is not warranted due to WebGPU's restrictions.
|
||||
wgpu::Texture mMsaaSidecarTexture = nullptr;
|
||||
|
||||
[[nodiscard]] wgpu::TextureView makeTextureView(const uint8_t& baseLevel,
|
||||
const uint8_t& levelCount, const uint32_t& baseArrayLayer,
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
@@ -57,26 +56,22 @@ namespace {
|
||||
case ElementType::BYTE3:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format BYTE3 (normalized) but getting "
|
||||
"wgpu::VertexFormat::Snorm8x4 (no direct mapping in wgpu for x3 byte)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Snorm8x4 (no direct mapping in wgpu for x3 byte)";
|
||||
return VertexFormat::Snorm8x4; // NOT MINSPEC
|
||||
case ElementType::UBYTE3:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format UBYTE3 (normalized) but getting "
|
||||
"wgpu::VertexFormat::Unorm8x4 (no direct mapping in wgpu for x3 byte)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Unorm8x4 (no direct mapping in wgpu for x3 byte)";
|
||||
return VertexFormat::Unorm8x4; // NOT MINSPEC
|
||||
case ElementType::SHORT3:
|
||||
FWGPU_LOGW << "Requested Filament vertex format SHORT3 (normalized) but getting "
|
||||
"wgpu::VertexFormat::Snorm16x4 (no direct mapping in wgpu for x3 "
|
||||
"half/short)"
|
||||
<< utils::io::endl;
|
||||
"half/short)";
|
||||
return VertexFormat::Snorm16x4; // NOT MINSPEC
|
||||
case ElementType::USHORT3:
|
||||
FWGPU_LOGW << "Requested Filament vertex format USHORT3 (normalized) but getting "
|
||||
"wgpu::VertexFormat::Unorm16x4 (no direct mapping in wgpu for x3 "
|
||||
"half/short)"
|
||||
<< utils::io::endl;
|
||||
"half/short)";
|
||||
return VertexFormat::Unorm16x4; // NOT MINSPEC
|
||||
// Four Component Types
|
||||
case ElementType::BYTE4: return VertexFormat::Snorm8x4;
|
||||
@@ -97,26 +92,22 @@ namespace {
|
||||
case ElementType::BYTE:
|
||||
if (integer) return VertexFormat::Sint8;
|
||||
FWGPU_LOGW << "Requested Filament vertex format BYTE (float) but getting "
|
||||
"wgpu::VertexFormat::Float16 (no direct mapping in wgpu for 8 bit float)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16 (no direct mapping in wgpu for 8 bit float)";
|
||||
return wgpu::VertexFormat::Float16;
|
||||
case ElementType::UBYTE:
|
||||
if (integer) return VertexFormat::Uint8;
|
||||
FWGPU_LOGW << "Requested Filament vertex format UBYTE (float) but getting "
|
||||
"wgpu::VertexFormat::Float16 (no direct mapping in wgpu for 8 bit float)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16 (no direct mapping in wgpu for 8 bit float)";
|
||||
return VertexFormat::Float16;
|
||||
case ElementType::SHORT:
|
||||
if (integer) return VertexFormat::Sint16;
|
||||
FWGPU_LOGW << "Requested Filament vertex format SHORT (float) and getting "
|
||||
"wgpu::VertexFormat::Float16 (potential loss of precision?)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16 (potential loss of precision?)";
|
||||
return VertexFormat ::Float16;
|
||||
case ElementType::USHORT:
|
||||
if (integer) return VertexFormat::Uint16;
|
||||
FWGPU_LOGW << "Requested Filament vertex format USHORT (float) and getting "
|
||||
"wgpu::VertexFormat::Float16 (potential loss of precision?)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16 (potential loss of precision?)";
|
||||
return VertexFormat::Float16;
|
||||
case ElementType::HALF: return VertexFormat::Float16;
|
||||
case ElementType::INT: return VertexFormat::Sint32;
|
||||
@@ -127,58 +118,49 @@ namespace {
|
||||
if (integer) return VertexFormat::Sint8x2;
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format BYTE2 (float) but getting "
|
||||
"wgpu::VertexFormat::Float16x2 (no direct mapping in wgpu for 8 bit float)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x2 (no direct mapping in wgpu for 8 bit float)";
|
||||
return VertexFormat::Float16x2;
|
||||
case ElementType::UBYTE2:
|
||||
if (integer) return VertexFormat::Uint8x2;
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format UBYTE2 (float) but getting "
|
||||
"wgpu::VertexFormat::Float16x2 (no direct mapping in wgpu for 8 bit float)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x2 (no direct mapping in wgpu for 8 bit float)";
|
||||
return VertexFormat::Float16x2;
|
||||
case ElementType::SHORT2:
|
||||
if (integer) return VertexFormat::Sint16x2;
|
||||
FWGPU_LOGW << "Requested Filament vertex format SHORT2 (float) but getting "
|
||||
"wgpu::VertexFormat::Float16x2 (potential loss of precision?)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x2 (potential loss of precision?)";
|
||||
return VertexFormat::Float16x2;
|
||||
case ElementType::USHORT2:
|
||||
if (integer) return VertexFormat::Uint16x2;
|
||||
FWGPU_LOGW << "Requested Filament vertex format USHORT2 (float) but getting "
|
||||
"wgpu::VertexFormat::Float16x2 (potential loss of precision?)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x2 (potential loss of precision?)";
|
||||
return VertexFormat::Float16x2;
|
||||
case ElementType::HALF2: return VertexFormat::Float16x2;
|
||||
case ElementType::FLOAT2: return VertexFormat::Float32x2;
|
||||
// Three Component Types
|
||||
case ElementType::BYTE3:
|
||||
FWGPU_LOGW << "Requested Filament vertex format BYTE3 but getting "
|
||||
"wgpu::VertexFormat::Sint8x4 (no direct mapping in wgpu for x3 byte)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Sint8x4 (no direct mapping in wgpu for x3 byte)";
|
||||
return VertexFormat::Sint8x4; // NOT MINSPEC
|
||||
case ElementType::UBYTE3:
|
||||
FWGPU_LOGW << "Requested Filament vertex format UBYTE3 but getting "
|
||||
"wgpu::VertexFormat::Uint8x4 (no direct mapping in wgpu for x3 byte)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Uint8x4 (no direct mapping in wgpu for x3 byte)";
|
||||
return VertexFormat::Uint8x4; // NOT MINSPEC
|
||||
case ElementType::SHORT3:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format SHORT3 but getting "
|
||||
"wgpu::VertexFormat::Sint16x4 (no direct mapping in wgpu for x3 half/short)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Sint16x4 (no direct mapping in wgpu for x3 half/short)";
|
||||
return VertexFormat::Sint16x4; // NOT MINSPEC
|
||||
case ElementType::USHORT3:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format USHORT3 but getting "
|
||||
"wgpu::VertexFormat::Uint16x4 (no direct mapping in wgpu for x3 half/short)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Uint16x4 (no direct mapping in wgpu for x3 half/short)";
|
||||
return VertexFormat::Uint16x4; // NOT MINSPEC
|
||||
case ElementType::HALF3:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format HALF3 but getting "
|
||||
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for x3 half/short)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for x3 half/short)";
|
||||
return VertexFormat::Float16x4; // NOT MINSPEC
|
||||
case ElementType::FLOAT3: return VertexFormat::Float32x3;
|
||||
// Four Component Types
|
||||
@@ -186,27 +168,23 @@ namespace {
|
||||
if (integer) return VertexFormat::Sint8x4;
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format BYTE4 (float) but getting "
|
||||
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for 8 bit float)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for 8 bit float)";
|
||||
return VertexFormat::Float16x4;
|
||||
case ElementType::UBYTE4:
|
||||
if (integer) return VertexFormat::Uint8x4;
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format UBYTE4 (float) but getting "
|
||||
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for 8 bit float)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for 8 bit float)";
|
||||
return VertexFormat::Float16x4;
|
||||
case ElementType::SHORT4:
|
||||
if (integer) return VertexFormat::Sint16x4;
|
||||
FWGPU_LOGW << "Requested Filament vertex format SHORT4 (float) but getting "
|
||||
"wgpu::VertexFormat::Float16x4 (potential loss of precision?)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x4 (potential loss of precision?)";
|
||||
return VertexFormat::Float16x4;
|
||||
case ElementType::USHORT4:
|
||||
if (integer) return VertexFormat::Uint16x4;
|
||||
FWGPU_LOGW << "Requested Filament vertex format USHORT4 (float) but getting "
|
||||
"wgpu::VertexFormat::Float16x4 (potential loss of precision?)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x4 (potential loss of precision?)";
|
||||
return VertexFormat::Float16x4;
|
||||
case ElementType::HALF4: return VertexFormat::Float16x4;
|
||||
case ElementType::FLOAT4: return VertexFormat::Float32x4;
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include <backend/Platform.h>
|
||||
#include <utils/Hash.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <dawn/webgpu_cpp_print.h>
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
@@ -39,6 +38,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#endif
|
||||
@@ -53,10 +53,11 @@ namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::array REQUIRED_FEATURES = { wgpu::FeatureName::TransientAttachments,
|
||||
/*To make filtering assumptions like we want while waiting for Filament to provide that info,
|
||||
float 32 needs to be filterable*/
|
||||
wgpu::FeatureName::Float32Filterable,
|
||||
// The SPD Algorithm can make use of up to 12 storage texture attachments
|
||||
constexpr uint32_t MAX_MIPMAP_STORAGE_TEXTURES_PER_STAGE = 12u;
|
||||
|
||||
constexpr std::array REQUIRED_FEATURES = {
|
||||
wgpu::FeatureName::TransientAttachments,
|
||||
// Qualcomm 500 and 600 GPUs do not support this so it is not part of core webgpu spec. To
|
||||
// support such devices, we will either need Filament to not attempt this, or find another
|
||||
// workaround. https://github.com/gpuweb/gpuweb/issues/2648
|
||||
@@ -117,28 +118,6 @@ constexpr void forEachLimitToValidate(std::function<bool(LimitToValidate const)>
|
||||
}
|
||||
}
|
||||
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
utils::io::ostream& operator<<(utils::io::ostream& out,
|
||||
const wgpu::WGSLLanguageFeatureName languageFeatureName) noexcept {
|
||||
return streamInsertWebGPUPrintable(out, languageFeatureName);
|
||||
}
|
||||
|
||||
utils::io::ostream& operator<<(utils::io::ostream& out,
|
||||
const wgpu::FeatureName featureName) noexcept {
|
||||
return streamInsertWebGPUPrintable(out, featureName);
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename STREAM_TYPE>
|
||||
STREAM_TYPE& operator<<(STREAM_TYPE& out, wgpu::RequestAdapterOptions const& options) noexcept {
|
||||
return streamInsertRequestAdapterOptions(out, options);
|
||||
}
|
||||
|
||||
template<class STREAM_TYPE>
|
||||
STREAM_TYPE& operator<<(STREAM_TYPE& out, wgpu::AdapterInfo const& info) noexcept {
|
||||
return streamInsertRequestAdapterInfo(out, info);
|
||||
}
|
||||
|
||||
constexpr bool isDefined(uint32_t limit) { return limit < wgpu::kLimitU32Undefined; }
|
||||
|
||||
[[maybe_unused]] constexpr bool isDefined(uint64_t limit) {
|
||||
@@ -259,7 +238,7 @@ void printInstanceDetails(wgpu::Instance const& instance) {
|
||||
supportedWGSLLanguageFeatures.features +
|
||||
supportedWGSLLanguageFeatures.featureCount,
|
||||
[](wgpu::WGSLLanguageFeatureName const featureName) {
|
||||
FWGPU_LOGI << " " << featureName;
|
||||
FWGPU_LOGI << " " << webGPUPrintableToString(featureName);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -299,23 +278,22 @@ void printInstanceDetails(wgpu::Instance const& instance) {
|
||||
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
void printLimit(std::string_view name, const std::variant<uint32_t, uint64_t> value) {
|
||||
FWGPU_LOGI << " " << name.data() << ": ";
|
||||
static constexpr std::string_view indent = " ";
|
||||
bool undefined = true;
|
||||
if (std::holds_alternative<uint32_t>(value)) {
|
||||
if (std::get<uint32_t>(value) != WGPU_LIMIT_U32_UNDEFINED) {
|
||||
undefined = false;
|
||||
FWGPU_LOGI << std::get<uint32_t>(value);
|
||||
FWGPU_LOGI << indent << name.data() << ": " << std::get<uint32_t>(value);
|
||||
}
|
||||
} else if (std::holds_alternative<uint64_t>(value)) {
|
||||
if (std::get<uint64_t>(value) != WGPU_LIMIT_U64_UNDEFINED) {
|
||||
undefined = false;
|
||||
FWGPU_LOGI << std::get<uint64_t>(value);
|
||||
FWGPU_LOGI << indent << name.data() << ": " << std::get<uint64_t>(value);
|
||||
}
|
||||
}
|
||||
if (undefined) {
|
||||
FWGPU_LOGI << "UNDEFINED";
|
||||
FWGPU_LOGI << indent << name.data() << ": UNDEFINED";
|
||||
}
|
||||
FWGPU_LOGI << "";
|
||||
}
|
||||
#endif// FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
|
||||
@@ -393,16 +371,16 @@ struct AdapterDetails final {
|
||||
}
|
||||
};
|
||||
|
||||
template<class STREAM_TYPE>
|
||||
STREAM_TYPE& operator<<(STREAM_TYPE& out, AdapterDetails const& details) noexcept {
|
||||
out << details.info
|
||||
[[nodiscard]] std::string toString(AdapterDetails const& details) {
|
||||
std::stringstream out;
|
||||
out << adapterInfoToString(details.info)
|
||||
<< " power preference " << powerPreferenceToString(details.powerPreference);
|
||||
return out;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
void printAdapterDetails(AdapterDetails const& details) {
|
||||
FWGPU_LOGI << "Selected WebGPU adapter info: " << details;
|
||||
FWGPU_LOGI << "Selected WebGPU adapter info: " << toString(details);
|
||||
wgpu::SupportedFeatures supportedFeatures{};
|
||||
details.adapter.GetFeatures(&supportedFeatures);
|
||||
FWGPU_LOGI << "WebGPU adapter supported features (" << supportedFeatures.featureCount
|
||||
@@ -411,7 +389,7 @@ void printAdapterDetails(AdapterDetails const& details) {
|
||||
std::for_each(supportedFeatures.features,
|
||||
supportedFeatures.features + supportedFeatures.featureCount,
|
||||
[](wgpu::FeatureName const featureName) {
|
||||
FWGPU_LOGI << " " << featureName;
|
||||
FWGPU_LOGI << " " << webGPUPrintableToString(featureName);
|
||||
});
|
||||
}
|
||||
wgpu::Limits supportedLimits{};
|
||||
@@ -444,12 +422,12 @@ struct AdapterDetailsHash final {
|
||||
return details.adapter.HasFeature(featureName);
|
||||
})) {
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
FWGPU_LOGI << "WebGPU adapter " << details
|
||||
FWGPU_LOGI << "WebGPU adapter " << toString(details)
|
||||
<< " does not have the minimum required features.";
|
||||
FWGPU_LOGI << "Missing required feature(s): ";
|
||||
for (wgpu::FeatureName const& requiredFeature: REQUIRED_FEATURES) {
|
||||
if (!details.adapter.HasFeature(requiredFeature)) {
|
||||
FWGPU_LOGI << requiredFeature;
|
||||
FWGPU_LOGI << webGPUPrintableToString(requiredFeature);
|
||||
}
|
||||
}
|
||||
FWGPU_LOGI << "";
|
||||
@@ -458,10 +436,10 @@ struct AdapterDetailsHash final {
|
||||
}
|
||||
wgpu::Limits supportedLimits {};
|
||||
FILAMENT_CHECK_POSTCONDITION(details.adapter.GetLimits(&supportedLimits))
|
||||
<< "Failed to get limits for WebGPU adapter: " << details;
|
||||
<< "Failed to get limits for WebGPU adapter: " << toString(details);
|
||||
if (!satisfiesLimits(supportedLimits)) {
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
FWGPU_LOGI << " (for WebGPU adapter " << details << ")";
|
||||
FWGPU_LOGI << " (for WebGPU adapter " << toString(details) << ")";
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
@@ -487,14 +465,15 @@ struct AdapterDetailsHash final {
|
||||
status != wgpu::RequestAdapterStatus::CallbackCancelled)
|
||||
<< "Failed to request a WebGPU adapter due to the request callback "
|
||||
"being cancelled? Options: "
|
||||
<< options << " " << message.data;
|
||||
<< adapterOptionsToString(options) << " " << message.data;
|
||||
FILAMENT_CHECK_POSTCONDITION(status != wgpu::RequestAdapterStatus::Error)
|
||||
<< "Failed to request a WebGPU adapter due to an error. Options: "
|
||||
<< options << " Error: " << message.data;
|
||||
<< adapterOptionsToString(options) << " Error: " << message.data;
|
||||
if (status == wgpu::RequestAdapterStatus::Success) {
|
||||
AdapterDetails details = AdapterDetails(readyAdapter);
|
||||
FILAMENT_CHECK_POSTCONDITION(readyAdapter.GetInfo(&details.info))
|
||||
<< "Failed to get info for adapter (options: " << options << ")";
|
||||
<< "Failed to get info for adapter (options: "
|
||||
<< adapterOptionsToString(options) << ")";
|
||||
const std::lock_guard<std::mutex> lock(adaptersMutex);
|
||||
compatibleAdapters.emplace(std::move(details.info), details.powerPreference,
|
||||
std::move(details.adapter));
|
||||
@@ -509,9 +488,11 @@ struct AdapterDetailsHash final {
|
||||
wgpu::Future& future = futures[i];
|
||||
wgpu::WaitStatus status = instance.WaitAny(future, REQUEST_ADAPTER_TIMEOUT_NANOSECONDS);
|
||||
FILAMENT_CHECK_POSTCONDITION(status != wgpu::WaitStatus::TimedOut)
|
||||
<< "Timed out requesting a WebGPU adapter with options " << options;
|
||||
<< "Timed out requesting a WebGPU adapter with options "
|
||||
<< adapterOptionsToString(options);
|
||||
FILAMENT_CHECK_POSTCONDITION(status != wgpu::WaitStatus::Error)
|
||||
<< "Failed to request a WebGPU adapter with options " << options
|
||||
<< "Failed to request a WebGPU adapter with options "
|
||||
<< adapterOptionsToString(options)
|
||||
<< " due to an error (as request was made synchronous)";
|
||||
assert_invariant(status == wgpu::WaitStatus::Success);
|
||||
}
|
||||
@@ -519,7 +500,7 @@ struct AdapterDetailsHash final {
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
FWGPU_LOGI << compatibleAdapters.size() << " WebGPU adapter(s) found:";
|
||||
for (auto& details: compatibleAdapters) {
|
||||
FWGPU_LOGI << " WebGPU adapter: " << details;
|
||||
FWGPU_LOGI << " WebGPU adapter: " << toString(details);
|
||||
}
|
||||
#endif
|
||||
return compatibleAdapters;
|
||||
@@ -586,7 +567,7 @@ void printDeviceDetails(wgpu::Device const& device) {
|
||||
std::for_each(supportedFeatures.features,
|
||||
supportedFeatures.features + supportedFeatures.featureCount,
|
||||
[](wgpu::FeatureName const featureName) {
|
||||
FWGPU_LOGI << " " << featureName;
|
||||
FWGPU_LOGI << " " << webGPUPrintableToString(featureName);
|
||||
});
|
||||
}
|
||||
wgpu::Limits supportedLimits{};
|
||||
@@ -628,7 +609,21 @@ wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
|
||||
deviceDescriptor.defaultQueue.label = "default_queue";
|
||||
deviceDescriptor.requiredFeatureCount = enabledFeatures.size();
|
||||
deviceDescriptor.requiredFeatures = enabledFeatures.data();
|
||||
deviceDescriptor.requiredLimits = &REQUIRED_LIMITS;
|
||||
|
||||
// It is helpful to increase maxStorageTexturesPerShaderStage if the hardware supports it, but
|
||||
// not required. This allows our mipmap generation to perform as well as possible for the given
|
||||
// hardware. If we have more cases of "Nice to increase" limits like this we can generalize it
|
||||
// in the future.
|
||||
wgpu::Limits supportedLimits{};
|
||||
FILAMENT_CHECK_POSTCONDITION(
|
||||
adapter.GetLimits(&supportedLimits).status == wgpu::Status::Success)
|
||||
<< "Failed to get limits for WebGPU adapter";
|
||||
auto limitsToRequest = REQUIRED_LIMITS;
|
||||
limitsToRequest.maxStorageTexturesPerShaderStage =
|
||||
std::min(MAX_MIPMAP_STORAGE_TEXTURES_PER_STAGE,
|
||||
supportedLimits.maxStorageTexturesPerShaderStage);
|
||||
deviceDescriptor.requiredLimits = &limitsToRequest;
|
||||
|
||||
deviceDescriptor.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous,
|
||||
[](wgpu::Device const&, wgpu::DeviceLostReason const& reason,
|
||||
wgpu::StringView message) {
|
||||
@@ -684,7 +679,6 @@ wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
|
||||
"adapter should support them: %s\n",
|
||||
missingFeatures, featureNamesStream.str().data());
|
||||
}
|
||||
wgpu::Limits supportedLimits {};
|
||||
FILAMENT_CHECK_POSTCONDITION(device.GetLimits(&supportedLimits))
|
||||
<< "Failed to get limits for the device?";
|
||||
FILAMENT_CHECK_POSTCONDITION(satisfiesLimits(supportedLimits))
|
||||
|
||||
@@ -40,18 +40,24 @@ using namespace filament::math;
|
||||
|
||||
using namespace image;
|
||||
#endif
|
||||
#include <iostream>
|
||||
|
||||
namespace test {
|
||||
|
||||
Backend BackendTest::sBackend = Backend::NOOP;
|
||||
OperatingSystem BackendTest::sOperatingSystem = OperatingSystem::OTHER;
|
||||
bool BackendTest::sIsMobilePlatform = false;
|
||||
int BackendTest::sArgc = 0;
|
||||
char** BackendTest::sArgv = nullptr;
|
||||
std::vector<std::string> BackendTest::sFailedImages;
|
||||
|
||||
void BackendTest::init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform) {
|
||||
void BackendTest::init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform,
|
||||
int argc, char** argv) {
|
||||
sBackend = backend;
|
||||
sOperatingSystem = operatingSystem;
|
||||
sIsMobilePlatform = isMobilePlatform;
|
||||
sArgc = argc;
|
||||
sArgv = argv;
|
||||
}
|
||||
|
||||
BackendTest::BackendTest() : commandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE,
|
||||
@@ -160,6 +166,11 @@ void BackendTest::markImageAsFailure(std::string failedImageName) {
|
||||
sFailedImages.emplace_back(std::move(failedImageName));
|
||||
}
|
||||
|
||||
std::filesystem::path BackendTest::binaryDirectory() {
|
||||
assert(sArgc >= 1);
|
||||
return std::filesystem::path(sArgv[0]).remove_filename().string();
|
||||
}
|
||||
|
||||
void BackendTest::recordFailedImages() {
|
||||
if (!sFailedImages.empty()) {
|
||||
std::string failedImages;
|
||||
@@ -187,8 +198,9 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile, int& argc, char* argv[]) {
|
||||
BackendTest::init(backend, operatingSystem, isMobile);
|
||||
void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile, int& argc,
|
||||
char* argv[]) {
|
||||
BackendTest::init(backend, operatingSystem, isMobile, argc, argv);
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
::testing::AddGlobalTestEnvironment(new Environment);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <backend/Platform.h>
|
||||
|
||||
#include "private/backend/CommandBufferQueue.h"
|
||||
@@ -31,16 +33,20 @@ namespace test {
|
||||
|
||||
class BackendTest : public ::testing::Test {
|
||||
public:
|
||||
|
||||
static void init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform);
|
||||
static void init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform,
|
||||
int argc, char** argv);
|
||||
|
||||
static Backend sBackend;
|
||||
static OperatingSystem sOperatingSystem;
|
||||
static bool sIsMobilePlatform;
|
||||
static int sArgc;
|
||||
static char** sArgv;
|
||||
|
||||
// Takes the name of the image that wasn't correct, without the .png suffix
|
||||
static void markImageAsFailure(std::string failedImageName);
|
||||
|
||||
static std::filesystem::path binaryDirectory();
|
||||
|
||||
protected:
|
||||
|
||||
BackendTest();
|
||||
|
||||
@@ -59,28 +59,28 @@ uint32_t ScreenshotParams::expectedHash() const {
|
||||
return mExpectedPixelHash;
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::actualDirectoryPath() {
|
||||
return "images/actual_images";
|
||||
std::filesystem::path ScreenshotParams::actualDirectoryPath() {
|
||||
return BackendTest::binaryDirectory().append("images/actual_images");
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::actualFileName() const {
|
||||
return absl::StrFormat("%s_actual.png", mFileName);
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::actualFilePath() const {
|
||||
return absl::StrFormat("%s/%s", actualDirectoryPath(), actualFileName());
|
||||
std::filesystem::path ScreenshotParams::actualFilePath() const {
|
||||
return actualDirectoryPath().append(actualFileName());
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::expectedDirectoryPath() {
|
||||
return "images/expected_images";
|
||||
std::filesystem::path ScreenshotParams::expectedDirectoryPath() {
|
||||
return BackendTest::binaryDirectory().append("images/expected_images");
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::expectedFileName() const {
|
||||
return absl::StrFormat("%s.png", mFileName);
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::expectedFilePath() const {
|
||||
return absl::StrFormat("%s/%s", expectedDirectoryPath(), expectedFileName());
|
||||
std::filesystem::path ScreenshotParams::expectedFilePath() const {
|
||||
return expectedDirectoryPath().append(expectedFileName());
|
||||
}
|
||||
|
||||
const std::string ScreenshotParams::filePrefix() const {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifndef TNT_IMAGE_EXPECTATIONS_H
|
||||
#define TNT_IMAGE_EXPECTATIONS_H
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
@@ -51,12 +52,12 @@ public:
|
||||
bool isSrgb() const;
|
||||
uint32_t expectedHash() const;
|
||||
|
||||
static std::string actualDirectoryPath();
|
||||
static std::filesystem::path actualDirectoryPath();
|
||||
std::string actualFileName() const;
|
||||
std::string actualFilePath() const;
|
||||
static std::string expectedDirectoryPath();
|
||||
std::filesystem::path actualFilePath() const;
|
||||
static std::filesystem::path expectedDirectoryPath();
|
||||
std::string expectedFileName() const;
|
||||
std::string expectedFilePath() const;
|
||||
std::filesystem::path expectedFilePath() const;
|
||||
const std::string filePrefix() const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -139,6 +139,24 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
return bufferDimension;
|
||||
}
|
||||
|
||||
void exportScreenshot(void* pixelData) const {
|
||||
#ifndef FILAMENT_IOS
|
||||
const size_t width = readRect.width, height = readRect.height;
|
||||
LinearImage image(width, height, 4);
|
||||
if (format == PixelDataFormat::RGBA && type == PixelDataType::UBYTE) {
|
||||
image = toLinearWithAlpha<uint8_t>(width, height, width * 4, (uint8_t*)pixelData);
|
||||
}
|
||||
if (format == PixelDataFormat::RGBA && type == PixelDataType::FLOAT) {
|
||||
memcpy(image.getPixelRef(), pixelData, width * height * sizeof(math::float4));
|
||||
}
|
||||
std::string png = std::string(testName) + ".png";
|
||||
std::filesystem::path path = ScreenshotParams::actualDirectoryPath();
|
||||
path.append(png);
|
||||
std::ofstream outputStream(path.c_str(), std::ios::binary | std::ios::trunc);
|
||||
ImageEncoder::encode(outputStream, ImageEncoder::Format::PNG, image, "", png);
|
||||
#endif
|
||||
}
|
||||
|
||||
// The format and type for the readPixels call.
|
||||
PixelDataFormat format = PixelDataFormat::RGBA;
|
||||
PixelDataType type = PixelDataType::UBYTE;
|
||||
@@ -311,6 +329,8 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
const auto* test = (const TestCase*)user;
|
||||
assert_invariant(test);
|
||||
|
||||
test->exportScreenshot(buffer);
|
||||
|
||||
// Hash the contents of the buffer and check that they match.
|
||||
uint32_t hash = utils::hash::murmur3((const uint32_t*)buffer, size / 4, 0);
|
||||
|
||||
|
||||
@@ -118,6 +118,7 @@ INPUT = ../libs/filabridge/include \
|
||||
../libs/gltfio/include \
|
||||
../libs/utils/include \
|
||||
backend/include \
|
||||
backend/include/private/backend/DriverAPI.dox \
|
||||
include
|
||||
|
||||
INPUT_ENCODING = UTF-8
|
||||
|
||||
@@ -190,6 +190,7 @@ public:
|
||||
using FeatureLevel = backend::FeatureLevel;
|
||||
using StereoscopicType = backend::StereoscopicType;
|
||||
using Driver = backend::Driver;
|
||||
using GpuContextPriority = backend::Platform::GpuContextPriority;
|
||||
|
||||
/**
|
||||
* Config is used to define the memory footprint used by the engine, such as the
|
||||
@@ -410,6 +411,11 @@ public:
|
||||
* @deprecated use "backend.opengl.assert_native_window_is_valid" feature flag instead
|
||||
*/
|
||||
bool assertNativeWindowIsValid = false;
|
||||
|
||||
/**
|
||||
* GPU context priority level. Controls GPU work scheduling and preemption.
|
||||
*/
|
||||
GpuContextPriority gpuContextPriority = GpuContextPriority::DEFAULT;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -57,7 +57,8 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI {
|
||||
|
||||
public:
|
||||
using CullingMode = backend::CullingMode;
|
||||
using TransparencyMode = TransparencyMode;
|
||||
// ReSharper disable once CppRedundantQualifier
|
||||
using TransparencyMode = filament::TransparencyMode;
|
||||
using DepthFunc = backend::SamplerCompareFunc;
|
||||
using StencilCompareFunc = backend::SamplerCompareFunc;
|
||||
using StencilOperation = backend::StencilOperation;
|
||||
|
||||
@@ -481,6 +481,7 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
|
||||
uint8_t fieldType = 0;
|
||||
uint8_t fieldFormat = 0;
|
||||
uint8_t fieldPrecision = 0;
|
||||
bool fieldUnfilterable = false;
|
||||
bool fieldMultisample = false;
|
||||
|
||||
if (!unflattener.read(&fieldName)) {
|
||||
@@ -503,6 +504,10 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!unflattener.read(&fieldUnfilterable)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!unflattener.read(&fieldMultisample)) {
|
||||
return false;
|
||||
}
|
||||
@@ -512,6 +517,7 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
|
||||
SamplerInterfaceBlock::Type(fieldType),
|
||||
SamplerInterfaceBlock::Format(fieldFormat),
|
||||
SamplerInterfaceBlock::Precision(fieldPrecision),
|
||||
fieldUnfilterable,
|
||||
fieldMultisample);
|
||||
}
|
||||
|
||||
|
||||
@@ -2989,7 +2989,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscale(FrameGraph& fg, bool
|
||||
}
|
||||
|
||||
if (dsrOptions.quality == QualityLevel::LOW) {
|
||||
return upscaleBilinear(fg, translucent, dsrOptions, input, vp, outDesc, filter);
|
||||
return upscaleBilinear(fg, dsrOptions, input, vp, outDesc, filter);
|
||||
}
|
||||
if (dsrOptions.quality == QualityLevel::MEDIUM) {
|
||||
return upscaleSGSR1(fg, sourceHasLuminance, dsrOptions, input, vp, outDesc);
|
||||
@@ -2997,7 +2997,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscale(FrameGraph& fg, bool
|
||||
return upscaleFSR1(fg, dsrOptions, input, vp, outDesc);
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph& fg, bool translucent,
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph& fg,
|
||||
DynamicResolutionOptions dsrOptions, FrameGraphId<FrameGraphTexture> const input,
|
||||
filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
|
||||
SamplerMagFilter filter) noexcept {
|
||||
@@ -3016,18 +3016,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
|
||||
.attachments = { .color = { data.output } },
|
||||
.clearFlags = TargetBufferFlags::DEPTH });
|
||||
},
|
||||
[this, vp, translucent, filter](FrameGraphResources const& resources,
|
||||
[this, vp, filter](FrameGraphResources const& resources,
|
||||
auto const& data, DriverApi& driver) {
|
||||
bindPostProcessDescriptorSet(driver);
|
||||
|
||||
// helper to enable blending
|
||||
auto enableTranslucentBlending = [](PipelineState& pipeline) {
|
||||
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
|
||||
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
|
||||
pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_ALPHA;
|
||||
pipeline.rasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_ALPHA;
|
||||
};
|
||||
|
||||
auto color = resources.getTexture(data.input);
|
||||
auto const& inputDesc = resources.getDescriptor(data.input);
|
||||
|
||||
@@ -3055,9 +3047,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
|
||||
auto out = resources.getRenderPassInfo();
|
||||
|
||||
auto pipeline = getPipelineState(material.getMaterial(mEngine));
|
||||
if (translucent) {
|
||||
enableTranslucentBlending(pipeline);
|
||||
}
|
||||
renderFullScreenQuad(out, pipeline, driver);
|
||||
});
|
||||
|
||||
@@ -3065,7 +3054,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
|
||||
|
||||
// if we had to take the low quality fallback, we still do the "sharpen pass"
|
||||
if (dsrOptions.sharpness > 0.0f) {
|
||||
output = rcas(fg, dsrOptions.sharpness, output, outDesc, translucent);
|
||||
output = rcas(fg, dsrOptions.sharpness, output, outDesc,
|
||||
false /* translucent=false, because dst buffer is allocated */);
|
||||
}
|
||||
|
||||
// we rely on automatic culling of unused render passes
|
||||
@@ -3443,9 +3433,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
|
||||
return input;
|
||||
}
|
||||
|
||||
// we currently don't support stencil resolve
|
||||
assert_invariant(!isStencilFormat(inDesc.format));
|
||||
|
||||
// The Metal / Vulkan backends currently don't support depth/stencil resolve.
|
||||
if (isDepthFormat(inDesc.format) && (!mEngine.getDriverApi().isDepthStencilResolveSupported())) {
|
||||
return resolveDepth(fg, outputBufferName, input, outDesc);
|
||||
@@ -3463,6 +3450,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
|
||||
|
||||
auto const& ppResolve = fg.addPass<ResolveData>("resolve",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
// we currently don't support stencil resolve.
|
||||
assert_invariant(!isStencilFormat(inDesc.format));
|
||||
|
||||
data.input = builder.read(input, FrameGraphTexture::Usage::BLIT_SRC);
|
||||
data.output = builder.createTexture(outputBufferName, outDesc);
|
||||
data.output = builder.write(data.output, FrameGraphTexture::Usage::BLIT_DST);
|
||||
@@ -3514,6 +3504,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolveDepth(FrameGraph& fg,
|
||||
|
||||
auto const& ppResolve = fg.addPass<ResolveData>("resolveDepth",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
// we currently don't support stencil resolve
|
||||
assert_invariant(!isStencilFormat(inDesc.format));
|
||||
|
||||
data.input = builder.sample(input);
|
||||
data.output = builder.createTexture(outputBufferName, outDesc);
|
||||
data.output = builder.write(data.output, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
|
||||
@@ -257,7 +257,7 @@ public:
|
||||
FrameGraphId<FrameGraphTexture> input, Viewport const& vp,
|
||||
FrameGraphTexture::Descriptor const& outDesc, backend::SamplerMagFilter filter) noexcept;
|
||||
|
||||
FrameGraphId<FrameGraphTexture> upscaleBilinear(FrameGraph& fg, bool translucent,
|
||||
FrameGraphId<FrameGraphTexture> upscaleBilinear(FrameGraph& fg,
|
||||
DynamicResolutionOptions dsrOptions, FrameGraphId<FrameGraphTexture> input,
|
||||
Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
|
||||
backend::SamplerMagFilter filter) noexcept;
|
||||
|
||||
@@ -141,6 +141,7 @@ Engine* FEngine::create(Builder const& builder) {
|
||||
.stereoscopicType = instance->getConfig().stereoscopicType,
|
||||
.assertNativeWindowIsValid = instance->features.backend.opengl.assert_native_window_is_valid,
|
||||
.metalDisablePanicOnDrawableFailure = instance->getConfig().metalDisablePanicOnDrawableFailure,
|
||||
.gpuContextPriority = instance->getConfig().gpuContextPriority,
|
||||
};
|
||||
instance->mDriver = platform->createDriver(sharedContext, driverConfig);
|
||||
|
||||
@@ -761,6 +762,7 @@ int FEngine::loop() {
|
||||
.stereoscopicType = mConfig.stereoscopicType,
|
||||
.assertNativeWindowIsValid = features.backend.opengl.assert_native_window_is_valid,
|
||||
.metalDisablePanicOnDrawableFailure = mConfig.metalDisablePanicOnDrawableFailure,
|
||||
.gpuContextPriority = mConfig.gpuContextPriority,
|
||||
};
|
||||
mDriver = mPlatform->createDriver(mSharedGLContext, driverConfig);
|
||||
|
||||
@@ -822,9 +824,9 @@ int FEngine::loop() {
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
if(debug.server) {
|
||||
delete debug.server;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if FILAMENT_ENABLE_FGVIEWER
|
||||
#if FILAMENT_ENABLE_FGVIEWER
|
||||
if(debug.fgviewerServer) {
|
||||
delete debug.fgviewerServer;
|
||||
}
|
||||
|
||||
@@ -98,6 +98,14 @@ namespace filament::fgviewer {
|
||||
} // namespace filament::fgviewer
|
||||
#endif
|
||||
|
||||
// We have added correctness assertions that breaks clients' projects. We add this define to allow
|
||||
// for the client's to address these assertions at a more gradual pace.
|
||||
#if defined(FILAMENT_RELAXED_CORRECTNESS_ASSERTIONS)
|
||||
#define CORRECTNESS_ASSERTION_DEFAULT false
|
||||
#else
|
||||
#define CORRECTNESS_ASSERTION_DEFAULT true
|
||||
#endif
|
||||
|
||||
namespace filament {
|
||||
|
||||
class Renderer;
|
||||
@@ -715,10 +723,14 @@ public:
|
||||
bool use_shadow_atlas = false;
|
||||
} shadows;
|
||||
struct {
|
||||
// TODO: default the following two flags to true.
|
||||
bool assert_material_instance_in_use = false;
|
||||
bool assert_destroy_material_before_material_instance = false;
|
||||
bool assert_vertex_buffer_count_exceeds_8 = false;
|
||||
// TODO: clean-up the following flags (equivalent to setting them to true) when
|
||||
// clients have addressed their usages.
|
||||
bool assert_material_instance_in_use = CORRECTNESS_ASSERTION_DEFAULT;
|
||||
bool assert_destroy_material_before_material_instance =
|
||||
CORRECTNESS_ASSERTION_DEFAULT;
|
||||
bool assert_vertex_buffer_count_exceeds_8 = CORRECTNESS_ASSERTION_DEFAULT;
|
||||
bool assert_vertex_buffer_attribute_stride_mult_of_4 =
|
||||
CORRECTNESS_ASSERTION_DEFAULT;
|
||||
} debug;
|
||||
} engine;
|
||||
struct {
|
||||
@@ -759,6 +771,9 @@ public:
|
||||
{ "features.engine.debug.assert_vertex_buffer_count_exceeds_8",
|
||||
"Assert when a client's number of buffers for a VertexBuffer exceeds 8.",
|
||||
&features.engine.debug.assert_vertex_buffer_count_exceeds_8, false },
|
||||
{ "features.engine.debug.assert_vertex_buffer_attribute_stride_mult_of_4",
|
||||
"Assert that the attribute stride of a vertex buffer is a multiple of 4.",
|
||||
&features.engine.debug.assert_vertex_buffer_attribute_stride_mult_of_4, false },
|
||||
}};
|
||||
|
||||
utils::Slice<const FeatureFlag> getFeatureFlags() const noexcept {
|
||||
|
||||
@@ -386,8 +386,9 @@ void FTexture::setImage(FEngine& engine, size_t const level,
|
||||
FILAMENT_CHECK_PRECONDITION(any(mUsage & Texture::Usage::UPLOADABLE))
|
||||
<< "Texture is not uploadable.";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(mSampleCount <= 1) << "Operation not supported with multisample ("
|
||||
<< unsigned(mSampleCount) << ") texture.";
|
||||
FILAMENT_CHECK_PRECONDITION(mSampleCount <= 1)
|
||||
<< "Operation not supported with multisample ("
|
||||
<< unsigned(mSampleCount) << ") texture.";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(xoffset + width <= valueForLevel(level, mWidth))
|
||||
<< "xoffset (" << unsigned(xoffset) << ") + width (" << unsigned(width)
|
||||
@@ -428,28 +429,46 @@ void FTexture::setImage(FEngine& engine, size_t const level,
|
||||
<< ") > texture depth (" << effectiveTextureDepthOrLayers << ") at level ("
|
||||
<< unsigned(level) << ")";
|
||||
|
||||
if (UTILS_UNLIKELY(!width || !height || !depth)) {
|
||||
// The operation is a no-op, return immediately. The PixelBufferDescriptor callback
|
||||
// should be called automatically when the object is destroyed.
|
||||
// The precondition check below assumes width, height, depth non null.
|
||||
return;
|
||||
}
|
||||
|
||||
if (p.type != PixelDataType::COMPRESSED) {
|
||||
using PBD = PixelBufferDescriptor;
|
||||
size_t const stride = p.stride ? p.stride : width;
|
||||
size_t const bpp = PBD::computePixelSize(p.format, p.type);
|
||||
size_t const bpp = PBD::computeDataSize(p.format, p.type, 1, 1, 1);
|
||||
size_t const bpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment);
|
||||
size_t const pbdWidth = stride;
|
||||
// TODO: PBD should have a "layer stride" (using "depth" as a substitute).
|
||||
size_t const pbdDepth = depth;
|
||||
size_t const pbdHeight = p.size / bpr / pbdDepth;
|
||||
assert_invariant(p.size % bpr == 0 && (p.size / bpr) % pbdDepth == 0);
|
||||
|
||||
size_t const bpl = bpr * height; // TODO: PBD should have a "layer stride"
|
||||
// TODO: PBD should have a p.depth (# layers to skip)
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
bpp * (pbdWidth - p.left) * (pbdHeight - p.top) * (pbdDepth - 0) >=
|
||||
bpp * (width - xoffset) * (height - yoffset) * (depth - zoffset))
|
||||
|
||||
/* Calculates the byte offset of the last pixel in a 3D sub-region. */
|
||||
auto const calculateLastPixelOffset = [bpp, bpr, bpl](
|
||||
size_t xoff, size_t yoff, size_t zoff,
|
||||
size_t width, size_t height, size_t depth) {
|
||||
// The 0-indexed coordinates of the last pixel are:
|
||||
// x = xoff + width - 1
|
||||
// y = yoff + height - 1
|
||||
// z = zoff + depth - 1
|
||||
// The offset is calculated as: (z * bpl) + (y * bpr) + (x * bpp)
|
||||
return ((zoff + depth - 1) * bpl) +
|
||||
((yoff + height - 1) * bpr) +
|
||||
((xoff + width - 1) * bpp);
|
||||
};
|
||||
|
||||
size_t const lastPixelOffset = calculateLastPixelOffset(
|
||||
p.left, p.top, 0, width, height, depth);
|
||||
|
||||
// make sure the whole last pixel is in the buffer
|
||||
FILAMENT_CHECK_PRECONDITION(lastPixelOffset + bpp <= p.size)
|
||||
<< "buffer overflow: (size=" << size_t(p.size) << ", stride=" << size_t(p.stride)
|
||||
<< ", left=" << unsigned(p.left) << ", top=" << unsigned(p.top)
|
||||
<< ") smaller than specified region "
|
||||
"{{"
|
||||
<< unsigned(xoffset) << "," << unsigned(yoffset) << "," << unsigned(zoffset)
|
||||
<< "},{" << unsigned(width) << "," << unsigned(height) << "," << unsigned(depth)
|
||||
<< ")}}";
|
||||
<< unsigned(xoffset) << "," << unsigned(yoffset) << "," << unsigned(zoffset) << "},{"
|
||||
<< unsigned(width) << "," << unsigned(height) << "," << unsigned(depth) << ")}}";
|
||||
}
|
||||
|
||||
engine.getDriverApi().update3DImage(mHandle, uint8_t(level), xoffset, yoffset, zoffset, width,
|
||||
|
||||
@@ -162,8 +162,7 @@ VertexBuffer* VertexBuffer::Builder::build(Engine& engine) {
|
||||
FILAMENT_CHECK_PRECONDITION(mImpl->mBufferCount <= DOCUMENTED_MAX_VERTEX_BUFFER_COUNT)
|
||||
<< "bufferCount cannot be more than " << DOCUMENTED_MAX_VERTEX_BUFFER_COUNT;
|
||||
} else if (mImpl->mBufferCount > DOCUMENTED_MAX_VERTEX_BUFFER_COUNT) {
|
||||
utils::slog.w << "bufferCount cannot be more than " << DOCUMENTED_MAX_VERTEX_BUFFER_COUNT
|
||||
<< utils::io::endl;
|
||||
LOG(WARNING) << "bufferCount cannot be more than " << DOCUMENTED_MAX_VERTEX_BUFFER_COUNT;
|
||||
}
|
||||
|
||||
// Next we check if any unused buffer slots have been allocated. This helps prevent errors
|
||||
@@ -178,9 +177,15 @@ VertexBuffer* VertexBuffer::Builder::build(Engine& engine) {
|
||||
<< "attribute " << j << " offset=" << attributes[j].offset
|
||||
<< " is not multiple of 4";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION((attributes[j].stride & 0x3u) == 0)
|
||||
<< "attribute " << j << " stride=" << attributes[j].stride
|
||||
<< " is not multiple of 4";
|
||||
FEngine* fengine = static_cast<FEngine*>(&engine);
|
||||
if (fengine->features.engine.debug.assert_vertex_buffer_attribute_stride_mult_of_4) {
|
||||
FILAMENT_CHECK_PRECONDITION((attributes[j].stride & 0x3u) == 0)
|
||||
<< "attribute " << j << " stride=" << attributes[j].stride
|
||||
<< " is not multiple of 4";
|
||||
} else if ((attributes[j].stride & 0x3u) != 0) {
|
||||
LOG(WARNING) << "attribute " << j << " stride=" << attributes[j].stride
|
||||
<< " is not multiple of 4";
|
||||
}
|
||||
|
||||
if (engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0) {
|
||||
FILAMENT_CHECK_PRECONDITION(!(attributes[j].flags & Attribute::FLAG_INTEGER_TARGET))
|
||||
|
||||
@@ -66,6 +66,7 @@ DescriptorSet& DescriptorSet::operator=(DescriptorSet&& rhs) noexcept {
|
||||
mDirty = rhs.mDirty;
|
||||
mValid = rhs.mValid;
|
||||
mSetAfterCommitWarning = rhs.mSetAfterCommitWarning;
|
||||
mSetUndefinedParameterWarning = rhs.mSetUndefinedParameterWarning;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -106,11 +107,12 @@ void DescriptorSet::commitSlow(DescriptorSetLayout const& layout,
|
||||
});
|
||||
|
||||
auto const unsetValidDescriptors = layout.getValidDescriptors() & ~mValid;
|
||||
if (UTILS_VERY_UNLIKELY(!unsetValidDescriptors.empty())) {
|
||||
if (UTILS_VERY_UNLIKELY(!unsetValidDescriptors.empty() && !mSetUndefinedParameterWarning)) {
|
||||
unsetValidDescriptors.forEachSetBit([&](auto i) {
|
||||
LOG(WARNING) << (layout.isSampler(i) ? "Sampler" : "Buffer") << " descriptor " << i
|
||||
<< " of " << mName.c_str() << " is not set. Please report this issue.";
|
||||
});
|
||||
mSetUndefinedParameterWarning = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,6 +110,7 @@ private:
|
||||
mutable utils::bitset64 mValid; // 8
|
||||
backend::DescriptorSetHandle mDescriptorSetHandle; // 4
|
||||
mutable bool mSetAfterCommitWarning = false; // 1
|
||||
mutable bool mSetUndefinedParameterWarning = false; // 1
|
||||
utils::StaticString mName; // 16
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high
|
||||
precision: high,
|
||||
unfilterable: true
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
|
||||
@@ -4,7 +4,8 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: medium
|
||||
precision: medium,
|
||||
unfilterable: true
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
|
||||
@@ -9,7 +9,8 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high
|
||||
precision: high,
|
||||
unfilterable: true
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
@@ -77,4 +78,3 @@ fragment {
|
||||
postProcess.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: medium
|
||||
precision: medium,
|
||||
unfilterable: true
|
||||
},
|
||||
{
|
||||
type : float2,
|
||||
|
||||
@@ -9,7 +9,8 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: medium
|
||||
precision: medium,
|
||||
unfilterable: true
|
||||
},
|
||||
{
|
||||
type : float2,
|
||||
|
||||
@@ -5,7 +5,8 @@ material {
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high,
|
||||
multisample : true
|
||||
multisample : true,
|
||||
unfilterable: true
|
||||
}
|
||||
],
|
||||
outputs : [
|
||||
|
||||
@@ -4,7 +4,8 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high
|
||||
precision: high,
|
||||
unfilterable: true
|
||||
},
|
||||
{
|
||||
type : mat4,
|
||||
|
||||
@@ -4,7 +4,8 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high
|
||||
precision: high,
|
||||
unfilterable: true
|
||||
},
|
||||
{
|
||||
type : mat4,
|
||||
|
||||
@@ -4,7 +4,8 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high
|
||||
precision: high,
|
||||
unfilterable: true
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
|
||||
@@ -4,7 +4,8 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high
|
||||
precision: high,
|
||||
unfilterable: true
|
||||
},
|
||||
{
|
||||
type : mat4,
|
||||
|
||||
@@ -4,7 +4,8 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high
|
||||
precision: high,
|
||||
unfilterable: true
|
||||
},
|
||||
{
|
||||
type : mat4,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.61.2"
|
||||
spec.version = "1.62.0"
|
||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
spec.homepage = "https://google.github.io/filament"
|
||||
spec.authors = "Google LLC."
|
||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||
spec.platform = :ios, "11.0"
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.61.2/filament-v1.61.2-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.62.0/filament-v1.62.0-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
namespace filament {
|
||||
|
||||
// update this when a new version of filament wouldn't work with older materials
|
||||
static constexpr size_t MATERIAL_VERSION = 61;
|
||||
static constexpr size_t MATERIAL_VERSION = 62;
|
||||
|
||||
/**
|
||||
* Supported shading models
|
||||
|
||||
@@ -61,6 +61,7 @@ public:
|
||||
Type type; // type of this sampler
|
||||
Format format; // format of this sampler
|
||||
Precision precision; // precision of this sampler
|
||||
bool unfilterable; // whether the sampling should be unfiltered.
|
||||
bool multisample; // multisample capable
|
||||
ShaderStageFlags stages; // stages the sampler can be accessed from
|
||||
};
|
||||
@@ -83,6 +84,7 @@ public:
|
||||
Type type; // type of this sampler
|
||||
Format format; // format of this sampler
|
||||
Precision precision; // precision of this sampler
|
||||
bool unfilterable = false; // whether the sampling should be unfiltered.
|
||||
bool multisample = false; // multisample capable
|
||||
ShaderStageFlags stages =
|
||||
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS; // shader stages using this sampler
|
||||
@@ -95,7 +97,8 @@ public:
|
||||
|
||||
// Add a sampler
|
||||
Builder& add(std::string_view samplerName, Binding binding, Type type, Format format,
|
||||
Precision precision = Precision::MEDIUM, bool multisample = false,
|
||||
Precision precision = Precision::MEDIUM, bool unfilterable = false,
|
||||
bool multisample = false,
|
||||
ShaderStageFlags stages = ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) noexcept;
|
||||
|
||||
// Add multiple samplers
|
||||
|
||||
@@ -96,7 +96,7 @@ static constexpr std::initializer_list<DescriptorSetLayoutBinding> perRenderable
|
||||
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::OBJECT_UNIFORMS, DescriptorFlags::DYNAMIC_OFFSET },
|
||||
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::BONES_UNIFORMS, DescriptorFlags::DYNAMIC_OFFSET },
|
||||
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::MORPHING_UNIFORMS },
|
||||
{ DescriptorType::SAMPLER_2D_ARRAY_FLOAT, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_POSITIONS },
|
||||
{ DescriptorType::SAMPLER_2D_ARRAY_FLOAT, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_POSITIONS, DescriptorFlags::UNFILTERABLE },
|
||||
{ DescriptorType::SAMPLER_2D_ARRAY_INT, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_TANGENTS },
|
||||
{ DescriptorType::SAMPLER_2D_ARRAY_FLOAT, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::BONES_INDICES_AND_WEIGHTS },
|
||||
};
|
||||
|
||||
@@ -50,12 +50,12 @@ SamplerInterfaceBlock::Builder::stageFlags(backend::ShaderStageFlags stageFlags)
|
||||
}
|
||||
|
||||
SamplerInterfaceBlock::Builder& SamplerInterfaceBlock::Builder::add(std::string_view samplerName,
|
||||
Binding binding, Type type, Format format, Precision precision, bool multisample,
|
||||
ShaderStageFlags stages) noexcept {
|
||||
Binding binding, Type type, Format format, Precision precision, bool unfilterable,
|
||||
bool multisample, ShaderStageFlags stages) noexcept {
|
||||
mEntries.push_back({
|
||||
{ samplerName.data(), samplerName.size() }, // name
|
||||
{ }, // uniform name
|
||||
binding, type, format, precision, multisample, stages });
|
||||
{ samplerName.data(), samplerName.size() }, // name
|
||||
{}, // uniform name
|
||||
binding, type, format, precision, unfilterable, multisample, stages });
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -65,8 +65,9 @@ SamplerInterfaceBlock SamplerInterfaceBlock::Builder::build() {
|
||||
|
||||
SamplerInterfaceBlock::Builder& SamplerInterfaceBlock::Builder::add(
|
||||
std::initializer_list<ListEntry> list) noexcept {
|
||||
for (auto& e : list) {
|
||||
add(e.name, e.binding, e.type, e.format, e.precision, e.multisample, e.stages);
|
||||
for (auto& e: list) {
|
||||
add(e.name, e.binding, e.type, e.format, e.precision, e.unfilterable, e.multisample,
|
||||
e.stages);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/compiler.h>
|
||||
@@ -323,8 +324,8 @@ public:
|
||||
*/
|
||||
MaterialBuilder& parameter(const char* name, SamplerType samplerType,
|
||||
SamplerFormat format = SamplerFormat::FLOAT,
|
||||
ParameterPrecision precision = ParameterPrecision::DEFAULT, bool multisample = false,
|
||||
const char* transformName = "",
|
||||
ParameterPrecision precision = ParameterPrecision::DEFAULT, bool unfilterable = false,
|
||||
bool multisample = false, const char* transformName = "",
|
||||
ShaderStageFlags stages = ShaderStageFlags::ALL_SHADER_STAGE_FLAGS);
|
||||
|
||||
MaterialBuilder& buffer(filament::BufferInterfaceBlock bib);
|
||||
@@ -658,17 +659,39 @@ public:
|
||||
|
||||
// Sampler
|
||||
Parameter(const char* paramName, SamplerType t, SamplerFormat f, ParameterPrecision p,
|
||||
bool ms, const char* tn, ShaderStageFlags s)
|
||||
: name(paramName), size(1), precision(p), samplerType(t), format(f),
|
||||
parameterType(SAMPLER), multisample(ms), transformName(tn), stages(s) { }
|
||||
bool unfilterable, bool ms, const char* tn, ShaderStageFlags s)
|
||||
: name(paramName),
|
||||
size(1),
|
||||
precision(p),
|
||||
samplerType(t),
|
||||
format(f),
|
||||
unfilterable(unfilterable),
|
||||
multisample(ms),
|
||||
transformName(tn),
|
||||
stages(s),
|
||||
parameterType(SAMPLER) {}
|
||||
|
||||
// Uniform
|
||||
Parameter(const char* paramName, UniformType t, size_t typeSize, ParameterPrecision p)
|
||||
: name(paramName), size(typeSize), uniformType(t), precision(p), parameterType(UNIFORM) { }
|
||||
: name(paramName),
|
||||
size(typeSize),
|
||||
uniformType(t),
|
||||
precision(p),
|
||||
format{ 0 },
|
||||
unfilterable(false),
|
||||
multisample(false),
|
||||
parameterType(UNIFORM) {}
|
||||
|
||||
// Subpass
|
||||
Parameter(const char* paramName, SubpassType t, SamplerFormat f, ParameterPrecision p)
|
||||
: name(paramName), size(1), precision(p), subpassType(t), format(f), parameterType(SUBPASS) { }
|
||||
: name(paramName),
|
||||
size(1),
|
||||
precision(p),
|
||||
subpassType(t),
|
||||
format(f),
|
||||
unfilterable(false),
|
||||
multisample(false),
|
||||
parameterType(SUBPASS) {}
|
||||
|
||||
utils::CString name;
|
||||
size_t size;
|
||||
@@ -677,6 +700,7 @@ public:
|
||||
SamplerType samplerType;
|
||||
SubpassType subpassType;
|
||||
SamplerFormat format;
|
||||
bool unfilterable;
|
||||
bool multisample;
|
||||
utils::CString transformName;
|
||||
ShaderStageFlags stages;
|
||||
|
||||
@@ -300,19 +300,19 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, UniformType const
|
||||
return parameter(name, 1, type, precision);
|
||||
}
|
||||
|
||||
|
||||
MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType samplerType,
|
||||
SamplerFormat format, ParameterPrecision precision, bool multisample,
|
||||
SamplerFormat format, ParameterPrecision precision, bool unfilterable, bool multisample,
|
||||
const char* transformName, ShaderStageFlags stages) {
|
||||
FILAMENT_CHECK_PRECONDITION(!multisample ||
|
||||
(format != SamplerFormat::SHADOW &&
|
||||
(samplerType == SamplerType::SAMPLER_2D ||
|
||||
samplerType == SamplerType::SAMPLER_2D_ARRAY)))
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
!multisample || (format != SamplerFormat::SHADOW &&
|
||||
(samplerType == SamplerType::SAMPLER_2D ||
|
||||
samplerType == SamplerType::SAMPLER_2D_ARRAY)))
|
||||
<< "multisample samplers only possible with SAMPLER_2D or SAMPLER_2D_ARRAY,"
|
||||
" as long as type is not SHADOW";
|
||||
|
||||
FILAMENT_CHECK_POSTCONDITION(mParameterCount < MAX_PARAMETERS_COUNT) << "Too many parameters";
|
||||
mParameters[mParameterCount++] = { name, samplerType, format, precision, multisample, transformName, stages };
|
||||
mParameters[mParameterCount++] = { name, samplerType, format, precision, unfilterable,
|
||||
multisample, transformName, stages };
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -639,10 +639,12 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept {
|
||||
assert_invariant(!param.isSubpass());
|
||||
if (param.isSampler()) {
|
||||
sbb.add({ param.name.data(), param.name.size() }, binding, param.samplerType,
|
||||
param.format, param.precision, param.multisample, param.stages);
|
||||
param.format, param.precision, param.unfilterable, param.multisample,
|
||||
param.stages);
|
||||
if (!param.transformName.empty()) {
|
||||
ibb.add({{{ param.transformName.data(), param.transformName.size() }, uint8_t(binding),
|
||||
0, UniformType::MAT3, Precision::DEFAULT, FeatureLevel::FEATURE_LEVEL_0 }});
|
||||
ibb.add({ { { param.transformName.data(), param.transformName.size() },
|
||||
uint8_t(binding), 0, UniformType::MAT3, Precision::DEFAULT,
|
||||
FeatureLevel::FEATURE_LEVEL_0 } });
|
||||
}
|
||||
binding++;
|
||||
} else if (param.isUniform()) {
|
||||
|
||||
@@ -77,6 +77,7 @@ void MaterialSamplerInterfaceBlockChunk::flatten(Flattener& f) {
|
||||
f.writeUint8(static_cast<uint8_t>(sInfo.type));
|
||||
f.writeUint8(static_cast<uint8_t>(sInfo.format));
|
||||
f.writeUint8(static_cast<uint8_t>(sInfo.precision));
|
||||
f.writeBool(sInfo.unfilterable);
|
||||
f.writeBool(sInfo.multisample);
|
||||
}
|
||||
}
|
||||
@@ -230,7 +231,11 @@ void MaterialDescriptorSetLayoutChunk::flatten(Flattener& f) {
|
||||
f.writeUint8(uint8_t(descriptor_sets::getDescriptorType(entry.type, entry.format)));
|
||||
f.writeUint8(uint8_t(entry.stages));
|
||||
f.writeUint8(entry.binding);
|
||||
f.writeUint8(uint8_t(DescriptorFlags::NONE));
|
||||
if (entry.unfilterable) {
|
||||
f.writeUint8(uint8_t(DescriptorFlags::UNFILTERABLE));
|
||||
} else {
|
||||
f.writeUint8(uint8_t(DescriptorFlags::NONE));
|
||||
}
|
||||
f.writeUint16(0);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user