Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04efb9e18d |
@@ -54,6 +54,7 @@ jobs:
|
||||
build-android:
|
||||
name: build-android
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -72,6 +73,7 @@ jobs:
|
||||
build-ios:
|
||||
name: build-iOS
|
||||
runs-on: macos-14-xlarge
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -87,6 +89,7 @@ jobs:
|
||||
build-web:
|
||||
name: build-web
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -104,12 +107,11 @@ jobs:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- id: get_commit_msg
|
||||
uses: ./.github/actions/get-commit-msg
|
||||
- name: Check for manual edits to /docs
|
||||
run: |
|
||||
COMMIT_ID=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | head -n 1 | sed "s/commit //g")
|
||||
bash docs_src/build/presubmit_check.sh ${COMMIT_ID}
|
||||
echo "${{ github.event.pull_request.head.sha }} -- ${{ github.event.pull_request.head.sha }}"
|
||||
# disable for now
|
||||
# bash docs_src/build/presubmit_check.sh ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
test-renderdiff:
|
||||
name: test-renderdiff
|
||||
@@ -136,7 +138,7 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: presubmit-renderdiff-result
|
||||
path: ./out/renderdiff
|
||||
path: ./out/renderdiff_tests
|
||||
|
||||
validate-wgsl-webgpu:
|
||||
name: validate-wgsl-webgpu
|
||||
|
||||
@@ -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.62.0'
|
||||
implementation 'com.google.android.filament:filament-android:1.61.1'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.62.0'
|
||||
pod 'Filament', '~> 1.61.1'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -7,15 +7,6 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.62.1
|
||||
|
||||
- Add new shader define `VARIANT_DEPTH`, defined when a material is compiled for depth variants
|
||||
(e.g., shadows) [**Requires recompiling materials**]
|
||||
|
||||
## v1.62.0
|
||||
|
||||
- Add new `unfilterable` field to Filament Material's `sampler` [⚠️ **New Material Version**]
|
||||
|
||||
## v1.61.2
|
||||
|
||||
- samples: samples now have a CLI to select backend api
|
||||
|
||||
@@ -556,8 +556,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
|
||||
jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge,
|
||||
jboolean disableHandleUseAfterFreeCheck,
|
||||
jint preferredShaderLanguage,
|
||||
jboolean forceGLES2Context, jboolean assertNativeWindowIsValid,
|
||||
jint gpuContextPriority) {
|
||||
jboolean forceGLES2Context, jboolean assertNativeWindowIsValid) {
|
||||
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
|
||||
Engine::Config config = {
|
||||
.commandBufferSizeMB = (uint32_t) commandBufferSizeMB,
|
||||
@@ -575,7 +574,6 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
|
||||
.preferredShaderLanguage = (Engine::Config::ShaderLanguage) preferredShaderLanguage,
|
||||
.forceGLES2Context = (bool) forceGLES2Context,
|
||||
.assertNativeWindowIsValid = (bool) assertNativeWindowIsValid,
|
||||
.gpuContextPriority = (Engine::GpuContextPriority) gpuContextPriority,
|
||||
};
|
||||
builder->config(&config);
|
||||
}
|
||||
|
||||
@@ -175,36 +175,6 @@ public class Engine {
|
||||
MULTIVIEW,
|
||||
};
|
||||
|
||||
/**
|
||||
* This controls the priority level for GPU work scheduling, which helps prioritize the
|
||||
* submitted GPU work and enables preemption.
|
||||
*/
|
||||
public enum GpuContextPriority {
|
||||
/**
|
||||
* Backend default GPU context priority (typically MEDIUM)
|
||||
*/
|
||||
DEFAULT,
|
||||
/**
|
||||
* For non-interactive, deferrable workloads. This should not interfere with standard
|
||||
* applications.
|
||||
*/
|
||||
LOW,
|
||||
/**
|
||||
* The default priority level for standard applications.
|
||||
*/
|
||||
MEDIUM,
|
||||
/**
|
||||
* For high-priority, latency-sensitive workloads that are more important than standard
|
||||
* applications.
|
||||
*/
|
||||
HIGH,
|
||||
/**
|
||||
* The highest priority, intended for system-critical, real-time applications where missing
|
||||
* deadlines is unacceptable (e.g., VR/AR compositors or other system-critical tasks).
|
||||
*/
|
||||
REALTIME,
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs <code>Engine</code> objects using a builder pattern.
|
||||
*/
|
||||
@@ -263,8 +233,7 @@ public class Engine {
|
||||
config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge,
|
||||
config.disableHandleUseAfterFreeCheck,
|
||||
config.preferredShaderLanguage.ordinal(),
|
||||
config.forceGLES2Context, config.assertNativeWindowIsValid,
|
||||
config.gpuContextPriority.ordinal());
|
||||
config.forceGLES2Context, config.assertNativeWindowIsValid);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -520,11 +489,6 @@ public class Engine {
|
||||
* @Deprecated use "backend.opengl.assert_native_window_is_valid" feature flag instead
|
||||
*/
|
||||
public boolean assertNativeWindowIsValid = false;
|
||||
|
||||
/**
|
||||
* GPU context priority level. Controls GPU work scheduling and preemption.
|
||||
*/
|
||||
public GpuContextPriority gpuContextPriority = GpuContextPriority.DEFAULT;
|
||||
}
|
||||
|
||||
private Engine(long nativeEngine, Config config) {
|
||||
@@ -1528,8 +1492,7 @@ public class Engine {
|
||||
long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge,
|
||||
boolean disableHandleUseAfterFreeCheck,
|
||||
int preferredShaderLanguage,
|
||||
boolean forceGLES2Context, boolean assertNativeWindowIsValid,
|
||||
int gpuContextPriority);
|
||||
boolean forceGLES2Context, boolean assertNativeWindowIsValid);
|
||||
private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal);
|
||||
private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext);
|
||||
private static native void nSetBuilderPaused(long nativeBuilder, boolean paused);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.62.0
|
||||
VERSION_NAME=1.61.1
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
BIN
Binary file not shown.
+1
-2
@@ -1,7 +1,6 @@
|
||||
#Wed Nov 17 10:40:18 PST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
Vendored
+109
-173
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -17,99 +17,67 @@
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
@@ -119,9 +87,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -130,120 +98,88 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
Vendored
+16
-19
@@ -14,7 +14,7 @@
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,8 +25,7 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -41,13 +40,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -57,11 +56,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -76,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
Binary file not shown.
+1
-19
@@ -18,7 +18,6 @@ package com.google.android.filament.gltf
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
@@ -51,7 +50,6 @@ class MainActivity : Activity() {
|
||||
// Load the library for the utility layer, which in turn loads gltfio and the Filament core.
|
||||
init { Utils.init() }
|
||||
private const val TAG = "gltf-viewer"
|
||||
private const val STATIC_MODEL_TAG = "use-static-model"
|
||||
}
|
||||
|
||||
private lateinit var surfaceView: SurfaceView
|
||||
@@ -71,7 +69,6 @@ class MainActivity : Activity() {
|
||||
private var loadStartTime = 0L
|
||||
private var loadStartFence: Fence? = null
|
||||
private val viewerContent = AutomationEngine.ViewerContent()
|
||||
private var useStaticModel = false
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -86,12 +83,6 @@ class MainActivity : Activity() {
|
||||
doubleTapDetector = GestureDetector(applicationContext, doubleTapListener)
|
||||
singleTapDetector = GestureDetector(applicationContext, singleTapListener)
|
||||
|
||||
val intent: Intent = intent
|
||||
val bundle: Bundle? = intent.extras
|
||||
bundle?.let {
|
||||
useStaticModel = it.getBoolean(STATIC_MODEL_TAG, false)
|
||||
}
|
||||
|
||||
modelViewer = ModelViewer(surfaceView)
|
||||
viewerContent.view = modelViewer.view
|
||||
viewerContent.sunlight = modelViewer.light
|
||||
@@ -150,16 +141,7 @@ class MainActivity : Activity() {
|
||||
}
|
||||
|
||||
private fun createDefaultRenderables() {
|
||||
// Sometimes it's useful to set to the default model to something static. You can enable
|
||||
// the static model by launching the app from adb, as in
|
||||
// `adb shell am start -n com.google.android.filament.gltf/.MainActivity --ez "use-static-model" true`
|
||||
val modelPath = if (useStaticModel) {
|
||||
"models/helmet.glb"
|
||||
} else {
|
||||
"models/scene.gltf"
|
||||
}
|
||||
|
||||
val buffer = assets.open(modelPath).use { input ->
|
||||
val buffer = assets.open("models/scene.gltf").use { input ->
|
||||
val bytes = ByteArray(input.available())
|
||||
input.read(bytes)
|
||||
ByteBuffer.wrap(bytes)
|
||||
|
||||
@@ -1027,11 +1027,8 @@ samplerCubemap | Cubemap texture
|
||||
[Table [materialParamsTypes]: Material parameter types]
|
||||
|
||||
Samplers
|
||||
: Sampler types can have the following fields:
|
||||
- `format` : which can be either `int` or `float` (defaults to `float`).
|
||||
- `multisample` : a boolean to indicate whether the sampler is meant for multisampling (defaults to `false`)
|
||||
- `unfilterable` : a boolean to indicate whether the sampling is not filtered (defaults to `false`)
|
||||
|
||||
: Sampler types can also specify a `format` which can be either `int` or `float` (defaults to
|
||||
`float`).
|
||||
|
||||
Arrays
|
||||
: A parameter can define an array of values by appending `[size]` after the type name, where
|
||||
|
||||
@@ -30,6 +30,11 @@ from utils import execute, ArgParseImpl
|
||||
CUR_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT_DIR = os.path.join(CUR_DIR, '../../')
|
||||
|
||||
def get_last_commit_hash():
|
||||
res, ret = execute('git rev-parse HEAD', cwd=ROOT_DIR)
|
||||
assert res == 0, 'Failed to get the last commit hash'
|
||||
return ret.strip()
|
||||
|
||||
def get_edited_files(commit_hash):
|
||||
INSERT = '#####?????'
|
||||
res, ret = execute(f'git show --name-only --pretty=%b{INSERT} {commit_hash}')
|
||||
|
||||
@@ -39,13 +39,13 @@ function download_mdbook() {
|
||||
fi
|
||||
|
||||
local CHECK_UNAME="
|
||||
import sys
|
||||
parts=[a.lower() for a in sys.stdin.read().strip().split(' ')]
|
||||
import sys;
|
||||
parts=[a.lower() for a in sys.stdin.read().strip().split(' ')];
|
||||
def get_type():
|
||||
if 'darwin' in parts:
|
||||
if 'x86_64' in parts:
|
||||
return ${DARWIN_X86}
|
||||
elif 'aarch' in parts or 'arm64' in parts:
|
||||
elif 'aarch' in parts:
|
||||
return ${DARWIN_ARM}
|
||||
elif 'linux' in parts:
|
||||
if 'x86_64' in parts:
|
||||
@@ -63,12 +63,11 @@ print(get_type())
|
||||
echo "*** Need to install Rust ***"
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
fi
|
||||
source "${HOME}/.cargo/env"
|
||||
if ! (command -v cargo >/dev/null 2>&1); then
|
||||
echo "*** Still cannot find cargo ***"
|
||||
echo "*** Still cannot find `cargo` ***"
|
||||
exit 1
|
||||
fi
|
||||
cargo install --force mdbook
|
||||
cargo install mdbook
|
||||
else
|
||||
# Download prebuilts from github
|
||||
mkdir -p ${HOME}/.cargo/bin
|
||||
|
||||
@@ -13,7 +13,7 @@ This document is part of the [Filament project](https://github.com/google/filame
|
||||
## Authors
|
||||
|
||||
- [Romain Guy](https://github.com/romainguy), [@romainguy](https://twitter.com/romainguy)
|
||||
- [Mathias Agopian](https://github.com/pixelflinger), [@pixelflinger](https://bsky.app/profile/pixelflinger.bsky.social)
|
||||
- [Mathias Agopian](https://github.com/pixelflinger), [@darthmoosious](https://twitter.com/darthmoosious)
|
||||
|
||||
# Overview
|
||||
|
||||
@@ -78,27 +78,26 @@ in table [standardProperties].
|
||||
Property | Definition
|
||||
-----------------------:|:---------------------
|
||||
**baseColor** | Diffuse albedo for non-metallic surfaces, and specular color for metallic surfaces
|
||||
**roughness** | Perceived smoothness (1.0) or roughness (0.0) of a surface. Smooth surfaces exhibit sharp reflections
|
||||
**metallic** | Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1)
|
||||
**roughness** | Perceived smoothness (1.0) or roughness (0.0) of a surface. Smooth surfaces exhibit sharp reflections
|
||||
**reflectance** | Fresnel reflectance at normal incidence for dielectric surfaces. This directly controls the strength of the reflections
|
||||
**ambientOcclusion** | Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0
|
||||
**clearCoat** | Strength of the clear coat layer
|
||||
**clearCoatRoughness** | Perceived smoothness or roughness of the clear coat layer
|
||||
**clearCoatNormal** | A detail normal used to perturb the clear coat layer using _bump mapping_ (_normal mapping_)
|
||||
**anisotropy** | Amount of anisotropy in either the tangent or bitangent direction
|
||||
**anisotropyDirection** | Local surface direction in tangent space
|
||||
**thickness** | Thickness of the solid volume of refractive objects
|
||||
**sheenColor** | Strength of the sheen layer
|
||||
**sheenRoughness** | Perceived smoothness or roughness of the sheen layer
|
||||
**emissive** | Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass
|
||||
**clearCoat** | Strength of the clear coat layer
|
||||
**clearCoatRoughness** | Perceived smoothness or roughness of the clear coat layer
|
||||
**anisotropy** | Amount of anisotropy in either the tangent or bitangent direction
|
||||
**anisotropyDirection** | Local surface direction in tangent space
|
||||
**ambientOcclusion** | Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0
|
||||
**normal** | A detail normal used to perturb the surface using _bump mapping_ (_normal mapping_)
|
||||
**postLightingColor** | Additional color that can be blended with the result of the lighting computations. See `postLightingBlending`
|
||||
**absorption** | Absorption factor for refractive objects
|
||||
**transmission** | Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is
|
||||
**ior** | Index of refraction, either for refractive objects or as an alternative to reflectance
|
||||
**microThickness** | Thickness of the thin layer of refractive objects
|
||||
**bentNormal** | A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality
|
||||
**shadowStrength** | Strength factor between 0 and 1 for all shadows received by this material
|
||||
**clearCoatNormal** | A detail normal used to perturb the clear coat layer using _bump mapping_ (_normal mapping_)
|
||||
**emissive** | Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass
|
||||
**postLightingColor** | Additional color that can be blended with the result of the lighting computations. See `postLightingBlending`
|
||||
**ior** | Index of refraction, either for refractive objects or as an alternative to reflectance
|
||||
**transmission** | Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is
|
||||
**absorption** | Absorption factor for refractive objects
|
||||
**microThickness** | Thickness of the thin layer of refractive objects
|
||||
**thickness** | Thickness of the solid volume of refractive objects
|
||||
[Table [standardProperties]: Properties of the standard model]
|
||||
|
||||
The type and range of each property is described in table [standardPropertiesTypes].
|
||||
@@ -992,13 +991,10 @@ Type
|
||||
|
||||
Value
|
||||
: Each entry is an object with the properties `name` and `type`, both of `string` type. The
|
||||
name must be a valid GLSL identifier. Entries have an optional `precision`, which can be
|
||||
name must be a valid GLSL identifier. Entries also have an optional `precision`, which can be
|
||||
one of `default` (best precision for the platform, typically `high` on desktop, `medium` on
|
||||
mobile), `low`, `medium`, `high`. The type must be one of the types described in
|
||||
table [materialParamsTypes]. For Android external textures, entries also have an optional
|
||||
transformName parameter to specify the name of the material parameter that will be
|
||||
used to expose the transform matrix associated with the external sampler. In iOS and Vulkan,
|
||||
this will always be identity.
|
||||
table [materialParamsTypes].
|
||||
|
||||
Type | Description
|
||||
:----------------------|:---------------------------------
|
||||
@@ -1027,8 +1023,12 @@ samplerCubemap | Cubemap texture
|
||||
[Table [materialParamsTypes]: Material parameter types]
|
||||
|
||||
Samplers
|
||||
: Sampler types can also specify a `format` which can be either `int` or `float` (defaults to
|
||||
`float`).
|
||||
: Sampler types can specify additional options:
|
||||
|
||||
- `format`: either `int` or `float` (defaults to `float`).
|
||||
- `stages`: array of strings containing the list of shader stages this
|
||||
sampler can be accessed from. Each entry must be either `vertex` or
|
||||
`fragment` (defaults to both).
|
||||
|
||||
Arrays
|
||||
: A parameter can define an array of values by appending `[size]` after the type name, where
|
||||
@@ -1273,9 +1273,6 @@ Description
|
||||
when selecting any shading model that is not `unlit`. See the shader sections of this document
|
||||
for more information on how to access these attributes from the shaders.
|
||||
|
||||
!!! Note: Interaction with custom variables
|
||||
When the `color` attribute is specified, only four custom variables are available instead of five.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
|
||||
material {
|
||||
parameters : [
|
||||
@@ -1306,7 +1303,7 @@ Type
|
||||
: array of `string`
|
||||
|
||||
Value
|
||||
: Up to 5 strings, each must be a valid GLSL identifier.
|
||||
: Up to 4 strings, each must be a valid GLSL identifier.
|
||||
|
||||
Description
|
||||
: Defines custom interpolants (or variables) that are output by the material's vertex shader.
|
||||
@@ -1322,10 +1319,6 @@ Description
|
||||
particular if `default` is specified the default precision is used is the fragment shader
|
||||
(`mediump`) and in the vertex shader (`highp`).
|
||||
|
||||
!!! Warning: Interaction with required attributes
|
||||
If the `color` attribute is specified in the `required` list, then only four variables can be used
|
||||
instead of five.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
|
||||
material {
|
||||
name : Skybox,
|
||||
|
||||
@@ -146,7 +146,7 @@ This document is part of the <a href="https://github.com/google/filament">Filame
|
||||
<ul>
|
||||
<li class="minus"><a href="https://github.com/romainguy">Romain Guy</a>, <a href="https://twitter.com/romainguy">@romainguy</a>
|
||||
</li>
|
||||
<li class="minus"><a href="https://github.com/pixelflinger">Mathias Agopian</a>, <a href="https://bsky.app/profile/pixelflinger.bsky.social">@pixelflinger</a></li></ul>
|
||||
<li class="minus"><a href="https://github.com/pixelflinger">Mathias Agopian</a>, <a href="https://twitter.com/darthmoosious">@darthmoosious</a></li></ul>
|
||||
</p>
|
||||
<a class="target" name="overview"> </a><a class="target" name="overview"> </a><a class="target" name="toc2"> </a><h1>Overview</h1>
|
||||
<p>
|
||||
@@ -217,27 +217,26 @@ 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">roughness</strong> </td><td style="text-align:left"> Perceived smoothness (1.0) or roughness (0.0) of a surface. Smooth surfaces exhibit sharp reflections </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">metallic</strong> </td><td style="text-align:left"> Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1) </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">roughness</strong> </td><td style="text-align:left"> Perceived smoothness (1.0) or roughness (0.0) of a surface. Smooth surfaces exhibit sharp reflections </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">reflectance</strong> </td><td style="text-align:left"> Fresnel reflectance at normal incidence for dielectric surfaces. This directly controls the strength of the reflections </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">ambientOcclusion</strong> </td><td style="text-align:left"> Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0 </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">clearCoat</strong> </td><td style="text-align:left"> Strength of the clear coat layer </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">clearCoatRoughness</strong> </td><td style="text-align:left"> Perceived smoothness or roughness of the clear coat layer </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">clearCoatNormal</strong> </td><td style="text-align:left"> A detail normal used to perturb the clear coat layer using <em class="underscore">bump mapping</em> (<em class="underscore">normal mapping</em>) </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">anisotropy</strong> </td><td style="text-align:left"> Amount of anisotropy in either the tangent or bitangent direction </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">anisotropyDirection</strong> </td><td style="text-align:left"> Local surface direction in tangent space </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">thickness</strong> </td><td style="text-align:left"> Thickness of the solid volume of refractive objects </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">sheenColor</strong> </td><td style="text-align:left"> Strength of the sheen layer </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">sheenRoughness</strong> </td><td style="text-align:left"> Perceived smoothness or roughness of the sheen layer </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">emissive</strong> </td><td style="text-align:left"> Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">clearCoat</strong> </td><td style="text-align:left"> Strength of the clear coat layer </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">clearCoatRoughness</strong> </td><td style="text-align:left"> Perceived smoothness or roughness of the clear coat layer </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">anisotropy</strong> </td><td style="text-align:left"> Amount of anisotropy in either the tangent or bitangent direction </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">anisotropyDirection</strong> </td><td style="text-align:left"> Local surface direction in tangent space </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">ambientOcclusion</strong> </td><td style="text-align:left"> Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0 </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">normal</strong> </td><td style="text-align:left"> A detail normal used to perturb the surface using <em class="underscore">bump mapping</em> (<em class="underscore">normal mapping</em>) </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">postLightingColor</strong> </td><td style="text-align:left"> Additional color that can be blended with the result of the lighting computations. See <code>postLightingBlending</code> </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">absorption</strong> </td><td style="text-align:left"> Absorption factor for refractive objects </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">transmission</strong> </td><td style="text-align:left"> Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">ior</strong> </td><td style="text-align:left"> Index of refraction, either for refractive objects or as an alternative to reflectance </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">microThickness</strong> </td><td style="text-align:left"> Thickness of the thin layer of refractive objects </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">bentNormal</strong> </td><td style="text-align:left"> A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">shadowStrength</strong> </td><td style="text-align:left"> Strength factor between 0 and 1 for all shadows received by this material </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">clearCoatNormal</strong> </td><td style="text-align:left"> A detail normal used to perturb the clear coat layer using <em class="underscore">bump mapping</em> (<em class="underscore">normal mapping</em>) </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">emissive</strong> </td><td style="text-align:left"> Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">postLightingColor</strong> </td><td style="text-align:left"> Additional color that can be blended with the result of the lighting computations. See <code>postLightingBlending</code> </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">ior</strong> </td><td style="text-align:left"> Index of refraction, either for refractive objects or as an alternative to reflectance </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">transmission</strong> </td><td style="text-align:left"> Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">absorption</strong> </td><td style="text-align:left"> Absorption factor for refractive objects </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">microThickness</strong> </td><td style="text-align:left"> Thickness of the thin layer of refractive objects </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">thickness</strong> </td><td style="text-align:left"> Thickness of the solid volume of refractive objects </td></tr>
|
||||
</table><center><div class="tablecaption"><b style="font-style:normal;">Table 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>.
|
||||
@@ -1098,13 +1097,10 @@ non-shader data.
|
||||
|
||||
<dl><dt>Type</dt><dd><p> array of parameter objects
|
||||
</p></dd><dt>Value</dt><dd><p> Each entry is an object with the properties <code>name</code> and <code>type</code>, both of <code>string</code> type. The
|
||||
name must be a valid GLSL identifier. Entries have an optional <code>precision</code>, which can be
|
||||
name must be a valid GLSL identifier. Entries also have an optional <code>precision</code>, which can be
|
||||
one of <code>default</code> (best precision for the platform, typically <code>high</code> on desktop, <code>medium</code> on
|
||||
mobile), <code>low</code>, <code>medium</code>, <code>high</code>. The type must be one of the types described in
|
||||
<a href="#table_materialparamstypes" target="_self">table 14</a>. For Android external textures, entries also have an optional
|
||||
transformName parameter to specify the name of the material parameter that will be
|
||||
used to expose the transform matrix associated with the external sampler. In iOS and Vulkan,
|
||||
this will always be identity.
|
||||
<a href="#table_materialparamstypes" target="_self">table 14</a>.
|
||||
</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>
|
||||
@@ -1326,10 +1322,7 @@ non-shader data.
|
||||
required and does not need to be specified. The <code>tangents</code> attribute is automatically required
|
||||
when selecting any shading model that is not <code>unlit</code>. See the shader sections of this document
|
||||
for more information on how to access these attributes from the shaders.
|
||||
</p></dd></dl><div class="admonition note"><div class="admonition-title"> Interaction with custom variables</div>
|
||||
</p><p>
|
||||
When the <code>color</code> attribute is specified, only four custom variables are available instead of five.</div>
|
||||
</p><pre class="listing tilde"><code><span class="line"></span>material <span class="hljs-punctuation">{</span>
|
||||
</p></dd></dl></p><pre class="listing tilde"><code><span class="line"></span>material <span class="hljs-punctuation">{</span>
|
||||
<span class="line"></span> parameters <span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
|
||||
<span class="line"></span> <span class="hljs-punctuation">{</span>
|
||||
<span class="line"></span> type <span class="hljs-punctuation">:</span> sampler2d<span class="hljs-punctuation">,</span>
|
||||
@@ -1354,7 +1347,7 @@ non-shader data.
|
||||
<p>
|
||||
|
||||
<dl><dt>Type</dt><dd><p> array of <code>string</code>
|
||||
</p></dd><dt>Value</dt><dd><p> Up to 5 strings, each must be a valid GLSL identifier.
|
||||
</p></dd><dt>Value</dt><dd><p> Up to 4 strings, each must be a valid GLSL identifier.
|
||||
</p></dd><dt>Description</dt><dd><p> Defines custom interpolants (or variables) that are output by the material's vertex shader.
|
||||
Each entry of the array defines the name of an interpolant. The full name in the fragment
|
||||
shader is the name of the interpolant with the <code>variable_</code> prefix. For instance, if you
|
||||
@@ -1367,11 +1360,7 @@ non-shader data.
|
||||
In this case the specified precision is used as-is in both fragment and vertex stages, in
|
||||
particular if <code>default</code> is specified the default precision is used is the fragment shader
|
||||
(<code>mediump</code>) and in the vertex shader (<code>highp</code>).
|
||||
</p></dd></dl><div class="admonition warning"><div class="admonition-title"> Interaction with required attributes</div>
|
||||
</p><p>
|
||||
If the <code>color</code> attribute is specified in the <code>required</code> list, then only four variables can be used
|
||||
instead of five.</div>
|
||||
</p><pre class="listing tilde"><code><span class="line"></span>material <span class="hljs-punctuation">{</span>
|
||||
</p></dd></dl></p><pre class="listing tilde"><code><span class="line"></span>material <span class="hljs-punctuation">{</span>
|
||||
<span class="line"></span> name <span class="hljs-punctuation">:</span> Skybox<span class="hljs-punctuation">,</span>
|
||||
<span class="line"></span> parameters <span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
|
||||
<span class="line"></span> <span class="hljs-punctuation">{</span>
|
||||
|
||||
@@ -269,20 +269,12 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
src/webgpu/WebGPUDescriptorSetLayout.h
|
||||
src/webgpu/WebGPUDriver.cpp
|
||||
src/webgpu/WebGPUDriver.h
|
||||
src/webgpu/WebGPUFence.cpp
|
||||
src/webgpu/WebGPUFence.h
|
||||
src/webgpu/WebGPUIndexBuffer.cpp
|
||||
src/webgpu/WebGPUIndexBuffer.h
|
||||
src/webgpu/WebGPUMsaaTextureResolver.cpp
|
||||
src/webgpu/WebGPUMsaaTextureResolver.h
|
||||
src/webgpu/WebGPUPipelineCache.cpp
|
||||
src/webgpu/WebGPUPipelineCache.h
|
||||
src/webgpu/WebGPUPipelineLayoutCache.cpp
|
||||
src/webgpu/WebGPUPipelineLayoutCache.h
|
||||
src/webgpu/WebGPUPipelineCreation.cpp
|
||||
src/webgpu/WebGPUPipelineCreation.h
|
||||
src/webgpu/WebGPUProgram.cpp
|
||||
src/webgpu/WebGPUProgram.h
|
||||
src/webgpu/WebGPURenderPassMipmapGenerator.cpp
|
||||
src/webgpu/WebGPURenderPassMipmapGenerator.h
|
||||
src/webgpu/WebGPURenderPrimitive.h
|
||||
src/webgpu/WebGPURenderTarget.cpp
|
||||
src/webgpu/WebGPURenderTarget.h
|
||||
@@ -511,20 +503,11 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
target_compile_definitions(${TARGET} PRIVATE $<$<BOOL:${FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING}>:FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING>)
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Linker flags
|
||||
# ==================================================================================================
|
||||
|
||||
target_link_libraries(${TARGET} PRIVATE
|
||||
${OSMESA_LINKER_FLAGS}
|
||||
$<$<AND:$<PLATFORM_ID:Linux>,$<CONFIG:Release>>:${LINUX_LINKER_OPTIMIZATION_FLAGS}>
|
||||
)
|
||||
|
||||
if (LINUX AND FILAMENT_SUPPORTS_WEBGPU)
|
||||
# Unclear why this needs to be explicit while other linux libs do not, but it is necessary for
|
||||
# WebGPU to build on linux.
|
||||
target_link_libraries(${TARGET} PRIVATE -lxcb)
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Installation
|
||||
# ==================================================================================================
|
||||
|
||||
@@ -474,12 +474,7 @@ constexpr std::string_view to_string(DescriptorType type) noexcept {
|
||||
|
||||
enum class DescriptorFlags : uint8_t {
|
||||
NONE = 0x00,
|
||||
|
||||
// Indicate a UNIFORM_BUFFER will have dynamic offsets.
|
||||
DYNAMIC_OFFSET = 0x01,
|
||||
|
||||
// To indicate a texture/sampler type should be unfiltered.
|
||||
UNFILTERABLE = 0x02,
|
||||
DYNAMIC_OFFSET = 0x01
|
||||
};
|
||||
|
||||
using descriptor_set_t = uint8_t;
|
||||
|
||||
@@ -107,36 +107,6 @@ public:
|
||||
MULTIVIEW,
|
||||
};
|
||||
|
||||
/**
|
||||
* This controls the priority level for GPU work scheduling, which helps prioritize the
|
||||
* submitted GPU work and enables preemption.
|
||||
*/
|
||||
enum class GpuContextPriority : uint8_t {
|
||||
/**
|
||||
* Backend default GPU context priority (typically MEDIUM)
|
||||
*/
|
||||
DEFAULT,
|
||||
/**
|
||||
* For non-interactive, deferrable workloads. This should not interfere with standard
|
||||
* applications.
|
||||
*/
|
||||
LOW,
|
||||
/**
|
||||
* The default priority level for standard applications.
|
||||
*/
|
||||
MEDIUM,
|
||||
/**
|
||||
* For high-priority, latency-sensitive workloads that are more important than standard
|
||||
* applications.
|
||||
*/
|
||||
HIGH,
|
||||
/**
|
||||
* The highest priority, intended for system-critical, real-time applications where missing
|
||||
* deadlines is unacceptable (e.g., VR/AR compositors or other system-critical tasks).
|
||||
*/
|
||||
REALTIME,
|
||||
};
|
||||
|
||||
struct DriverConfig {
|
||||
/**
|
||||
* Size of handle arena in bytes. Setting to 0 indicates default value is to be used.
|
||||
@@ -186,13 +156,6 @@ public:
|
||||
* - PlatformMetal
|
||||
*/
|
||||
bool metalDisablePanicOnDrawableFailure = false;
|
||||
|
||||
/**
|
||||
* GPU context priority level. Controls GPU work scheduling and preemption.
|
||||
* This is only supported for:
|
||||
* - PlatformEGL
|
||||
*/
|
||||
GpuContextPriority gpuContextPriority = GpuContextPriority::DEFAULT;
|
||||
};
|
||||
|
||||
Platform() noexcept;
|
||||
|
||||
@@ -116,7 +116,7 @@ public:
|
||||
* Destroys the object D at Handle<B> and construct a new D in its place
|
||||
* e.g.:
|
||||
* Handle<ConcreteTexture> h = allocateAndConstruct(w, h);
|
||||
* ConcreteTexture* p = destroyAndConstruct(h, w, h);
|
||||
* ConcreteTexture* p = reconstruct(h, w, h);
|
||||
*/
|
||||
template<typename D, typename B, typename ... ARGS>
|
||||
std::enable_if_t<std::is_base_of_v<B, D>, D>*
|
||||
|
||||
@@ -66,7 +66,7 @@ static std::string to_string(int const i) { return std::to_string(i); }
|
||||
static std::string to_string(float const f) { return "float(" + std::to_string(f) + ")"; }
|
||||
|
||||
static void logCompilationError(ShaderStage shaderType, const char* name, GLuint shaderId,
|
||||
Program::ShaderBlob const& sourceCode) noexcept;
|
||||
CString const& sourceCode) noexcept;
|
||||
static void logProgramLinkError(char const* name, GLuint program) noexcept;
|
||||
|
||||
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext const& context, char* source,
|
||||
@@ -88,7 +88,7 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||
ShaderCompilerService& compiler;
|
||||
CString const& name;
|
||||
FixedCapacityVector<std::pair<CString, uint8_t>> attributes;
|
||||
Program::ShaderSource shaderSourceCode;
|
||||
shaders_source_t shaderSourceCode;
|
||||
void* user = nullptr;
|
||||
struct {
|
||||
shaders_t shaders{};
|
||||
@@ -121,12 +121,6 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||
}
|
||||
}
|
||||
|
||||
// Checks the token's completion status. The token is considered ready if the program was
|
||||
// created successfully or if a shader compilation error occurred.
|
||||
bool isReady() const noexcept {
|
||||
return gl.program || compilationFailed;
|
||||
}
|
||||
|
||||
std::optional<CallbackManager::Handle> handle{};
|
||||
|
||||
// Only valid when the blob functions are provided by users. The validity of this variable
|
||||
@@ -140,9 +134,6 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||
|
||||
// Indicate this program was created from the cache blob.
|
||||
bool retrievedFromBlobCache = false;
|
||||
|
||||
// Indicates that shader compilation failed.
|
||||
bool compilationFailed = false;
|
||||
};
|
||||
|
||||
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() {
|
||||
@@ -306,33 +297,22 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
|
||||
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
|
||||
assert_invariant(mMode != Mode::THREAD_POOL);
|
||||
|
||||
if (token->gl.program) {
|
||||
// Program linking has been initiated.
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// Check link completion if link was initiated.
|
||||
if (token->gl.program) {
|
||||
return isLinkCompleted(token);
|
||||
}
|
||||
return true; // In sync mode, if program exists, we're done with this job.
|
||||
}
|
||||
|
||||
// Program not linked yet. Check if shaders are compiled.
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// Link hasn't been initiated, then check compile completion.
|
||||
if (!isCompileCompleted(token)) {
|
||||
return false; // Wait for compilation to finish.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Shaders are compiled (or we're in sync mode). Let's link.
|
||||
if (!linkProgram(mDriver.getContext(), token)) {
|
||||
// Shader compilation failed. Stop processing this program.
|
||||
return true;
|
||||
if (!token->gl.program) {
|
||||
linkProgram(mDriver.getContext(), token);
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
return false;// Wait until the link finishes.
|
||||
}
|
||||
}
|
||||
|
||||
// Program is now linking.
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
return false; // Wait for linking to finish.
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
break;
|
||||
@@ -414,14 +394,6 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) {
|
||||
assert_invariant(token);// This function should be called when the token is still alive.
|
||||
|
||||
ensureTokenIsReady(token);
|
||||
|
||||
if (token->compilationFailed) {
|
||||
// Cleanup the token.
|
||||
token->compiler.cancelTickOp(token);
|
||||
token = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert_invariant(token->gl.program);
|
||||
|
||||
// Check status of program linking. If it failed, errors will be logged.
|
||||
@@ -447,7 +419,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) {
|
||||
}
|
||||
|
||||
void ShaderCompilerService::ensureTokenIsReady(program_token_t const& token) {
|
||||
if (token->isReady()) {
|
||||
if (token->gl.program) {
|
||||
return;// It's ready.
|
||||
}
|
||||
|
||||
@@ -640,7 +612,7 @@ void ShaderCompilerService::executeTickOps() noexcept {
|
||||
#ifndef NDEBUG
|
||||
// for debugging we return the original shader source (without the modifications we
|
||||
// made here), otherwise the line numbers wouldn't match.
|
||||
token->shaderSourceCode[i] = std::move(shader);
|
||||
token->shaderSourceCode[i] = { shader_src, shader_len };
|
||||
#endif
|
||||
token->gl.shaders[i] = shaderId;
|
||||
}
|
||||
@@ -666,10 +638,9 @@ void ShaderCompilerService::executeTickOps() noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ bool ShaderCompilerService::checkCompileStatus(program_token_t const& token) noexcept {
|
||||
/* static */ void ShaderCompilerService::checkCompileStatus(program_token_t const& token) noexcept {
|
||||
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
|
||||
|
||||
bool success = true;
|
||||
UTILS_NOUNROLL
|
||||
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
|
||||
const GLuint shader = token->gl.shaders[i];
|
||||
@@ -685,21 +656,15 @@ void ShaderCompilerService::executeTickOps() noexcept {
|
||||
// Something went wrong. Log the error message.
|
||||
const ShaderStage type = static_cast<ShaderStage>(i);
|
||||
logCompilationError(type, token->name.c_str_safe(), shader, token->shaderSourceCode[i]);
|
||||
success = false;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/* static */ bool ShaderCompilerService::linkProgram(OpenGLContext const& context,
|
||||
/* static */ void ShaderCompilerService::linkProgram(OpenGLContext const& context,
|
||||
program_token_t const& token) noexcept {
|
||||
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
|
||||
|
||||
// Shader compilation should be completed by now. Check the status and log errors on failure.
|
||||
if (!checkCompileStatus(token)) {
|
||||
token->compilationFailed = true;
|
||||
token->trySubmittingCallback();
|
||||
return false;
|
||||
}
|
||||
checkCompileStatus(token);
|
||||
|
||||
// Link program
|
||||
GLuint const program = glCreateProgram();
|
||||
@@ -716,7 +681,6 @@ void ShaderCompilerService::executeTickOps() noexcept {
|
||||
glLinkProgram(program);
|
||||
token->gl.program = program;
|
||||
token->trySubmittingCallback();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ bool ShaderCompilerService::isLinkCompleted(program_token_t const& token) noexcept {
|
||||
@@ -794,8 +758,7 @@ void ShaderCompilerService::executeTickOps() noexcept {
|
||||
|
||||
UTILS_NOINLINE
|
||||
/* static */ void logCompilationError(ShaderStage shaderType, const char* name,
|
||||
GLuint const shaderId,
|
||||
UTILS_UNUSED_IN_RELEASE Program::ShaderBlob const& sourceCode) noexcept {
|
||||
GLuint const shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
|
||||
|
||||
{ // scope for the temporary string storage
|
||||
auto to_string = [](ShaderStage type) -> const char* {
|
||||
@@ -822,8 +785,7 @@ UTILS_NOINLINE
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
std::string_view const shader{ reinterpret_cast<const char*>(sourceCode.data()),
|
||||
sourceCode.size() };
|
||||
std::string_view const shader{ sourceCode.data(), sourceCode.size() };
|
||||
size_t lc = 1;
|
||||
size_t start = 0;
|
||||
std::string line;
|
||||
|
||||
@@ -58,6 +58,7 @@ class ShaderCompilerService {
|
||||
public:
|
||||
using program_token_t = std::shared_ptr<OpenGLProgramToken>;
|
||||
using shaders_t = std::array<GLuint, Program::SHADER_TYPE_COUNT>;
|
||||
using shaders_source_t = std::array<utils::CString, Program::SHADER_TYPE_COUNT>;
|
||||
|
||||
explicit ShaderCompilerService(OpenGLDriver& driver);
|
||||
|
||||
@@ -155,14 +156,13 @@ private:
|
||||
static bool isCompileCompleted(program_token_t const& token) noexcept;
|
||||
|
||||
// Check compilation status of the shaders and log errors on failure.
|
||||
static bool checkCompileStatus(program_token_t const& token) noexcept;
|
||||
static void checkCompileStatus(program_token_t const& token) noexcept;
|
||||
|
||||
// Create a program by linking the compiled shaders. If the previous shader compilation was
|
||||
// failed, `compilationFailed` is set, then this function returns false. Otherwise `gl.program`
|
||||
// is populated with a valid program ID, then returns true. However this doesn't necessarily
|
||||
// mean the program is successfully linked. The link error can be checked by calling
|
||||
// `checkLinkStatusAndCleanupShaders` later.
|
||||
static bool linkProgram(OpenGLContext const& context, program_token_t const& token) noexcept;
|
||||
// Create a program by linking the compiled shaders. `gl.program` is always populated with a
|
||||
// valid program ID after this method. But this doesn't necessarily mean the program is
|
||||
// successfully linked. Errors can be checked by calling `checkLinkStatusAndCleanupShaders`
|
||||
// later.
|
||||
static void linkProgram(OpenGLContext const& context, program_token_t const& token) noexcept;
|
||||
|
||||
// Check if the program link is completed. You may want to call this when the extension
|
||||
// `KHR_parallel_shader_compile` is enabled.
|
||||
|
||||
@@ -209,42 +209,10 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& drive
|
||||
}
|
||||
#endif
|
||||
|
||||
// Configure GPU context priority level for scheduling and preemption
|
||||
if (driverConfig.gpuContextPriority != Platform::GpuContextPriority::DEFAULT) {
|
||||
if (extensions.has("EGL_IMG_context_priority")) {
|
||||
EGLint priorityLevel = EGL_CONTEXT_PRIORITY_MEDIUM_IMG;
|
||||
const char* priorityName;
|
||||
switch (driverConfig.gpuContextPriority) {
|
||||
case Platform::GpuContextPriority::DEFAULT:
|
||||
assert_invariant(false);
|
||||
break;
|
||||
case Platform::GpuContextPriority::LOW:
|
||||
priorityLevel = EGL_CONTEXT_PRIORITY_LOW_IMG;
|
||||
priorityName = "LOW";
|
||||
break;
|
||||
case Platform::GpuContextPriority::MEDIUM:
|
||||
priorityLevel = EGL_CONTEXT_PRIORITY_MEDIUM_IMG;
|
||||
priorityName = "MEDIUM";
|
||||
break;
|
||||
case Platform::GpuContextPriority::HIGH:
|
||||
priorityLevel = EGL_CONTEXT_PRIORITY_HIGH_IMG;
|
||||
priorityName = "HIGH";
|
||||
break;
|
||||
case Platform::GpuContextPriority::REALTIME:
|
||||
priorityLevel = EGL_CONTEXT_PRIORITY_HIGH_IMG;
|
||||
priorityName = "REALTIME(=HIGH)";
|
||||
break;
|
||||
}
|
||||
contextAttribs[EGL_CONTEXT_PRIORITY_LEVEL_IMG] = priorityLevel;
|
||||
LOG(INFO) << "EGL: Enabling GPU context priority: " << priorityName;
|
||||
} else {
|
||||
LOG(WARNING) << "EGL: GPU context priority requested but not supported";
|
||||
}
|
||||
}
|
||||
|
||||
// config use for creating the context
|
||||
EGLConfig eglConfig = EGL_NO_CONFIG_KHR;
|
||||
|
||||
|
||||
if (UTILS_UNLIKELY(!ext.egl.KHR_no_config_context)) {
|
||||
// find a config we can use if we don't have "EGL_KHR_no_config_context" and that we can use
|
||||
// for the dummy pbuffer surface.
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
*/
|
||||
|
||||
#include "VulkanBufferProxy.h"
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanMemory.h"
|
||||
|
||||
#include "VulkanBufferCache.h"
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanContext.h"
|
||||
#include "VulkanMemory.h"
|
||||
|
||||
using namespace bluevk;
|
||||
@@ -36,38 +36,6 @@ VulkanBufferProxy::VulkanBufferProxy(VmaAllocator allocator, VulkanStagePool& st
|
||||
|
||||
void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* cpuData,
|
||||
uint32_t byteOffset, uint32_t numBytes) {
|
||||
// The VulkanBuffer is available if the only object reference is hold by the
|
||||
// `VulkanBufferProxy`. This means that the buffer is not currently referenced by a
|
||||
// `VulkanCommandBuffer`.
|
||||
bool const isAvailable = mBuffer->getCount() == 1;
|
||||
|
||||
if (isAvailable) {
|
||||
// We are up to date with all the update operations, so no need to synchronize with previous
|
||||
// updates.
|
||||
mUpdatedBytes = 0;
|
||||
mUpdatedOffset = 0;
|
||||
}
|
||||
|
||||
// Keep track of the VulkanBuffer usage
|
||||
commands.acquire(mBuffer);
|
||||
|
||||
// Check if we can just memcpy directly to the GPU memory.
|
||||
// This is only allowed for UNIFORMS that are AVAILABLE and the memory is HOST_VISIBLE
|
||||
// (supports memcpy from host). This works regardless if it's a full or partial update of the
|
||||
// buffer.
|
||||
bool const isMemcopyable = mBuffer->getGpuBuffer()->allocationInfo.pMappedData != nullptr;
|
||||
bool const isUniform = getUsage() == VulkanBufferUsage::UNIFORM;
|
||||
bool const useMemcpy =
|
||||
isUniform && isMemcopyable && isAvailable && !FVK_FORCE_STAGING_FOR_BUFFER_UPDATES;
|
||||
if (useMemcpy) {
|
||||
char* dest = static_cast<char*>(mBuffer->getGpuBuffer()->allocationInfo.pMappedData) +
|
||||
byteOffset;
|
||||
memcpy(dest, cpuData, numBytes);
|
||||
vmaFlushAllocation(mAllocator, mBuffer->getGpuBuffer()->vmaAllocation, byteOffset,
|
||||
numBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: this should be stored within the command buffer before going out of
|
||||
// scope, so that the command buffer can manage its lifecycle.
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> stage = mStagePool.acquireStage(numBytes);
|
||||
@@ -160,4 +128,4 @@ VulkanBufferUsage VulkanBufferProxy::getUsage() const noexcept {
|
||||
return mBuffer->getGpuBuffer()->usage;
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -49,6 +49,6 @@ private:
|
||||
uint32_t mUpdatedBytes = 0;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
}// namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKANBUFFERPROXY_H
|
||||
#endif// TNT_FILAMENT_BACKEND_VULKANBUFFERPROXY_H
|
||||
|
||||
@@ -214,8 +214,4 @@ constexpr static const int FVK_MAX_PIPELINE_AGE = FVK_MAX_COMMAND_BUFFERS;
|
||||
// destroying any unused pipeline object.
|
||||
static_assert(FVK_MAX_PIPELINE_AGE >= FVK_MAX_COMMAND_BUFFERS);
|
||||
|
||||
// Whether the buffer updates always use staging or not. Otherwise its allowed to also use memcpy
|
||||
// for buffer updates. Default is false
|
||||
constexpr static const bool FVK_FORCE_STAGING_FOR_BUFFER_UPDATES = false;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -394,13 +394,7 @@ void VulkanDriver::collectGarbage() {
|
||||
void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
int64_t refreshIntervalNs, uint32_t frameId) {
|
||||
FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
|
||||
|
||||
// Check if any command have finished and reset all its used resources. The resources
|
||||
// wont be destroyed but their reference count will decreased if the command is already
|
||||
// completed.
|
||||
//
|
||||
// This will let us check if any VulkanBuffer is currently in flight or not.
|
||||
mCommands.gc();
|
||||
// Do nothing.
|
||||
|
||||
if (mAppState.hasExternalSamplers()) {
|
||||
mExternalImageManager.onBeginFrame();
|
||||
@@ -1242,8 +1236,7 @@ void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor
|
||||
scheduleDestroy(std::move(p));
|
||||
}
|
||||
|
||||
void VulkanDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh,
|
||||
BufferObjectStreamDescriptor&& streams) {
|
||||
void VulkanDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh, BufferObjectStreamDescriptor&& streams) {
|
||||
// Noop
|
||||
}
|
||||
|
||||
|
||||
@@ -34,23 +34,16 @@ namespace {
|
||||
static constexpr uint32_t MAX_EMPTY_STAGES_TO_RETAIN = 1;
|
||||
constexpr uint32_t STAGE_SIZE = 1048576;
|
||||
|
||||
uint32_t alignValue(uint32_t valueToAlign, uint32_t alignment) {
|
||||
if (alignment == 0) {
|
||||
return valueToAlign;
|
||||
}
|
||||
|
||||
uint32_t remainder = valueToAlign % alignment;
|
||||
return remainder == 0 ? valueToAlign : valueToAlign + (alignment - remainder);
|
||||
}
|
||||
|
||||
}// namespace
|
||||
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStage::acquireSegment(
|
||||
fvkmemory::ResourceManager* resManager, uint32_t segmentOffset, uint32_t numBytes) {
|
||||
auto segment = fvkmemory::resource_ptr<Segment>::construct(resManager, this, numBytes,
|
||||
segmentOffset, [this](uint32_t offset) { mSegments.erase(offset); });
|
||||
mSegments.insert({ segmentOffset, segment.get() });
|
||||
mCurrentOffset = segmentOffset + numBytes;
|
||||
fvkmemory::ResourceManager* resManager, uint32_t numBytes) {
|
||||
auto segment = fvkmemory::resource_ptr<Segment>::construct(
|
||||
resManager, this, numBytes, mCurrentOffset, [this](uint32_t offset) {
|
||||
mSegments.erase(offset);
|
||||
});
|
||||
mSegments.insert({mCurrentOffset, segment.get()});
|
||||
mCurrentOffset += numBytes;
|
||||
return segment;
|
||||
}
|
||||
|
||||
@@ -61,8 +54,7 @@ VulkanStagePool::VulkanStagePool(VmaAllocator allocator, fvkmemory::ResourceMana
|
||||
mCommands(commands),
|
||||
mDeviceLimits(deviceLimits) {}
|
||||
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStagePool::acquireStage(uint32_t numBytes,
|
||||
uint32_t alignment) {
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStagePool::acquireStage(uint32_t numBytes) {
|
||||
// Apply alignment to the byte count to ensure that, when we later flush
|
||||
// data written by the host, we only flush the atoms that we modified, and
|
||||
// no adjacent atoms.
|
||||
@@ -72,35 +64,17 @@ fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStagePool::acquireStage(uint
|
||||
// equal to the requested size.
|
||||
auto iter = mStages.lower_bound(numBytes);
|
||||
|
||||
// If we have potential buffer candidates, loop through and try to find one
|
||||
// that works with our alignment. Also, set the offset for the buffer.
|
||||
// If we have no buffer, create a new one.
|
||||
VulkanStage* pStage = nullptr;
|
||||
uint32_t segmentOffset = 0;
|
||||
while (iter != mStages.end()) {
|
||||
segmentOffset = alignValue(iter->second->currentOffset(), alignment);
|
||||
|
||||
// Check for overflow + if there's space.
|
||||
if (segmentOffset >= iter->second->currentOffset() &&
|
||||
iter->second->capacity() - numBytes >= segmentOffset) {
|
||||
// This buffer has enough space.
|
||||
pStage = iter->second;
|
||||
mStages.erase(iter);
|
||||
break;
|
||||
}
|
||||
|
||||
// This buffer wasn't suitable. Check the next larger one.
|
||||
++iter;
|
||||
}
|
||||
|
||||
if (!pStage) {
|
||||
VulkanStage* pStage;
|
||||
if (iter != mStages.end()) {
|
||||
pStage = iter->second;
|
||||
mStages.erase(iter);
|
||||
} else {
|
||||
pStage = allocateNewStage(std::max(numBytes, STAGE_SIZE));
|
||||
}
|
||||
|
||||
// Note: this allocation updates `currentOffset` and `segments` within
|
||||
// the parent stage. When destroyed, it will update `segments`.
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> pSegment =
|
||||
pStage->acquireSegment(mResManager, segmentOffset, numBytes);
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> pSegment = pStage->acquireSegment(mResManager, numBytes);
|
||||
|
||||
// Update the stage's metadata, and reinsert it with the remaining segment
|
||||
// capacity.
|
||||
@@ -112,7 +86,12 @@ fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStagePool::acquireStage(uint
|
||||
|
||||
uint32_t VulkanStagePool::alignToNonCoherentAtomSize(uint32_t bytes) {
|
||||
VkDeviceSize alignment = mDeviceLimits->nonCoherentAtomSize;
|
||||
return alignValue(bytes, alignment);
|
||||
if (alignment == 0) {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
uint32_t remainder = bytes % alignment;
|
||||
return remainder == 0 ? bytes : bytes + (alignment - remainder);
|
||||
}
|
||||
|
||||
VulkanStage* VulkanStagePool::allocateNewStage(uint32_t capacity) {
|
||||
@@ -156,27 +135,27 @@ void VulkanStagePool::destroyStage(VulkanStage const*&& stage) {
|
||||
delete stage;
|
||||
}
|
||||
|
||||
fvkmemory::resource_ptr<VulkanStageImage::Resource> VulkanStagePool::acquireImage(
|
||||
PixelDataFormat format, PixelDataType type, uint32_t width, uint32_t height) {
|
||||
// Helper lambda so we can return stage images wrapped as resources that can
|
||||
// be held by command buffers until no longer needed.
|
||||
auto wrapAsResource = [this](VulkanStageImage* image) {
|
||||
auto recycleFn = [this](VulkanStageImage* image) {
|
||||
this->mFreeImages.insert(image);
|
||||
};
|
||||
return fvkmemory::resource_ptr<VulkanStageImage::Resource>::construct(
|
||||
this->mResManager, image, recycleFn);
|
||||
};
|
||||
|
||||
VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, PixelDataType type,
|
||||
uint32_t width, uint32_t height) {
|
||||
const VkFormat vkformat = fvkutils::getVkFormat(format, type);
|
||||
for (auto stageImage : mFreeImages) {
|
||||
if (stageImage->format() == vkformat && stageImage->width() == width && stageImage->height() == height) {
|
||||
mFreeImages.erase(stageImage);
|
||||
stageImage->mLastAccessed = mCurrentFrame;
|
||||
return wrapAsResource(stageImage);
|
||||
for (auto image : mFreeImages) {
|
||||
if (image->format == vkformat && image->width == width && image->height == height) {
|
||||
mFreeImages.erase(image);
|
||||
image->lastAccessed = mCurrentFrame;
|
||||
mUsedImages.push_back(image);
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
VulkanStageImage* image = new VulkanStageImage({
|
||||
.format = vkformat,
|
||||
.width = width,
|
||||
.height = height,
|
||||
.lastAccessed = mCurrentFrame,
|
||||
});
|
||||
|
||||
mUsedImages.push_back(image);
|
||||
|
||||
const VkImageCreateInfo imageInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
@@ -194,19 +173,14 @@ fvkmemory::resource_ptr<VulkanStageImage::Resource> VulkanStagePool::acquireImag
|
||||
.usage = VMA_MEMORY_USAGE_CPU_TO_GPU
|
||||
};
|
||||
|
||||
VkImage image;
|
||||
VmaAllocation memory;
|
||||
const UTILS_UNUSED VkResult result = vmaCreateImage(mAllocator, &imageInfo, &allocInfo,
|
||||
&image, &memory, nullptr);
|
||||
&image->image, &image->memory, nullptr);
|
||||
|
||||
assert_invariant(result == VK_SUCCESS);
|
||||
|
||||
VkImageAspectFlags const aspectFlags = fvkutils::getImageAspect(vkformat);
|
||||
VkCommandBuffer const cmdbuffer = mCommands->get().buffer();
|
||||
|
||||
VulkanStageImage* stageImage = new VulkanStageImage(
|
||||
vkformat, width, height, memory, image, mCurrentFrame);
|
||||
|
||||
// We use VK_IMAGE_LAYOUT_GENERAL here because the spec says:
|
||||
// "Host access to image memory is only well-defined for linear images and for image
|
||||
// subresources of those images which are currently in either the
|
||||
@@ -214,13 +188,12 @@ fvkmemory::resource_ptr<VulkanStageImage::Resource> VulkanStagePool::acquireImag
|
||||
// vkGetImageSubresourceLayout for a linear image returns a subresource layout mapping that is
|
||||
// valid for either of those image layouts."
|
||||
fvkutils::transitionLayout(cmdbuffer, {
|
||||
.image = stageImage->image(),
|
||||
.image = image->image,
|
||||
.oldLayout = VulkanLayout::UNDEFINED,
|
||||
.newLayout = VulkanLayout::STAGING, // (= VK_IMAGE_LAYOUT_GENERAL)
|
||||
.subresources = { aspectFlags, 0, 1, 0, 1 },
|
||||
});
|
||||
|
||||
return wrapAsResource(stageImage);
|
||||
return image;
|
||||
}
|
||||
|
||||
void VulkanStagePool::gc() noexcept {
|
||||
@@ -268,14 +241,25 @@ void VulkanStagePool::gc() noexcept {
|
||||
decltype(mFreeImages) freeImages;
|
||||
freeImages.swap(mFreeImages);
|
||||
for (auto image : freeImages) {
|
||||
if (image->mLastAccessed < evictionTime) {
|
||||
vmaDestroyImage(mAllocator, image->image(), image->memory());
|
||||
if (image->lastAccessed < evictionTime) {
|
||||
vmaDestroyImage(mAllocator, image->image, image->memory);
|
||||
delete image;
|
||||
} else {
|
||||
mFreeImages.insert(image);
|
||||
}
|
||||
}
|
||||
|
||||
// Reclaim images that are no longer being used by any command buffer.
|
||||
decltype(mUsedImages) usedImages;
|
||||
usedImages.swap(mUsedImages);
|
||||
for (auto image : usedImages) {
|
||||
if (image->lastAccessed < evictionTime) {
|
||||
image->lastAccessed = mCurrentFrame;
|
||||
mFreeImages.insert(image);
|
||||
} else {
|
||||
mUsedImages.push_back(image);
|
||||
}
|
||||
}
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
@@ -285,8 +269,14 @@ void VulkanStagePool::terminate() noexcept {
|
||||
}
|
||||
mStages.clear();
|
||||
|
||||
for (auto image : mUsedImages) {
|
||||
vmaDestroyImage(mAllocator, image->image, image->memory);
|
||||
delete image;
|
||||
}
|
||||
mUsedImages.clear();
|
||||
|
||||
for (auto image : mFreeImages) {
|
||||
vmaDestroyImage(mAllocator, image->image(), image->memory());
|
||||
vmaDestroyImage(mAllocator, image->image, image->memory);
|
||||
delete image;
|
||||
}
|
||||
mFreeImages.clear();
|
||||
|
||||
@@ -105,11 +105,8 @@ public:
|
||||
// Marks a region of the block as "in-use", and provides information about
|
||||
// the allocated region to the caller. Note: this assumes that numBytes
|
||||
// is aligned to the physical device's nonCoherentAtomSize.
|
||||
// Takes the offset to use as the segment's start offset. It is assumed
|
||||
// that this is greater than the segment's current offset, and it is
|
||||
// undefined behavior if not.
|
||||
fvkmemory::resource_ptr<Segment> acquireSegment(fvkmemory::ResourceManager* resManager,
|
||||
uint32_t segmentOffset, uint32_t numBytes);
|
||||
uint32_t numBytes);
|
||||
|
||||
private:
|
||||
const VmaAllocation mMemory;
|
||||
@@ -126,70 +123,13 @@ private:
|
||||
std::unordered_map<uint32_t, Segment*> mSegments;
|
||||
};
|
||||
|
||||
class VulkanStageImage {
|
||||
public:
|
||||
class Resource : public fvkmemory::Resource {
|
||||
public:
|
||||
using RecycleFn = std::function<void(VulkanStageImage*)>;
|
||||
|
||||
Resource(VulkanStageImage* image, RecycleFn&& onRecycleFn)
|
||||
: mImage(image),
|
||||
mOnRecycleFn(onRecycleFn)
|
||||
{}
|
||||
|
||||
~Resource() {
|
||||
if (mOnRecycleFn) {
|
||||
mOnRecycleFn(mImage);
|
||||
}
|
||||
}
|
||||
|
||||
inline VkFormat format() const { return mImage->format(); }
|
||||
inline uint32_t width() const { return mImage->width(); }
|
||||
inline uint32_t height() const { return mImage->height(); }
|
||||
inline VmaAllocation memory() const { return mImage->memory(); }
|
||||
inline VkImage image() const { return mImage->image(); }
|
||||
|
||||
private:
|
||||
Resource() = delete;
|
||||
Resource(const Resource& other) = delete;
|
||||
Resource(Resource&& other) = delete;
|
||||
Resource& operator=(const Resource& other) = delete;
|
||||
Resource& operator=(Resource&& other) = delete;
|
||||
|
||||
VulkanStageImage* const mImage;
|
||||
RecycleFn mOnRecycleFn;
|
||||
};
|
||||
|
||||
VulkanStageImage(VkFormat format, uint32_t width, uint32_t height, VmaAllocation memory,
|
||||
VkImage image, uint64_t lastAccessed)
|
||||
: mFormat(format),
|
||||
mWidth(width),
|
||||
mHeight(height),
|
||||
mMemory(memory),
|
||||
mImage(image),
|
||||
mLastAccessed(lastAccessed) {}
|
||||
|
||||
VulkanStageImage(const VulkanStageImage& other) = delete;
|
||||
VulkanStageImage(VulkanStageImage&& other) = delete;
|
||||
VulkanStageImage& operator=(const VulkanStageImage& other) = delete;
|
||||
VulkanStageImage& operator=(VulkanStageImage&& other) = delete;
|
||||
|
||||
inline VkFormat format() const { return mFormat; }
|
||||
inline uint32_t width() const { return mWidth; }
|
||||
inline uint32_t height() const { return mHeight; }
|
||||
inline VmaAllocation memory() const { return mMemory; }
|
||||
inline VkImage image() const { return mImage; }
|
||||
|
||||
private:
|
||||
const VkFormat mFormat;
|
||||
const uint32_t mWidth;
|
||||
const uint32_t mHeight;
|
||||
const VmaAllocation mMemory;
|
||||
const VkImage mImage;
|
||||
|
||||
uint64_t mLastAccessed;
|
||||
// Denote as a friend so that it can update mLastAccessed.
|
||||
friend class VulkanStagePool;
|
||||
struct VulkanStageImage {
|
||||
VkFormat format;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
mutable uint64_t lastAccessed;
|
||||
VmaAllocation memory;
|
||||
VkImage image;
|
||||
};
|
||||
|
||||
// Manages a pool of stages, periodically releasing stages that have been unused for a while.
|
||||
@@ -204,13 +144,10 @@ public:
|
||||
// buffers so that we have less objects around that we have to keep track
|
||||
// of.
|
||||
// This function is NOT thread-safe.
|
||||
// numBytes - the number of bytes required by this segment.
|
||||
// alignment - the multiple to align the buffer offset to
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> acquireStage(uint32_t numBytes,
|
||||
uint32_t alignment = 0);
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> acquireStage(uint32_t numBytes);
|
||||
|
||||
// Images have VK_IMAGE_LAYOUT_GENERAL and must not be transitioned to any other layout
|
||||
fvkmemory::resource_ptr<VulkanStageImage::Resource> acquireImage(PixelDataFormat format, PixelDataType type,
|
||||
VulkanStageImage const* acquireImage(PixelDataFormat format, PixelDataType type,
|
||||
uint32_t width, uint32_t height);
|
||||
|
||||
// Evicts old unused stages and bumps the current frame number.
|
||||
@@ -250,7 +187,8 @@ private:
|
||||
// Use an ordered multimap for quick (capacity => stage) lookups using lower_bound().
|
||||
std::multimap<uint32_t, VulkanStage*> mStages;
|
||||
|
||||
std::unordered_set<VulkanStageImage*> mFreeImages;
|
||||
std::unordered_set<VulkanStageImage const*> mFreeImages;
|
||||
std::vector<VulkanStageImage const*> mUsedImages;
|
||||
|
||||
// Store the current "time" (really just a frame count) and LRU eviction parameters.
|
||||
uint64_t mCurrentFrame = 0;
|
||||
|
||||
@@ -227,7 +227,7 @@ void adjustedMemcpy(void* mapped, PixelBufferDescriptor const& p, size_t width,
|
||||
|
||||
// Slow path of copying row by row
|
||||
assert_invariant(pbdStride >= width);
|
||||
if (UTILS_UNLIKELY(pixelSize > 0 && (p.left > 0 || p.top > 0 || pbdStride > width))) {
|
||||
if (UTILS_UNLIKELY(p.left > 0 || p.top > 0 || pbdStride > width)) {
|
||||
size_t const pbdRowSize =
|
||||
PixelBufferDescriptor::computeDataSize(p.format, p.type, pbdStride, 1, p.alignment);
|
||||
size_t const pbdHeight = p.size / pixelSize / pbdStride / depth;
|
||||
@@ -248,28 +248,11 @@ void adjustedMemcpy(void* mapped, PixelBufferDescriptor const& p, size_t width,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Note: if pixelSize = 0, we're dealing with a compressed format; we
|
||||
// should fall back to the buffer's size.
|
||||
size_t const writeSize = pixelSize > 0 ? pixelSize * (width * height * depth) : p.size;
|
||||
size_t const writeSize = pixelSize * (width * height * depth);
|
||||
memcpy(mapped, buf, writeSize);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t getAlignmentForBufferToImageCopy(VkFormat format) {
|
||||
// VUID-vkCmdCopyBufferToImage-dstImage-07978
|
||||
if(fvkutils::isVkDepthFormat(format) || fvkutils::isVkStencilFormat(format)) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
if (fvkutils::isVKYcbcrConversionFormat(format)) {
|
||||
assert_invariant(false && "Multi planar format is not supported");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// VUID-vkCmdCopyBufferToImage-dstImage-07975
|
||||
return fvkutils::getTexelBlockSize(format);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
VulkanTextureState::VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands,
|
||||
@@ -534,16 +517,13 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
|
||||
// Otherwise, use vkCmdCopyBufferToImage.
|
||||
size_t const bpp =
|
||||
PixelBufferDescriptor::computeDataSize(hostData->format, hostData->type, 1, 1, 1);
|
||||
// Note: if bpp = 0, we're dealing with a compressed format; we should fall
|
||||
// back to the buffer's size.
|
||||
size_t const writeSize = bpp > 0 ? width * height * depth * bpp : hostData->size;
|
||||
size_t const writeSize = width * height * depth * bpp;
|
||||
|
||||
// Note: the following stageSegment must be stored within the command buffer
|
||||
// before going out of scope, to ensure proper bookkeeping within the
|
||||
// staging buffer pool.
|
||||
uint8_t alignment = getAlignmentForBufferToImageCopy(mState->mVkFormat);
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> stageSegment =
|
||||
mState->mStagePool.acquireStage(writeSize, alignment);
|
||||
mState->mStagePool.acquireStage(writeSize);
|
||||
assert_invariant(stageSegment->memory());
|
||||
adjustedMemcpy(stageSegment->mapping(), *hostData, width, height, depth);
|
||||
vmaFlushAllocation(mState->mAllocator, stageSegment->memory(), stageSegment->offset(),
|
||||
@@ -611,21 +591,18 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& data, uint3
|
||||
uint32_t height, uint32_t depth, uint32_t miplevel) {
|
||||
// Otherwise, use vkCmdCopyBufferToImage.
|
||||
size_t const bpp = PixelBufferDescriptor::computeDataSize(data.format, data.type, 1, 1, 1);
|
||||
// Note: if bpp = 0, we're dealing with a compressed format; we should fall
|
||||
// back to the buffer's size.
|
||||
size_t const writeSize = bpp > 0 ? width * height * depth * bpp : data.size;
|
||||
size_t const writeSize = width * height * depth * bpp;
|
||||
|
||||
void* mapped = nullptr;
|
||||
fvkmemory::resource_ptr<VulkanStageImage::Resource> stage
|
||||
VulkanStageImage const* stage
|
||||
= mState->mStagePool.acquireImage(data.format, data.type, width, height);
|
||||
vmaMapMemory(mState->mAllocator, stage->memory(), &mapped);
|
||||
vmaMapMemory(mState->mAllocator, stage->memory, &mapped);
|
||||
adjustedMemcpy(mapped, data, width, height, depth);
|
||||
vmaUnmapMemory(mState->mAllocator, stage->memory());
|
||||
vmaFlushAllocation(mState->mAllocator, stage->memory(), 0, writeSize);
|
||||
vmaUnmapMemory(mState->mAllocator, stage->memory);
|
||||
vmaFlushAllocation(mState->mAllocator, stage->memory, 0, writeSize);
|
||||
|
||||
VulkanCommandBuffer& commands = mState->mCommands->get();
|
||||
VkCommandBuffer const cmdbuf = commands.buffer();
|
||||
commands.acquire(stage);
|
||||
commands.acquire(fvkmemory::resource_ptr<VulkanTexture>::cast(this));
|
||||
|
||||
// TODO: support blit-based format conversion for 3D images and cubemaps.
|
||||
@@ -648,7 +625,7 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& data, uint3
|
||||
VulkanLayout const oldLayout = getLayout(layer, miplevel);
|
||||
transitionLayout(&commands, range, newLayout);
|
||||
|
||||
vkCmdBlitImage(cmdbuf, stage->image(), fvkutils::getVkLayout(VulkanLayout::TRANSFER_SRC),
|
||||
vkCmdBlitImage(cmdbuf, stage->image, fvkutils::getVkLayout(VulkanLayout::TRANSFER_SRC),
|
||||
mState->mTextureImage, fvkutils::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST);
|
||||
|
||||
transitionLayout(&commands, range, oldLayout);
|
||||
|
||||
@@ -27,7 +27,6 @@ template ResourceType getTypeEnum<VulkanProgram>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanRenderTarget>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanSwapChain>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanStage::Segment>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanStageImage::Resource>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanRenderPrimitive>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanTexture>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanTextureState>() noexcept;
|
||||
@@ -59,9 +58,6 @@ ResourceType getTypeEnum() noexcept {
|
||||
if constexpr (std::is_same_v<D, VulkanStage::Segment>) {
|
||||
return ResourceType::STAGE_SEGMENT;
|
||||
}
|
||||
if constexpr (std::is_same_v<D, VulkanStageImage::Resource>) {
|
||||
return ResourceType::STAGE_IMAGE;
|
||||
}
|
||||
if constexpr (std::is_same_v<D, VulkanRenderPrimitive>) {
|
||||
return ResourceType::RENDER_PRIMITIVE;
|
||||
}
|
||||
@@ -109,8 +105,6 @@ std::string getTypeStr(ResourceType type) {
|
||||
return "SwapChain";
|
||||
case ResourceType::STAGE_SEGMENT:
|
||||
return "Stage::Segment";
|
||||
case ResourceType::STAGE_IMAGE:
|
||||
return "Stage::Image";
|
||||
case ResourceType::RENDER_PRIMITIVE:
|
||||
return "RenderPrimitive";
|
||||
case ResourceType::TEXTURE:
|
||||
|
||||
@@ -51,8 +51,7 @@ enum class ResourceType : uint8_t {
|
||||
FENCE = 13,
|
||||
VULKAN_BUFFER = 14,
|
||||
STAGE_SEGMENT = 15,
|
||||
STAGE_IMAGE = 16,
|
||||
UNDEFINED_TYPE = 17, // Must be the last enum because we use it for iterating over the enums.
|
||||
UNDEFINED_TYPE = 16, // Must be the last enum because we use it for iterating over the enums.
|
||||
};
|
||||
|
||||
template<typename D>
|
||||
@@ -72,8 +71,6 @@ struct Resource {
|
||||
restype(ResourceType::UNDEFINED_TYPE),
|
||||
mHandleConsideredDestroyed(false) {}
|
||||
|
||||
uint32_t getCount() const { return mCount; }
|
||||
|
||||
private:
|
||||
inline void inc() noexcept {
|
||||
mCount++;
|
||||
|
||||
@@ -81,9 +81,6 @@ void ResourceManager::destroyWithType(ResourceType type, HandleId id) {
|
||||
case ResourceType::STAGE_SEGMENT:
|
||||
destruct<VulkanStage::Segment>(Handle<VulkanStage::Segment>(id));
|
||||
break;
|
||||
case ResourceType::STAGE_IMAGE:
|
||||
destruct<VulkanStageImage::Resource>(Handle<VulkanStageImage::Resource>(id));
|
||||
break;
|
||||
case ResourceType::RENDER_PRIMITIVE:
|
||||
destruct<VulkanRenderPrimitive>(Handle<VulkanRenderPrimitive>(id));
|
||||
break;
|
||||
|
||||
@@ -832,9 +832,6 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
context.mPhysicalDeviceFeatures.features.shaderClipDistance = VK_FALSE;
|
||||
}
|
||||
|
||||
// TODO: Add support of VK_KHR_global_priority with `driverConfig.gpuContextPriority`
|
||||
// in VulkanPlatform::createDriver.
|
||||
|
||||
ExtensionSet deviceExts;
|
||||
// If using a shared context, we do not assume any extensions.
|
||||
if (!mImpl->mSharedContext) {
|
||||
@@ -926,7 +923,7 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
// Keep a copy of context for swapchains.
|
||||
mImpl->mContext = context;
|
||||
|
||||
return VulkanDriver::create(this, mImpl->mContext, driverConfig);
|
||||
return VulkanDriver::create(this, context, driverConfig);
|
||||
}
|
||||
|
||||
// This needs to be explictly written for
|
||||
|
||||
@@ -233,304 +233,6 @@ VkFormat getVkFormat(TextureFormat format) {
|
||||
}
|
||||
}
|
||||
|
||||
// As per
|
||||
// https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#formats-compatibility-classes).
|
||||
uint8_t getTexelBlockSize(VkFormat format) {
|
||||
switch (format) {
|
||||
// 8-bit formats.
|
||||
case VK_FORMAT_R4G4_UNORM_PACK8:
|
||||
case VK_FORMAT_R8_UNORM:
|
||||
case VK_FORMAT_R8_SNORM:
|
||||
case VK_FORMAT_R8_USCALED:
|
||||
case VK_FORMAT_R8_SSCALED:
|
||||
case VK_FORMAT_R8_UINT:
|
||||
case VK_FORMAT_R8_SINT:
|
||||
case VK_FORMAT_R8_SRGB:
|
||||
case VK_FORMAT_S8_UINT:
|
||||
return 1;
|
||||
|
||||
// 16-bit formats.
|
||||
case VK_FORMAT_R10X6_UNORM_PACK16:
|
||||
case VK_FORMAT_R12X4_UNORM_PACK16:
|
||||
case VK_FORMAT_A4R4G4B4_UNORM_PACK16:
|
||||
case VK_FORMAT_A4B4G4R4_UNORM_PACK16:
|
||||
case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
|
||||
case VK_FORMAT_B4G4R4A4_UNORM_PACK16:
|
||||
case VK_FORMAT_R5G6B5_UNORM_PACK16:
|
||||
case VK_FORMAT_B5G6R5_UNORM_PACK16:
|
||||
case VK_FORMAT_R5G5B5A1_UNORM_PACK16:
|
||||
case VK_FORMAT_B5G5R5A1_UNORM_PACK16:
|
||||
case VK_FORMAT_A1R5G5B5_UNORM_PACK16:
|
||||
case VK_FORMAT_R8G8_UNORM:
|
||||
case VK_FORMAT_R8G8_SNORM:
|
||||
case VK_FORMAT_R8G8_USCALED:
|
||||
case VK_FORMAT_R8G8_SSCALED:
|
||||
case VK_FORMAT_R8G8_UINT:
|
||||
case VK_FORMAT_R8G8_SINT:
|
||||
case VK_FORMAT_R8G8_SRGB:
|
||||
case VK_FORMAT_R16_UNORM:
|
||||
case VK_FORMAT_R16_SNORM:
|
||||
case VK_FORMAT_R16_USCALED:
|
||||
case VK_FORMAT_R16_SSCALED:
|
||||
case VK_FORMAT_R16_UINT:
|
||||
case VK_FORMAT_R16_SINT:
|
||||
case VK_FORMAT_R16_SFLOAT:
|
||||
case VK_FORMAT_D16_UNORM:
|
||||
return 2;
|
||||
|
||||
// 24-bit formats.
|
||||
case VK_FORMAT_R8G8B8_UNORM:
|
||||
case VK_FORMAT_R8G8B8_SNORM:
|
||||
case VK_FORMAT_R8G8B8_USCALED:
|
||||
case VK_FORMAT_R8G8B8_SSCALED:
|
||||
case VK_FORMAT_R8G8B8_UINT:
|
||||
case VK_FORMAT_R8G8B8_SINT:
|
||||
case VK_FORMAT_R8G8B8_SRGB:
|
||||
case VK_FORMAT_B8G8R8_UNORM:
|
||||
case VK_FORMAT_B8G8R8_SNORM:
|
||||
case VK_FORMAT_B8G8R8_USCALED:
|
||||
case VK_FORMAT_B8G8R8_SSCALED:
|
||||
case VK_FORMAT_B8G8R8_UINT:
|
||||
case VK_FORMAT_B8G8R8_SINT:
|
||||
case VK_FORMAT_B8G8R8_SRGB:
|
||||
case VK_FORMAT_D16_UNORM_S8_UINT:
|
||||
case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM:
|
||||
case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
|
||||
case VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM:
|
||||
case VK_FORMAT_G8_B8R8_2PLANE_422_UNORM:
|
||||
case VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM:
|
||||
case VK_FORMAT_G8_B8R8_2PLANE_444_UNORM:
|
||||
return 3;
|
||||
|
||||
// 32-bit formats.
|
||||
case VK_FORMAT_R10X6G10X6_UNORM_2PACK16:
|
||||
case VK_FORMAT_R12X4G12X4_UNORM_2PACK16:
|
||||
case VK_FORMAT_R8G8B8A8_UNORM:
|
||||
case VK_FORMAT_R8G8B8A8_SNORM:
|
||||
case VK_FORMAT_R8G8B8A8_USCALED:
|
||||
case VK_FORMAT_R8G8B8A8_SSCALED:
|
||||
case VK_FORMAT_R8G8B8A8_UINT:
|
||||
case VK_FORMAT_R8G8B8A8_SINT:
|
||||
case VK_FORMAT_R8G8B8A8_SRGB:
|
||||
case VK_FORMAT_B8G8R8A8_UNORM:
|
||||
case VK_FORMAT_B8G8R8A8_SNORM:
|
||||
case VK_FORMAT_B8G8R8A8_USCALED:
|
||||
case VK_FORMAT_B8G8R8A8_SSCALED:
|
||||
case VK_FORMAT_B8G8R8A8_UINT:
|
||||
case VK_FORMAT_B8G8R8A8_SINT:
|
||||
case VK_FORMAT_B8G8R8A8_SRGB:
|
||||
case VK_FORMAT_A8B8G8R8_UNORM_PACK32:
|
||||
case VK_FORMAT_A8B8G8R8_SNORM_PACK32:
|
||||
case VK_FORMAT_A8B8G8R8_USCALED_PACK32:
|
||||
case VK_FORMAT_A8B8G8R8_SSCALED_PACK32:
|
||||
case VK_FORMAT_A8B8G8R8_UINT_PACK32:
|
||||
case VK_FORMAT_A8B8G8R8_SINT_PACK32:
|
||||
case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
|
||||
case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
|
||||
case VK_FORMAT_A2R10G10B10_SNORM_PACK32:
|
||||
case VK_FORMAT_A2R10G10B10_USCALED_PACK32:
|
||||
case VK_FORMAT_A2R10G10B10_SSCALED_PACK32:
|
||||
case VK_FORMAT_A2R10G10B10_UINT_PACK32:
|
||||
case VK_FORMAT_A2R10G10B10_SINT_PACK32:
|
||||
case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
|
||||
case VK_FORMAT_A2B10G10R10_SNORM_PACK32:
|
||||
case VK_FORMAT_A2B10G10R10_USCALED_PACK32:
|
||||
case VK_FORMAT_A2B10G10R10_SSCALED_PACK32:
|
||||
case VK_FORMAT_A2B10G10R10_UINT_PACK32:
|
||||
case VK_FORMAT_A2B10G10R10_SINT_PACK32:
|
||||
case VK_FORMAT_R16G16_UNORM:
|
||||
case VK_FORMAT_R16G16_SNORM:
|
||||
case VK_FORMAT_R16G16_USCALED:
|
||||
case VK_FORMAT_R16G16_SSCALED:
|
||||
case VK_FORMAT_R16G16_UINT:
|
||||
case VK_FORMAT_R16G16_SINT:
|
||||
case VK_FORMAT_R16G16_SFLOAT:
|
||||
case VK_FORMAT_R32_UINT:
|
||||
case VK_FORMAT_R32_SINT:
|
||||
case VK_FORMAT_R32_SFLOAT:
|
||||
case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
|
||||
case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
|
||||
case VK_FORMAT_X8_D24_UNORM_PACK32:
|
||||
case VK_FORMAT_D32_SFLOAT:
|
||||
case VK_FORMAT_D24_UNORM_S8_UINT:
|
||||
case VK_FORMAT_G8B8G8R8_422_UNORM:
|
||||
case VK_FORMAT_B8G8R8G8_422_UNORM:
|
||||
return 4;
|
||||
|
||||
// 40-bit formats.
|
||||
case VK_FORMAT_D32_SFLOAT_S8_UINT:
|
||||
return 5;
|
||||
|
||||
// 48-bit formats.
|
||||
case VK_FORMAT_R16G16B16_UNORM:
|
||||
case VK_FORMAT_R16G16B16_SNORM:
|
||||
case VK_FORMAT_R16G16B16_USCALED:
|
||||
case VK_FORMAT_R16G16B16_SSCALED:
|
||||
case VK_FORMAT_R16G16B16_UINT:
|
||||
case VK_FORMAT_R16G16B16_SINT:
|
||||
case VK_FORMAT_R16G16B16_SFLOAT:
|
||||
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16:
|
||||
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16:
|
||||
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16:
|
||||
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16:
|
||||
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16:
|
||||
case VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM:
|
||||
case VK_FORMAT_G16_B16R16_2PLANE_420_UNORM:
|
||||
case VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM:
|
||||
case VK_FORMAT_G16_B16R16_2PLANE_422_UNORM:
|
||||
case VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM:
|
||||
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16:
|
||||
case VK_FORMAT_G16_B16R16_2PLANE_444_UNORM:
|
||||
return 6;
|
||||
|
||||
// 64-bit formats.
|
||||
case VK_FORMAT_R16G16B16A16_UNORM:
|
||||
case VK_FORMAT_R16G16B16A16_SNORM:
|
||||
case VK_FORMAT_R16G16B16A16_USCALED:
|
||||
case VK_FORMAT_R16G16B16A16_SSCALED:
|
||||
case VK_FORMAT_R16G16B16A16_UINT:
|
||||
case VK_FORMAT_R16G16B16A16_SINT:
|
||||
case VK_FORMAT_R16G16B16A16_SFLOAT:
|
||||
case VK_FORMAT_R32G32_UINT:
|
||||
case VK_FORMAT_R32G32_SINT:
|
||||
case VK_FORMAT_R32G32_SFLOAT:
|
||||
case VK_FORMAT_R64_UINT:
|
||||
case VK_FORMAT_R64_SINT:
|
||||
case VK_FORMAT_R64_SFLOAT:
|
||||
case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC4_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC4_SNORM_BLOCK:
|
||||
case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
|
||||
case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
|
||||
case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
|
||||
case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
|
||||
case VK_FORMAT_EAC_R11_UNORM_BLOCK:
|
||||
case VK_FORMAT_EAC_R11_SNORM_BLOCK:
|
||||
case VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16:
|
||||
case VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16:
|
||||
case VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16:
|
||||
case VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16:
|
||||
case VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16:
|
||||
case VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16:
|
||||
case VK_FORMAT_G16B16G16R16_422_UNORM:
|
||||
case VK_FORMAT_B16G16R16G16_422_UNORM:
|
||||
case VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG:
|
||||
case VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG:
|
||||
case VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG:
|
||||
case VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG:
|
||||
case VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG:
|
||||
case VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG:
|
||||
case VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG:
|
||||
case VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG:
|
||||
return 8;
|
||||
|
||||
// 96-bit formats.
|
||||
case VK_FORMAT_R32G32B32_UINT:
|
||||
case VK_FORMAT_R32G32B32_SINT:
|
||||
case VK_FORMAT_R32G32B32_SFLOAT:
|
||||
return 12;
|
||||
|
||||
// 128-bit formats.
|
||||
case VK_FORMAT_BC2_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC2_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC3_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC3_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC5_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC5_SNORM_BLOCK:
|
||||
case VK_FORMAT_BC6H_UFLOAT_BLOCK:
|
||||
case VK_FORMAT_BC6H_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_BC7_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC7_SRGB_BLOCK:
|
||||
case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
|
||||
case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
|
||||
case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
|
||||
case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_4x4_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_4x4_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x4_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x4_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x5_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x5_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x5_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x5_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x6_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x6_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x5_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x5_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x6_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x6_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x8_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x8_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x5_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x5_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x6_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x6_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x8_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x8_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x10_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x10_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x10_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x10_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x12_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x12_SRGB_BLOCK:
|
||||
case VK_FORMAT_R32G32B32A32_UINT:
|
||||
case VK_FORMAT_R32G32B32A32_SINT:
|
||||
case VK_FORMAT_R32G32B32A32_SFLOAT:
|
||||
case VK_FORMAT_R64G64_UINT:
|
||||
case VK_FORMAT_R64G64_SINT:
|
||||
case VK_FORMAT_R64G64_SFLOAT:
|
||||
return 16;
|
||||
|
||||
// 192-bit formats.
|
||||
case VK_FORMAT_R64G64B64_UINT:
|
||||
case VK_FORMAT_R64G64B64_SINT:
|
||||
case VK_FORMAT_R64G64B64_SFLOAT:
|
||||
return 24;
|
||||
|
||||
// 256-bit formats.
|
||||
case VK_FORMAT_R64G64B64A64_UINT:
|
||||
case VK_FORMAT_R64G64B64A64_SINT:
|
||||
case VK_FORMAT_R64G64B64A64_SFLOAT:
|
||||
return 32;
|
||||
|
||||
case VK_FORMAT_UNDEFINED:
|
||||
// In cases where we've explicitly already determined that the
|
||||
// format is not supported, let the rest of the system handle
|
||||
// that explicitly. Return '1' for no alignment.
|
||||
return 1;
|
||||
|
||||
default:
|
||||
// This is the case where there's a format that we added support
|
||||
// for in getVkFormat that we have not added support for in this
|
||||
// function, and should be treated as an error.
|
||||
assert_invariant(false && "Unknown data type, conversion is not supported.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
VkFormat getVkFormat(PixelDataFormat format, PixelDataType type) {
|
||||
if (type == PixelDataType::USHORT_565) return VK_FORMAT_R5G6B5_UNORM_PACK16;
|
||||
if (type == PixelDataType::UINT_2_10_10_10_REV) return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
|
||||
|
||||
@@ -49,9 +49,6 @@ VkFormat getVkFormatLinear(VkFormat format);
|
||||
// than a driver-level Texture format, and can account for a specified byte alignment.
|
||||
uint32_t getBytesPerPixel(TextureFormat format);
|
||||
|
||||
// Required for aligning textures in staging buffers.
|
||||
uint8_t getTexelBlockSize(VkFormat format);
|
||||
|
||||
VkCompareOp getCompareOp(SamplerCompareFunc func);
|
||||
VkBlendFactor getBlendFactor(BlendFunction mode);
|
||||
VkCullModeFlags getCullMode(CullingMode mode);
|
||||
|
||||
@@ -172,11 +172,15 @@ SPDPipeline& MipmapGenerator::GetOrCreatePipeline(const PipelineCacheKey& key) {
|
||||
entry.binding = i;
|
||||
entry.visibility = wgpu::ShaderStage::Compute;
|
||||
if (i == 0) {
|
||||
entry.texture.sampleType = (key.scalarType == SPDScalarType::I32)
|
||||
? wgpu::TextureSampleType::Sint
|
||||
: (key.scalarType == SPDScalarType::U32)
|
||||
? wgpu::TextureSampleType::Uint
|
||||
: wgpu::TextureSampleType::UnfilterableFloat;
|
||||
if (key.scalarType == SPDScalarType::I32) {
|
||||
entry.texture.sampleType = wgpu::TextureSampleType::Sint;
|
||||
} else if (key.scalarType == SPDScalarType::U32) {
|
||||
entry.texture.sampleType = wgpu::TextureSampleType::Uint;
|
||||
} else if (key.scalarType == SPDScalarType::F32 || key.scalarType == SPDScalarType::F16) {
|
||||
entry.texture.sampleType = wgpu::TextureSampleType::Float;
|
||||
} else {
|
||||
entry.texture.sampleType = wgpu::TextureSampleType::UnfilterableFloat;
|
||||
}
|
||||
entry.texture.viewDimension = wgpu::TextureViewDimension::e2DArray;
|
||||
} else {
|
||||
entry.storageTexture.access = wgpu::StorageTextureAccess::WriteOnly;
|
||||
|
||||
@@ -37,8 +37,8 @@ namespace {
|
||||
uint32_t size, const char* const label) {
|
||||
// Write size must be divisible by WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4).
|
||||
// If the whole buffer is written to as is common, so must the buffer size.
|
||||
size += (FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - (size % FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS)) %
|
||||
FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
|
||||
size += (WEBGPU_BUFFER_SIZE_MODULUS - (size % WEBGPU_BUFFER_SIZE_MODULUS)) %
|
||||
WEBGPU_BUFFER_SIZE_MODULUS;
|
||||
wgpu::BufferDescriptor descriptor{
|
||||
.label = label,
|
||||
.usage = usage,
|
||||
@@ -62,15 +62,15 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
||||
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.size + byteOffset <= mBuffer.GetSize())
|
||||
<< "Attempting to copy " << bufferDescriptor.size << " bytes into a buffer of size "
|
||||
<< mBuffer.GetSize() << " at offset " << byteOffset;
|
||||
FILAMENT_CHECK_PRECONDITION(byteOffset % FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS == 0)
|
||||
<< "Byte offset must be a multiple of " << FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS
|
||||
<< " but is " << byteOffset;
|
||||
FILAMENT_CHECK_PRECONDITION(byteOffset % WEBGPU_BUFFER_SIZE_MODULUS == 0)
|
||||
<< "Byte offset must be a multiple of " << WEBGPU_BUFFER_SIZE_MODULUS << " but is "
|
||||
<< byteOffset;
|
||||
|
||||
// TODO: All buffer objects are created with CopyDst usage.
|
||||
// This may have some performance implications. That should be investigated later.
|
||||
assert_invariant(mBuffer.GetUsage() & wgpu::BufferUsage::CopyDst);
|
||||
|
||||
const size_t remainder = bufferDescriptor.size % FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
|
||||
const size_t remainder = bufferDescriptor.size % WEBGPU_BUFFER_SIZE_MODULUS;
|
||||
|
||||
// WriteBuffer is an async call. But cpu buffer data is already written to the staging
|
||||
// buffer on return from the WriteBuffer.
|
||||
@@ -82,11 +82,10 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
||||
memcpy(mRemainderChunk.data(), remainderStart, remainder);
|
||||
// Pad the remainder with zeros to ensure deterministic behavior, though GPU shouldn't
|
||||
// access this
|
||||
std::memset(mRemainderChunk.data() + remainder, 0,
|
||||
FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - remainder);
|
||||
std::memset(mRemainderChunk.data() + remainder, 0, WEBGPU_BUFFER_SIZE_MODULUS - remainder);
|
||||
|
||||
queue.WriteBuffer(mBuffer, byteOffset + legalSize, &mRemainderChunk,
|
||||
FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS);
|
||||
WEBGPU_BUFFER_SIZE_MODULUS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ protected:
|
||||
|
||||
private:
|
||||
const wgpu::Buffer mBuffer;
|
||||
// FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4) bytes to hold any extra chunk we need.
|
||||
std::array<uint8_t, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS> mRemainderChunk{};
|
||||
// WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4) bytes to hold any extra chunk we need.
|
||||
std::array<uint8_t, WEBGPU_BUFFER_SIZE_MODULUS> mRemainderChunk{};
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
constexpr size_t FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS = 4;
|
||||
constexpr size_t WEBGPU_BUFFER_SIZE_MODULUS = 4;
|
||||
|
||||
// FWGPU is short for Filament WebGPU
|
||||
|
||||
@@ -65,25 +65,13 @@ constexpr size_t FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS = 4;
|
||||
#define FWGPU_LOGI LOG(INFO)
|
||||
#endif
|
||||
|
||||
constexpr uint64_t FILAMENT_WEBGPU_REQUEST_ADAPTER_TIMEOUT_NANOSECONDS =
|
||||
constexpr uint64_t REQUEST_ADAPTER_TIMEOUT_NANOSECONDS =
|
||||
/* milliseconds */ 1000u * /* converted to ns */ 1000000u;
|
||||
|
||||
constexpr uint64_t FILAMENT_WEBGPU_REQUEST_DEVICE_TIMEOUT_NANOSECONDS =
|
||||
constexpr uint64_t REQUEST_DEVICE_TIMEOUT_NANOSECONDS =
|
||||
/* milliseconds */ 1000u * /* converted to ns */ 1000000u;
|
||||
|
||||
constexpr uint64_t FILAMENT_WEBGPU_SHADER_COMPILATION_TIMEOUT_NANOSECONDS =
|
||||
constexpr uint64_t SHADER_COMPILATION_TIMEOUT_NANOSECONDS =
|
||||
/* milliseconds */ 1000u * /* converted to ns */ 1000000u;
|
||||
|
||||
// if a render pipeline is not used in this number of consecutive frames,
|
||||
// then expire/release it from the cache.
|
||||
// A smaller number means more frequent pipeline creation events, taking more time.
|
||||
// A larger number means more pipelines stored, taking more memory.
|
||||
constexpr uint64_t FILAMENT_WEBGPU_RENDER_PIPELINE_EXPIRATION_IN_FRAME_COUNT = 45;
|
||||
|
||||
// if a pipeline layout is not used in this number of consecutive frames,
|
||||
// then expire/release it from the cache.
|
||||
// A smaller number means more frequent pipeline layout creation events, taking more time.
|
||||
// A larger number means more layouts stored, taking more memory.
|
||||
constexpr uint64_t FILAMENT_WEBGPU_PIPELINE_LAYOUT_EXPIRATION_IN_FRAME_COUNT = 90;
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_WEBGPUCONSTANTS_H
|
||||
|
||||
@@ -117,10 +117,10 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
|
||||
samplerEntryInfo.binding = samplerEntry.binding;
|
||||
samplerEntry.visibility = wEntry.visibility;
|
||||
wEntry.texture.multisampled = isMultiSampledTypeDescriptor(fEntry.type);
|
||||
// TODO: Set once we have the filtering values
|
||||
if (isDepthDescriptor(fEntry.type)) {
|
||||
samplerEntry.sampler.type = wgpu::SamplerBindingType::Comparison;
|
||||
} else if (isIntDescriptor(fEntry.type) ||
|
||||
any(fEntry.flags & DescriptorFlags::UNFILTERABLE)) {
|
||||
} else if (isIntDescriptor(fEntry.type)) {
|
||||
samplerEntry.sampler.type = wgpu::SamplerBindingType::NonFiltering;
|
||||
} else {
|
||||
samplerEntry.sampler.type = wgpu::SamplerBindingType::Filtering;
|
||||
@@ -151,11 +151,8 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
|
||||
if (isDepthDescriptor(fEntry.type)) {
|
||||
wEntry.texture.sampleType = wgpu::TextureSampleType::Depth;
|
||||
} else if (isFloatDescriptor(fEntry.type)) {
|
||||
if (any(fEntry.flags & DescriptorFlags::UNFILTERABLE)) {
|
||||
wEntry.texture.sampleType = wgpu::TextureSampleType::UnfilterableFloat;
|
||||
} else {
|
||||
wEntry.texture.sampleType = wgpu::TextureSampleType::Float;
|
||||
}
|
||||
// TODO: Set once we have the filtering values
|
||||
wEntry.texture.sampleType = wgpu::TextureSampleType::Float;
|
||||
} else if (isIntDescriptor(fEntry.type)) {
|
||||
wEntry.texture.sampleType = wgpu::TextureSampleType::Sint;
|
||||
} else if (isUnsignedIntDescriptor(fEntry.type)) {
|
||||
|
||||
@@ -18,14 +18,11 @@
|
||||
#include "WebGPUBufferObject.h"
|
||||
#include "WebGPUDescriptorSet.h"
|
||||
#include "WebGPUDescriptorSetLayout.h"
|
||||
#include "WebGPUFence.h"
|
||||
#include "WebGPUIndexBuffer.h"
|
||||
#include "WebGPUPipelineCache.h"
|
||||
#include "WebGPUPipelineLayoutCache.h"
|
||||
#include "WebGPUPipelineCreation.h"
|
||||
#include "WebGPUProgram.h"
|
||||
#include "WebGPURenderPrimitive.h"
|
||||
#include "WebGPURenderTarget.h"
|
||||
#include "WebGPUStrings.h"
|
||||
#include "WebGPUSwapChain.h"
|
||||
#include "WebGPUTexture.h"
|
||||
#include "WebGPUVertexBuffer.h"
|
||||
@@ -44,7 +41,7 @@
|
||||
#include <utils/CString.h>
|
||||
#include <utils/Hash.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
@@ -73,10 +70,7 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
|
||||
mAdapter{ mPlatform.requestAdapter(nullptr) },
|
||||
mDevice{ mPlatform.requestDevice(mAdapter) },
|
||||
mQueue{ mDevice.GetQueue() },
|
||||
mPipelineLayoutCache{ mDevice },
|
||||
mPipelineCache{ mDevice },
|
||||
mRenderPassMipmapGenerator{ mDevice },
|
||||
mSpdComputePassMipmapGenerator{ mDevice },
|
||||
mMipMapGenerator{ mDevice },
|
||||
mHandleAllocator{ "Handles", driverConfig.handleArenaSize,
|
||||
driverConfig.disableHandleUseAfterFreeCheck, driverConfig.disableHeapHandleTags } {
|
||||
mDevice.GetLimits(&mDeviceLimits);
|
||||
@@ -109,7 +103,6 @@ void WebGPUDriver::terminate() {
|
||||
|
||||
void WebGPUDriver::tick(int) {
|
||||
mDevice.Tick();
|
||||
mAdapter.GetInstance().ProcessEvents();
|
||||
}
|
||||
|
||||
void WebGPUDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
@@ -129,9 +122,7 @@ void WebGPUDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||
void WebGPUDriver::setPresentationTime(int64_t monotonic_clock_ns) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::endFrame(const uint32_t /* frameId */) {
|
||||
mPipelineLayoutCache.onFrameEnd();
|
||||
mPipelineCache.onFrameEnd();
|
||||
void WebGPUDriver::endFrame(uint32_t frameId) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::flush(int) {
|
||||
@@ -273,9 +264,7 @@ Handle<HwProgram> WebGPUDriver::createProgramS() noexcept {
|
||||
}
|
||||
|
||||
Handle<HwFence> WebGPUDriver::createFenceS() noexcept {
|
||||
// The handle must be constructed here, as a synchronous call to get the status
|
||||
// might happen before createFenceR is executed.
|
||||
return allocAndConstructHandle<WebGPUFence, HwFence>();
|
||||
return Handle<HwFence>((Handle<HwFence>::HandleId) mNextFakeHandle++);
|
||||
}
|
||||
|
||||
Handle<HwTimerQuery> WebGPUDriver::createTimerQueryS() noexcept {
|
||||
@@ -459,23 +448,15 @@ void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> renderTarge
|
||||
}
|
||||
|
||||
void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> renderTargetHandle,
|
||||
const TargetBufferFlags targetFlags, const uint32_t width, const uint32_t height,
|
||||
const TargetBufferFlags targets, const uint32_t width, const uint32_t height,
|
||||
const uint8_t samples, const uint8_t layerCount, const MRT color,
|
||||
const TargetBufferInfo depth, const TargetBufferInfo stencil) {
|
||||
constructHandle<WebGPURenderTarget>(
|
||||
renderTargetHandle, width, height, samples, layerCount, color, depth, stencil,
|
||||
targetFlags,
|
||||
[&](const Handle<HwTexture> textureHandle) {
|
||||
return handleCast<WebGPUTexture>(textureHandle);
|
||||
},
|
||||
mDevice);
|
||||
constructHandle<WebGPURenderTarget>(renderTargetHandle, width, height, samples, layerCount,
|
||||
color, depth, stencil);
|
||||
}
|
||||
|
||||
void WebGPUDriver::createFenceR(Handle<HwFence> fenceHandle, const int /* dummy */) {
|
||||
// the handle was already constructed in createFenceS
|
||||
const auto fence = handleCast<WebGPUFence>(fenceHandle);
|
||||
assert_invariant(mQueue);
|
||||
fence->addMarkerToQueueState(mQueue);
|
||||
void WebGPUDriver::createFenceR(Handle<HwFence> fh, int) {
|
||||
//todo
|
||||
}
|
||||
|
||||
void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {}
|
||||
@@ -528,18 +509,13 @@ void WebGPUDriver::updateStreams(CommandStream* driver) {
|
||||
//todo
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyFence(Handle<HwFence> fenceHandle) {
|
||||
if (fenceHandle) {
|
||||
destructHandle<WebGPUFence>(fenceHandle);
|
||||
}
|
||||
void WebGPUDriver::destroyFence(Handle<HwFence> fh) {
|
||||
//todo
|
||||
}
|
||||
|
||||
FenceStatus WebGPUDriver::getFenceStatus(Handle<HwFence> fenceHandle) {
|
||||
const auto fence = handleCast<WebGPUFence>(fenceHandle);
|
||||
if (!fence) {
|
||||
return FenceStatus::ERROR;
|
||||
}
|
||||
return fence->getStatus();
|
||||
FenceStatus WebGPUDriver::getFenceStatus(Handle<HwFence> fh) {
|
||||
//todo
|
||||
return FenceStatus::CONDITION_SATISFIED;
|
||||
}
|
||||
|
||||
// We create all textures using VK_IMAGE_TILING_OPTIMAL, so our definition of "supported" is that
|
||||
@@ -596,7 +572,7 @@ bool WebGPUDriver::isParallelShaderCompileSupported() {
|
||||
}
|
||||
|
||||
bool WebGPUDriver::isDepthStencilResolveSupported() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebGPUDriver::isDepthStencilBlitSupported(const TextureFormat format) {
|
||||
@@ -729,6 +705,27 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
|
||||
|
||||
const uint8_t* dataBuff = static_cast<const uint8_t*>(data->buffer);
|
||||
size_t dataSize = data->size;
|
||||
std::unique_ptr<uint8_t[]> paddedBuffer;
|
||||
|
||||
if (bytesPerRow % 256 != 0) {
|
||||
uint32_t padding = 256 - (bytesPerRow % 256);
|
||||
uint32_t paddedBytesPerRow = bytesPerRow + padding;
|
||||
|
||||
size_t paddedBufferSize = static_cast<size_t>(paddedBytesPerRow) * height * depth;
|
||||
paddedBuffer = std::make_unique<uint8_t[]>(paddedBufferSize);
|
||||
uint8_t* dest = paddedBuffer.get();
|
||||
|
||||
for (uint32_t z = 0; z < depth; ++z) {
|
||||
for (uint32_t y = 0; y < height; ++y) {
|
||||
std::memcpy(dest, dataBuff, bytesPerRow);
|
||||
dest += paddedBytesPerRow;
|
||||
dataBuff += bytesPerRow;
|
||||
}
|
||||
}
|
||||
dataBuff = paddedBuffer.get();
|
||||
dataSize = paddedBufferSize;
|
||||
bytesPerRow = paddedBytesPerRow;
|
||||
}
|
||||
|
||||
auto layout = wgpu::TexelCopyBufferLayout{ .bytesPerRow = bytesPerRow, .rowsPerImage = height };
|
||||
|
||||
@@ -759,58 +756,41 @@ void WebGPUDriver::generateMipmaps(Handle<HwTexture> textureHandle) {
|
||||
wgpu::Texture wgpuTexture = texture->getTexture();
|
||||
assert_invariant(wgpuTexture);
|
||||
|
||||
const uint32_t totalMipLevels = wgpuTexture.GetMipLevelCount();
|
||||
if (totalMipLevels <= 1) {
|
||||
return; // nothing to do
|
||||
}
|
||||
|
||||
const auto usage = wgpuTexture.GetUsage();
|
||||
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::TextureBinding)
|
||||
<< "Texture for mipmap generation must have TextureBinding usage.";
|
||||
<< "Texture for mipmap generation must have TextureBinding usage. "
|
||||
"supportsMultipleMipLevels for texture format "
|
||||
<< static_cast<uint32_t>(wgpuTexture.GetFormat()) << " is "
|
||||
<< WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(wgpuTexture.GetFormat());
|
||||
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::StorageBinding)
|
||||
<< "Texture for mipmap generation must have StorageBinding usage. "
|
||||
"supportsMultipleMipLevels for texture format "
|
||||
<< static_cast<uint32_t>(wgpuTexture.GetFormat()) << " is "
|
||||
<< WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(wgpuTexture.GetFormat());
|
||||
|
||||
switch (texture->getMipmapGenerationStrategy()) {
|
||||
case WebGPUTexture::MipmapGenerationStrategy::RENDER_PASS:
|
||||
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::RenderAttachment)
|
||||
<< "Texture for mipmap generation via render pass blit must have "
|
||||
"RenderAttachment usage.";
|
||||
mRenderPassMipmapGenerator.generateMipmaps(mQueue, texture->getTexture());
|
||||
break;
|
||||
case WebGPUTexture::MipmapGenerationStrategy::NONE:
|
||||
FWGPU_LOGW << "Trying to generate mipmaps for a texture we already know we cannot do "
|
||||
"such generation. Texture sample type "
|
||||
<< to_string(texture->target) << " WebGPU texture format "
|
||||
<< webGPUTextureFormatToString(texture->getViewFormat()) << " samples "
|
||||
<< static_cast<uint32_t>(texture->samples)
|
||||
<< ". Going to try using the SPD_COMPUTE_PASS approach.";
|
||||
UTILS_FALLTHROUGH;
|
||||
case WebGPUTexture::MipmapGenerationStrategy::SPD_COMPUTE_PASS:
|
||||
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::StorageBinding)
|
||||
<< "Texture for mipmap generation must have StorageBinding usage "
|
||||
"(as a compute pass strategy was selected for use). "
|
||||
"supportsMultipleMipLevels for WebGPU texture format "
|
||||
<< webGPUTextureFormatToString(wgpuTexture.GetFormat()) << " is "
|
||||
<< WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(
|
||||
wgpuTexture.GetFormat());
|
||||
// We will record all passes into a single command encoder.
|
||||
wgpu::CommandEncoderDescriptor encoderDesc = {};
|
||||
encoderDesc.label = "Mipmap Command Encoder";
|
||||
wgpu::CommandEncoder encoder = mDevice.CreateCommandEncoder(&encoderDesc);
|
||||
|
||||
spd::SPDPassConfig spdConfig = { .filter = spd::SPDFilter::Average,
|
||||
.targetTexture = wgpuTexture,
|
||||
.numMips = totalMipLevels,
|
||||
.halfPrecision = false,
|
||||
.sourceMipLevel = 0 };
|
||||
|
||||
mSpdComputePassMipmapGenerator.Generate(encoder, wgpuTexture, spdConfig);
|
||||
|
||||
// Finish the encoder and submit all the passes at once.
|
||||
wgpu::CommandBufferDescriptor cmdBufferDesc = {};
|
||||
cmdBufferDesc.label = "Mipmap Command Buffer";
|
||||
wgpu::CommandBuffer commandBuffer = encoder.Finish(&cmdBufferDesc);
|
||||
mQueue.Submit(1, &commandBuffer);
|
||||
break;
|
||||
const uint32_t totalMipLevels = wgpuTexture.GetMipLevelCount();
|
||||
if (totalMipLevels <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We will record all passes into a single command encoder.
|
||||
wgpu::CommandEncoderDescriptor encoderDesc = {};
|
||||
encoderDesc.label = "Mipmap Command Encoder";
|
||||
wgpu::CommandEncoder encoder = mDevice.CreateCommandEncoder(&encoderDesc);
|
||||
|
||||
spd::SPDPassConfig spdConfig = { .filter = spd::SPDFilter::Average,
|
||||
.targetTexture = wgpuTexture,
|
||||
.numMips = totalMipLevels,
|
||||
.halfPrecision = false,
|
||||
.sourceMipLevel = 0 };
|
||||
|
||||
mMipMapGenerator.Generate(encoder, wgpuTexture, spdConfig);
|
||||
|
||||
// Finish the encoder and submit all the passes at once.
|
||||
wgpu::CommandBufferDescriptor cmdBufferDesc = {};
|
||||
cmdBufferDesc.label = "Mipmap Command Buffer";
|
||||
wgpu::CommandBuffer commandBuffer = encoder.Finish(&cmdBufferDesc);
|
||||
mQueue.Submit(1, &commandBuffer);
|
||||
}
|
||||
|
||||
void WebGPUDriver::compilePrograms(CompilerPriorityQueue priority,
|
||||
@@ -829,16 +809,12 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
|
||||
wgpu::TextureView defaultDepthStencilView = nullptr;
|
||||
wgpu::TextureFormat defaultDepthStencilFormat = wgpu::TextureFormat::Undefined;
|
||||
|
||||
const bool msaaSidecarsRequired{ renderTarget->getSamples() > 1 &&
|
||||
renderTarget->getSampleCountPerAttachment() <= 1 };
|
||||
|
||||
std::array<wgpu::TextureView, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> customColorViews{};
|
||||
std::array<wgpu::TextureView, customColorViews.size()> customColorMsaaSidecarViews{};
|
||||
uint32_t customColorViewCount = 0;
|
||||
|
||||
wgpu::TextureView customDepthStencilView = nullptr;
|
||||
wgpu::TextureView customDepthStencilMsaaSidecarTextureView = nullptr;
|
||||
wgpu::TextureFormat customDepthStencilFormat = wgpu::TextureFormat::Undefined;
|
||||
wgpu::TextureView customDepthView = nullptr;
|
||||
wgpu::TextureFormat customDepthFormat = wgpu::TextureFormat::Undefined;
|
||||
wgpu::TextureFormat customStencilFormat = wgpu::TextureFormat::Undefined;
|
||||
wgpu::TextureView customStencilView = nullptr;
|
||||
|
||||
mCurrentRenderTarget = renderTarget;
|
||||
if (renderTarget->isDefaultRenderTarget()) {
|
||||
@@ -846,20 +822,6 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
|
||||
defaultColorView = mTextureView;
|
||||
defaultDepthStencilView = mSwapChain->getDepthTextureView();
|
||||
defaultDepthStencilFormat = mSwapChain->getDepthFormat();
|
||||
|
||||
if (any(renderTarget->getTargetFlags() & TargetBufferFlags::STENCIL) &&
|
||||
!(hasStencil(defaultDepthStencilFormat))) {
|
||||
FILAMENT_CHECK_POSTCONDITION(false)
|
||||
<< "Default render target requested stencil, but swap chain's depth format "
|
||||
<< (uint32_t) defaultDepthStencilFormat << " does not have a stencil aspect.";
|
||||
}
|
||||
|
||||
if (any(renderTarget->getTargetFlags() & TargetBufferFlags::DEPTH) &&
|
||||
!(hasDepth(defaultDepthStencilFormat))) {
|
||||
FILAMENT_CHECK_POSTCONDITION(false)
|
||||
<< "Default render target requested depth, but swap chain's depth format "
|
||||
<< (uint32_t) defaultDepthStencilFormat << " does not have a depth aspect.";
|
||||
}
|
||||
} else {
|
||||
// Resolve views for custom render target
|
||||
const auto& colorInfos = renderTarget->getColorAttachmentInfos();
|
||||
@@ -874,78 +836,41 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
|
||||
<< ".";
|
||||
const uint8_t mipLevel = colorInfos[i].level;
|
||||
const uint32_t arrayLayer = colorInfos[i].layer;
|
||||
customColorViews[customColorViewCount] =
|
||||
customColorViews[customColorViewCount++] =
|
||||
colorTexture->getOrMakeTextureView(mipLevel, arrayLayer);
|
||||
if (msaaSidecarsRequired) {
|
||||
const wgpu::TextureView msaaSidecarView{
|
||||
colorTexture->makeMsaaSidecarTextureViewIfTextureSidecarExists(
|
||||
renderTarget->getSamples(), mipLevel, arrayLayer)
|
||||
};
|
||||
FILAMENT_CHECK_POSTCONDITION(msaaSidecarView)
|
||||
<< "Could not get a required MSAA sidecar texture view for color "
|
||||
<< customColorViewCount << "?";
|
||||
customColorMsaaSidecarViews[customColorViewCount] = msaaSidecarView;
|
||||
}
|
||||
customColorViewCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto& depthInfo = renderTarget->getDepthAttachmentInfo();
|
||||
const auto& stencilInfo = renderTarget->getStencilAttachmentInfo();
|
||||
|
||||
Handle<HwTexture> depthStencilSourceHandle = {};
|
||||
uint8_t depthStencilMipLevel = 0;
|
||||
uint32_t depthStencilArrayLayer = 0;
|
||||
|
||||
if (depthInfo.handle && stencilInfo.handle) {
|
||||
auto const depthTexture{handleCast<WebGPUTexture>(depthInfo.handle)->getTexture()};
|
||||
auto const stencilTexture{handleCast<WebGPUTexture>(stencilInfo.handle)->getTexture()};
|
||||
FILAMENT_CHECK_POSTCONDITION(depthTexture.Get() == stencilTexture.Get())
|
||||
<< "Filament must reference the same resource if both depth and stencil "
|
||||
"textures are present";
|
||||
}
|
||||
|
||||
if (depthInfo.handle) {
|
||||
depthStencilSourceHandle = depthInfo.handle;
|
||||
depthStencilMipLevel = depthInfo.level;
|
||||
depthStencilArrayLayer = depthInfo.layer;
|
||||
} else if (stencilInfo.handle) {
|
||||
depthStencilSourceHandle = stencilInfo.handle;
|
||||
depthStencilMipLevel = stencilInfo.level;
|
||||
depthStencilArrayLayer = stencilInfo.layer;
|
||||
FILAMENT_CHECK_POSTCONDITION(depthInfo.layer < renderTarget->getLayerCount())
|
||||
<< "Depth attachment requests layer " << depthInfo.layer
|
||||
<< "but render target has only " << renderTarget->getLayerCount() << ".";
|
||||
auto depthTexture = handleCast<WebGPUTexture>(depthInfo.handle);
|
||||
if (depthTexture) {
|
||||
const uint8_t depthMipLevel = depthInfo.level;
|
||||
const uint32_t depthArrayLayer = depthInfo.layer;
|
||||
customDepthView =
|
||||
depthTexture->getOrMakeTextureView(depthMipLevel, depthArrayLayer);
|
||||
customDepthFormat = depthTexture->getViewFormat();
|
||||
}
|
||||
}
|
||||
|
||||
if (depthStencilSourceHandle) {
|
||||
auto dsTexture = handleCast<WebGPUTexture>(depthStencilSourceHandle);
|
||||
if (dsTexture) {
|
||||
customDepthStencilView = dsTexture->getOrMakeTextureView(depthStencilMipLevel,
|
||||
depthStencilArrayLayer);
|
||||
if (msaaSidecarsRequired) {
|
||||
customDepthStencilMsaaSidecarTextureView =
|
||||
dsTexture->makeMsaaSidecarTextureViewIfTextureSidecarExists(
|
||||
renderTarget->getSamples(), depthStencilMipLevel,
|
||||
depthStencilArrayLayer);
|
||||
FILAMENT_CHECK_POSTCONDITION(customDepthStencilMsaaSidecarTextureView)
|
||||
<< "Could not get a required MSAA sidecar texture view for "
|
||||
"depth/stencil?";
|
||||
}
|
||||
customDepthStencilFormat = dsTexture->getViewFormat();
|
||||
|
||||
if (any(renderTarget->getTargetFlags() & TargetBufferFlags::STENCIL) &&
|
||||
!(hasStencil(customDepthStencilFormat))) {
|
||||
FILAMENT_CHECK_POSTCONDITION(false)
|
||||
<< "Custom render target requested stencil, but the provided texture"
|
||||
"format number"
|
||||
<< (uint32_t) customDepthStencilFormat
|
||||
<< " does not have a stencil aspect.";
|
||||
}
|
||||
if (any(renderTarget->getTargetFlags() & TargetBufferFlags::DEPTH) &&
|
||||
!(hasDepth(customDepthStencilFormat))) {
|
||||
FILAMENT_CHECK_POSTCONDITION(false) << "Custom render target requested depth, "
|
||||
"but the provided texture format number"
|
||||
<< (uint32_t) customDepthStencilFormat
|
||||
<< " does not have a depth aspect.";
|
||||
}
|
||||
const auto& stencilInfo = renderTarget->getStencilAttachmentInfo();
|
||||
if (stencilInfo.handle) {
|
||||
// If depth and stencil use the same texture handle, this will re-cast but that's fine.
|
||||
auto stencilTexture = handleCast<WebGPUTexture>(stencilInfo.handle);
|
||||
if (stencilTexture) {
|
||||
FILAMENT_CHECK_POSTCONDITION(stencilInfo.layer < renderTarget->getLayerCount())
|
||||
<< "Stencil attachment requests layer " << stencilInfo.layer
|
||||
<< " but render target has only " << renderTarget->getLayerCount()
|
||||
<< " layers.";
|
||||
const uint8_t stencilMipLevel = stencilInfo.level;
|
||||
const uint32_t stencilArrayLayer = stencilInfo.layer;
|
||||
customStencilView =
|
||||
stencilTexture->getOrMakeTextureView(stencilMipLevel, stencilArrayLayer);
|
||||
customStencilFormat = stencilTexture->getViewFormat();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -954,11 +879,13 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
|
||||
params,
|
||||
defaultColorView,
|
||||
defaultDepthStencilView,
|
||||
defaultDepthStencilFormat,
|
||||
customColorViews.data(),
|
||||
customColorMsaaSidecarViews.data(),
|
||||
customColorViewCount,
|
||||
customDepthStencilView,
|
||||
customDepthStencilMsaaSidecarTextureView);
|
||||
customDepthView,
|
||||
customStencilView,
|
||||
customDepthFormat,
|
||||
customStencilFormat);
|
||||
|
||||
mRenderPassEncoder = mCommandEncoder.BeginRenderPass(&renderPassDescriptor);
|
||||
|
||||
@@ -993,25 +920,9 @@ void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
|
||||
wgpu::Extent2D surfaceSize = mPlatform.getSurfaceExtent(mNativeWindow);
|
||||
mTextureView = mSwapChain->getCurrentSurfaceTextureView(surfaceSize);
|
||||
assert_invariant(mTextureView);
|
||||
|
||||
assert_invariant(mDefaultRenderTarget);
|
||||
|
||||
wgpu::TextureFormat depthFormat = mSwapChain->getDepthFormat();
|
||||
TargetBufferFlags newTargetFlags = filament::backend::TargetBufferFlags::NONE;
|
||||
|
||||
//Assuming Color always present in default render target.
|
||||
newTargetFlags |= filament::backend::TargetBufferFlags::COLOR;
|
||||
if (depthFormat != wgpu::TextureFormat::Undefined) {
|
||||
if (hasDepth(depthFormat)) {
|
||||
newTargetFlags |= filament::backend::TargetBufferFlags::DEPTH;
|
||||
}
|
||||
if (hasStencil(depthFormat)) {
|
||||
newTargetFlags |= filament::backend::TargetBufferFlags::STENCIL;
|
||||
}
|
||||
}
|
||||
mDefaultRenderTarget->setTargetFlags(newTargetFlags);
|
||||
|
||||
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = { .label = "frame_command_encoder" };
|
||||
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = {
|
||||
.label = "command_encoder"
|
||||
};
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
||||
assert_invariant(mCommandEncoder);
|
||||
}
|
||||
@@ -1092,33 +1003,7 @@ void WebGPUDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
void WebGPUDriver::resolve(Handle<HwTexture> destinationTextureHandle, const uint8_t sourceLevel,
|
||||
const uint8_t sourceLayer, Handle<HwTexture> sourceTextureHandle,
|
||||
const uint8_t destinationLevel, const uint8_t destinationLayer) {
|
||||
FILAMENT_CHECK_PRECONDITION(mCommandEncoder)
|
||||
<< "Resolve assumes there is a valid command encoder to piggyback on.";
|
||||
FILAMENT_CHECK_PRECONDITION(mRenderPassEncoder == nullptr)
|
||||
<< "Resolve cannot be called during an existing render pass";
|
||||
const auto sourceTexture{ handleCast<WebGPUTexture>(sourceTextureHandle) };
|
||||
const auto destinationTexture{ handleCast<WebGPUTexture>(destinationTextureHandle) };
|
||||
assert_invariant(sourceTexture);
|
||||
assert_invariant(destinationTexture);
|
||||
const WebGPUMsaaTextureResolver::ResolveRequest request{
|
||||
.commandEncoder = mCommandEncoder,
|
||||
.viewFormat = sourceTexture->getViewFormat(),
|
||||
.source = {
|
||||
.texture = sourceTexture->getTexture(),
|
||||
.viewDimension = sourceTexture->getViewDimension(),
|
||||
.mipLevel = sourceLevel,
|
||||
.layer = sourceLayer,
|
||||
.aspect = sourceTexture->getAspect(),
|
||||
},
|
||||
.destination = {
|
||||
.texture = destinationTexture->getTexture(),
|
||||
.viewDimension = destinationTexture->getViewDimension(),
|
||||
.mipLevel = destinationLevel,
|
||||
.layer = destinationLayer,
|
||||
.aspect = destinationTexture->getAspect(),
|
||||
},
|
||||
};
|
||||
mMsaaTextureResolver.resolve(request);
|
||||
// todo
|
||||
}
|
||||
|
||||
void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_t sourceLevel,
|
||||
@@ -1129,93 +1014,99 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
|
||||
}
|
||||
|
||||
void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
assert_invariant(mRenderPassEncoder);
|
||||
const auto program{ handleCast<WebGPUProgram>(pipelineState.program) };
|
||||
// TODO Investigate implications of this hash more closely. Vulkan has a whole class
|
||||
// VulkanPipelineCache to handle this, may be missing nuance
|
||||
static auto pipleineStateHasher = utils::hash::MurmurHashFn<filament::backend::PipelineState>();
|
||||
auto hash = pipleineStateHasher(pipelineState);
|
||||
if (mPipelineMap.find(hash) != mPipelineMap.end()) {
|
||||
mRenderPassEncoder.SetPipeline(mPipelineMap[hash]);
|
||||
return;
|
||||
}
|
||||
const auto program = handleCast<WebGPUProgram>(pipelineState.program);
|
||||
assert_invariant(program);
|
||||
WebGPURenderTarget const* renderTarget{ mCurrentRenderTarget };
|
||||
assert_invariant(renderTarget);
|
||||
assert_invariant(program->computeShaderModule == nullptr &&
|
||||
"WebGPU backend does not (yet) support compute pipelines.");
|
||||
FILAMENT_CHECK_POSTCONDITION(program->vertexShaderModule)
|
||||
<< "WebGPU backend requires a vertex shader module for a render pipeline";
|
||||
const auto vertexBufferInfo{ handleCast<WebGPUVertexBufferInfo>(
|
||||
pipelineState.vertexBufferInfo) };
|
||||
assert_invariant(vertexBufferInfo);
|
||||
std::array<wgpu::BindGroupLayout, MAX_DESCRIPTOR_SET_COUNT> bindGroupLayouts{};
|
||||
assert_invariant(bindGroupLayouts.size() >= pipelineState.pipelineLayout.setLayout.size());
|
||||
size_t bindGroupLayoutCount{ 0 };
|
||||
for (size_t i{ 0 }; i < bindGroupLayouts.size(); i++) {
|
||||
const auto handle{ pipelineState.pipelineLayout.setLayout[bindGroupLayoutCount] };
|
||||
size_t bindGroupLayoutCount = 0;
|
||||
for (size_t i = 0; i < bindGroupLayouts.size(); i++) {
|
||||
const auto handle = pipelineState.pipelineLayout.setLayout[bindGroupLayoutCount];
|
||||
if (handle.getId() == HandleBase::nullid) {
|
||||
continue;
|
||||
}
|
||||
bindGroupLayouts[bindGroupLayoutCount++] =
|
||||
handleCast<WebGPUDescriptorSetLayout>(handle)->getLayout();
|
||||
}
|
||||
const WebGPUPipelineLayoutCache::PipelineLayoutRequest pipelineLayoutRequest{
|
||||
.label = program->name,
|
||||
.bindGroupLayouts = bindGroupLayouts,
|
||||
std::stringstream layoutLabelStream;
|
||||
layoutLabelStream << program->name.c_str() << " layout";
|
||||
const auto layoutLabel = layoutLabelStream.str();
|
||||
const wgpu::PipelineLayoutDescriptor layoutDescriptor{
|
||||
.label = wgpu::StringView(layoutLabel),
|
||||
.bindGroupLayoutCount = bindGroupLayoutCount,
|
||||
.bindGroupLayouts = bindGroupLayouts.data()
|
||||
// TODO investigate immediateDataRangeByteSize
|
||||
};
|
||||
wgpu::PipelineLayout const& layout{ mPipelineLayoutCache.getOrCreatePipelineLayout(
|
||||
pipelineLayoutRequest) };
|
||||
uint8_t colorFormatCount{ 0 };
|
||||
std::array<wgpu::TextureFormat, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> colorFormats{
|
||||
wgpu::TextureFormat::Undefined
|
||||
};
|
||||
wgpu::TextureFormat depthStencilFormat{ wgpu::TextureFormat::Undefined };
|
||||
if (renderTarget->isDefaultRenderTarget()) {
|
||||
// default render target color(s) (one)...
|
||||
colorFormatCount = 1;
|
||||
colorFormats[0] = mSwapChain->getColorFormat();
|
||||
// default render target depth/stencil...
|
||||
depthStencilFormat = mSwapChain->getDepthFormat();
|
||||
const wgpu::PipelineLayout layout = mDevice.CreatePipelineLayout(&layoutDescriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(layout)
|
||||
<< "Failed to create wgpu::PipelineLayout for render pipeline for "
|
||||
<< layoutDescriptor.label;
|
||||
const auto vertexBufferInfo =
|
||||
handleCast<WebGPUVertexBufferInfo>(pipelineState.vertexBufferInfo);
|
||||
assert_invariant(vertexBufferInfo);
|
||||
|
||||
std::vector<wgpu::TextureFormat> pipelineColorFormats;
|
||||
wgpu::TextureFormat pipelineDepthFormat = wgpu::TextureFormat::Undefined;
|
||||
uint8_t pipelineSamples = 1;
|
||||
|
||||
if (mCurrentRenderTarget->isDefaultRenderTarget()) {
|
||||
pipelineColorFormats.push_back(mSwapChain->getColorFormat());
|
||||
pipelineDepthFormat = mSwapChain->getDepthFormat();
|
||||
pipelineSamples =
|
||||
mCurrentRenderTarget->getSamples();// Default RT should have samples (usually 1)
|
||||
} else {
|
||||
// custom render target color(s)...
|
||||
MRT const& mrtColorAttachments{ mCurrentRenderTarget->getColorAttachmentInfos() };
|
||||
for (size_t i{ 0 }; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; ++i) {
|
||||
const auto& mrtColorAttachments = mCurrentRenderTarget->getColorAttachmentInfos();
|
||||
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; ++i) {
|
||||
if (mrtColorAttachments[i].handle) {
|
||||
const auto colorTexture{ handleCast<WebGPUTexture>(mrtColorAttachments[i].handle) };
|
||||
const auto colorTexture = handleCast<WebGPUTexture>(mrtColorAttachments[i].handle);
|
||||
if (colorTexture) {
|
||||
colorFormats[colorFormatCount++] = colorTexture->getTexture().GetFormat();
|
||||
pipelineColorFormats.push_back(colorTexture->getTexture().GetFormat());
|
||||
}
|
||||
}
|
||||
}
|
||||
// custom render target depth/stencil...
|
||||
|
||||
const auto& depthInfo = mCurrentRenderTarget->getDepthAttachmentInfo();
|
||||
const auto& stencilInfo = mCurrentRenderTarget->getStencilAttachmentInfo();
|
||||
Handle<HwTexture> depthStencilHandle{};
|
||||
if (depthInfo.handle) {
|
||||
depthStencilHandle = depthInfo.handle;
|
||||
} else if (stencilInfo.handle) {
|
||||
depthStencilHandle = stencilInfo.handle;
|
||||
}
|
||||
if (depthStencilHandle) {
|
||||
const auto depthStencilTexture{ handleCast<WebGPUTexture>(depthStencilHandle) };
|
||||
if (depthStencilTexture) {
|
||||
depthStencilFormat = depthStencilTexture->getTexture().GetFormat();
|
||||
FILAMENT_CHECK_POSTCONDITION(!stencilInfo.handle)
|
||||
<< "depth and stencil attachments cannot both be provided for WebGPU";
|
||||
const auto depthTexture = handleCast<WebGPUTexture>(depthInfo.handle);
|
||||
if (depthTexture) pipelineDepthFormat = depthTexture->getTexture().GetFormat();
|
||||
} else {
|
||||
if (stencilInfo.handle) {
|
||||
const auto stencilTexture = handleCast<WebGPUTexture>(stencilInfo.handle);
|
||||
// Assuming combined depth/stencil format if only stencil is present
|
||||
if (stencilTexture) {
|
||||
pipelineDepthFormat = stencilTexture->getTexture().GetFormat();
|
||||
}
|
||||
}
|
||||
}
|
||||
pipelineSamples = mCurrentRenderTarget->getSamples();
|
||||
}
|
||||
const WebGPUPipelineCache::RenderPipelineRequest pipelineRequest{
|
||||
.label = program->name,
|
||||
.vertexShaderModule = program->vertexShaderModule,
|
||||
.fragmentShaderModule = program->fragmentShaderModule,
|
||||
.vertexBufferSlots = vertexBufferInfo->getWebGPUSlotBindingInfos(),
|
||||
.vertexBufferLayouts = vertexBufferInfo->getVertexBufferLayouts(),
|
||||
.pipelineLayout = layout,
|
||||
.primitiveType = pipelineState.primitiveType,
|
||||
.rasterState = pipelineState.rasterState,
|
||||
.stencilState = pipelineState.stencilState,
|
||||
.polygonOffset = pipelineState.polygonOffset,
|
||||
.targetRenderFlags = renderTarget->getTargetFlags(),
|
||||
.multisampleCount = renderTarget->getSamples(),
|
||||
.depthStencilFormat = depthStencilFormat,
|
||||
.colorFormatCount = colorFormatCount,
|
||||
.colorFormats = colorFormats.data(),
|
||||
};
|
||||
wgpu::RenderPipeline const& pipeline{ mPipelineCache.getOrCreateRenderPipeline(
|
||||
pipelineRequest) };
|
||||
|
||||
// TODO: We expected this to be a sane check, however it complains when running shadowtest.
|
||||
//if (program->fragmentShaderModule != nullptr) {
|
||||
// FILAMENT_CHECK_POSTCONDITION(!pipelineColorFormats.empty())
|
||||
// << "Render pipeline with fragment shader must have at least one color target "
|
||||
// "format.";
|
||||
//}
|
||||
wgpu::RenderPipeline pipeline = createWebGPURenderPipeline(mDevice, *program, *vertexBufferInfo,
|
||||
layout, pipelineState.rasterState, pipelineState.stencilState,
|
||||
pipelineState.polygonOffset, pipelineState.primitiveType, pipelineColorFormats,
|
||||
pipelineDepthFormat, pipelineSamples);
|
||||
assert_invariant(pipeline);
|
||||
mPipelineMap[hash] = pipeline;
|
||||
mRenderPassEncoder.SetPipeline(pipeline);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,6 @@
|
||||
|
||||
#include "WebGPURenderTarget.h"
|
||||
#include "webgpu/WebGPUConstants.h"
|
||||
#include "webgpu/WebGPUMsaaTextureResolver.h"
|
||||
#include "webgpu/WebGPUPipelineCache.h"
|
||||
#include "webgpu/WebGPUPipelineLayoutCache.h"
|
||||
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
|
||||
#include "DriverBase.h"
|
||||
@@ -34,6 +30,7 @@
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include "SpdMipmapGenerator/SpdMipmapGenerator.h"
|
||||
#include <tsl/robin_map.h>
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -58,7 +55,7 @@ public:
|
||||
[[nodiscard]] static Driver* create(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept;
|
||||
|
||||
private:
|
||||
WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept;
|
||||
explicit WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept;
|
||||
[[nodiscard]] ShaderModel getShaderModel() const noexcept final;
|
||||
[[nodiscard]] ShaderLanguage getShaderLanguage() const noexcept final;
|
||||
[[nodiscard]] wgpu::Sampler makeSampler(SamplerParams const& params);
|
||||
@@ -80,11 +77,9 @@ private:
|
||||
wgpu::CommandBuffer mCommandBuffer = nullptr;
|
||||
WebGPURenderTarget* mDefaultRenderTarget = nullptr;
|
||||
WebGPURenderTarget* mCurrentRenderTarget = nullptr;
|
||||
WebGPUPipelineLayoutCache mPipelineLayoutCache;
|
||||
WebGPUPipelineCache mPipelineCache;
|
||||
WebGPURenderPassMipmapGenerator mRenderPassMipmapGenerator;
|
||||
spd::MipmapGenerator mSpdComputePassMipmapGenerator;
|
||||
WebGPUMsaaTextureResolver mMsaaTextureResolver{};
|
||||
spd::MipmapGenerator mMipMapGenerator;
|
||||
|
||||
tsl::robin_map<uint32_t, wgpu::RenderPipeline> mPipelineMap;
|
||||
|
||||
struct DescriptorSetBindingInfo{
|
||||
wgpu::BindGroup bindGroup;
|
||||
@@ -92,7 +87,6 @@ private:
|
||||
backend::DescriptorSetOffsetArray offsets;
|
||||
};
|
||||
std::array<DescriptorSetBindingInfo,MAX_DESCRIPTOR_SET_COUNT> mCurrentDescriptorSets;
|
||||
|
||||
/*
|
||||
* Driver interface
|
||||
*/
|
||||
@@ -127,11 +121,6 @@ private:
|
||||
return mHandleAllocator.construct<D>(handle, std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
template<typename D, typename B, typename... ARGS>
|
||||
Handle<B> allocAndConstructHandle(ARGS&&... args) {
|
||||
return mHandleAllocator.allocateAndConstruct<D>(std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
template<typename D, typename B>
|
||||
D* handleCast(Handle<B> handle) noexcept {
|
||||
return mHandleAllocator.handle_cast<D*>(handle);
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "WebGPUFence.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
FenceStatus WebGPUFence::getStatus() { return mStatus.load(); }
|
||||
|
||||
void WebGPUFence::addMarkerToQueueState(wgpu::Queue const& queue) {
|
||||
queue.OnSubmittedWorkDone(
|
||||
wgpu::CallbackMode::AllowSpontaneous,
|
||||
[](const wgpu::QueueWorkDoneStatus status,
|
||||
std::atomic<FenceStatus>* const fenceStatus) {
|
||||
if (!fenceStatus) {
|
||||
return;
|
||||
}
|
||||
switch (status) {
|
||||
case wgpu::QueueWorkDoneStatus::Success:
|
||||
fenceStatus->store(FenceStatus::CONDITION_SATISFIED);
|
||||
break;
|
||||
case wgpu::QueueWorkDoneStatus::CallbackCancelled:
|
||||
case wgpu::QueueWorkDoneStatus::Error:
|
||||
fenceStatus->store(FenceStatus::ERROR);
|
||||
break;
|
||||
}
|
||||
},
|
||||
&mStatus);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_WEBGPUFENCE_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUFENCE_H
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace wgpu {
|
||||
class Queue;
|
||||
} // namespace wgpu
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPUFence final : public HwFence {
|
||||
public:
|
||||
[[nodiscard]] FenceStatus getStatus();
|
||||
|
||||
void addMarkerToQueueState(wgpu::Queue const&);
|
||||
|
||||
private:
|
||||
std::atomic<FenceStatus> mStatus{ FenceStatus::TIMEOUT_EXPIRED };
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_WEBGPUFENCE_H
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "WebGPUMsaaTextureResolver.h"
|
||||
|
||||
#include "WebGPUTexture.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
void resolveColorTextures(wgpu::CommandEncoder const& commandEncoder,
|
||||
wgpu::TextureView const& sourceTextureView,
|
||||
wgpu::TextureView const& destinationTextureView) {
|
||||
const wgpu::RenderPassColorAttachment colorAttachment{
|
||||
.view = sourceTextureView,
|
||||
.depthSlice = wgpu::kDepthSliceUndefined, // being explicit for consistent behavior
|
||||
.resolveTarget = destinationTextureView,
|
||||
.loadOp = wgpu::LoadOp::Load,
|
||||
.storeOp = wgpu::StoreOp::Store,
|
||||
.clearValue = {}, // being explicit for consistent behavior
|
||||
};
|
||||
const wgpu::RenderPassDescriptor renderPassDescriptor{
|
||||
.label = "resolve_render_pass",
|
||||
.colorAttachmentCount = 1,
|
||||
.colorAttachments = &colorAttachment,
|
||||
.depthStencilAttachment = nullptr, // being explicit for consistent behavior
|
||||
.occlusionQuerySet = nullptr, // being explicit for consistent behavior
|
||||
.timestampWrites = nullptr, // being explicit for consistent behavior
|
||||
};
|
||||
const wgpu::RenderPassEncoder renderPassEncoder{ commandEncoder.BeginRenderPass(
|
||||
&renderPassDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(renderPassEncoder)
|
||||
<< "Failed to create wgpu::RenderPassEncoder for WebGPUDriver::resolve";
|
||||
renderPassEncoder.End(); // only the implicit resolve is happening in the pass
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void WebGPUMsaaTextureResolver::resolve(ResolveRequest const& request) {
|
||||
ResolveRequest::TextureInfo const& source{ request.source };
|
||||
ResolveRequest::TextureInfo const& destination{ request.destination };
|
||||
FILAMENT_CHECK_PRECONDITION(destination.texture.GetWidth() == source.texture.GetWidth() &&
|
||||
destination.texture.GetHeight() == source.texture.GetHeight())
|
||||
<< "invalid resolve: source and destination sizes don't match";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
source.texture.GetSampleCount() > 1 && destination.texture.GetSampleCount() == 1)
|
||||
<< "invalid resolve: source.samples=" << source.texture.GetSampleCount()
|
||||
<< ", destination.samples=" << destination.texture.GetSampleCount();
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(source.texture.GetFormat() == destination.texture.GetFormat())
|
||||
<< "source and destination texture format don't match";
|
||||
const wgpu::TextureFormat format{ source.texture.GetFormat() };
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!hasDepth(format)) << "can't resolve depth formats";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!hasStencil(format)) << "can't resolve stencil formats";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(source.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
|
||||
<< "source texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
destination.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
|
||||
<< "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(destination.texture.GetUsage() & wgpu::TextureUsage::TextureBinding)
|
||||
<< "destination texture usage doesn't have wgpu::TextureUsage::TextureBinding";
|
||||
|
||||
const wgpu::TextureViewDescriptor sourceTextureViewDescriptor{
|
||||
.label = "resolve_source_texture_view",
|
||||
.format = request.viewFormat,
|
||||
.dimension = source.viewDimension,
|
||||
.baseMipLevel = source.mipLevel,
|
||||
.mipLevelCount = 1,
|
||||
.baseArrayLayer = source.layer,
|
||||
.arrayLayerCount = 1,
|
||||
.aspect = source.aspect,
|
||||
.usage = source.texture.GetUsage(),
|
||||
};
|
||||
const wgpu::TextureView sourceTextureView{ source.texture.CreateView(
|
||||
&sourceTextureViewDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(sourceTextureView)
|
||||
<< "Failed to create wgpu::TextureView sourceTextureView";
|
||||
const wgpu::TextureViewDescriptor destinationTextureViewDescriptor{
|
||||
.label = "resolve_destination_texture_view",
|
||||
.format = request.viewFormat,
|
||||
.dimension = destination.viewDimension,
|
||||
.baseMipLevel = destination.mipLevel,
|
||||
.mipLevelCount = 1,
|
||||
.baseArrayLayer = destination.layer,
|
||||
.arrayLayerCount = 1,
|
||||
.aspect = destination.aspect,
|
||||
.usage = destination.texture.GetUsage(),
|
||||
};
|
||||
const wgpu::TextureView destinationTextureView{ destination.texture.CreateView(
|
||||
&destinationTextureViewDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(destinationTextureView)
|
||||
<< "Failed to create wgpu::TextureView destinationTextureView.";
|
||||
|
||||
if (hasDepth(format)) {
|
||||
PANIC_PRECONDITION("DEPTH RESOLVE NOT IMPLEMENTED YET");
|
||||
} else {
|
||||
resolveColorTextures(request.commandEncoder, sourceTextureView, destinationTextureView);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_WEBGPUMSAATEXTURERESOLVER_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUMSAATEXTURERESOLVER_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace wgpu {
|
||||
class CommandEncoder;
|
||||
class Texture;
|
||||
enum class TextureAspect : uint32_t;
|
||||
enum class TextureFormat : uint32_t;
|
||||
enum class TextureViewDimension : uint32_t;
|
||||
} // namespace wgpu
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPUTexture;
|
||||
|
||||
class WebGPUMsaaTextureResolver final {
|
||||
public:
|
||||
struct ResolveRequest final {
|
||||
struct TextureInfo final {
|
||||
wgpu::Texture const& texture;
|
||||
wgpu::TextureViewDimension viewDimension;
|
||||
uint8_t mipLevel;
|
||||
uint8_t layer;
|
||||
wgpu::TextureAspect aspect;
|
||||
};
|
||||
wgpu::CommandEncoder const& commandEncoder;
|
||||
wgpu::TextureFormat viewFormat;
|
||||
TextureInfo source;
|
||||
TextureInfo destination;
|
||||
};
|
||||
|
||||
void resolve(ResolveRequest const&);
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_WEBGPUMSAATEXTURERESOLVER_H
|
||||
@@ -1,382 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "WebGPUPipelineCache.h"
|
||||
|
||||
#include "WebGPUConstants.h"
|
||||
#include "WebGPUTexture.h"
|
||||
#include "WebGPUVertexBufferInfo.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] constexpr uint8_t toUint8(const bool value) { return value ? 1 : 0; }
|
||||
|
||||
[[nodiscard]] constexpr wgpu::PrimitiveTopology toWebGPU(const PrimitiveType primitiveType) {
|
||||
switch (primitiveType) {
|
||||
case PrimitiveType::POINTS: return wgpu::PrimitiveTopology::PointList;
|
||||
case PrimitiveType::LINES: return wgpu::PrimitiveTopology::LineList;
|
||||
case PrimitiveType::LINE_STRIP: return wgpu::PrimitiveTopology::LineStrip;
|
||||
case PrimitiveType::TRIANGLES: return wgpu::PrimitiveTopology::TriangleList;
|
||||
case PrimitiveType::TRIANGLE_STRIP: return wgpu::PrimitiveTopology::TriangleStrip;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr wgpu::CullMode toWebGPU(const CullingMode cullMode) {
|
||||
switch (cullMode) {
|
||||
case CullingMode::NONE: return wgpu::CullMode::None;
|
||||
case CullingMode::FRONT: return wgpu::CullMode::Front;
|
||||
case CullingMode::BACK: return wgpu::CullMode::Back;
|
||||
case CullingMode::FRONT_AND_BACK:
|
||||
// no WegGPU equivalent of front and back
|
||||
FILAMENT_CHECK_POSTCONDITION(false)
|
||||
<< "WebGPU does not support CullingMode::FRONT_AND_BACK";
|
||||
return wgpu::CullMode::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr wgpu::CompareFunction toWebGPU(const SamplerCompareFunc compareFunction) {
|
||||
switch (compareFunction) {
|
||||
case SamplerCompareFunc::LE: return wgpu::CompareFunction::LessEqual;
|
||||
case SamplerCompareFunc::GE: return wgpu::CompareFunction::GreaterEqual;
|
||||
case SamplerCompareFunc::L: return wgpu::CompareFunction::Less;
|
||||
case SamplerCompareFunc::G: return wgpu::CompareFunction::Greater;
|
||||
case SamplerCompareFunc::E: return wgpu::CompareFunction::Equal;
|
||||
case SamplerCompareFunc::NE: return wgpu::CompareFunction::NotEqual;
|
||||
case SamplerCompareFunc::A: return wgpu::CompareFunction::Always;
|
||||
case SamplerCompareFunc::N: return wgpu::CompareFunction::Never;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr wgpu::StencilOperation toWebGPU(const StencilOperation stencilOp) {
|
||||
switch (stencilOp) {
|
||||
case StencilOperation::KEEP: return wgpu::StencilOperation::Keep;
|
||||
case StencilOperation::ZERO: return wgpu::StencilOperation::Zero;
|
||||
case StencilOperation::REPLACE: return wgpu::StencilOperation::Replace;
|
||||
case StencilOperation::INCR: return wgpu::StencilOperation::IncrementClamp;
|
||||
case StencilOperation::INCR_WRAP: return wgpu::StencilOperation::IncrementWrap;
|
||||
case StencilOperation::DECR: return wgpu::StencilOperation::DecrementClamp;
|
||||
case StencilOperation::DECR_WRAP: return wgpu::StencilOperation::DecrementWrap;
|
||||
case StencilOperation::INVERT: return wgpu::StencilOperation::Invert;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr wgpu::BlendOperation toWebGPU(const BlendEquation blendOp) {
|
||||
switch (blendOp) {
|
||||
case BlendEquation::ADD: return wgpu::BlendOperation::Add;
|
||||
case BlendEquation::SUBTRACT: return wgpu::BlendOperation::Subtract;
|
||||
case BlendEquation::REVERSE_SUBTRACT: return wgpu::BlendOperation::ReverseSubtract;
|
||||
case BlendEquation::MIN: return wgpu::BlendOperation::Min;
|
||||
case BlendEquation::MAX: return wgpu::BlendOperation::Max;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr wgpu::BlendFactor toWebGPU(const BlendFunction blendFunction) {
|
||||
switch (blendFunction) {
|
||||
case BlendFunction::ZERO: return wgpu::BlendFactor::Zero;
|
||||
case BlendFunction::ONE: return wgpu::BlendFactor::One;
|
||||
case BlendFunction::SRC_COLOR: return wgpu::BlendFactor::Src;
|
||||
case BlendFunction::ONE_MINUS_SRC_COLOR: return wgpu::BlendFactor::OneMinusSrc;
|
||||
case BlendFunction::DST_COLOR: return wgpu::BlendFactor::Dst;
|
||||
case BlendFunction::ONE_MINUS_DST_COLOR: return wgpu::BlendFactor::OneMinusDst;
|
||||
case BlendFunction::SRC_ALPHA: return wgpu::BlendFactor::SrcAlpha;
|
||||
case BlendFunction::ONE_MINUS_SRC_ALPHA: return wgpu::BlendFactor::OneMinusSrcAlpha;
|
||||
case BlendFunction::DST_ALPHA: return wgpu::BlendFactor::DstAlpha;
|
||||
case BlendFunction::ONE_MINUS_DST_ALPHA: return wgpu::BlendFactor::OneMinusDstAlpha;
|
||||
case BlendFunction::SRC_ALPHA_SATURATE: return wgpu::BlendFactor::SrcAlphaSaturated;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WebGPUPipelineCache::WebGPUPipelineCache(wgpu::Device const& device)
|
||||
: mDevice{ device } {}
|
||||
|
||||
wgpu::RenderPipeline const& WebGPUPipelineCache::getOrCreateRenderPipeline(
|
||||
RenderPipelineRequest const& request) {
|
||||
RenderPipelineKey key{};
|
||||
populateKey(request, key);
|
||||
if (auto iterator{ mRenderPipelines.find(key) }; iterator != mRenderPipelines.end()) {
|
||||
RenderPipelineCacheEntry& entry{ iterator.value() };
|
||||
entry.lastUsedFrameCount = mFrameCount;
|
||||
return entry.pipeline;
|
||||
}
|
||||
const wgpu::RenderPipeline pipeline{ createRenderPipeline(request) };
|
||||
mRenderPipelines.emplace(key, RenderPipelineCacheEntry{
|
||||
.pipeline = pipeline,
|
||||
.lastUsedFrameCount = mFrameCount,
|
||||
});
|
||||
return mRenderPipelines[key].pipeline;
|
||||
}
|
||||
|
||||
void WebGPUPipelineCache::onFrameEnd() {
|
||||
++mFrameCount;
|
||||
removeExpiredPipelines();
|
||||
}
|
||||
|
||||
void WebGPUPipelineCache::populateKey(RenderPipelineRequest const& request,
|
||||
RenderPipelineKey& outKey) {
|
||||
outKey.vertexShaderModuleHandle =
|
||||
request.vertexShaderModule ? request.vertexShaderModule.Get() : nullptr;
|
||||
outKey.fragmentShaderModuleHandle =
|
||||
request.fragmentShaderModule ? request.fragmentShaderModule.Get() : nullptr;
|
||||
outKey.pipelineLayoutHandle = request.pipelineLayout ? request.pipelineLayout.Get() : nullptr;
|
||||
outKey.depthBias = static_cast<int32_t>(request.polygonOffset.constant);
|
||||
outKey.depthBiasSlopeScale = request.polygonOffset.slope;
|
||||
outKey.primitiveType = request.primitiveType;
|
||||
outKey.stencilFrontCompare = request.stencilState.front.stencilFunc;
|
||||
outKey.stencilFrontFailOperation = request.stencilState.front.stencilOpStencilFail;
|
||||
outKey.stencilFrontDepthFailOperation = request.stencilState.front.stencilOpDepthFail;
|
||||
outKey.stencilFrontPassOperation = request.stencilState.front.stencilOpDepthStencilPass;
|
||||
outKey.stencilWrite = toUint8(request.stencilState.stencilWrite);
|
||||
outKey.stencilFrontReadMask = request.stencilState.front.readMask;
|
||||
outKey.stencilFrontWriteMask = request.stencilState.front.writeMask;
|
||||
outKey.stencilBackCompare = request.stencilState.back.stencilFunc;
|
||||
outKey.stencilBackFailOperation = request.stencilState.back.stencilOpStencilFail;
|
||||
outKey.stencilBackDepthFailOperation = request.stencilState.back.stencilOpDepthFail;
|
||||
outKey.stencilBackPassOperation = request.stencilState.back.stencilOpDepthStencilPass;
|
||||
outKey.cullingMode = request.rasterState.culling;
|
||||
outKey.inverseFrontFaces = toUint8(request.rasterState.inverseFrontFaces);
|
||||
outKey.depthWriteEnabled = toUint8(request.rasterState.depthWrite);
|
||||
outKey.depthCompare = request.rasterState.depthFunc;
|
||||
outKey.depthClamp = toUint8(request.rasterState.depthClamp);
|
||||
outKey.colorWrite = toUint8(request.rasterState.colorWrite);
|
||||
outKey.alphaToCoverageEnabled = toUint8(request.rasterState.alphaToCoverage);
|
||||
outKey.colorBlendOperation = request.rasterState.blendEquationRGB;
|
||||
outKey.colorBlendSourceFactor = request.rasterState.blendFunctionSrcRGB;
|
||||
outKey.colorBlendDestinationFactor = request.rasterState.blendFunctionDstRGB;
|
||||
outKey.alphaBlendOperation = request.rasterState.blendEquationAlpha;
|
||||
outKey.alphaBlendSourceFactor = request.rasterState.blendFunctionSrcAlpha;
|
||||
outKey.alphaBlendDestinationFactor = request.rasterState.blendFunctionDstAlpha;
|
||||
outKey.targetRenderFlags = request.targetRenderFlags;
|
||||
outKey.multisampleCount = request.multisampleCount;
|
||||
outKey.depthStencilFormat = request.depthStencilFormat;
|
||||
assert_invariant(request.colorFormatCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
|
||||
outKey.colorFormatCount = request.colorFormatCount;
|
||||
for (size_t colorIndex{ 0 }; colorIndex < request.colorFormatCount; colorIndex++) {
|
||||
outKey.colorFormats[colorIndex] = request.colorFormats[colorIndex];
|
||||
}
|
||||
// vertex buffers...
|
||||
for (WebGPUVertexBufferInfo::WebGPUSlotBindingInfo const& vertexBufferSlot:
|
||||
request.vertexBufferSlots) {
|
||||
assert_invariant(vertexBufferSlot.sourceBufferIndex < MAX_VERTEX_BUFFER_COUNT);
|
||||
outKey.vertexBuffers[vertexBufferSlot.sourceBufferIndex].stride = vertexBufferSlot.stride;
|
||||
outKey.vertexBuffers[vertexBufferSlot.sourceBufferIndex].offset =
|
||||
vertexBufferSlot.bufferOffset;
|
||||
}
|
||||
// vertex attributes...
|
||||
uint8_t currentAttributeIndex{ 0 };
|
||||
for (size_t bufferIndex{ 0 }; bufferIndex < request.vertexBufferSlots.size(); bufferIndex++) {
|
||||
wgpu::VertexBufferLayout const& vertexBufferLayout{
|
||||
request.vertexBufferLayouts[bufferIndex]
|
||||
};
|
||||
for (size_t attributeIndex{ 0 }; attributeIndex < vertexBufferLayout.attributeCount;
|
||||
attributeIndex++) {
|
||||
assert_invariant(attributeIndex < MAX_VERTEX_ATTRIBUTE_COUNT);
|
||||
wgpu::VertexAttribute const& vertexAttribute{
|
||||
vertexBufferLayout.attributes[attributeIndex]
|
||||
};
|
||||
outKey.vertexAttributes[currentAttributeIndex].bufferIndex = bufferIndex;
|
||||
outKey.vertexAttributes[currentAttributeIndex].offset =
|
||||
static_cast<uint8_t>(vertexAttribute.offset);
|
||||
outKey.vertexAttributes[currentAttributeIndex].shaderLocation =
|
||||
static_cast<uint8_t>(vertexAttribute.shaderLocation);
|
||||
outKey.vertexAttributes[currentAttributeIndex].format = vertexAttribute.format;
|
||||
currentAttributeIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
|
||||
RenderPipelineRequest const& request) {
|
||||
assert_invariant(request.vertexShaderModule);
|
||||
wgpu::DepthStencilState depthStencilState{};
|
||||
const bool requestedDepth{ any(request.targetRenderFlags & TargetBufferFlags::DEPTH) };
|
||||
const bool requestedStencil{ any(request.targetRenderFlags & TargetBufferFlags::STENCIL) };
|
||||
const bool depthOrStencilRequested{ requestedDepth || requestedStencil };
|
||||
// depth/stencil...
|
||||
if (depthOrStencilRequested) {
|
||||
FILAMENT_CHECK_PRECONDITION(request.depthStencilFormat != wgpu::TextureFormat::Undefined)
|
||||
<< "Depth or Stencil requested for pipeline, but depthStencilFormat is "
|
||||
"wgpu::TextureFormat::Undefined.";
|
||||
depthStencilState.format = request.depthStencilFormat;
|
||||
if (requestedDepth) {
|
||||
assert_invariant(hasDepth(depthStencilState.format));
|
||||
depthStencilState.depthWriteEnabled = request.rasterState.depthWrite;
|
||||
depthStencilState.depthCompare = toWebGPU(request.rasterState.depthFunc);
|
||||
depthStencilState.depthBias = static_cast<int32_t>(request.polygonOffset.constant);
|
||||
depthStencilState.depthBiasSlopeScale = request.polygonOffset.slope;
|
||||
depthStencilState.depthBiasClamp = 0.0f;
|
||||
} else {
|
||||
depthStencilState.depthWriteEnabled = false;
|
||||
depthStencilState.depthCompare = wgpu::CompareFunction::Undefined;
|
||||
depthStencilState.depthBias = 0;
|
||||
depthStencilState.depthBiasSlopeScale = 0.0f;
|
||||
depthStencilState.depthBiasClamp = 0.0f;
|
||||
}
|
||||
if (requestedStencil) {
|
||||
assert_invariant(hasStencil(depthStencilState.format));
|
||||
depthStencilState.stencilFront = {
|
||||
.compare = toWebGPU(request.stencilState.front.stencilFunc),
|
||||
.failOp = toWebGPU(request.stencilState.front.stencilOpStencilFail),
|
||||
.depthFailOp = toWebGPU(request.stencilState.front.stencilOpDepthFail),
|
||||
.passOp = toWebGPU(request.stencilState.front.stencilOpDepthStencilPass),
|
||||
};
|
||||
depthStencilState.stencilBack = {
|
||||
.compare = toWebGPU(request.stencilState.back.stencilFunc),
|
||||
.failOp = toWebGPU(request.stencilState.back.stencilOpStencilFail),
|
||||
.depthFailOp = toWebGPU(request.stencilState.back.stencilOpDepthFail),
|
||||
.passOp = toWebGPU(request.stencilState.back.stencilOpDepthStencilPass),
|
||||
};
|
||||
// TODO: should we also consider the back readMask and writeMask?
|
||||
depthStencilState.stencilReadMask = request.stencilState.front.readMask;
|
||||
depthStencilState.stencilWriteMask =
|
||||
request.stencilState.stencilWrite ? request.stencilState.front.writeMask : 0u;
|
||||
} else {
|
||||
depthStencilState.stencilFront.compare = wgpu::CompareFunction::Undefined;
|
||||
depthStencilState.stencilFront.failOp = wgpu::StencilOperation::Keep;
|
||||
depthStencilState.stencilFront.depthFailOp = wgpu::StencilOperation::Keep;
|
||||
depthStencilState.stencilFront.passOp = wgpu::StencilOperation::Keep;
|
||||
depthStencilState.stencilBack = depthStencilState.stencilFront;
|
||||
depthStencilState.stencilReadMask = 0;
|
||||
depthStencilState.stencilWriteMask = 0;
|
||||
}
|
||||
}
|
||||
wgpu::RenderPipelineDescriptor pipelineDescriptor{
|
||||
.label = wgpu::StringView(request.label.c_str_safe()),
|
||||
.layout = request.pipelineLayout,
|
||||
.vertex = {
|
||||
.module = request.vertexShaderModule,
|
||||
.entryPoint = "main",
|
||||
// we do not use WebGPU's override constants due to 2 limitations
|
||||
// (at least at the time of write this):
|
||||
// 1. they cannot be used for the size of an array, which is needed
|
||||
// 2. if we pass the WebGPU API (CPU-side) constants not referenced in the
|
||||
// shader WebGPU fails. This is a problem with how Filament is designed,
|
||||
// where certain constants may be optimized out of the shader based
|
||||
// on build configuration, etc.
|
||||
//
|
||||
// to bypass these problems, we do not use override constants in the
|
||||
// WebGPU backend, instead replacing placeholder constants in the shader
|
||||
// text before creating the shader module (essentially implementing
|
||||
// override constants ourselves)
|
||||
.constantCount = 0,
|
||||
.constants = nullptr,
|
||||
.bufferCount = request.vertexBufferSlots.size(),
|
||||
.buffers = request.vertexBufferLayouts,
|
||||
},
|
||||
.primitive = {
|
||||
.topology = toWebGPU(request.primitiveType),
|
||||
// TODO should we assume some constant format here or is there a way to get
|
||||
// this from PipelineState somehow or elsewhere?
|
||||
// Perhaps, cache/assert format from index buffers as they are requested?
|
||||
.stripIndexFormat = wgpu::IndexFormat::Undefined,
|
||||
.frontFace = request.rasterState.inverseFrontFaces ? wgpu::FrontFace::CW : wgpu::FrontFace::CCW,
|
||||
.cullMode = toWebGPU(request.rasterState.culling),
|
||||
// TODO no depth clamp in WebGPU supported directly. unclippedDepth is close, so we are
|
||||
// starting there
|
||||
.unclippedDepth = !request.rasterState.depthClamp &&
|
||||
mDevice.HasFeature(wgpu::FeatureName::DepthClipControl),
|
||||
},
|
||||
.depthStencil = depthOrStencilRequested ? &depthStencilState: nullptr,
|
||||
.multisample = {
|
||||
.count = request.multisampleCount,
|
||||
.mask = 0xFFFFFFFF,
|
||||
.alphaToCoverageEnabled = (request.multisampleCount > 1) && request.rasterState.alphaToCoverage
|
||||
},
|
||||
.fragment = nullptr // will add below if fragment module is included
|
||||
};
|
||||
wgpu::FragmentState fragmentState = {};
|
||||
const wgpu::BlendState blendState {
|
||||
.color = {
|
||||
.operation = toWebGPU(request.rasterState.blendEquationRGB),
|
||||
.srcFactor = toWebGPU(request.rasterState.blendFunctionSrcRGB),
|
||||
.dstFactor = toWebGPU(request.rasterState.blendFunctionDstRGB)
|
||||
},
|
||||
.alpha = {
|
||||
.operation = toWebGPU(request.rasterState.blendEquationAlpha),
|
||||
.srcFactor = toWebGPU(request.rasterState.blendFunctionSrcAlpha),
|
||||
.dstFactor = toWebGPU(request.rasterState.blendFunctionDstAlpha)
|
||||
}
|
||||
};
|
||||
// According to the WebGPU spec, a pipeline cannot have a fragment stage with zero color
|
||||
// targets. This situation can arise in Filament during depth-only passes (like shadow map
|
||||
// generation) if the material variant still includes a fragment shader.
|
||||
//
|
||||
// To handle this, we check if any color targets are configured for this pipeline. If not, we
|
||||
// create a pipeline *without* a fragment stage. This makes the pipeline valid for a
|
||||
// depth-only pass, allowing depth writes to proceed correctly.
|
||||
if (request.fragmentShaderModule != nullptr && request.colorFormatCount > 0) {
|
||||
fragmentState.module = request.fragmentShaderModule;
|
||||
fragmentState.entryPoint = "main";
|
||||
// see the comment about constants for the vertex state, as the same reasoning applies
|
||||
// here
|
||||
fragmentState.constantCount = 0;
|
||||
fragmentState.constants = nullptr;
|
||||
fragmentState.targetCount = request.colorFormatCount;
|
||||
std::array<wgpu::ColorTargetState, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> colorTargets {};
|
||||
assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
|
||||
for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) {
|
||||
wgpu::ColorTargetState& colorTarget = colorTargets[targetIndex];
|
||||
colorTarget.format = request.colorFormats[targetIndex];
|
||||
colorTarget.blend = request.rasterState.hasBlending() ? &blendState : nullptr;
|
||||
colorTarget.writeMask = request.rasterState.colorWrite ? wgpu::ColorWriteMask::All
|
||||
: wgpu::ColorWriteMask::None;
|
||||
}
|
||||
fragmentState.targets = colorTargets.data();
|
||||
pipelineDescriptor.fragment = &fragmentState;
|
||||
}
|
||||
const wgpu::RenderPipeline pipeline{ mDevice.CreateRenderPipeline(&pipelineDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create render pipeline for " << pipelineDescriptor.label;
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
bool WebGPUPipelineCache::RenderPipelineKeyEqual::operator()(RenderPipelineKey const& key1,
|
||||
RenderPipelineKey const& key2) const {
|
||||
return 0 == memcmp(reinterpret_cast<void const*>(&key1), reinterpret_cast<void const*>(&key2),
|
||||
sizeof(key1));
|
||||
}
|
||||
|
||||
void WebGPUPipelineCache::removeExpiredPipelines() {
|
||||
using Iterator = decltype(mRenderPipelines)::const_iterator;
|
||||
for (Iterator iterator{ mRenderPipelines.begin() }; iterator != mRenderPipelines.end();) {
|
||||
RenderPipelineCacheEntry const& entry{ iterator.value() };
|
||||
if (mFrameCount > (entry.lastUsedFrameCount +
|
||||
FILAMENT_WEBGPU_RENDER_PIPELINE_EXPIRATION_IN_FRAME_COUNT)) {
|
||||
// pipeline expired...
|
||||
iterator = mRenderPipelines.erase(iterator);
|
||||
} else {
|
||||
// pipeline not yet expired...
|
||||
++iterator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} //namespace filament::backend
|
||||
@@ -1,192 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_WEBGPUPIPELINECACHE_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUPIPELINECACHE_H
|
||||
|
||||
#include "WebGPUVertexBufferInfo.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/Hash.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPUPipelineCache final {
|
||||
public:
|
||||
struct RenderPipelineRequest final {
|
||||
utils::CString const& label;
|
||||
wgpu::ShaderModule const& vertexShaderModule;
|
||||
wgpu::ShaderModule const& fragmentShaderModule;
|
||||
std::vector<WebGPUVertexBufferInfo::WebGPUSlotBindingInfo> const& vertexBufferSlots;
|
||||
wgpu::VertexBufferLayout const* vertexBufferLayouts;
|
||||
wgpu::PipelineLayout const& pipelineLayout;
|
||||
const PrimitiveType primitiveType;
|
||||
RasterState const& rasterState;
|
||||
StencilState const& stencilState;
|
||||
PolygonOffset const& polygonOffset;
|
||||
const TargetBufferFlags targetRenderFlags;
|
||||
const uint8_t multisampleCount;
|
||||
const wgpu::TextureFormat depthStencilFormat;
|
||||
const uint8_t colorFormatCount;
|
||||
wgpu::TextureFormat const* colorFormats;
|
||||
};
|
||||
|
||||
explicit WebGPUPipelineCache(wgpu::Device const&);
|
||||
WebGPUPipelineCache(WebGPUPipelineCache const&) = delete;
|
||||
WebGPUPipelineCache(WebGPUPipelineCache const&&) = delete;
|
||||
WebGPUPipelineCache& operator=(WebGPUPipelineCache const&) = delete;
|
||||
WebGPUPipelineCache& operator=(WebGPUPipelineCache const&&) = delete;
|
||||
|
||||
[[nodiscard]] wgpu::RenderPipeline const& getOrCreateRenderPipeline(
|
||||
RenderPipelineRequest const&);
|
||||
|
||||
void onFrameEnd();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Part of the pipeline key specifically about one of the vertex attributes
|
||||
*/
|
||||
struct VertexAttribute final { // size : offset (need multiples of 4 bytes for hashing)
|
||||
uint8_t bufferIndex{ 0 }; // 1 : 0
|
||||
// this is the webgpu offset, //
|
||||
// bytes from the start of //
|
||||
// the vertex data //
|
||||
// (interleaved offset) //
|
||||
uint8_t offset{ 0 }; // 1 : 1
|
||||
uint8_t shaderLocation{ 0 }; // 1 : 2
|
||||
uint8_t padding { 0 }; // 1 : 3
|
||||
wgpu::VertexFormat format{ 0 }; // 4 : 4
|
||||
};
|
||||
static_assert(sizeof(VertexAttribute) == 8, "VertexAttribute must not have implicit padding.");
|
||||
static_assert(std::is_trivially_copyable<VertexAttribute>::value,
|
||||
"VertexAttribute must be a trivially copyable POD for fast hashing.");
|
||||
/**
|
||||
* Part of the pipeline key specifically about one of the vertex buffers
|
||||
*/
|
||||
struct VertexBuffer final { // size : offset (need multiples of 4 bytes for hashing)
|
||||
uint8_t stride{ 0 }; // 1 : 0
|
||||
uint8_t padding[3]{ 0 }; // 3 : 1
|
||||
// offset in bytes from //
|
||||
// the start of the //
|
||||
// (physical) GPU buffer //
|
||||
// (not logical buffer //
|
||||
// partition) //
|
||||
uint32_t offset{ 0 }; // 4 : 4
|
||||
};
|
||||
static_assert(sizeof(VertexBuffer) == 8, "VertexBuffer must not have implicit padding.");
|
||||
static_assert(std::is_trivially_copyable<VertexBuffer>::value,
|
||||
"VertexAttribute must be a trivially copyable POD for fast hashing.");
|
||||
/**
|
||||
* Key designed for efficient hashing and uniquely identifying all the parameters for
|
||||
* creating a render pipeline.
|
||||
* The efficient hashing requires a small memory footprint
|
||||
* (using the smallest representations of enums, just handle instances instead of wrapper class
|
||||
* instances, single bytes for booleans etc.), trivial copying and comparison (byte by byte),
|
||||
* and a word-aligned structure with a size in bytes as a multiple of 4 (for murmer hash).
|
||||
*/
|
||||
struct RenderPipelineKey final { // size : offset (need multiples of 4 bytes for hashing)
|
||||
// shaders... //
|
||||
WGPUShaderModule vertexShaderModuleHandle{ nullptr }; // 8 : 0
|
||||
WGPUShaderModule fragmentShaderModuleHandle{ nullptr }; // 8 : 8
|
||||
// vertex attributes... //
|
||||
VertexAttribute vertexAttributes[MAX_VERTEX_ATTRIBUTE_COUNT]{}; // 128 : 16
|
||||
VertexBuffer vertexBuffers[MAX_VERTEX_BUFFER_COUNT]{}; // 128 : 144
|
||||
// pipeline layout... //
|
||||
WGPUPipelineLayout pipelineLayoutHandle{ nullptr }; // 8 : 272
|
||||
// general settings... //
|
||||
int32_t depthBias{ 0 }; // 4 : 280
|
||||
float depthBiasSlopeScale { 0.0f }; // 4 : 284
|
||||
PrimitiveType primitiveType{ PrimitiveType::POINTS }; // 1 : 288
|
||||
// stencil state... //
|
||||
SamplerCompareFunc stencilFrontCompare{ SamplerCompareFunc::LE }; // 1 : 289
|
||||
StencilOperation stencilFrontFailOperation{ StencilOperation::KEEP }; // 1 : 290
|
||||
StencilOperation stencilFrontDepthFailOperation{ StencilOperation::KEEP }; // 1 : 291
|
||||
StencilOperation stencilFrontPassOperation{ StencilOperation::KEEP }; // 1 : 292
|
||||
/* bool, 0 -> false, 1 -> true */ uint8_t stencilWrite{ 0 }; // 1 : 293
|
||||
uint8_t stencilFrontReadMask{ 0 }; // 1 : 294
|
||||
uint8_t stencilFrontWriteMask{ 0 }; // 1 : 295
|
||||
SamplerCompareFunc stencilBackCompare{ SamplerCompareFunc::LE }; // 1 : 296
|
||||
StencilOperation stencilBackFailOperation{ StencilOperation::KEEP }; // 1 : 297
|
||||
StencilOperation stencilBackDepthFailOperation{ StencilOperation::KEEP }; // 1 : 298
|
||||
StencilOperation stencilBackPassOperation{ StencilOperation::KEEP }; // 1 : 299
|
||||
// general rasterization settings... //
|
||||
CullingMode cullingMode{ CullingMode::NONE }; // 1 : 300
|
||||
/* bool, 0 -> counter-clockwise, 1 -> clockwise */ uint8_t inverseFrontFaces{ 0 }; // 1 : 301
|
||||
// rasterization depth state... //
|
||||
/* bool, 0 -> false, 1 -> true */ uint8_t depthWriteEnabled{ 0 }; // 1 : 302
|
||||
SamplerCompareFunc depthCompare{ SamplerCompareFunc::LE }; // 1 : 303
|
||||
/* bool, 0 -> false, 1 -> true */ uint8_t depthClamp{ 0 }; // 1 : 304
|
||||
// more rasterization flags... //
|
||||
/* bool, 0 -> false, 1 -> true */ uint8_t colorWrite{ 0 }; // 1 : 305
|
||||
/* bool, 0 -> false, 1 -> true */ uint8_t alphaToCoverageEnabled{ 0 }; // 1 : 306
|
||||
// color blending... //
|
||||
BlendEquation colorBlendOperation{ BlendEquation::ADD }; // 1 : 307
|
||||
BlendFunction colorBlendSourceFactor{ BlendFunction::ZERO }; // 1 : 308
|
||||
BlendFunction colorBlendDestinationFactor{ BlendFunction::ZERO }; // 1 : 309
|
||||
// alpha blending... //
|
||||
BlendEquation alphaBlendOperation{ BlendEquation::ADD }; // 1 : 310
|
||||
BlendFunction alphaBlendSourceFactor{ BlendFunction::ZERO }; // 1 : 311
|
||||
BlendFunction alphaBlendDestinationFactor{ BlendFunction::ZERO }; // 1 : 312
|
||||
// render targets... //
|
||||
uint8_t multisampleCount{ 0 }; // 1 : 313
|
||||
uint8_t colorFormatCount{ 0 }; // 1 : 314
|
||||
uint8_t padding[5]{ 0 }; // 5 : 319
|
||||
TargetBufferFlags targetRenderFlags{ TargetBufferFlags::NONE }; // 4 : 320
|
||||
wgpu::TextureFormat depthStencilFormat { wgpu::TextureFormat::Undefined }; // 4 : 324
|
||||
wgpu::TextureFormat colorFormats[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]{ //
|
||||
wgpu::TextureFormat::Undefined //
|
||||
}; // 32 : 328
|
||||
};
|
||||
static_assert(sizeof(RenderPipelineKey) == 360,
|
||||
"RenderPipelineKey must not have implicit padding.");
|
||||
static_assert(std::is_trivially_copyable<RenderPipelineKey>::value,
|
||||
"RenderPipelineKey must be a trivially copyable POD for fast hashing.");
|
||||
|
||||
struct RenderPipelineKeyEqual {
|
||||
bool operator()(RenderPipelineKey const&, RenderPipelineKey const&) const;
|
||||
};
|
||||
|
||||
struct RenderPipelineCacheEntry final {
|
||||
wgpu::RenderPipeline pipeline{ nullptr };
|
||||
uint64_t lastUsedFrameCount{ 0 };
|
||||
};
|
||||
|
||||
static void populateKey(RenderPipelineRequest const&, RenderPipelineKey& outKey);
|
||||
|
||||
[[nodiscard]] wgpu::RenderPipeline createRenderPipeline(RenderPipelineRequest const&);
|
||||
|
||||
void removeExpiredPipelines();
|
||||
|
||||
wgpu::Device mDevice;
|
||||
tsl::robin_map<RenderPipelineKey, RenderPipelineCacheEntry,
|
||||
utils::hash::MurmurHashFn<RenderPipelineKey>, RenderPipelineKeyEqual>
|
||||
mRenderPipelines{};
|
||||
uint64_t mFrameCount{ 0 };
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_WEBGPUPIPELINECACHE_H
|
||||
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "WebGPUPipelineCreation.h"
|
||||
|
||||
#include "WebGPUProgram.h"
|
||||
#include "WebGPURenderTarget.h"
|
||||
#include "WebGPUVertexBufferInfo.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <sstream>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr wgpu::PrimitiveTopology toWebGPU(PrimitiveType primitiveType) {
|
||||
switch (primitiveType) {
|
||||
case PrimitiveType::POINTS:
|
||||
return wgpu::PrimitiveTopology::PointList;
|
||||
case PrimitiveType::LINES:
|
||||
return wgpu::PrimitiveTopology::LineList;
|
||||
case PrimitiveType::LINE_STRIP:
|
||||
return wgpu::PrimitiveTopology::LineStrip;
|
||||
case PrimitiveType::TRIANGLES:
|
||||
return wgpu::PrimitiveTopology::TriangleList;
|
||||
case PrimitiveType::TRIANGLE_STRIP:
|
||||
return wgpu::PrimitiveTopology::TriangleStrip;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr wgpu::CullMode toWebGPU(CullingMode cullMode) {
|
||||
switch (cullMode) {
|
||||
case CullingMode::NONE:
|
||||
return wgpu::CullMode::None;
|
||||
case CullingMode::FRONT:
|
||||
return wgpu::CullMode::Front;
|
||||
case CullingMode::BACK:
|
||||
return wgpu::CullMode::Back;
|
||||
case CullingMode::FRONT_AND_BACK:
|
||||
// no WegGPU equivalent of front and back
|
||||
FILAMENT_CHECK_POSTCONDITION(false)
|
||||
<< "WebGPU does not support CullingMode::FRONT_AND_BACK";
|
||||
return wgpu::CullMode::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
bool hasStencilAspect(wgpu::TextureFormat format) {
|
||||
switch (format) {
|
||||
case wgpu::TextureFormat::Stencil8:
|
||||
case wgpu::TextureFormat::Depth24PlusStencil8:
|
||||
case wgpu::TextureFormat::Depth32FloatStencil8:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr wgpu::CompareFunction toWebGPU(SamplerCompareFunc compareFunction) {
|
||||
switch (compareFunction) {
|
||||
case SamplerCompareFunc::LE:
|
||||
return wgpu::CompareFunction::LessEqual;
|
||||
case SamplerCompareFunc::GE:
|
||||
return wgpu::CompareFunction::GreaterEqual;
|
||||
case SamplerCompareFunc::L:
|
||||
return wgpu::CompareFunction::Less;
|
||||
case SamplerCompareFunc::G:
|
||||
return wgpu::CompareFunction::Greater;
|
||||
case SamplerCompareFunc::E:
|
||||
return wgpu::CompareFunction::Equal;
|
||||
case SamplerCompareFunc::NE:
|
||||
return wgpu::CompareFunction::NotEqual;
|
||||
case SamplerCompareFunc::A:
|
||||
return wgpu::CompareFunction::Always;
|
||||
case SamplerCompareFunc::N:
|
||||
return wgpu::CompareFunction::Never;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr wgpu::StencilOperation toWebGPU(StencilOperation stencilOp) {
|
||||
switch (stencilOp) {
|
||||
case StencilOperation::KEEP:
|
||||
return wgpu::StencilOperation::Keep;
|
||||
case StencilOperation::ZERO:
|
||||
return wgpu::StencilOperation::Zero;
|
||||
case StencilOperation::REPLACE:
|
||||
return wgpu::StencilOperation::Replace;
|
||||
case StencilOperation::INCR:
|
||||
return wgpu::StencilOperation::IncrementClamp;
|
||||
case StencilOperation::INCR_WRAP:
|
||||
return wgpu::StencilOperation::IncrementWrap;
|
||||
case StencilOperation::DECR:
|
||||
return wgpu::StencilOperation::DecrementClamp;
|
||||
case StencilOperation::DECR_WRAP:
|
||||
return wgpu::StencilOperation::DecrementWrap;
|
||||
case StencilOperation::INVERT:
|
||||
return wgpu::StencilOperation::Invert;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr wgpu::BlendOperation toWebGPU(BlendEquation blendOp) {
|
||||
switch (blendOp) {
|
||||
case BlendEquation::ADD:
|
||||
return wgpu::BlendOperation::Add;
|
||||
case BlendEquation::SUBTRACT:
|
||||
return wgpu::BlendOperation::Subtract;
|
||||
case BlendEquation::REVERSE_SUBTRACT:
|
||||
return wgpu::BlendOperation::ReverseSubtract;
|
||||
case BlendEquation::MIN:
|
||||
return wgpu::BlendOperation::Min;
|
||||
case BlendEquation::MAX:
|
||||
return wgpu::BlendOperation::Max;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr wgpu::BlendFactor toWebGPU(BlendFunction blendFunction) {
|
||||
switch (blendFunction) {
|
||||
case BlendFunction::ZERO:
|
||||
return wgpu::BlendFactor::Zero;
|
||||
case BlendFunction::ONE:
|
||||
return wgpu::BlendFactor::One;
|
||||
case BlendFunction::SRC_COLOR:
|
||||
return wgpu::BlendFactor::Src;
|
||||
case BlendFunction::ONE_MINUS_SRC_COLOR:
|
||||
return wgpu::BlendFactor::OneMinusSrc;
|
||||
case BlendFunction::DST_COLOR:
|
||||
return wgpu::BlendFactor::Dst;
|
||||
case BlendFunction::ONE_MINUS_DST_COLOR:
|
||||
return wgpu::BlendFactor::OneMinusDst;
|
||||
case BlendFunction::SRC_ALPHA:
|
||||
return wgpu::BlendFactor::SrcAlpha;
|
||||
case BlendFunction::ONE_MINUS_SRC_ALPHA:
|
||||
return wgpu::BlendFactor::OneMinusSrcAlpha;
|
||||
case BlendFunction::DST_ALPHA:
|
||||
return wgpu::BlendFactor::DstAlpha;
|
||||
case BlendFunction::ONE_MINUS_DST_ALPHA:
|
||||
return wgpu::BlendFactor::OneMinusDstAlpha;
|
||||
case BlendFunction::SRC_ALPHA_SATURATE:
|
||||
return wgpu::BlendFactor::SrcAlphaSaturated;
|
||||
}
|
||||
}
|
||||
|
||||
}// namespace
|
||||
|
||||
wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device,
|
||||
WebGPUProgram const& program, WebGPUVertexBufferInfo const& vertexBufferInfo,
|
||||
wgpu::PipelineLayout const& layout, RasterState const& rasterState,
|
||||
StencilState const& stencilState, PolygonOffset const& polygonOffset,
|
||||
const PrimitiveType primitiveType, std::vector<wgpu::TextureFormat> const& colorFormats,
|
||||
const wgpu::TextureFormat depthFormat, const uint8_t samplesCount) {
|
||||
assert_invariant(program.vertexShaderModule);
|
||||
wgpu::DepthStencilState depthStencilState{};
|
||||
if (depthFormat != wgpu::TextureFormat::Undefined) {
|
||||
depthStencilState.format = depthFormat;
|
||||
depthStencilState.depthWriteEnabled = rasterState.depthWrite;
|
||||
depthStencilState.depthCompare = toWebGPU(rasterState.depthFunc);
|
||||
depthStencilState.stencilFront = {
|
||||
.compare = toWebGPU(stencilState.front.stencilFunc),
|
||||
.failOp = toWebGPU(stencilState.front.stencilOpStencilFail),
|
||||
.depthFailOp = toWebGPU(stencilState.front.stencilOpDepthFail),
|
||||
.passOp = toWebGPU(stencilState.front.stencilOpDepthStencilPass),
|
||||
};
|
||||
depthStencilState.stencilBack = {
|
||||
.compare = toWebGPU(stencilState.back.stencilFunc),
|
||||
.failOp = toWebGPU(stencilState.back.stencilOpStencilFail),
|
||||
.depthFailOp = toWebGPU(stencilState.back.stencilOpDepthFail),
|
||||
.passOp = toWebGPU(stencilState.back.stencilOpDepthStencilPass),
|
||||
};
|
||||
depthStencilState.stencilReadMask =
|
||||
stencilState.front.readMask; // Use front face's comparison mask for read mask
|
||||
depthStencilState.stencilWriteMask = stencilState.stencilWrite ? 0xFFFFFFFF : 0u;
|
||||
depthStencilState.depthBias = static_cast<int32_t>(polygonOffset.constant);
|
||||
depthStencilState.depthBiasSlopeScale = polygonOffset.slope;
|
||||
depthStencilState.depthBiasClamp = 0.0f;
|
||||
|
||||
if (!hasStencilAspect(depthFormat)) {
|
||||
depthStencilState.stencilFront.compare = wgpu::CompareFunction::Always;
|
||||
depthStencilState.stencilFront.failOp = wgpu::StencilOperation::Keep;
|
||||
depthStencilState.stencilFront.depthFailOp = wgpu::StencilOperation::Keep;
|
||||
depthStencilState.stencilFront.passOp = wgpu::StencilOperation::Keep;
|
||||
depthStencilState.stencilBack =
|
||||
depthStencilState.stencilFront; // Keep back and front consistent
|
||||
depthStencilState.stencilReadMask = 0;
|
||||
depthStencilState.stencilWriteMask = 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::stringstream pipelineLabelStream;
|
||||
pipelineLabelStream << program.name.c_str() << " pipeline";
|
||||
const auto pipelineLabel = pipelineLabelStream.str();
|
||||
wgpu::RenderPipelineDescriptor pipelineDescriptor{
|
||||
.label = wgpu::StringView(pipelineLabel),
|
||||
.layout = layout,
|
||||
.vertex = { .module = program.vertexShaderModule,
|
||||
.entryPoint = "main",
|
||||
// we do not use WebGPU's override constants due to 2 limitations
|
||||
// (at least at the time of write this):
|
||||
// 1. they cannot be used for the size of an array, which is needed
|
||||
// 2. if we pass the WebGPU API (CPU-side) constants not referenced in the
|
||||
// shader WebGPU fails. This is a problem with how Filament is designed,
|
||||
// where certain constants may be optimized out of the shader based
|
||||
// on build configuration, etc.
|
||||
//
|
||||
// to bypass these problems, we do not use override constants in the
|
||||
// WebGPU backend, instead replacing placeholder constants in the shader
|
||||
// text before creating the shader module (essentially implementing
|
||||
// override constants ourselves)
|
||||
.constantCount = 0,
|
||||
.constants = nullptr,
|
||||
.bufferCount = vertexBufferInfo.getVertexBufferLayoutCount(),
|
||||
.buffers = vertexBufferInfo.getVertexBufferLayouts()
|
||||
},
|
||||
.primitive = {
|
||||
.topology = toWebGPU(primitiveType),
|
||||
// TODO should we assume some constant format here or is there a way to get
|
||||
// this from PipelineState somehow or elsewhere?
|
||||
// Perhaps, cache/assert format from index buffers as they are requested?
|
||||
.stripIndexFormat = wgpu::IndexFormat::Undefined,
|
||||
.frontFace = rasterState.inverseFrontFaces ? wgpu::FrontFace::CW : wgpu::FrontFace::CCW,
|
||||
.cullMode = toWebGPU(rasterState.culling),
|
||||
// TODO no depth clamp in WebGPU supported directly. unclippedDepth is close, so we are
|
||||
// starting there
|
||||
.unclippedDepth = !rasterState.depthClamp &&
|
||||
device.HasFeature(wgpu::FeatureName::DepthClipControl)
|
||||
},
|
||||
.depthStencil = depthFormat != wgpu::TextureFormat::Undefined ? &depthStencilState: nullptr,
|
||||
.multisample = {
|
||||
.count = samplesCount,
|
||||
.mask = 0xFFFFFFFF,
|
||||
.alphaToCoverageEnabled = rasterState.alphaToCoverage
|
||||
},
|
||||
.fragment = nullptr // will add below if fragment module is included
|
||||
};
|
||||
wgpu::FragmentState fragmentState = {};
|
||||
std::array<wgpu::ColorTargetState, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> colorTargets {};
|
||||
const wgpu::BlendState blendState {
|
||||
.color = {
|
||||
.operation = toWebGPU(rasterState.blendEquationRGB),
|
||||
.srcFactor = toWebGPU(rasterState.blendFunctionSrcRGB),
|
||||
.dstFactor = toWebGPU(rasterState.blendFunctionDstRGB)
|
||||
},
|
||||
.alpha = {
|
||||
.operation = toWebGPU(rasterState.blendEquationAlpha),
|
||||
.srcFactor = toWebGPU(rasterState.blendFunctionSrcAlpha),
|
||||
.dstFactor = toWebGPU(rasterState.blendFunctionDstAlpha)
|
||||
}
|
||||
};
|
||||
if (program.fragmentShaderModule != nullptr) {
|
||||
fragmentState.module = program.fragmentShaderModule;
|
||||
fragmentState.entryPoint = "main";
|
||||
// see the comment about constants for the vertex state, as the same reasoning applies
|
||||
// here
|
||||
fragmentState.constantCount = 0,
|
||||
fragmentState.constants = nullptr,
|
||||
fragmentState.targetCount = colorFormats.size();
|
||||
fragmentState.targets = colorTargets.data();
|
||||
assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
|
||||
// We expect a fragment shader implies at least one color target if it outputs color.
|
||||
// This should be guaranteed by the caller ensuring colorFormats is not empty.
|
||||
// However, this fails on shadowtest.cpp, TODO investigate why
|
||||
// assert_invariant(fragmentState.targetCount > 0);
|
||||
for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) {
|
||||
auto& colorTarget = colorTargets[targetIndex];
|
||||
colorTarget.format = colorFormats[targetIndex];
|
||||
colorTarget.blend = rasterState.hasBlending() ? &blendState : nullptr;
|
||||
colorTarget.writeMask =
|
||||
rasterState.colorWrite ? wgpu::ColorWriteMask::All : wgpu::ColorWriteMask::None;
|
||||
}
|
||||
pipelineDescriptor.fragment = &fragmentState;
|
||||
}
|
||||
const wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create render pipeline for " << pipelineDescriptor.label;
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
}// namespace filament::backend
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_WEBGPUPIPELINECREATION_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUPIPELINECREATION_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace wgpu {
|
||||
class Device;
|
||||
class PipelineLayout;
|
||||
class RenderPipeline;
|
||||
enum class TextureFormat : uint32_t;
|
||||
}// namespace wgpu
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
struct PolygonOffset;
|
||||
enum class PrimitiveType : uint8_t;
|
||||
struct RasterState;
|
||||
struct StencilState;
|
||||
|
||||
class WebGPUVertexBufferInfo;
|
||||
class WebGPUProgram;
|
||||
|
||||
[[nodiscard]] wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const&,
|
||||
WebGPUProgram const&, WebGPUVertexBufferInfo const&, wgpu::PipelineLayout const&,
|
||||
RasterState const&, StencilState const&, PolygonOffset const&, PrimitiveType primitiveType,
|
||||
std::vector<wgpu::TextureFormat> const& colorFormats, wgpu::TextureFormat depthFormat,
|
||||
uint8_t samplesCount);
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_WEBGPUPIPELINECREATION_H
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "WebGPUPipelineLayoutCache.h"
|
||||
|
||||
#include "WebGPUConstants.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
WebGPUPipelineLayoutCache::WebGPUPipelineLayoutCache(wgpu::Device const& device)
|
||||
: mDevice{ device } {}
|
||||
|
||||
wgpu::PipelineLayout const& WebGPUPipelineLayoutCache::getOrCreatePipelineLayout(
|
||||
PipelineLayoutRequest const& request) {
|
||||
PipelineLayoutKey key{};
|
||||
populateKey(request, key);
|
||||
if (auto iterator{ mPipelineLayouts.find(key) }; iterator != mPipelineLayouts.end()) {
|
||||
PipelineLayoutCacheEntry& entry{ iterator.value() };
|
||||
entry.lastUsedFrameCount = mFrameCount;
|
||||
return entry.layout;
|
||||
}
|
||||
const wgpu::PipelineLayout layout{ createPipelineLayout(request) };
|
||||
mPipelineLayouts.emplace(key, PipelineLayoutCacheEntry{
|
||||
.layout = layout,
|
||||
.lastUsedFrameCount = mFrameCount,
|
||||
});
|
||||
return mPipelineLayouts[key].layout;
|
||||
}
|
||||
|
||||
void WebGPUPipelineLayoutCache::onFrameEnd() {
|
||||
++mFrameCount;
|
||||
removeExpiredPipelineLayouts();
|
||||
}
|
||||
|
||||
void WebGPUPipelineLayoutCache::populateKey(PipelineLayoutRequest const& request,
|
||||
PipelineLayoutKey& outKey) {
|
||||
outKey.bindGroupLayoutCount = static_cast<uint8_t>(request.bindGroupLayoutCount);
|
||||
for (size_t bindGroupIndex{ 0 }; bindGroupIndex < request.bindGroupLayoutCount;
|
||||
++bindGroupIndex) {
|
||||
outKey.bindGroupLayoutHandles[bindGroupIndex] =
|
||||
request.bindGroupLayouts[bindGroupIndex]
|
||||
? request.bindGroupLayouts[bindGroupIndex].Get()
|
||||
: nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
wgpu::PipelineLayout WebGPUPipelineLayoutCache::createPipelineLayout(
|
||||
PipelineLayoutRequest const& request) {
|
||||
const wgpu::PipelineLayoutDescriptor descriptor{
|
||||
.label = wgpu::StringView(request.label.c_str_safe()),
|
||||
.bindGroupLayoutCount = request.bindGroupLayoutCount,
|
||||
.bindGroupLayouts = request.bindGroupLayouts.data(),
|
||||
// TODO investigate immediateDataRangeByteSize
|
||||
};
|
||||
const wgpu::PipelineLayout layout{ mDevice.CreatePipelineLayout(&descriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(layout)
|
||||
<< "Failed to create pipeline layout " << descriptor.label << "?";
|
||||
return layout;
|
||||
}
|
||||
|
||||
bool WebGPUPipelineLayoutCache::PipelineLayoutKeyEqual::operator()(PipelineLayoutKey const& key1,
|
||||
PipelineLayoutKey const& key2) const {
|
||||
return 0 == memcmp(reinterpret_cast<void const*>(&key1), reinterpret_cast<void const*>(&key2),
|
||||
sizeof(key1));
|
||||
}
|
||||
|
||||
void WebGPUPipelineLayoutCache::removeExpiredPipelineLayouts() {
|
||||
using Iterator = decltype(mPipelineLayouts)::const_iterator;
|
||||
for (Iterator iterator{ mPipelineLayouts.begin() }; iterator != mPipelineLayouts.end();) {
|
||||
PipelineLayoutCacheEntry const& entry{ iterator.value() };
|
||||
if (mFrameCount > (entry.lastUsedFrameCount +
|
||||
FILAMENT_WEBGPU_PIPELINE_LAYOUT_EXPIRATION_IN_FRAME_COUNT)) {
|
||||
// pipeline layout expired...
|
||||
iterator = mPipelineLayouts.erase(iterator);
|
||||
} else {
|
||||
// pipeline layout not yet expired...
|
||||
++iterator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_WEBGPUPIPELINELAYOUTCACHE_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUPIPELINELAYOUTCACHE_H
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/Hash.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPUPipelineLayoutCache final {
|
||||
public:
|
||||
struct PipelineLayoutRequest final {
|
||||
utils::CString const& label;
|
||||
std::array<wgpu::BindGroupLayout, MAX_DESCRIPTOR_SET_COUNT> const& bindGroupLayouts;
|
||||
size_t bindGroupLayoutCount;
|
||||
};
|
||||
|
||||
explicit WebGPUPipelineLayoutCache(wgpu::Device const&);
|
||||
WebGPUPipelineLayoutCache(WebGPUPipelineLayoutCache const&) = delete;
|
||||
WebGPUPipelineLayoutCache(WebGPUPipelineLayoutCache const&&) = delete;
|
||||
WebGPUPipelineLayoutCache& operator=(WebGPUPipelineLayoutCache const&) = delete;
|
||||
WebGPUPipelineLayoutCache& operator=(WebGPUPipelineLayoutCache const&&) = delete;
|
||||
|
||||
[[nodiscard]] wgpu::PipelineLayout const& getOrCreatePipelineLayout(
|
||||
PipelineLayoutRequest const&);
|
||||
|
||||
void onFrameEnd();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Key designed for efficient hashing and uniquely identifying all the parameters for
|
||||
* creating a pipeline layout.
|
||||
* The efficient hashing requires a small memory footprint
|
||||
* (using the smallest representations of enums, just handle instances instead of wrapper class
|
||||
* instances, single bytes for booleans etc.), trivial copying and comparison (byte by byte),
|
||||
* and a word-aligned structure with a size in bytes as a multiple of 4 (for murmer hash).
|
||||
*/
|
||||
struct PipelineLayoutKey final { // size : offset (need multiples of 4 bytes for hashing)
|
||||
WGPUBindGroupLayout bindGroupLayoutHandles[MAX_DESCRIPTOR_SET_COUNT]{ nullptr }; // 32 : 0
|
||||
uint8_t bindGroupLayoutCount{ 0 }; // 1 : 32
|
||||
uint8_t padding[7]{ 0 }; // 7 : 33
|
||||
};
|
||||
static_assert(sizeof(PipelineLayoutKey) == 40,
|
||||
"PipelineLayoutKey must not have implicit padding.");
|
||||
static_assert(std::is_trivially_copyable<PipelineLayoutKey>::value,
|
||||
"PipelineLayoutKey must be a trivially copyable POD for fast hashing.");
|
||||
|
||||
struct PipelineLayoutKeyEqual {
|
||||
bool operator()(PipelineLayoutKey const&, PipelineLayoutKey const&) const;
|
||||
};
|
||||
|
||||
struct PipelineLayoutCacheEntry final {
|
||||
wgpu::PipelineLayout layout{ nullptr };
|
||||
uint64_t lastUsedFrameCount{ 0 };
|
||||
};
|
||||
|
||||
static void populateKey(PipelineLayoutRequest const&, PipelineLayoutKey& outKey);
|
||||
|
||||
[[nodiscard]] wgpu::PipelineLayout createPipelineLayout(PipelineLayoutRequest const&);
|
||||
|
||||
void removeExpiredPipelineLayouts();
|
||||
|
||||
wgpu::Device mDevice;
|
||||
tsl::robin_map<PipelineLayoutKey, PipelineLayoutCacheEntry,
|
||||
utils::hash::MurmurHashFn<PipelineLayoutKey>, PipelineLayoutKeyEqual>
|
||||
mPipelineLayouts{};
|
||||
uint64_t mFrameCount{ 0 };
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_WEBGPUPIPELINELAYOUTCACHE_H
|
||||
@@ -234,7 +234,7 @@ namespace {
|
||||
FWGPU_LOGD << descriptor.label << " compiled successfully";
|
||||
#endif
|
||||
}),
|
||||
FILAMENT_WEBGPU_SHADER_COMPILATION_TIMEOUT_NANOSECONDS);
|
||||
SHADER_COMPILATION_TIMEOUT_NANOSECONDS);
|
||||
switch (waitResult) {
|
||||
case wgpu::WaitStatus::Success:
|
||||
break;
|
||||
|
||||
@@ -1,552 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "WebGPURenderPassMipmapGenerator.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
// The shaders expect a single-sampled 2D texture
|
||||
// with a format compatible with scalar sample format f32
|
||||
// (this is checked with the getCompatibilityFor function)
|
||||
constexpr uint32_t TEXTURE_BIND_GROUP_INDEX{ 0 };
|
||||
constexpr size_t TEXTURE_BIND_GROUP_ENTRY_SIZE{ 2 }; // sampler and texture
|
||||
constexpr uint32_t SAMPLER_BINDING_INDEX{ 0 };
|
||||
constexpr uint32_t TEXTURE_BINDING_INDEX{ 1 };
|
||||
constexpr std::string_view VERTEX_SHADER_ENTRY_POINT{ "vertexShaderMain" };
|
||||
constexpr std::string_view FRAGMENT_SHADER_ENTRY_POINT{ "fragmentShaderMain" };
|
||||
// Note: would be nice to generate this constexpr string from the constexpr variables above
|
||||
// to avoid them getting out of sync while still having the compile/build time optimization
|
||||
constexpr std::string_view SHADER_SOURCE{ R"(
|
||||
@group(0) @binding(0) var previousMipLevelSampler: sampler;
|
||||
@group(0) @binding(1) var previousMipLevelTexture: texture_2d<f32>;
|
||||
|
||||
struct VertexShaderOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) textureCoordinate: vec2<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vertexShaderMain(@builtin(vertex_index) vertexIndex: u32) -> VertexShaderOutput {
|
||||
let fullScreenTriangleVertices = array<vec2<f32>, 3>(
|
||||
vec2<f32>(-1.0, -1.0),
|
||||
vec2<f32>(3.0, -1.0),
|
||||
vec2<f32>(-1.0, 3.0)
|
||||
);
|
||||
var out: VertexShaderOutput;
|
||||
let vertex = fullScreenTriangleVertices[vertexIndex];
|
||||
out.position = vec4<f32>(vertex * 2.0 - 1.0, 0.0, 1.0);
|
||||
out.textureCoordinate = vec2<f32>(vertex.x, 1.0 - vertex.y);
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragmentShaderMain(in: VertexShaderOutput) -> @location(0) vec4<f32> {
|
||||
return textureSample(previousMipLevelTexture,
|
||||
previousMipLevelSampler,
|
||||
in.textureCoordinate);
|
||||
}
|
||||
)" };
|
||||
|
||||
[[nodiscard]] wgpu::Sampler createPreviousMipLevelSampler(wgpu::Device const& device) {
|
||||
const wgpu::SamplerDescriptor descriptor{
|
||||
.label = "previous_mip_level_sampler",
|
||||
.addressModeU = wgpu::AddressMode::ClampToEdge,
|
||||
.addressModeV = wgpu::AddressMode::ClampToEdge,
|
||||
.addressModeW = wgpu::AddressMode::Undefined, // only 2D textures supported for now
|
||||
.magFilter = wgpu::FilterMode::Linear,
|
||||
.minFilter = wgpu::FilterMode::Linear,
|
||||
.mipmapFilter = wgpu::MipmapFilterMode::Nearest, // unused for this purpose
|
||||
.lodMinClamp = 0.0f, // should not matter, just being consistently defined
|
||||
.lodMaxClamp = 32.0f, // should not matter, just being consistently defined
|
||||
.compare = wgpu::CompareFunction::Undefined, // should not matter, just being consistently
|
||||
// defined
|
||||
.maxAnisotropy = 1, // should not matter, just being consistently defined
|
||||
};
|
||||
const wgpu::Sampler sampler{ device.CreateSampler(&descriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create previous mip level sampler?";
|
||||
return sampler;
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::ShaderModule createShaderModule(wgpu::Device const& device) {
|
||||
wgpu::ShaderModuleWGSLDescriptor wgslDescriptor{};
|
||||
wgslDescriptor.code = SHADER_SOURCE.data();
|
||||
const wgpu::ShaderModuleDescriptor shaderModuleDescriptor{
|
||||
.nextInChain = &wgslDescriptor,
|
||||
.label = "render_pass_mipmap_generation_shaders",
|
||||
};
|
||||
const wgpu::ShaderModule shaderModule{ device.CreateShaderModule(&shaderModuleDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(shaderModule)
|
||||
<< "Failed to create shader module for render pass mipmap generation?";
|
||||
return shaderModule;
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::BindGroupLayout createTextureBindGroupLayout(wgpu::Device const& device) {
|
||||
const wgpu::BindGroupLayoutEntry bindGroupLayoutEntries[TEXTURE_BIND_GROUP_ENTRY_SIZE] {
|
||||
{
|
||||
.binding = SAMPLER_BINDING_INDEX,
|
||||
.visibility = wgpu::ShaderStage::Fragment,
|
||||
.sampler = {
|
||||
.type = wgpu::SamplerBindingType::Filtering, // only hardware accelerated filtering
|
||||
// supported
|
||||
},
|
||||
},
|
||||
{
|
||||
.binding = TEXTURE_BINDING_INDEX,
|
||||
.visibility = wgpu::ShaderStage::Fragment,
|
||||
.texture = {
|
||||
.sampleType = wgpu::TextureSampleType::Float, // only F32 scalar sample
|
||||
// type supported for now
|
||||
.viewDimension = wgpu::TextureViewDimension::e2D, // only 2D supported for now
|
||||
.multisampled = false, // MSAA textures not supported for now
|
||||
},
|
||||
},
|
||||
};
|
||||
const wgpu::BindGroupLayoutDescriptor textureBindGroupLayoutDescriptor{
|
||||
.label = "render_pass_mipmap_generation_texture_bind_group_layout",
|
||||
.entryCount = TEXTURE_BIND_GROUP_ENTRY_SIZE,
|
||||
.entries = bindGroupLayoutEntries,
|
||||
};
|
||||
const wgpu::BindGroupLayout textureBindGroupLayout{ device.CreateBindGroupLayout(
|
||||
&textureBindGroupLayoutDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(textureBindGroupLayout)
|
||||
<< "Failed to create texture bind group layout for render pass mipmap generation?";
|
||||
return textureBindGroupLayout;
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::PipelineLayout createPipelineLayout(wgpu::Device const& device,
|
||||
wgpu::BindGroupLayout const& textureBindGroupLayout) {
|
||||
const wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor{
|
||||
.label = "render_pass_mipmap_generation_pipeline_layout",
|
||||
.bindGroupLayoutCount = 1,
|
||||
.bindGroupLayouts = &textureBindGroupLayout,
|
||||
};
|
||||
const wgpu::PipelineLayout pipelineLayout{ device.CreatePipelineLayout(
|
||||
&pipelineLayoutDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipelineLayout)
|
||||
<< "Failed to create pipeline layout for render pass mipmap generation?";
|
||||
return pipelineLayout;
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::RenderPipeline createPipeline(wgpu::Device const& device,
|
||||
wgpu::ShaderModule const& shaderModule, wgpu::PipelineLayout const& pipelineLayout,
|
||||
const wgpu::TextureFormat textureFormat) {
|
||||
const wgpu::ColorTargetState colorTargetState{ .format = textureFormat };
|
||||
const wgpu::FragmentState fragmentState{
|
||||
.module = shaderModule,
|
||||
.entryPoint = FRAGMENT_SHADER_ENTRY_POINT,
|
||||
.constantCount = 0, // should not matter, just being consistently defined
|
||||
.constants = nullptr, // should not matter, just being consistently defined
|
||||
.targetCount = 1,
|
||||
.targets = &colorTargetState,
|
||||
};
|
||||
const wgpu::RenderPipelineDescriptor pipelineDescriptor{
|
||||
.label = "render_pass_mipmap_generation_pipeline",
|
||||
.layout = pipelineLayout,
|
||||
.vertex = {
|
||||
.module = shaderModule,
|
||||
.entryPoint = VERTEX_SHADER_ENTRY_POINT,
|
||||
.constantCount = 0, // should not matter, just being consistently defined
|
||||
.constants = nullptr, // should not matter, just being consistently defined
|
||||
.bufferCount = 0, // using hardcoded vertices in the shader for
|
||||
// performance and simplicity
|
||||
.buffers = nullptr,
|
||||
},
|
||||
.primitive = {
|
||||
.topology = wgpu::PrimitiveTopology::TriangleList,
|
||||
.stripIndexFormat = wgpu::IndexFormat::Undefined, // should not matter
|
||||
// (not using a strip topology),
|
||||
// just being consistently defined
|
||||
.frontFace = wgpu::FrontFace::Undefined, // should not matter (no cull mode),
|
||||
// just being consistently defined
|
||||
.cullMode = wgpu::CullMode::None,
|
||||
.unclippedDepth = false, // should not matter, just being consistently defined
|
||||
},
|
||||
.depthStencil = nullptr, // not applicable, but explicitly set here for
|
||||
// consistent definition
|
||||
.multisample = {
|
||||
.count = 1, // no multi-sampling
|
||||
.mask = 0xFFFFFFFF,
|
||||
.alphaToCoverageEnabled = false,
|
||||
},
|
||||
.fragment = &fragmentState,
|
||||
};
|
||||
const wgpu::RenderPipeline pipeline{ device.CreateRenderPipeline(&pipelineDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create pipeline for render pass mipmap generation?";
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WebGPURenderPassMipmapGenerator::WebGPURenderPassMipmapGenerator(wgpu::Device const& device)
|
||||
: mDevice{ device },
|
||||
mPreviousMipLevelSampler{ createPreviousMipLevelSampler(mDevice) },
|
||||
mShaderModule{ createShaderModule(mDevice) },
|
||||
mTextureBindGroupLayout{ createTextureBindGroupLayout(mDevice) },
|
||||
mPipelineLayout{ createPipelineLayout(mDevice, mTextureBindGroupLayout) } {}
|
||||
|
||||
WebGPURenderPassMipmapGenerator::FormatCompatibility
|
||||
WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat format,
|
||||
const wgpu::TextureDimension dimension, const uint32_t sampleCount) {
|
||||
// check that the format is compatible (currently expects that the scalar sample type is
|
||||
// f32)...
|
||||
switch (format) {
|
||||
case wgpu::TextureFormat::Depth16Unorm:
|
||||
case wgpu::TextureFormat::Depth24Plus:
|
||||
case wgpu::TextureFormat::Depth24PlusStencil8:
|
||||
case wgpu::TextureFormat::Depth32Float:
|
||||
case wgpu::TextureFormat::Depth32FloatStencil8:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "A depth texture format requires special sampler treatment and does "
|
||||
"not "
|
||||
"generally support linear filtering needed for render pass based "
|
||||
"mipmap "
|
||||
"generation, thus this texture is not supported, as it is a kind of "
|
||||
"depth texture.",
|
||||
};
|
||||
case wgpu::TextureFormat::Undefined:
|
||||
case wgpu::TextureFormat::External:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "Undefined or External textures are not supported for render pass "
|
||||
"based "
|
||||
"mipmap generation",
|
||||
};
|
||||
default:
|
||||
const ScalarSampleType scalarSampleType{ getScalarSampleTypeFrom(format) };
|
||||
switch (scalarSampleType) {
|
||||
case ScalarSampleType::U32:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "The provided texture format requires an unsigned integer "
|
||||
"sampler, which does not natively support linear filtering, "
|
||||
"which is needed for render pass based mipmap generation.",
|
||||
};
|
||||
case ScalarSampleType::I32:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "The provided texture format requires a signed integer "
|
||||
"sampler, which does not natively support linear filtering, "
|
||||
"which is needed for render pass based mipmap generation.",
|
||||
};
|
||||
case ScalarSampleType::F32:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// check that the dimensionality is compatible (currently expects 2D textures)...
|
||||
switch (dimension) {
|
||||
case wgpu::TextureDimension::Undefined:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "Texture has an undefined texture dimension (expected 2D), thus it is "
|
||||
"not "
|
||||
"supported by the render pass based mipmap generation implementation.",
|
||||
};
|
||||
case wgpu::TextureDimension::e1D:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "Texture has a 1D texture dimension (expected 2D), thus it is not "
|
||||
"current supported by the render pass based mipmap generation "
|
||||
"implementation (but could be extended to later if needed).",
|
||||
};
|
||||
case wgpu::TextureDimension::e3D:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "Texture has a 3D texture dimension (expected 2D), thus it is not "
|
||||
"current supported by the render pass based mipmap generation "
|
||||
"implementation (but could be extended to later if REALLY needed).",
|
||||
};
|
||||
case wgpu::TextureDimension::e2D:
|
||||
break;
|
||||
}
|
||||
// check that the texture is single-sampled (for now)...
|
||||
if (sampleCount > 1) {
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "Multi-sampled textures are not currently supported with the render pass "
|
||||
"based mipmap generation implementation (but could be extended to later if "
|
||||
"REALLY needed. The idea would be to resolve the texture to a single "
|
||||
"sampled "
|
||||
"texture before generating single-sample mipmaps, but that may still not "
|
||||
"necessarily be what is desired.).",
|
||||
};
|
||||
}
|
||||
return { .compatible = true, .reason = "" };
|
||||
}
|
||||
|
||||
constexpr WebGPURenderPassMipmapGenerator::ScalarSampleType
|
||||
WebGPURenderPassMipmapGenerator::getScalarSampleTypeFrom(const wgpu::TextureFormat format) {
|
||||
switch (format) {
|
||||
case wgpu::TextureFormat::R8Uint:
|
||||
case wgpu::TextureFormat::R16Uint:
|
||||
case wgpu::TextureFormat::R32Uint:
|
||||
case wgpu::TextureFormat::Stencil8:
|
||||
case wgpu::TextureFormat::RG8Uint:
|
||||
case wgpu::TextureFormat::RG16Uint:
|
||||
case wgpu::TextureFormat::RGBA8Uint:
|
||||
case wgpu::TextureFormat::RGB10A2Uint:
|
||||
case wgpu::TextureFormat::RG32Uint:
|
||||
case wgpu::TextureFormat::RGBA16Uint:
|
||||
case wgpu::TextureFormat::RGBA32Uint:
|
||||
return ScalarSampleType::U32;
|
||||
case wgpu::TextureFormat::R8Sint:
|
||||
case wgpu::TextureFormat::R16Sint:
|
||||
case wgpu::TextureFormat::R32Sint:
|
||||
case wgpu::TextureFormat::RG8Sint:
|
||||
case wgpu::TextureFormat::RG16Sint:
|
||||
case wgpu::TextureFormat::RGBA8Sint:
|
||||
case wgpu::TextureFormat::RG32Sint:
|
||||
case wgpu::TextureFormat::RGBA16Sint:
|
||||
case wgpu::TextureFormat::RGBA32Sint:
|
||||
return ScalarSampleType::I32;
|
||||
case wgpu::TextureFormat::R8Snorm:
|
||||
case wgpu::TextureFormat::R8Unorm:
|
||||
case wgpu::TextureFormat::R16Float:
|
||||
case wgpu::TextureFormat::RG16Float:
|
||||
case wgpu::TextureFormat::RG8Unorm:
|
||||
case wgpu::TextureFormat::RG8Snorm:
|
||||
case wgpu::TextureFormat::R32Float:
|
||||
case wgpu::TextureFormat::RGBA8Unorm:
|
||||
case wgpu::TextureFormat::RGBA8UnormSrgb:
|
||||
case wgpu::TextureFormat::RGBA8Snorm:
|
||||
case wgpu::TextureFormat::BGRA8Unorm:
|
||||
case wgpu::TextureFormat::BGRA8UnormSrgb:
|
||||
case wgpu::TextureFormat::RGB10A2Unorm:
|
||||
case wgpu::TextureFormat::RG11B10Ufloat:
|
||||
case wgpu::TextureFormat::RGB9E5Ufloat:
|
||||
case wgpu::TextureFormat::RG32Float:
|
||||
case wgpu::TextureFormat::RGBA16Float:
|
||||
case wgpu::TextureFormat::RGBA32Float:
|
||||
case wgpu::TextureFormat::BC1RGBAUnorm:
|
||||
case wgpu::TextureFormat::BC1RGBAUnormSrgb:
|
||||
case wgpu::TextureFormat::BC2RGBAUnorm:
|
||||
case wgpu::TextureFormat::BC2RGBAUnormSrgb:
|
||||
case wgpu::TextureFormat::BC3RGBAUnorm:
|
||||
case wgpu::TextureFormat::BC3RGBAUnormSrgb:
|
||||
case wgpu::TextureFormat::BC4RUnorm:
|
||||
case wgpu::TextureFormat::BC4RSnorm:
|
||||
case wgpu::TextureFormat::BC5RGUnorm:
|
||||
case wgpu::TextureFormat::BC5RGSnorm:
|
||||
case wgpu::TextureFormat::BC6HRGBUfloat:
|
||||
case wgpu::TextureFormat::BC6HRGBFloat:
|
||||
case wgpu::TextureFormat::BC7RGBAUnorm:
|
||||
case wgpu::TextureFormat::BC7RGBAUnormSrgb:
|
||||
case wgpu::TextureFormat::ETC2RGB8Unorm:
|
||||
case wgpu::TextureFormat::ETC2RGB8UnormSrgb:
|
||||
case wgpu::TextureFormat::ETC2RGB8A1Unorm:
|
||||
case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb:
|
||||
case wgpu::TextureFormat::ETC2RGBA8Unorm:
|
||||
case wgpu::TextureFormat::ETC2RGBA8UnormSrgb:
|
||||
case wgpu::TextureFormat::EACR11Unorm:
|
||||
case wgpu::TextureFormat::EACR11Snorm:
|
||||
case wgpu::TextureFormat::EACRG11Unorm:
|
||||
case wgpu::TextureFormat::EACRG11Snorm:
|
||||
case wgpu::TextureFormat::ASTC4x4Unorm:
|
||||
case wgpu::TextureFormat::ASTC4x4UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC5x4Unorm:
|
||||
case wgpu::TextureFormat::ASTC5x4UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC5x5Unorm:
|
||||
case wgpu::TextureFormat::ASTC5x5UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC6x5Unorm:
|
||||
case wgpu::TextureFormat::ASTC6x5UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC6x6Unorm:
|
||||
case wgpu::TextureFormat::ASTC6x6UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC8x5Unorm:
|
||||
case wgpu::TextureFormat::ASTC8x5UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC8x6Unorm:
|
||||
case wgpu::TextureFormat::ASTC8x6UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC8x8Unorm:
|
||||
case wgpu::TextureFormat::ASTC8x8UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC10x5Unorm:
|
||||
case wgpu::TextureFormat::ASTC10x5UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC10x6Unorm:
|
||||
case wgpu::TextureFormat::ASTC10x6UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC10x8Unorm:
|
||||
case wgpu::TextureFormat::ASTC10x8UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC10x10Unorm:
|
||||
case wgpu::TextureFormat::ASTC10x10UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC12x10Unorm:
|
||||
case wgpu::TextureFormat::ASTC12x10UnormSrgb:
|
||||
case wgpu::TextureFormat::ASTC12x12Unorm:
|
||||
case wgpu::TextureFormat::ASTC12x12UnormSrgb:
|
||||
case wgpu::TextureFormat::R16Unorm:
|
||||
case wgpu::TextureFormat::RG16Unorm:
|
||||
case wgpu::TextureFormat::RGBA16Unorm:
|
||||
case wgpu::TextureFormat::R16Snorm:
|
||||
case wgpu::TextureFormat::RG16Snorm:
|
||||
case wgpu::TextureFormat::RGBA16Snorm:
|
||||
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
|
||||
case wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm:
|
||||
case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm:
|
||||
case wgpu::TextureFormat::R8BG8Biplanar422Unorm:
|
||||
case wgpu::TextureFormat::R8BG8Biplanar444Unorm:
|
||||
case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm:
|
||||
case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm:
|
||||
return ScalarSampleType::F32;
|
||||
case wgpu::TextureFormat::Depth16Unorm:
|
||||
case wgpu::TextureFormat::Depth24Plus:
|
||||
case wgpu::TextureFormat::Depth24PlusStencil8:
|
||||
case wgpu::TextureFormat::Depth32Float:
|
||||
case wgpu::TextureFormat::Depth32FloatStencil8:
|
||||
PANIC_POSTCONDITION("A depth texture format requires special sampler treatment and "
|
||||
"does not generally support linear filtering (needed for mipmap "
|
||||
"generation). Thus no applicable scalar sample type for %d",
|
||||
format);
|
||||
break;
|
||||
case wgpu::TextureFormat::Undefined:
|
||||
case wgpu::TextureFormat::External:
|
||||
PANIC_POSTCONDITION("No scalar sample type for texture format %d", format);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WebGPURenderPassMipmapGenerator::generateMipmaps(wgpu::Queue const& queue,
|
||||
wgpu::Texture const& texture) {
|
||||
const uint32_t mipLevelCount{ texture.GetMipLevelCount() };
|
||||
if (mipLevelCount < 2) {
|
||||
return; // nothing to do
|
||||
}
|
||||
wgpu::RenderPipeline const& pipeline{ getOrCreatePipelineFor(texture.GetFormat()) };
|
||||
const wgpu::CommandEncoderDescriptor commandEncoderDescriptor{
|
||||
.label = "mipmap_generation_render_pass_cmd_encoder",
|
||||
};
|
||||
const wgpu::CommandEncoder commandEncoder{ mDevice.CreateCommandEncoder(
|
||||
&commandEncoderDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(commandEncoder)
|
||||
<< "Failed to create command encoder for layer for render pass mipmap generation?";
|
||||
const uint32_t layerCount{ texture.GetDepthOrArrayLayers() };
|
||||
for (uint32_t layer = 0; layer < layerCount; layer++) {
|
||||
for (uint32_t mipLevel = 1; mipLevel < mipLevelCount; mipLevel++) {
|
||||
generateMipmap(commandEncoder, texture, pipeline, layer, mipLevel);
|
||||
}
|
||||
}
|
||||
// submit the command buffer...
|
||||
const wgpu::CommandBufferDescriptor commandBufferDescriptor{
|
||||
.label = "mipmap_generation_render_pass_cmd_buffer",
|
||||
};
|
||||
const wgpu::CommandBuffer commandBuffer{ commandEncoder.Finish(&commandBufferDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(commandBuffer)
|
||||
<< "Failed to create command buffer for layer for render pass mipmap generation?";
|
||||
queue.Submit(1, &commandBuffer);
|
||||
}
|
||||
|
||||
void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const& commandEncoder,
|
||||
const wgpu::Texture& texture, const wgpu::RenderPipeline& pipeline, const uint32_t layer,
|
||||
const uint32_t mipLevel) {
|
||||
// create texture views for this pass...
|
||||
const wgpu::TextureFormat format{ texture.GetFormat() };
|
||||
// only 2D textures supported at this time...
|
||||
const wgpu::TextureViewDimension dimension{ wgpu::TextureViewDimension::e2D };
|
||||
const wgpu::TextureViewDescriptor sourceViewDescriptor{
|
||||
.label = "previous_mip_level_texture_view",
|
||||
.format = format,
|
||||
.dimension = dimension,
|
||||
.baseMipLevel = mipLevel - 1,
|
||||
.mipLevelCount = 1,
|
||||
.baseArrayLayer = layer,
|
||||
.arrayLayerCount = 1,
|
||||
.aspect = wgpu::TextureAspect::All,
|
||||
.usage = wgpu::TextureUsage::TextureBinding,
|
||||
};
|
||||
const wgpu::TextureView sourceView{ texture.CreateView(&sourceViewDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create source texture view for layer " << layer << " and mip level "
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
const wgpu::TextureViewDescriptor destinationViewDescriptor{
|
||||
.label = "next_mip_level_texture_view",
|
||||
.format = format,
|
||||
.dimension = dimension,
|
||||
.baseMipLevel = mipLevel,
|
||||
.mipLevelCount = 1,
|
||||
.baseArrayLayer = layer,
|
||||
.arrayLayerCount = 1,
|
||||
.aspect = wgpu::TextureAspect::All,
|
||||
.usage = wgpu::TextureUsage::RenderAttachment,
|
||||
};
|
||||
const wgpu::TextureView destinationView{ texture.CreateView(&destinationViewDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create destination texture view for layer " << layer << " and mip level "
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
// create the render pass...
|
||||
const wgpu::BindGroupEntry textureBindGroupEntries[TEXTURE_BIND_GROUP_ENTRY_SIZE]{
|
||||
{
|
||||
.binding = SAMPLER_BINDING_INDEX,
|
||||
.sampler = mPreviousMipLevelSampler,
|
||||
},
|
||||
{
|
||||
.binding = TEXTURE_BINDING_INDEX,
|
||||
.textureView = sourceView,
|
||||
},
|
||||
};
|
||||
const wgpu::BindGroupDescriptor textureBindGroupDescriptor{
|
||||
.label = "render_pass_mipmap_generation_texture_bind_group",
|
||||
.layout = mTextureBindGroupLayout,
|
||||
.entryCount = TEXTURE_BIND_GROUP_ENTRY_SIZE,
|
||||
.entries = textureBindGroupEntries,
|
||||
};
|
||||
const wgpu::BindGroup textureBindGroup{ mDevice.CreateBindGroup(&textureBindGroupDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create texture bind group for layer " << layer << " and mip level "
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
const wgpu::RenderPassColorAttachment colorAttachment{
|
||||
.view = destinationView,
|
||||
.depthSlice = wgpu::kDepthSliceUndefined, // not applicable
|
||||
.resolveTarget = nullptr, // not applicable
|
||||
.loadOp = wgpu::LoadOp::Clear,
|
||||
.storeOp = wgpu::StoreOp::Store,
|
||||
.clearValue = { .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 },
|
||||
};
|
||||
const wgpu::RenderPassDescriptor renderPassDescriptor{
|
||||
.label = "mipmap_generation_render_pass",
|
||||
.colorAttachmentCount = 1,
|
||||
.colorAttachments = &colorAttachment,
|
||||
.depthStencilAttachment = nullptr, // not applicable
|
||||
.occlusionQuerySet = nullptr, // not applicable
|
||||
.timestampWrites = nullptr, // not applicable
|
||||
};
|
||||
const wgpu::RenderPassEncoder renderPassEncoder{ commandEncoder.BeginRenderPass(
|
||||
&renderPassDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(renderPassEncoder)
|
||||
<< "Failed to create render pass encoder for layer " << layer << " and mip level "
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
renderPassEncoder.SetPipeline(pipeline);
|
||||
renderPassEncoder.SetBindGroup(TEXTURE_BIND_GROUP_INDEX, textureBindGroup);
|
||||
renderPassEncoder.Draw(3); // draw the full-screen triangle
|
||||
// with hard-coded vertices in the shader
|
||||
renderPassEncoder.End();
|
||||
}
|
||||
|
||||
wgpu::RenderPipeline& WebGPURenderPassMipmapGenerator::getOrCreatePipelineFor(
|
||||
const wgpu::TextureFormat textureFormat) {
|
||||
const uint32_t key{ static_cast<uint32_t>(textureFormat) };
|
||||
if (mPipelineByTextureFormat.find(key) == mPipelineByTextureFormat.end()) {
|
||||
mPipelineByTextureFormat[key] =
|
||||
createPipeline(mDevice, mShaderModule, mPipelineLayout, textureFormat);
|
||||
}
|
||||
return mPipelineByTextureFormat[key];
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_WEBGPURENDERPASSMIPMAPGENERATOR_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPURENDERPASSMIPMAPGENERATOR_H
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPURenderPassMipmapGenerator final {
|
||||
public:
|
||||
struct FormatCompatibility final {
|
||||
bool compatible{ false };
|
||||
std::string_view reason;
|
||||
};
|
||||
|
||||
[[nodiscard]] static FormatCompatibility getCompatibilityFor(wgpu::TextureFormat,
|
||||
wgpu::TextureDimension, uint32_t sampleCount);
|
||||
|
||||
explicit WebGPURenderPassMipmapGenerator(wgpu::Device const&);
|
||||
|
||||
void generateMipmaps(wgpu::Queue const&, wgpu::Texture const&);
|
||||
|
||||
private:
|
||||
enum class ScalarSampleType : uint8_t {
|
||||
F32,
|
||||
I32,
|
||||
U32,
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr static ScalarSampleType getScalarSampleTypeFrom(
|
||||
wgpu::TextureFormat format);
|
||||
|
||||
[[nodiscard]] wgpu::RenderPipeline& getOrCreatePipelineFor(wgpu::TextureFormat);
|
||||
|
||||
void generateMipmap(wgpu::CommandEncoder const&, wgpu::Texture const&,
|
||||
wgpu::RenderPipeline const&, uint32_t layer, uint32_t mipLevel);
|
||||
|
||||
wgpu::Device mDevice;
|
||||
const wgpu::Sampler mPreviousMipLevelSampler{ nullptr };
|
||||
const wgpu::ShaderModule mShaderModule{ nullptr };
|
||||
const wgpu::BindGroupLayout mTextureBindGroupLayout{ nullptr };
|
||||
const wgpu::PipelineLayout mPipelineLayout{ nullptr };
|
||||
tsl::robin_map<uint32_t, wgpu::RenderPipeline> mPipelineByTextureFormat;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_WEBGPURENDERPASSMIPMAPGENERATOR_H
|
||||
@@ -16,11 +16,7 @@
|
||||
|
||||
#include "WebGPURenderTarget.h"
|
||||
|
||||
#include "WebGPUTexture.h"
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <private/backend/BackendUtils.h>
|
||||
@@ -30,120 +26,30 @@
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Panics if the number of samples in the original attachment textures do not match.
|
||||
* @return The number of samples in the original attachment textures (the number/count in each one).
|
||||
* Returns 0 if there are no attachments.
|
||||
*/
|
||||
[[nodiscard]] uint8_t createMsaaSidecarTextures(const uint8_t renderTargetSampleCount,
|
||||
const TargetBufferFlags targetFlags, MRT const& colorAttachments,
|
||||
TargetBufferInfo const& depthAttachment, TargetBufferInfo const& stencilAttachment,
|
||||
std::function<WebGPUTexture*(const Handle<HwTexture>)> const& getWebGPUTexture,
|
||||
wgpu::Device const& device) {
|
||||
struct Target final {
|
||||
std::string_view name;
|
||||
TargetBufferFlags flag{ TargetBufferFlags::NONE };
|
||||
size_t colorIndex{ 0 };
|
||||
};
|
||||
// assuming 8 colors. hopefully the following static_assert will get tripped if another
|
||||
// color ever gets added
|
||||
static_assert(
|
||||
(TargetBufferFlags::COLOR0 | TargetBufferFlags::COLOR1 | TargetBufferFlags::COLOR2 |
|
||||
TargetBufferFlags::COLOR3 | TargetBufferFlags::COLOR4 |
|
||||
TargetBufferFlags::COLOR5 | TargetBufferFlags::COLOR6 |
|
||||
TargetBufferFlags::COLOR7) == TargetBufferFlags::COLOR_ALL);
|
||||
const static std::array TARGETS{
|
||||
Target{ .name = "COLOR0", .flag = TargetBufferFlags::COLOR0, .colorIndex = 0 },
|
||||
Target{ .name = "COLOR1", .flag = TargetBufferFlags::COLOR1, .colorIndex = 1 },
|
||||
Target{ .name = "COLOR2", .flag = TargetBufferFlags::COLOR2, .colorIndex = 2 },
|
||||
Target{ .name = "COLOR3", .flag = TargetBufferFlags::COLOR3, .colorIndex = 3 },
|
||||
Target{ .name = "COLOR4", .flag = TargetBufferFlags::COLOR4, .colorIndex = 4 },
|
||||
Target{ .name = "COLOR5", .flag = TargetBufferFlags::COLOR5, .colorIndex = 5 },
|
||||
Target{ .name = "COLOR6", .flag = TargetBufferFlags::COLOR6, .colorIndex = 6 },
|
||||
Target{ .name = "COLOR7", .flag = TargetBufferFlags::COLOR7, .colorIndex = 7 },
|
||||
Target{ .name = "DEPTH", .flag = TargetBufferFlags::DEPTH },
|
||||
Target{ .name = "STENCIL", .flag = TargetBufferFlags::STENCIL },
|
||||
};
|
||||
#ifndef NDEBUG
|
||||
for (auto const& target: TARGETS) {
|
||||
assert_invariant(target.colorIndex <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT &&
|
||||
"color index is >= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT, which should "
|
||||
"not be possible and could lead to us indexing into the colorAttachments "
|
||||
"array beyond its bounds.");
|
||||
}
|
||||
#endif
|
||||
bool firstAttachment{ true };
|
||||
uint8_t sampleCountPerAttachment{ 0 };
|
||||
for (auto const& target: TARGETS) {
|
||||
if (any(targetFlags & target.flag)) {
|
||||
const Handle<HwTexture> textureHandle{
|
||||
target.flag == TargetBufferFlags::DEPTH
|
||||
? depthAttachment.handle
|
||||
: (target.flag == TargetBufferFlags::STENCIL
|
||||
? stencilAttachment.handle
|
||||
: colorAttachments[target.colorIndex].handle)
|
||||
};
|
||||
WebGPUTexture* const texture{ getWebGPUTexture(textureHandle) };
|
||||
assert_invariant(texture != nullptr && "target flag indicate the use of an attachment "
|
||||
"for which we do not have a texture?");
|
||||
if (firstAttachment) {
|
||||
sampleCountPerAttachment = texture->samples;
|
||||
firstAttachment = false;
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION(texture->samples == sampleCountPerAttachment)
|
||||
<< target.name << " attachment texture has " << +texture->samples
|
||||
<< " but the other attachment(s) have " << +sampleCountPerAttachment;
|
||||
if (renderTargetSampleCount > 1 && sampleCountPerAttachment == 1) {
|
||||
texture->createMsaaSidecarTextureIfNotAlreadyCreated(renderTargetSampleCount,
|
||||
device);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_invariant(sampleCountPerAttachment);
|
||||
return sampleCountPerAttachment;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WebGPURenderTarget::WebGPURenderTarget(const uint32_t width, const uint32_t height,
|
||||
const uint8_t samples, const uint8_t layerCount, MRT const& colorAttachmentsMRT,
|
||||
Attachment const& depthAttachmentInfo, Attachment const& stencilAttachmentInfo,
|
||||
TargetBufferFlags const& targetFlags,
|
||||
std::function<WebGPUTexture*(const Handle<HwTexture>)> const& getWebGPUTexture,
|
||||
wgpu::Device const& device)
|
||||
Attachment const& depthAttachmentInfo, Attachment const& stencilAttachmentInfo)
|
||||
: HwRenderTarget{ width, height },
|
||||
mDefaultRenderTarget{ false },
|
||||
mTargetFlags{ targetFlags },
|
||||
mSamples{ samples },
|
||||
mLayerCount{ layerCount },
|
||||
mColorAttachments{ colorAttachmentsMRT },
|
||||
mDepthAttachment{ depthAttachmentInfo },
|
||||
mStencilAttachment{ stencilAttachmentInfo },
|
||||
mSampleCountPerAttachment{ createMsaaSidecarTextures(mSamples, mTargetFlags,
|
||||
mColorAttachments, mDepthAttachment, mStencilAttachment, getWebGPUTexture, device) } {
|
||||
// TODO consider possibly making this an array (that would avoid a heap allocation, but the
|
||||
// concern is the size limitation on the handle itself)
|
||||
mColorAttachmentDesc.reserve(MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
|
||||
mStencilAttachment{ stencilAttachmentInfo } {
|
||||
// TODO consider making this an array
|
||||
mColorAttachmentDescriptors.reserve(MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
|
||||
}
|
||||
|
||||
// Default constructor for the default render target
|
||||
WebGPURenderTarget::WebGPURenderTarget()
|
||||
: HwRenderTarget{ 0, 0 },
|
||||
mDefaultRenderTarget{ true },
|
||||
mTargetFlags{ TargetBufferFlags::NONE },
|
||||
mSamples{ 1 },
|
||||
mLayerCount{ 1 },
|
||||
mSampleCountPerAttachment{ 1 } {}
|
||||
mLayerCount{ 1 } {}
|
||||
|
||||
wgpu::LoadOp WebGPURenderTarget::getLoadOperation(RenderPassParams const& params,
|
||||
const TargetBufferFlags bufferToOperateOn) {
|
||||
@@ -151,7 +57,7 @@ wgpu::LoadOp WebGPURenderTarget::getLoadOperation(RenderPassParams const& params
|
||||
return wgpu::LoadOp::Clear;
|
||||
}
|
||||
if (any(params.flags.discardStart & bufferToOperateOn)) {
|
||||
return wgpu::LoadOp::Clear;
|
||||
return wgpu::LoadOp::Clear; // Or wgpu::LoadOp::Undefined if clear is not desired on discard
|
||||
}
|
||||
return wgpu::LoadOp::Load;
|
||||
}
|
||||
@@ -167,137 +73,128 @@ wgpu::StoreOp WebGPURenderTarget::getStoreOperation(RenderPassParams const& para
|
||||
void WebGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor& outDescriptor,
|
||||
RenderPassParams const& params, wgpu::TextureView const& defaultColorTextureView,
|
||||
wgpu::TextureView const& defaultDepthStencilTextureView,
|
||||
wgpu::TextureView const* customColorTextureViews,
|
||||
wgpu::TextureView const* customColorMsaaSidecarTextureViews,
|
||||
uint32_t customColorTextureViewCount,
|
||||
wgpu::TextureView const& customDepthStencilTextureView,
|
||||
wgpu::TextureView const& customDepthStencilMsaaSidecarTextureView) {
|
||||
const bool hasMsaaSidecars{ (customColorTextureViewCount > 0 &&
|
||||
customColorMsaaSidecarTextureViews[0]) ||
|
||||
customDepthStencilMsaaSidecarTextureView };
|
||||
// either all the textures have MSAA sidecars or none of them do
|
||||
if (hasMsaaSidecars) {
|
||||
FILAMENT_CHECK_PRECONDITION(std::all_of(customColorMsaaSidecarTextureViews,
|
||||
customColorMsaaSidecarTextureViews + customColorTextureViewCount,
|
||||
[](wgpu::TextureView const& msaaView) { return msaaView != nullptr; }))
|
||||
<< "A color or depth/stencil attachment texture has a MSAA sidecar but at least "
|
||||
"one other color attachment texture does not.";
|
||||
FILAMENT_CHECK_PRECONDITION(customDepthStencilMsaaSidecarTextureView != nullptr)
|
||||
<< "The color attachment texture(s) have MSAA sidecar(s) but the depth/stencil "
|
||||
"texture does not.";
|
||||
} else {
|
||||
FILAMENT_CHECK_PRECONDITION(std::all_of(customColorMsaaSidecarTextureViews,
|
||||
customColorMsaaSidecarTextureViews + customColorTextureViewCount,
|
||||
[](wgpu::TextureView const& msaaView) { return msaaView == nullptr; }))
|
||||
<< "A color or depth/stencil attachment texture does not have a MSAA sidecar but "
|
||||
"at least one color attachment texture does.";
|
||||
FILAMENT_CHECK_PRECONDITION(customDepthStencilMsaaSidecarTextureView == nullptr)
|
||||
<< "Custom color textures for the render target do not have MSAA sidecar(s) but "
|
||||
"the depth/stencil texture does.";
|
||||
}
|
||||
wgpu::TextureFormat const& defaultDepthStencilFormat,
|
||||
wgpu::TextureView const* customColorTextureViews, uint32_t customColorTextureViewCount,
|
||||
wgpu::TextureView const& customDepthTextureView,
|
||||
wgpu::TextureView const& customStencilTextureView,
|
||||
const wgpu::TextureFormat customDepthFormat,
|
||||
const wgpu::TextureFormat customStencilFormat) {
|
||||
mColorAttachmentDescriptors.clear();
|
||||
mHasDepthStencilAttachment = false;
|
||||
|
||||
mColorAttachmentDesc.clear();
|
||||
|
||||
const bool hasDepth = any(mTargetFlags & TargetBufferFlags::DEPTH);
|
||||
const bool hasStencil = any(mTargetFlags & TargetBufferFlags::STENCIL);
|
||||
mHasDepthStencilAttachment = hasDepth || hasStencil;
|
||||
bool const depthReadOnly = (params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0;
|
||||
|
||||
// Color attachments
|
||||
if (mDefaultRenderTarget) {
|
||||
assert_invariant(defaultColorTextureView);
|
||||
mColorAttachmentDesc.push_back({
|
||||
.view = defaultColorTextureView,
|
||||
mColorAttachmentDescriptors.push_back({ .view = defaultColorTextureView,
|
||||
.resolveTarget = nullptr,
|
||||
.loadOp = WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::COLOR0),
|
||||
.storeOp = WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::COLOR0),
|
||||
.clearValue = {
|
||||
.r = params.clearColor.r,
|
||||
.g = params.clearColor.g,
|
||||
.b = params.clearColor.b,
|
||||
.a = params.clearColor.a,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
.clearValue = { params.clearColor.r, params.clearColor.g, params.clearColor.b,
|
||||
params.clearColor.a } });
|
||||
|
||||
if (defaultDepthStencilTextureView) {
|
||||
mDepthStencilAttachmentDescriptor = {
|
||||
.view = defaultDepthStencilTextureView,
|
||||
.depthLoadOp =
|
||||
WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::DEPTH),
|
||||
.depthStoreOp =
|
||||
WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::DEPTH),
|
||||
.depthClearValue = static_cast<float>(params.clearDepth),
|
||||
.depthReadOnly =
|
||||
(params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0,
|
||||
.stencilLoadOp = wgpu::LoadOp::Undefined,
|
||||
.stencilStoreOp = wgpu::StoreOp::Undefined,
|
||||
.stencilClearValue = params.clearStencil,
|
||||
.stencilReadOnly =
|
||||
(params.readOnlyDepthStencil & RenderPassParams::READONLY_STENCIL) > 0,
|
||||
};
|
||||
|
||||
if (defaultDepthStencilFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
|
||||
defaultDepthStencilFormat == wgpu::TextureFormat::Depth32FloatStencil8 ||
|
||||
defaultDepthStencilFormat == wgpu::TextureFormat::Stencil8) {
|
||||
mDepthStencilAttachmentDescriptor.stencilLoadOp =
|
||||
WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::STENCIL);
|
||||
mDepthStencilAttachmentDescriptor.stencilStoreOp =
|
||||
WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::STENCIL);
|
||||
}
|
||||
mHasDepthStencilAttachment = true;
|
||||
}
|
||||
} else { // Custom Render Target
|
||||
for (uint32_t i = 0; i < customColorTextureViewCount; ++i) {
|
||||
if (customColorTextureViews[i]) {
|
||||
const wgpu::TextureView msaaSidecar{ customColorMsaaSidecarTextureViews[i] };
|
||||
mColorAttachmentDesc.push_back({
|
||||
.view = hasMsaaSidecars ? msaaSidecar : customColorTextureViews[i],
|
||||
.resolveTarget = hasMsaaSidecars ? customColorTextureViews[i] : nullptr,
|
||||
mColorAttachmentDescriptors.push_back({ .view = customColorTextureViews[i],
|
||||
// .resolveTarget = nullptr; // TODO: MSAA resolve for custom RT
|
||||
.loadOp =
|
||||
WebGPURenderTarget::getLoadOperation(params, getTargetBufferFlagsAt(i)),
|
||||
.storeOp = WebGPURenderTarget::getStoreOperation(params,
|
||||
getTargetBufferFlagsAt(i)),
|
||||
.clearValue = {
|
||||
.r = params.clearColor.r,
|
||||
.clearValue = { .r = params.clearColor.r,
|
||||
.g = params.clearColor.g,
|
||||
.b = params.clearColor.b,
|
||||
.a = params.clearColor.a,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
outDescriptor.colorAttachmentCount = mColorAttachmentDesc.size();
|
||||
outDescriptor.colorAttachments = mColorAttachmentDesc.data();
|
||||
|
||||
// Depth/Stencil Attachments
|
||||
if (mHasDepthStencilAttachment) {
|
||||
wgpu::TextureView depthStencilViewToUse{ nullptr };
|
||||
if (mDefaultRenderTarget) {
|
||||
depthStencilViewToUse = defaultDepthStencilTextureView;
|
||||
assert_invariant(depthStencilViewToUse);
|
||||
} else {
|
||||
if (customDepthStencilTextureView) {
|
||||
depthStencilViewToUse = hasMsaaSidecars ? customDepthStencilMsaaSidecarTextureView
|
||||
: customDepthStencilTextureView;
|
||||
.a = params.clearColor.a } });
|
||||
}
|
||||
}
|
||||
|
||||
if (depthStencilViewToUse) {
|
||||
mDepthStencilAttachmentDesc = {};
|
||||
mDepthStencilAttachmentDesc.view = depthStencilViewToUse;
|
||||
FILAMENT_CHECK_POSTCONDITION(!(customDepthTextureView && customStencilTextureView))
|
||||
<< "WebGPU CANNOT support separate texture views for depth + stencil. depth + "
|
||||
"stencil needs to be in one texture view";
|
||||
|
||||
const bool hasStencil =
|
||||
customStencilTextureView ||
|
||||
(customDepthFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
|
||||
customDepthFormat == wgpu::TextureFormat::Depth32FloatStencil8);
|
||||
|
||||
const bool hasDepth =
|
||||
customDepthTextureView ||
|
||||
(customStencilFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
|
||||
customDepthFormat == wgpu::TextureFormat::Depth32FloatStencil8);
|
||||
|
||||
if (customDepthTextureView || customStencilTextureView) {
|
||||
assert_invariant((hasDepth || hasStencil) &&
|
||||
"Depth or Texture view without a valid texture format");
|
||||
mDepthStencilAttachmentDescriptor = {};
|
||||
mDepthStencilAttachmentDescriptor.view =
|
||||
customDepthTextureView ? customDepthTextureView : customStencilTextureView;
|
||||
|
||||
if (hasDepth) {
|
||||
mDepthStencilAttachmentDesc.depthLoadOp =
|
||||
depthReadOnly ? wgpu::LoadOp::Undefined
|
||||
: getLoadOperation(params, TargetBufferFlags::DEPTH);
|
||||
mDepthStencilAttachmentDesc.depthStoreOp =
|
||||
depthReadOnly ? wgpu::StoreOp::Undefined
|
||||
: getStoreOperation(params, TargetBufferFlags::DEPTH);
|
||||
mDepthStencilAttachmentDesc.depthClearValue = static_cast<float>(params.clearDepth);
|
||||
mDepthStencilAttachmentDesc.depthReadOnly = depthReadOnly;
|
||||
mDepthStencilAttachmentDescriptor.depthLoadOp =
|
||||
WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::DEPTH);
|
||||
mDepthStencilAttachmentDescriptor.depthStoreOp =
|
||||
WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::DEPTH);
|
||||
mDepthStencilAttachmentDescriptor.depthClearValue =
|
||||
static_cast<float>(params.clearDepth);
|
||||
mDepthStencilAttachmentDescriptor.depthReadOnly =
|
||||
(params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0;
|
||||
} else {
|
||||
mDepthStencilAttachmentDesc.depthLoadOp = wgpu::LoadOp::Undefined;
|
||||
mDepthStencilAttachmentDesc.depthStoreOp = wgpu::StoreOp::Undefined;
|
||||
mDepthStencilAttachmentDesc.depthReadOnly = true;
|
||||
mDepthStencilAttachmentDescriptor.depthLoadOp = wgpu::LoadOp::Undefined;
|
||||
mDepthStencilAttachmentDescriptor.depthStoreOp = wgpu::StoreOp::Undefined;
|
||||
mDepthStencilAttachmentDescriptor.depthReadOnly = true;
|
||||
}
|
||||
|
||||
if (hasStencil) {
|
||||
mDepthStencilAttachmentDesc.stencilLoadOp =
|
||||
getLoadOperation(params, TargetBufferFlags::STENCIL);
|
||||
mDepthStencilAttachmentDesc.stencilStoreOp =
|
||||
getStoreOperation(params, TargetBufferFlags::STENCIL);
|
||||
mDepthStencilAttachmentDesc.stencilClearValue = params.clearStencil;
|
||||
mDepthStencilAttachmentDesc.stencilReadOnly =
|
||||
mDepthStencilAttachmentDescriptor.stencilLoadOp =
|
||||
WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::STENCIL);
|
||||
mDepthStencilAttachmentDescriptor.stencilStoreOp =
|
||||
WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::STENCIL);
|
||||
mDepthStencilAttachmentDescriptor.stencilClearValue = params.clearStencil;
|
||||
mDepthStencilAttachmentDescriptor.stencilReadOnly =
|
||||
(params.readOnlyDepthStencil & RenderPassParams::READONLY_STENCIL) > 0;
|
||||
} else {
|
||||
mDepthStencilAttachmentDesc.stencilLoadOp = wgpu::LoadOp::Undefined;
|
||||
mDepthStencilAttachmentDesc.stencilStoreOp = wgpu::StoreOp::Undefined;
|
||||
mDepthStencilAttachmentDesc.stencilReadOnly = true;
|
||||
mDepthStencilAttachmentDescriptor.stencilLoadOp = wgpu::LoadOp::Undefined;
|
||||
mDepthStencilAttachmentDescriptor.stencilStoreOp = wgpu::StoreOp::Undefined;
|
||||
mDepthStencilAttachmentDescriptor.stencilReadOnly = true;
|
||||
}
|
||||
outDescriptor.depthStencilAttachment = &mDepthStencilAttachmentDesc;
|
||||
|
||||
} else {
|
||||
outDescriptor.depthStencilAttachment = nullptr;
|
||||
mDepthStencilAttachmentDesc = {};
|
||||
mHasDepthStencilAttachment = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
outDescriptor.depthStencilAttachment = nullptr;
|
||||
mDepthStencilAttachmentDesc = {};
|
||||
}
|
||||
|
||||
outDescriptor.colorAttachmentCount = mColorAttachmentDescriptors.size();
|
||||
outDescriptor.colorAttachments = mColorAttachmentDescriptors.data();
|
||||
outDescriptor.depthStencilAttachment =
|
||||
mHasDepthStencilAttachment ? &mDepthStencilAttachmentDescriptor : nullptr;
|
||||
|
||||
// descriptor.sampleCount was removed from the core spec. If your webgpu.h still has it,
|
||||
// and your Dawn version expects it, you might need to set it here based on this->samples.
|
||||
// e.g., descriptor.sampleCount = this->samples;
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
|
||||
|
||||
#include "WebGPUTexture.h"
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
@@ -26,7 +24,6 @@
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
@@ -37,44 +34,44 @@ public:
|
||||
|
||||
WebGPURenderTarget(uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount,
|
||||
MRT const& colorAttachments, Attachment const& depthAttachment,
|
||||
Attachment const& stencilAttachment, TargetBufferFlags const& targetFlags,
|
||||
std::function<WebGPUTexture*(const Handle<HwTexture>)> const&, wgpu::Device const&);
|
||||
Attachment const& stencilAttachment);
|
||||
|
||||
// Default constructor for the default render target
|
||||
WebGPURenderTarget();
|
||||
|
||||
void setUpRenderPassAttachments(wgpu::RenderPassDescriptor& outDescriptor,
|
||||
// Updated signature: takes resolved views for custom RTs, and default views for default RT
|
||||
void setUpRenderPassAttachments(
|
||||
wgpu::RenderPassDescriptor& outDescriptor,
|
||||
RenderPassParams const& params,
|
||||
// For default render target:
|
||||
wgpu::TextureView const& defaultColorTextureView,
|
||||
wgpu::TextureView const& defaultDepthStencilTextureView,
|
||||
wgpu::TextureFormat const& defaultDepthStencilFormat,
|
||||
// For custom render targets:
|
||||
wgpu::TextureView const* customColorTextureViews, // Array of views
|
||||
wgpu::TextureView const* customColorMsaaSidecarTextureViews, // nullptrs if N/A
|
||||
wgpu::TextureView const* customColorTextureViews, // Array of views
|
||||
uint32_t customColorTextureViewCount,
|
||||
wgpu::TextureView const& customDepthStencilTextureView,
|
||||
wgpu::TextureView const& customDepthStencilMsaaSidecarTextureView /* nullptr if N/A */);
|
||||
wgpu::TextureView const& customDepthTextureView,
|
||||
wgpu::TextureView const& customStencilTextureView,
|
||||
wgpu::TextureFormat customDepthFormat,
|
||||
wgpu::TextureFormat customStencilFormat);
|
||||
|
||||
[[nodiscard]] bool isDefaultRenderTarget() const { return mDefaultRenderTarget; }
|
||||
[[nodiscard]] uint8_t getSamples() const { return mSamples; }
|
||||
[[nodiscard]] uint8_t getSampleCountPerAttachment() const { return mSampleCountPerAttachment; }
|
||||
[[nodiscard]] uint8_t getLayerCount() const { return mLayerCount; }
|
||||
|
||||
// Accessors for the driver to get stored attachment info
|
||||
[[nodiscard]] MRT const& getColorAttachmentInfos() const { return mColorAttachments; }
|
||||
[[nodiscard]] Attachment const& getDepthAttachmentInfo() const { return mDepthAttachment; }
|
||||
[[nodiscard]] Attachment const& getStencilAttachmentInfo() const { return mStencilAttachment; }
|
||||
|
||||
// Static helpers for load/store operations
|
||||
[[nodiscard]] static wgpu::LoadOp getLoadOperation(RenderPassParams const& params,
|
||||
TargetBufferFlags buffer);
|
||||
[[nodiscard]] static wgpu::StoreOp getStoreOperation(RenderPassParams const& params,
|
||||
TargetBufferFlags buffer);
|
||||
|
||||
[[nodiscard]] TargetBufferFlags getTargetFlags() const { return mTargetFlags; }
|
||||
void setTargetFlags( TargetBufferFlags value) { mTargetFlags = value; }
|
||||
|
||||
private:
|
||||
bool mDefaultRenderTarget = false;
|
||||
TargetBufferFlags mTargetFlags = TargetBufferFlags::NONE;
|
||||
uint8_t mSamples = 1;
|
||||
uint8_t mLayerCount = 1;
|
||||
|
||||
@@ -84,11 +81,9 @@ private:
|
||||
Attachment mDepthAttachment{};
|
||||
Attachment mStencilAttachment{};
|
||||
|
||||
uint8_t mSampleCountPerAttachment = 0;
|
||||
|
||||
// Cached descriptors for the render pass
|
||||
std::vector<wgpu::RenderPassColorAttachment> mColorAttachmentDesc;
|
||||
wgpu::RenderPassDepthStencilAttachment mDepthStencilAttachmentDesc{};
|
||||
std::vector<wgpu::RenderPassColorAttachment> mColorAttachmentDescriptors;
|
||||
wgpu::RenderPassDepthStencilAttachment mDepthStencilAttachmentDescriptor{};
|
||||
bool mHasDepthStencilAttachment = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -123,122 +123,6 @@ template<typename WebGPUPrintable>
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::string_view webGPUTextureFormatToString(
|
||||
const wgpu::TextureFormat format) {
|
||||
switch (format) {
|
||||
case wgpu::TextureFormat::Undefined: return "Undefined";
|
||||
case wgpu::TextureFormat::R8Unorm: return "R8Unorm";
|
||||
case wgpu::TextureFormat::R8Snorm: return "R8Snorm";
|
||||
case wgpu::TextureFormat::R8Uint: return "R8Uint";
|
||||
case wgpu::TextureFormat::R8Sint: return "R8Sint";
|
||||
case wgpu::TextureFormat::R16Uint: return "R16Uint";
|
||||
case wgpu::TextureFormat::R16Sint: return "R16Sint";
|
||||
case wgpu::TextureFormat::R16Float: return "R16Float";
|
||||
case wgpu::TextureFormat::RG8Unorm: return "RG8Unorm";
|
||||
case wgpu::TextureFormat::RG8Snorm: return "RG8Snorm";
|
||||
case wgpu::TextureFormat::RG8Uint: return "RG8Uint";
|
||||
case wgpu::TextureFormat::RG8Sint: return "RG8Sint";
|
||||
case wgpu::TextureFormat::R32Float: return "R32Float";
|
||||
case wgpu::TextureFormat::R32Uint: return "R32Uint";
|
||||
case wgpu::TextureFormat::R32Sint: return "R32Sint";
|
||||
case wgpu::TextureFormat::RG16Uint: return "RG16Uint";
|
||||
case wgpu::TextureFormat::RG16Sint: return "RG16Sint";
|
||||
case wgpu::TextureFormat::RG16Float: return "RG16Float";
|
||||
case wgpu::TextureFormat::RGBA8Unorm: return "RGBA8Unorm";
|
||||
case wgpu::TextureFormat::RGBA8UnormSrgb: return "RGBA8UnormSrgb";
|
||||
case wgpu::TextureFormat::RGBA8Snorm: return "RGBA8Snorm";
|
||||
case wgpu::TextureFormat::RGBA8Uint: return "RGBA8Uint";
|
||||
case wgpu::TextureFormat::RGBA8Sint: return "RGBA8Sint";
|
||||
case wgpu::TextureFormat::BGRA8Unorm: return "BGRA8Unorm";
|
||||
case wgpu::TextureFormat::BGRA8UnormSrgb: return "BGRA8UnormSrgb";
|
||||
case wgpu::TextureFormat::RGB10A2Uint: return "RGB10A2Uint";
|
||||
case wgpu::TextureFormat::RGB10A2Unorm: return "RGB10A2Unorm";
|
||||
case wgpu::TextureFormat::RG11B10Ufloat: return "RG11B10Ufloat";
|
||||
case wgpu::TextureFormat::RGB9E5Ufloat: return "RGB9E5Ufloat";
|
||||
case wgpu::TextureFormat::RG32Float: return "RG32Float";
|
||||
case wgpu::TextureFormat::RG32Uint: return "RG32Uint";
|
||||
case wgpu::TextureFormat::RG32Sint: return "RG32Sint";
|
||||
case wgpu::TextureFormat::RGBA16Uint: return "RGBA16Uint";
|
||||
case wgpu::TextureFormat::RGBA16Sint: return "RGBA16Sint";
|
||||
case wgpu::TextureFormat::RGBA16Float: return "RGBA16Float";
|
||||
case wgpu::TextureFormat::RGBA32Float: return "RGBA32Float";
|
||||
case wgpu::TextureFormat::RGBA32Uint: return "RGBA32Uint";
|
||||
case wgpu::TextureFormat::RGBA32Sint: return "RGBA32Sint";
|
||||
case wgpu::TextureFormat::Stencil8: return "Stencil8";
|
||||
case wgpu::TextureFormat::Depth16Unorm: return "Depth16Unorm";
|
||||
case wgpu::TextureFormat::Depth24Plus: return "Depth24Plus";
|
||||
case wgpu::TextureFormat::Depth24PlusStencil8: return "Depth24PlusStencil8";
|
||||
case wgpu::TextureFormat::Depth32Float: return "Depth32Float";
|
||||
case wgpu::TextureFormat::Depth32FloatStencil8: return "Depth32FloatStencil8";
|
||||
case wgpu::TextureFormat::BC1RGBAUnorm: return "BC1RGBAUnorm";
|
||||
case wgpu::TextureFormat::BC1RGBAUnormSrgb: return "BC1RGBAUnormSrgb";
|
||||
case wgpu::TextureFormat::BC2RGBAUnorm: return "BC2RGBAUnorm";
|
||||
case wgpu::TextureFormat::BC2RGBAUnormSrgb: return "BC2RGBAUnormSrgb";
|
||||
case wgpu::TextureFormat::BC3RGBAUnorm: return "BC3RGBAUnorm";
|
||||
case wgpu::TextureFormat::BC3RGBAUnormSrgb: return "BC3RGBAUnormSrgb";
|
||||
case wgpu::TextureFormat::BC4RUnorm: return "BC4RUnorm";
|
||||
case wgpu::TextureFormat::BC4RSnorm: return "BC4RSnorm";
|
||||
case wgpu::TextureFormat::BC5RGUnorm: return "BC5RGUnorm";
|
||||
case wgpu::TextureFormat::BC5RGSnorm: return "BC5RGSnorm";
|
||||
case wgpu::TextureFormat::BC6HRGBUfloat: return "BC6HRGBUfloat";
|
||||
case wgpu::TextureFormat::BC6HRGBFloat: return "BC6HRGBFloat";
|
||||
case wgpu::TextureFormat::BC7RGBAUnorm: return "BC7RGBAUnorm";
|
||||
case wgpu::TextureFormat::BC7RGBAUnormSrgb: return "BC7RGBAUnormSrgb";
|
||||
case wgpu::TextureFormat::ETC2RGB8Unorm: return "ETC2RGB8Unorm";
|
||||
case wgpu::TextureFormat::ETC2RGB8UnormSrgb: return "ETC2RGB8UnormSrgb";
|
||||
case wgpu::TextureFormat::ETC2RGB8A1Unorm: return "ETC2RGB8A1Unorm";
|
||||
case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb: return "ETC2RGB8A1UnormSrgb";
|
||||
case wgpu::TextureFormat::ETC2RGBA8Unorm: return "ETC2RGBA8Unorm";
|
||||
case wgpu::TextureFormat::ETC2RGBA8UnormSrgb: return "ETC2RGBA8UnormSrgb";
|
||||
case wgpu::TextureFormat::EACR11Unorm: return "EACR11Unorm";
|
||||
case wgpu::TextureFormat::EACR11Snorm: return "EACR11Snorm";
|
||||
case wgpu::TextureFormat::EACRG11Unorm: return "EACRG11Unorm";
|
||||
case wgpu::TextureFormat::EACRG11Snorm: return "EACRG11Snorm";
|
||||
case wgpu::TextureFormat::ASTC4x4Unorm: return "ASTC4x4Unorm";
|
||||
case wgpu::TextureFormat::ASTC4x4UnormSrgb: return "ASTC4x4UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC5x4Unorm: return "ASTC5x4Unorm";
|
||||
case wgpu::TextureFormat::ASTC5x4UnormSrgb: return "ASTC5x4UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC5x5Unorm: return "ASTC5x5Unorm";
|
||||
case wgpu::TextureFormat::ASTC5x5UnormSrgb: return "ASTC5x5UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC6x5Unorm: return "ASTC6x5Unorm";
|
||||
case wgpu::TextureFormat::ASTC6x5UnormSrgb: return "ASTC6x5UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC6x6Unorm: return "ASTC6x6Unorm";
|
||||
case wgpu::TextureFormat::ASTC6x6UnormSrgb: return "ASTC6x6UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC8x5Unorm: return "ASTC8x5Unorm";
|
||||
case wgpu::TextureFormat::ASTC8x5UnormSrgb: return "ASTC8x5UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC8x6Unorm: return "ASTC8x6Unorm";
|
||||
case wgpu::TextureFormat::ASTC8x6UnormSrgb: return "ASTC8x6UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC8x8Unorm: return "ASTC8x8Unorm";
|
||||
case wgpu::TextureFormat::ASTC8x8UnormSrgb: return "ASTC8x8UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC10x5Unorm: return "ASTC10x5Unorm";
|
||||
case wgpu::TextureFormat::ASTC10x5UnormSrgb: return "ASTC10x5UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC10x6Unorm: return "ASTC10x6Unorm";
|
||||
case wgpu::TextureFormat::ASTC10x6UnormSrgb: return "ASTC10x6UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC10x8Unorm: return "ASTC10x8Unorm";
|
||||
case wgpu::TextureFormat::ASTC10x8UnormSrgb: return "ASTC10x8UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC10x10Unorm: return "ASTC10x10Unorm";
|
||||
case wgpu::TextureFormat::ASTC10x10UnormSrgb: return "ASTC10x10UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC12x10Unorm: return "ASTC12x10Unorm";
|
||||
case wgpu::TextureFormat::ASTC12x10UnormSrgb: return "ASTC12x10UnormSrgb";
|
||||
case wgpu::TextureFormat::ASTC12x12Unorm: return "ASTC12x12Unorm";
|
||||
case wgpu::TextureFormat::ASTC12x12UnormSrgb: return "ASTC12x12UnormSrgb";
|
||||
case wgpu::TextureFormat::R16Unorm: return "R16Unorm";
|
||||
case wgpu::TextureFormat::RG16Unorm: return "RG16Unorm";
|
||||
case wgpu::TextureFormat::RGBA16Unorm: return "RGBA16Unorm";
|
||||
case wgpu::TextureFormat::R16Snorm: return "R16Snorm";
|
||||
case wgpu::TextureFormat::RG16Snorm: return "RG16Snorm";
|
||||
case wgpu::TextureFormat::RGBA16Snorm: return "RGBA16Snorm";
|
||||
case wgpu::TextureFormat::R8BG8Biplanar420Unorm: return "R8BG8Biplanar420Unorm";
|
||||
case wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm: return "R10X6BG10X6Biplanar420Unorm";
|
||||
case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm: return "R8BG8A8Triplanar420Unorm";
|
||||
case wgpu::TextureFormat::R8BG8Biplanar422Unorm: return "R8BG8Biplanar422Unorm";
|
||||
case wgpu::TextureFormat::R8BG8Biplanar444Unorm: return "R8BG8Biplanar444Unorm";
|
||||
case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm: return "R10X6BG10X6Biplanar422Unorm";
|
||||
case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm: return "R10X6BG10X6Biplanar444Unorm";
|
||||
case wgpu::TextureFormat::External: return "External";
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::string_view filamentShaderStageToString(const ShaderStage stage) {
|
||||
switch (stage) {
|
||||
case ShaderStage::VERTEX: return "vertex";
|
||||
|
||||
@@ -42,7 +42,7 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
|
||||
if (capabilities.formatCount > 0 && capabilities.formats != nullptr) {
|
||||
std::for_each(capabilities.formats, capabilities.formats + capabilities.formatCount,
|
||||
[](wgpu::TextureFormat const format) {
|
||||
FWGPU_LOGI << " " << webGPUTextureFormatToString(format);
|
||||
FWGPU_LOGI << " " << webGPUPrintableToString(format);
|
||||
});
|
||||
}
|
||||
FWGPU_LOGI << " surface present modes (" << capabilities.presentModeCount << "):";
|
||||
@@ -66,7 +66,7 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
|
||||
void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config,
|
||||
wgpu::TextureFormat depthFormat) {
|
||||
FWGPU_LOGI << "WebGPU surface configuration:";
|
||||
FWGPU_LOGI << " surface format: " << webGPUTextureFormatToString(config.format);
|
||||
FWGPU_LOGI << " surface format: " << webGPUPrintableToString(config.format);
|
||||
FWGPU_LOGI << " surface usage: " << webGPUPrintableToString(config.usage);
|
||||
FWGPU_LOGI << " surface view formats (" << config.viewFormatCount << "):";
|
||||
if (config.viewFormatCount > 0 && config.viewFormats != nullptr) {
|
||||
@@ -79,7 +79,7 @@ void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config,
|
||||
FWGPU_LOGI << " surface width: " << config.width;
|
||||
FWGPU_LOGI << " surface height: " << config.height;
|
||||
FWGPU_LOGI << " surface present mode: " << webGPUPrintableToString(config.presentMode);
|
||||
FWGPU_LOGI << "WebGPU selected depth format: " << webGPUTextureFormatToString(depthFormat);
|
||||
FWGPU_LOGI << "WebGPU selected depth format: " << webGPUPrintableToString(depthFormat);
|
||||
}
|
||||
#endif// FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
|
||||
@@ -110,6 +110,7 @@ void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config,
|
||||
return wgpu::TextureFormat::Depth24PlusStencil8;
|
||||
}
|
||||
} else {
|
||||
// other options: Depth16Unorm or Depth24Plus
|
||||
return wgpu::TextureFormat::Depth32Float;
|
||||
}
|
||||
}
|
||||
@@ -216,9 +217,10 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::TextureView createDepthTextureView(wgpu::Texture const& depthTexture,
|
||||
wgpu::TextureFormat const& depthFormat, bool const needStencil) {
|
||||
wgpu::TextureFormat depthFormat, bool needStencil) {
|
||||
wgpu::TextureViewDescriptor descriptor{
|
||||
.label = "depth_texture_view",
|
||||
.format = depthFormat,
|
||||
.dimension = wgpu::TextureViewDimension::e2D,
|
||||
.baseMipLevel = 0,
|
||||
.mipLevelCount = 1,
|
||||
@@ -229,16 +231,6 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
|
||||
};
|
||||
if (needStencil) {
|
||||
descriptor.aspect = wgpu::TextureAspect::All;
|
||||
descriptor.format = depthFormat;
|
||||
} else {
|
||||
descriptor.aspect = wgpu::TextureAspect::DepthOnly;
|
||||
if (depthFormat == wgpu::TextureFormat::Depth32FloatStencil8) {
|
||||
descriptor.format = wgpu::TextureFormat::Depth32Float;
|
||||
} else if (depthFormat == wgpu::TextureFormat::Depth24PlusStencil8) {
|
||||
descriptor.format = wgpu::TextureFormat::Depth24Plus;
|
||||
} else {
|
||||
descriptor.format = depthFormat;
|
||||
}
|
||||
}
|
||||
wgpu::TextureView depthTextureView = depthTexture.CreateView(&descriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(depthTextureView) << "Failed to create depth texture view";
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
#include "WebGPUTexture.h"
|
||||
|
||||
#include "WebGPUConstants.h"
|
||||
#include "WebGPURenderPassMipmapGenerator.h"
|
||||
#include "WebGPUStrings.h"
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include "private/backend/BackendUtils.h"
|
||||
@@ -142,13 +140,10 @@ namespace {
|
||||
* @param samples How many samples to use for MSAA
|
||||
* @param needsComputeStorageSupport if we need to use this texture as storage binding in something
|
||||
* like a compute shader
|
||||
* @param needsRenderAttachmentSupport if we need to use this texture as a render pass attachment
|
||||
* in something like a render pass blit (e.g. mipmap generation)
|
||||
* @return The appropriate texture usage flags for the underlying texture
|
||||
*/
|
||||
[[nodiscard]] wgpu::TextureUsage fToWGPUTextureUsage(TextureUsage const& fUsage,
|
||||
const uint8_t samples, const bool needsComputeStorageSupport,
|
||||
const bool needsRenderAttachmentSupport) {
|
||||
const uint8_t samples, const bool needsComputeStorageSupport) {
|
||||
wgpu::TextureUsage retUsage = wgpu::TextureUsage::None;
|
||||
|
||||
// if needsComputeStorageSupport we need to read and write to the texture in a shader and thus
|
||||
@@ -169,16 +164,6 @@ namespace {
|
||||
if (needsComputeStorageSupport) {
|
||||
retUsage |= wgpu::TextureUsage::StorageBinding;
|
||||
}
|
||||
if (needsRenderAttachmentSupport) {
|
||||
retUsage |= wgpu::TextureUsage::RenderAttachment;
|
||||
}
|
||||
if (any(TextureUsage::BLIT_SRC & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::RenderAttachment;
|
||||
}
|
||||
if (any(TextureUsage::BLIT_DST & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::RenderAttachment;
|
||||
retUsage |= wgpu::TextureUsage::TextureBinding;
|
||||
}
|
||||
// WGPU Render attachment covers either color or stencil situation dependant
|
||||
// NOTE: Depth attachment isn't used this way in Vulkan but logically maps to WGPU docs. If
|
||||
// issues, investigate here.
|
||||
@@ -273,26 +258,6 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] WebGPUTexture::MipmapGenerationStrategy determineMipmapGenerationStrategy(
|
||||
const wgpu::TextureFormat format, const SamplerType samplerType, const uint8_t sampleCount,
|
||||
const uint8_t mipmapLevels) {
|
||||
if (mipmapLevels <= 1) {
|
||||
return WebGPUTexture::MipmapGenerationStrategy::NONE; // no mipmap generation needed
|
||||
}
|
||||
const WebGPURenderPassMipmapGenerator::FormatCompatibility renderPassCompatibility{
|
||||
WebGPURenderPassMipmapGenerator::getCompatibilityFor(format,
|
||||
toWebGPUTextureDimension(samplerType), sampleCount)
|
||||
};
|
||||
if (renderPassCompatibility.compatible) {
|
||||
return WebGPUTexture::MipmapGenerationStrategy::RENDER_PASS;
|
||||
}
|
||||
if (WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(format)) {
|
||||
return WebGPUTexture::MipmapGenerationStrategy::SPD_COMPUTE_PASS;
|
||||
}
|
||||
// we don't have a way to generate mipmaps for this texture, at least at this time
|
||||
return WebGPUTexture::MipmapGenerationStrategy::NONE;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr wgpu::Extent3D toTextureSize(const SamplerType samplerType,
|
||||
const uint32_t width, const uint32_t height, const uint32_t depth) {
|
||||
switch (samplerType) {
|
||||
@@ -330,17 +295,14 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
|
||||
wgpu::Device const& device) noexcept
|
||||
: HwTexture{ samplerType, levels, samples, width, height, depth, format, usage },
|
||||
mViewFormat{ fToWGPUTextureFormat(format) },
|
||||
mMipmapGenerationStrategy{ determineMipmapGenerationStrategy(mViewFormat, samplerType,
|
||||
samples, levels) },
|
||||
mWebGPUFormat{ mMipmapGenerationStrategy == MipmapGenerationStrategy::SPD_COMPUTE_PASS
|
||||
mSupportsMultipleMipLevels{ levels > 1 &&
|
||||
supportsMultipleMipLevelsViaStorageBinding(mViewFormat) },
|
||||
mWebGPUFormat{ mSupportsMultipleMipLevels
|
||||
? storageBindingCompatibleFormatForViewFormat(mViewFormat)
|
||||
: mViewFormat },
|
||||
mAspect{ fToWGPUTextureViewAspect(usage, format) },
|
||||
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples,
|
||||
mMipmapGenerationStrategy == MipmapGenerationStrategy::SPD_COMPUTE_PASS,
|
||||
mMipmapGenerationStrategy == MipmapGenerationStrategy::RENDER_PASS) },
|
||||
mViewUsage{ fToWGPUTextureUsage(usage, samples, false, false) },
|
||||
mDimension{toWebGPUTextureViewDimension(samplerType)},
|
||||
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples, mSupportsMultipleMipLevels) },
|
||||
mViewUsage{ fToWGPUTextureUsage(usage, samples, false) },
|
||||
mBlockWidth{ filament::backend::getBlockWidth(format) },
|
||||
mBlockHeight{ filament::backend::getBlockHeight(format) },
|
||||
mDefaultMipLevel{ 0 },
|
||||
@@ -390,7 +352,7 @@ WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
|
||||
: HwTexture{ src->target, levelCount, src->samples, src->width, src->height, src->depth,
|
||||
src->format, src->usage },
|
||||
mViewFormat{ src->mViewFormat },
|
||||
mMipmapGenerationStrategy{ src->mMipmapGenerationStrategy },
|
||||
mSupportsMultipleMipLevels{ src->mSupportsMultipleMipLevels },
|
||||
mWebGPUFormat{ src->mWebGPUFormat },
|
||||
mAspect{ src->mAspect },
|
||||
mWebGPUUsage{ src->mWebGPUUsage },
|
||||
@@ -402,21 +364,7 @@ WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
|
||||
mDefaultMipLevel{ baseLevel },
|
||||
mDefaultBaseArrayLayer{ src->mArrayLayerCount },
|
||||
mDefaultTextureView{ makeTextureView(mDefaultMipLevel, levelCount, 0, mDefaultBaseArrayLayer,
|
||||
src->target) },
|
||||
mMsaaSidecarTexture{ src->mMsaaSidecarTexture } {}
|
||||
|
||||
wgpu::Texture const& WebGPUTexture::getMsaaSidecarTexture(const uint8_t sampleCount) const {
|
||||
if (mMsaaSidecarTexture == nullptr) {
|
||||
return mMsaaSidecarTexture; // nullptr (no such sidecar)
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION(sampleCount == mMsaaSidecarTexture.GetSampleCount())
|
||||
<< "The MSAA sidecar texture has a different sample count ("
|
||||
<< mMsaaSidecarTexture.GetSampleCount() << ") than requested (" << +sampleCount
|
||||
<< "). Note that this restriction was written when WebGPU only supported msaa "
|
||||
"textures with 4 samples. If that has changed, this implementation should be "
|
||||
"updated (e.g. map of sidecar textures by sampleCount or something).";
|
||||
return mMsaaSidecarTexture;
|
||||
}
|
||||
src->target) } {}
|
||||
|
||||
bool WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(const wgpu::TextureFormat format) {
|
||||
return storageBindingCompatibleFormatForViewFormat(format) != wgpu::TextureFormat::Undefined;
|
||||
@@ -431,68 +379,6 @@ wgpu::TextureView WebGPUTexture::getOrMakeTextureView(const uint8_t mipLevel,
|
||||
return makeTextureView(mipLevel, 1, arrayLayer, 1, target);
|
||||
}
|
||||
|
||||
void WebGPUTexture::createMsaaSidecarTextureIfNotAlreadyCreated(const uint8_t samples,
|
||||
wgpu::Device const& device) {
|
||||
FILAMENT_CHECK_PRECONDITION(samples > 1) << "Requesting to create a MSAA sidecar texture for "
|
||||
<< +samples << " samples? Invalid request.";
|
||||
if (mMsaaSidecarTexture) {
|
||||
FILAMENT_CHECK_PRECONDITION(mMsaaSidecarTexture.GetSampleCount() == samples)
|
||||
<< "An MSAA sidecar texture has already been created for this texture, but with a "
|
||||
"different sample count ("
|
||||
<< mMsaaSidecarTexture.GetSampleCount() << ") than requested (" << +samples
|
||||
<< "). Note that this restriction was written when WebGPU only supported msaa "
|
||||
"textures with 4 samples. If that has changed, this implementation should be "
|
||||
"updated (e.g. map of sidecar textures by sampleCount or something).";
|
||||
return; // we already have the sidecar created
|
||||
}
|
||||
const wgpu::TextureDescriptor descriptor{
|
||||
.label = "msaa_sidecar_texture",
|
||||
.usage = mTexture.GetUsage(),
|
||||
.dimension = mTexture.GetDimension(),
|
||||
.size = {
|
||||
.width = mTexture.GetWidth(),
|
||||
.height = mTexture.GetHeight(),
|
||||
.depthOrArrayLayers = mTexture.GetDepthOrArrayLayers(),
|
||||
},
|
||||
.format = mTexture.GetFormat(),
|
||||
.mipLevelCount = mTexture.GetMipLevelCount(),
|
||||
.sampleCount = samples,
|
||||
.viewFormatCount = 1,
|
||||
.viewFormats = &mViewFormat,
|
||||
};
|
||||
mMsaaSidecarTexture = device.CreateTexture(&descriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(mMsaaSidecarTexture) << "Failed to create MSAA sidecar texture";
|
||||
}
|
||||
|
||||
wgpu::TextureView WebGPUTexture::makeMsaaSidecarTextureViewIfTextureSidecarExists(
|
||||
const uint8_t samples, const uint8_t mipLevel, const uint32_t arrayLayer) const {
|
||||
if (mMsaaSidecarTexture == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION(mMsaaSidecarTexture.GetSampleCount() == samples)
|
||||
<< "An MSAA sidecar texture has already been created for this texture, but with a "
|
||||
"different sample count ("
|
||||
<< mMsaaSidecarTexture.GetSampleCount() << ") than requested for view (" << +samples
|
||||
<< "). Note that this restriction was written when WebGPU only supported msaa "
|
||||
"textures with 4 samples. If that has changed, this implementation should be "
|
||||
"updated (e.g. map of sidecar textures by sampleCount or something).";
|
||||
const wgpu::TextureViewDescriptor descriptor{
|
||||
.label = "msaa_sidecar_texture_view",
|
||||
.format = mViewFormat,
|
||||
.dimension = mDimension,
|
||||
.baseMipLevel = mipLevel,
|
||||
.mipLevelCount = 1,
|
||||
.baseArrayLayer = arrayLayer,
|
||||
.arrayLayerCount = 1,
|
||||
.aspect = mAspect,
|
||||
.usage = mViewUsage,
|
||||
};
|
||||
const wgpu::TextureView textureView{ mMsaaSidecarTexture.CreateView(&descriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(mMsaaSidecarTexture)
|
||||
<< "Failed to create MSAA sidecar texture view (" << +samples << " samples)";
|
||||
return textureView;
|
||||
}
|
||||
|
||||
wgpu::TextureFormat WebGPUTexture::fToWGPUTextureFormat(TextureFormat const& fFormat) {
|
||||
switch (fFormat) {
|
||||
case TextureFormat::R8: return wgpu::TextureFormat::R8Unorm;
|
||||
@@ -667,6 +553,13 @@ wgpu::TextureFormat WebGPUTexture::fToWGPUTextureFormat(TextureFormat const& fFo
|
||||
wgpu::TextureView WebGPUTexture::makeTextureView(const uint8_t& baseLevel,
|
||||
const uint8_t& levelCount, const uint32_t& baseArrayLayer, const uint32_t& arrayLayerCount,
|
||||
const SamplerType samplerType) const noexcept {
|
||||
#if FWGPU_ENABLED(FWGPU_DEBUG_VALIDATION)
|
||||
if (baseLevel > 0 && !mSupportsMultipleMipLevels) {
|
||||
FWGPU_LOGW << "Trying to make a texture view into a level ("
|
||||
<< static_cast<uint32_t>(baseLevel)
|
||||
<< ") for which we cannot generate mip levels.";
|
||||
}
|
||||
#endif
|
||||
const wgpu::TextureViewDescriptor textureViewDescriptor{
|
||||
.label = getUserTextureViewLabel(target),
|
||||
.format = mViewFormat,
|
||||
|
||||
@@ -26,28 +26,8 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
[[nodiscard]] constexpr bool hasStencil(const wgpu::TextureFormat textureFormat) {
|
||||
return textureFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
|
||||
textureFormat == wgpu::TextureFormat::Depth32FloatStencil8 ||
|
||||
textureFormat == wgpu::TextureFormat::Stencil8;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool hasDepth(const wgpu::TextureFormat textureFormat) {
|
||||
return textureFormat == wgpu::TextureFormat::Depth16Unorm ||
|
||||
textureFormat == wgpu::TextureFormat::Depth32Float ||
|
||||
textureFormat == wgpu::TextureFormat::Depth24Plus ||
|
||||
textureFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
|
||||
textureFormat == wgpu::TextureFormat::Depth32FloatStencil8;
|
||||
}
|
||||
|
||||
class WebGPUTexture : public HwTexture {
|
||||
public:
|
||||
enum class MipmapGenerationStrategy : uint8_t {
|
||||
RENDER_PASS,
|
||||
SPD_COMPUTE_PASS,
|
||||
NONE,
|
||||
};
|
||||
|
||||
WebGPUTexture(SamplerType, uint8_t levels, TextureFormat, uint8_t samples, uint32_t width,
|
||||
uint32_t height, uint32_t depth, TextureUsage, wgpu::Device const&) noexcept;
|
||||
|
||||
@@ -61,49 +41,17 @@ public:
|
||||
|
||||
[[nodiscard]] wgpu::Texture const& getTexture() const { return mTexture; }
|
||||
|
||||
[[nodiscard]] wgpu::Texture const& getMsaaSidecarTexture(uint8_t sampleCount) const;
|
||||
|
||||
[[nodiscard]] wgpu::TextureView const& getDefaultTextureView() const {
|
||||
return mDefaultTextureView;
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::TextureView getOrMakeTextureView(uint8_t mipLevel, uint32_t arrayLayer);
|
||||
|
||||
[[nodiscard]] wgpu::TextureViewDimension getViewDimension() const { return mDimension; }
|
||||
[[nodiscard]] wgpu::TextureFormat getViewFormat() const { return mViewFormat; }
|
||||
|
||||
[[nodiscard]] uint32_t getArrayLayerCount() const { return mArrayLayerCount; }
|
||||
|
||||
[[nodiscard]] MipmapGenerationStrategy getMipmapGenerationStrategy() const {
|
||||
return mMipmapGenerationStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the MSAA sidecar texture if it has not already been created.
|
||||
* If the sidecar already exists with a different number of samples this will panic
|
||||
* (at the time of writing this WebGPU only supports MSAA with 4 samples. If this changes, then
|
||||
* multiple sidecars with different number of samples could be supported if needed, but that
|
||||
* level of complexity is not warranted at this time).
|
||||
* Additionally, if samples is <= 1 this will panic as well, as that is not an MSAA texture.
|
||||
* @param samples The number of samples the texture will have
|
||||
*/
|
||||
void createMsaaSidecarTextureIfNotAlreadyCreated(uint8_t samples, wgpu::Device const&);
|
||||
|
||||
/**
|
||||
* @param samples The number of samples the underlying texture supports
|
||||
* @param mipLevel The mip level into the underyling texture for which this view will reference
|
||||
* (this view will only have one mip level)
|
||||
* @param arrayLayer The layer into the underyling texture for which this view will reference
|
||||
* (this view will only have one layer)
|
||||
* @return A texture view for the MSAA sidecar texture
|
||||
*/
|
||||
wgpu::TextureView makeMsaaSidecarTextureViewIfTextureSidecarExists(uint8_t samples,
|
||||
uint8_t mipLevel, uint32_t arrayLayer) const;
|
||||
|
||||
/**
|
||||
* @return nullptr if a MSAA sidecar texture is not appliable, otherwise a view to one
|
||||
*/
|
||||
[[nodiscard]] wgpu::TextureView makeMsaaSidecarTextureView(wgpu::Texture const&, uint8_t mipLevel, uint32_t arrayLayer) const;
|
||||
[[nodiscard]] bool supportsMultipleMipLevels() const { return mSupportsMultipleMipLevels; }
|
||||
|
||||
[[nodiscard]] static wgpu::TextureFormat fToWGPUTextureFormat(
|
||||
filament::backend::TextureFormat const& fFormat);
|
||||
@@ -123,7 +71,8 @@ private:
|
||||
// certain formats (e.g. compute shaders), but we need a view to that format elsewhere
|
||||
// (e.g. another shader or render target)
|
||||
wgpu::TextureFormat mViewFormat = wgpu::TextureFormat::Undefined;
|
||||
MipmapGenerationStrategy mMipmapGenerationStrategy = MipmapGenerationStrategy::NONE;
|
||||
// this is true IFF levels > 1 AND we can actually generate mipmaps for the texture
|
||||
bool mSupportsMultipleMipLevels = false;
|
||||
// format is inherited from HwTexture. This naming is to distinguish it from Filament's format
|
||||
// this is the underlying texture's format, not necessarily the "view" of it... see mViewFormat
|
||||
wgpu::TextureFormat mWebGPUFormat = wgpu::TextureFormat::Undefined;
|
||||
@@ -131,7 +80,6 @@ private:
|
||||
// usage is inherited from HwTexture. This naming is to distinguish it from Filament's usage
|
||||
wgpu::TextureUsage mWebGPUUsage = wgpu::TextureUsage::None;
|
||||
wgpu::TextureUsage mViewUsage = wgpu::TextureUsage::None;
|
||||
wgpu::TextureViewDimension mDimension = wgpu::TextureViewDimension::Undefined;
|
||||
size_t mBlockWidth = 0;
|
||||
size_t mBlockHeight = 0;
|
||||
uint32_t mArrayLayerCount = 1;
|
||||
@@ -139,11 +87,6 @@ private:
|
||||
uint32_t mDefaultMipLevel = 0;
|
||||
uint32_t mDefaultBaseArrayLayer = 0;
|
||||
wgpu::TextureView mDefaultTextureView = nullptr;
|
||||
// At the time of writing this, WebGPU only supported 4 samples in a multi-sampled texture.
|
||||
// If that has changed, then consider updating the implementation to have a map of msaa textures
|
||||
// by sampleCount or something like that.
|
||||
// For now that complexity and cost is not warranted due to WebGPU's restrictions.
|
||||
wgpu::Texture mMsaaSidecarTexture = nullptr;
|
||||
|
||||
[[nodiscard]] wgpu::TextureView makeTextureView(const uint8_t& baseLevel,
|
||||
const uint8_t& levelCount, const uint32_t& baseArrayLayer,
|
||||
|
||||
@@ -56,8 +56,10 @@ namespace {
|
||||
// The SPD Algorithm can make use of up to 12 storage texture attachments
|
||||
constexpr uint32_t MAX_MIPMAP_STORAGE_TEXTURES_PER_STAGE = 12u;
|
||||
|
||||
constexpr std::array REQUIRED_FEATURES = {
|
||||
wgpu::FeatureName::TransientAttachments,
|
||||
constexpr std::array REQUIRED_FEATURES = { wgpu::FeatureName::TransientAttachments,
|
||||
/*To make filtering assumptions like we want while waiting for Filament to provide that info,
|
||||
float 32 needs to be filterable*/
|
||||
wgpu::FeatureName::Float32Filterable,
|
||||
// Qualcomm 500 and 600 GPUs do not support this so it is not part of core webgpu spec. To
|
||||
// support such devices, we will either need Filament to not attempt this, or find another
|
||||
// workaround. https://github.com/gpuweb/gpuweb/issues/2648
|
||||
@@ -486,8 +488,7 @@ struct AdapterDetailsHash final {
|
||||
for (size_t i = 0; i < futures.size(); i++) {
|
||||
wgpu::RequestAdapterOptions const& options = requests[i];
|
||||
wgpu::Future& future = futures[i];
|
||||
wgpu::WaitStatus status =
|
||||
instance.WaitAny(future, FILAMENT_WEBGPU_REQUEST_ADAPTER_TIMEOUT_NANOSECONDS);
|
||||
wgpu::WaitStatus status = instance.WaitAny(future, REQUEST_ADAPTER_TIMEOUT_NANOSECONDS);
|
||||
FILAMENT_CHECK_POSTCONDITION(status != wgpu::WaitStatus::TimedOut)
|
||||
<< "Timed out requesting a WebGPU adapter with options "
|
||||
<< adapterOptionsToString(options);
|
||||
@@ -659,7 +660,7 @@ wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
|
||||
assert_invariant(status == wgpu::RequestDeviceStatus::Success);
|
||||
device = readyDevice;
|
||||
}),
|
||||
FILAMENT_WEBGPU_REQUEST_DEVICE_TIMEOUT_NANOSECONDS);
|
||||
REQUEST_DEVICE_TIMEOUT_NANOSECONDS);
|
||||
FILAMENT_CHECK_POSTCONDITION(status != wgpu::WaitStatus::TimedOut)
|
||||
<< "Failed to request a WebGPU device due to a timeout.";
|
||||
FILAMENT_CHECK_POSTCONDITION(status != wgpu::WaitStatus::Error)
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <vector>
|
||||
|
||||
// Platform specific includes and defines
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
|
||||
/**
|
||||
|
||||
@@ -120,11 +120,8 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a Box by a linear transform and a translation.
|
||||
*
|
||||
* @param m a linear transform matrix
|
||||
* @param box the box to transform
|
||||
* @return the bounding box of the transformed box
|
||||
* @deprecated Use transform() instead
|
||||
* @see transform()
|
||||
*/
|
||||
friend Box rigidTransform(Box const& box, const math::mat4f& m) noexcept {
|
||||
return transform(m.upperLeft(), m[3].xyz, box);
|
||||
@@ -238,10 +235,8 @@ struct UTILS_PUBLIC Aabb {
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies an affine transformation to the AABB.
|
||||
*
|
||||
* @param m the affine transformation to apply
|
||||
* @return the bounding box of the transformed box
|
||||
* @deprecated Use transform() instead
|
||||
* @see transform()
|
||||
*/
|
||||
Aabb transform(const math::mat4f& m) const noexcept {
|
||||
return transform(m.upperLeft(), m[3].xyz, *this);
|
||||
|
||||
@@ -190,7 +190,6 @@ public:
|
||||
using FeatureLevel = backend::FeatureLevel;
|
||||
using StereoscopicType = backend::StereoscopicType;
|
||||
using Driver = backend::Driver;
|
||||
using GpuContextPriority = backend::Platform::GpuContextPriority;
|
||||
|
||||
/**
|
||||
* Config is used to define the memory footprint used by the engine, such as the
|
||||
@@ -411,11 +410,6 @@ public:
|
||||
* @deprecated use "backend.opengl.assert_native_window_is_valid" feature flag instead
|
||||
*/
|
||||
bool assertNativeWindowIsValid = false;
|
||||
|
||||
/**
|
||||
* GPU context priority level. Controls GPU work scheduling and preemption.
|
||||
*/
|
||||
GpuContextPriority gpuContextPriority = GpuContextPriority::DEFAULT;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -339,24 +339,13 @@ public:
|
||||
static math::float4 getColorEstimate(const math::float3 sh[UTILS_NONNULL 9],
|
||||
math::float3 direction) noexcept;
|
||||
|
||||
/**
|
||||
* Helper to estimate the direction of the dominant light in the environment represented by
|
||||
* spherical harmonics.
|
||||
* Spherical harmonics must be set in the Builder or the result is undefined.
|
||||
* @see getDirectionEstimate(const math::float3)
|
||||
* @see Builder::irradiance(uint8_t, math::float3 const*)
|
||||
* @see Builder::radiance(uint8_t, math::float3 const*)
|
||||
*/
|
||||
|
||||
/** @deprecated use static versions instead */
|
||||
UTILS_DEPRECATED
|
||||
math::float3 getDirectionEstimate() const noexcept;
|
||||
|
||||
/**
|
||||
* Helper to estimate the color and relative intensity of the environment represented by
|
||||
* spherical harmonics in a given direction.
|
||||
* Spherical harmonics must be set in the Builder or the result is undefined.
|
||||
* @see getColorEstimate(const math::float3, math::float3)
|
||||
* @see Builder::irradiance(uint8_t, math::float3 const*)
|
||||
* @see Builder::radiance(uint8_t, math::float3 const*)
|
||||
*/
|
||||
/** @deprecated use static versions instead */
|
||||
UTILS_DEPRECATED
|
||||
math::float4 getColorEstimate(math::float3 direction) const noexcept;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -57,8 +57,7 @@ class UTILS_PUBLIC MaterialInstance : public FilamentAPI {
|
||||
|
||||
public:
|
||||
using CullingMode = backend::CullingMode;
|
||||
// ReSharper disable once CppRedundantQualifier
|
||||
using TransparencyMode = filament::TransparencyMode;
|
||||
using TransparencyMode = TransparencyMode;
|
||||
using DepthFunc = backend::SamplerCompareFunc;
|
||||
using StencilCompareFunc = backend::SamplerCompareFunc;
|
||||
using StencilOperation = backend::StencilOperation;
|
||||
|
||||
@@ -481,7 +481,6 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
|
||||
uint8_t fieldType = 0;
|
||||
uint8_t fieldFormat = 0;
|
||||
uint8_t fieldPrecision = 0;
|
||||
bool fieldUnfilterable = false;
|
||||
bool fieldMultisample = false;
|
||||
|
||||
if (!unflattener.read(&fieldName)) {
|
||||
@@ -504,10 +503,6 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!unflattener.read(&fieldUnfilterable)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!unflattener.read(&fieldMultisample)) {
|
||||
return false;
|
||||
}
|
||||
@@ -517,7 +512,6 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
|
||||
SamplerInterfaceBlock::Type(fieldType),
|
||||
SamplerInterfaceBlock::Format(fieldFormat),
|
||||
SamplerInterfaceBlock::Precision(fieldPrecision),
|
||||
fieldUnfilterable,
|
||||
fieldMultisample);
|
||||
}
|
||||
|
||||
|
||||
@@ -2989,7 +2989,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscale(FrameGraph& fg, bool
|
||||
}
|
||||
|
||||
if (dsrOptions.quality == QualityLevel::LOW) {
|
||||
return upscaleBilinear(fg, dsrOptions, input, vp, outDesc, filter);
|
||||
return upscaleBilinear(fg, translucent, dsrOptions, input, vp, outDesc, filter);
|
||||
}
|
||||
if (dsrOptions.quality == QualityLevel::MEDIUM) {
|
||||
return upscaleSGSR1(fg, sourceHasLuminance, dsrOptions, input, vp, outDesc);
|
||||
@@ -2997,7 +2997,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscale(FrameGraph& fg, bool
|
||||
return upscaleFSR1(fg, dsrOptions, input, vp, outDesc);
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph& fg, bool translucent,
|
||||
DynamicResolutionOptions dsrOptions, FrameGraphId<FrameGraphTexture> const input,
|
||||
filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
|
||||
SamplerMagFilter filter) noexcept {
|
||||
@@ -3016,10 +3016,18 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
|
||||
.attachments = { .color = { data.output } },
|
||||
.clearFlags = TargetBufferFlags::DEPTH });
|
||||
},
|
||||
[this, vp, filter](FrameGraphResources const& resources,
|
||||
[this, vp, translucent, filter](FrameGraphResources const& resources,
|
||||
auto const& data, DriverApi& driver) {
|
||||
bindPostProcessDescriptorSet(driver);
|
||||
|
||||
// helper to enable blending
|
||||
auto enableTranslucentBlending = [](PipelineState& pipeline) {
|
||||
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
|
||||
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
|
||||
pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_ALPHA;
|
||||
pipeline.rasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_ALPHA;
|
||||
};
|
||||
|
||||
auto color = resources.getTexture(data.input);
|
||||
auto const& inputDesc = resources.getDescriptor(data.input);
|
||||
|
||||
@@ -3047,6 +3055,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
|
||||
auto out = resources.getRenderPassInfo();
|
||||
|
||||
auto pipeline = getPipelineState(material.getMaterial(mEngine));
|
||||
if (translucent) {
|
||||
enableTranslucentBlending(pipeline);
|
||||
}
|
||||
renderFullScreenQuad(out, pipeline, driver);
|
||||
});
|
||||
|
||||
@@ -3054,8 +3065,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
|
||||
|
||||
// if we had to take the low quality fallback, we still do the "sharpen pass"
|
||||
if (dsrOptions.sharpness > 0.0f) {
|
||||
output = rcas(fg, dsrOptions.sharpness, output, outDesc,
|
||||
false /* translucent=false, because dst buffer is allocated */);
|
||||
output = rcas(fg, dsrOptions.sharpness, output, outDesc, translucent);
|
||||
}
|
||||
|
||||
// we rely on automatic culling of unused render passes
|
||||
@@ -3433,6 +3443,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
|
||||
return input;
|
||||
}
|
||||
|
||||
// we currently don't support stencil resolve
|
||||
assert_invariant(!isStencilFormat(inDesc.format));
|
||||
|
||||
// The Metal / Vulkan backends currently don't support depth/stencil resolve.
|
||||
if (isDepthFormat(inDesc.format) && (!mEngine.getDriverApi().isDepthStencilResolveSupported())) {
|
||||
return resolveDepth(fg, outputBufferName, input, outDesc);
|
||||
@@ -3450,9 +3463,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
|
||||
|
||||
auto const& ppResolve = fg.addPass<ResolveData>("resolve",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
// we currently don't support stencil resolve.
|
||||
assert_invariant(!isStencilFormat(inDesc.format));
|
||||
|
||||
data.input = builder.read(input, FrameGraphTexture::Usage::BLIT_SRC);
|
||||
data.output = builder.createTexture(outputBufferName, outDesc);
|
||||
data.output = builder.write(data.output, FrameGraphTexture::Usage::BLIT_DST);
|
||||
@@ -3504,9 +3514,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolveDepth(FrameGraph& fg,
|
||||
|
||||
auto const& ppResolve = fg.addPass<ResolveData>("resolveDepth",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
// we currently don't support stencil resolve
|
||||
assert_invariant(!isStencilFormat(inDesc.format));
|
||||
|
||||
data.input = builder.sample(input);
|
||||
data.output = builder.createTexture(outputBufferName, outDesc);
|
||||
data.output = builder.write(data.output, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
|
||||
@@ -257,7 +257,7 @@ public:
|
||||
FrameGraphId<FrameGraphTexture> input, Viewport const& vp,
|
||||
FrameGraphTexture::Descriptor const& outDesc, backend::SamplerMagFilter filter) noexcept;
|
||||
|
||||
FrameGraphId<FrameGraphTexture> upscaleBilinear(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> upscaleBilinear(FrameGraph& fg, bool translucent,
|
||||
DynamicResolutionOptions dsrOptions, FrameGraphId<FrameGraphTexture> input,
|
||||
Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
|
||||
backend::SamplerMagFilter filter) noexcept;
|
||||
|
||||
@@ -82,6 +82,8 @@ RenderPassBuilder& RenderPassBuilder::customCommand(
|
||||
|
||||
RenderPass RenderPassBuilder::build(FEngine const& engine, DriverApi& driver) const {
|
||||
assert_invariant(mRenderableSoa);
|
||||
assert_invariant(mScissorViewport.width <= std::numeric_limits<int32_t>::max());
|
||||
assert_invariant(mScissorViewport.height <= std::numeric_limits<int32_t>::max());
|
||||
return RenderPass{ engine, driver, *this };
|
||||
}
|
||||
|
||||
@@ -105,7 +107,8 @@ void RenderPass::DescriptorSetHandleDeleter::operator()(
|
||||
RenderPass::RenderPass(FEngine const& engine, DriverApi& driver,
|
||||
RenderPassBuilder const& builder) noexcept
|
||||
: mRenderableSoa(*builder.mRenderableSoa),
|
||||
mColorPassDescriptorSet(builder.mColorPassDescriptorSet) {
|
||||
mColorPassDescriptorSet(builder.mColorPassDescriptorSet),
|
||||
mScissorViewport(builder.mScissorViewport) {
|
||||
|
||||
// compute the number of commands we need
|
||||
updateSummedPrimitiveCounts(
|
||||
|
||||
@@ -306,12 +306,6 @@ public:
|
||||
// allocated commands ARE NOT freed, they're owned by the Arena
|
||||
~RenderPass() noexcept;
|
||||
|
||||
// Specifies the viewport for the scissor rectangle, that is, the final scissor rect is
|
||||
// offset by the viewport's left-top and clipped to the viewport's width/height.
|
||||
void setScissorViewport(backend::Viewport const viewport) noexcept {
|
||||
mScissorViewport = viewport;
|
||||
}
|
||||
|
||||
Command const* begin() const noexcept { return mCommandBegin; }
|
||||
Command const* end() const noexcept { return mCommandEnd; }
|
||||
bool empty() const noexcept { return begin() == end(); }
|
||||
@@ -467,7 +461,7 @@ private:
|
||||
|
||||
FScene::RenderableSoa const& mRenderableSoa;
|
||||
ColorPassDescriptorSet const* const mColorPassDescriptorSet;
|
||||
backend::Viewport mScissorViewport{ 0, 0, INT32_MAX, INT32_MAX };
|
||||
backend::Viewport const mScissorViewport{ 0, 0, INT32_MAX, INT32_MAX };
|
||||
Command const* /* const */ mCommandBegin = nullptr; // Pointer to the first command
|
||||
Command const* /* const */ mCommandEnd = nullptr; // Pointer to one past the last command
|
||||
mutable BufferObjectSharedHandle mInstancedUboHandle; // ubo for instanced primitives
|
||||
@@ -482,6 +476,7 @@ class RenderPassBuilder {
|
||||
|
||||
RenderPass::Arena& mArena;
|
||||
RenderPass::CommandTypeFlags mCommandTypeFlags{};
|
||||
backend::Viewport mScissorViewport{ 0, 0, INT32_MAX, INT32_MAX };
|
||||
FScene::RenderableSoa const* mRenderableSoa = nullptr;
|
||||
utils::Range<uint32_t> mVisibleRenderables{};
|
||||
math::float3 mCameraPosition{};
|
||||
@@ -512,6 +507,13 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Specifies the viewport for the scissor rectangle, that is, the final scissor rect is
|
||||
// offset by the viewport's left-top and clipped to the viewport's width/height.
|
||||
RenderPassBuilder& scissorViewport(backend::Viewport const viewport) noexcept {
|
||||
mScissorViewport = viewport;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// specifies the geometry to generate commands for
|
||||
RenderPassBuilder& geometry(
|
||||
FScene::RenderableSoa const& soa, utils::Range<uint32_t> const vr) noexcept {
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
@@ -269,94 +270,87 @@ RendererUtils::ColorPassOutput RendererUtils::colorPass(
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
RenderPass::Command const* RendererUtils::getFirstRefractionCommand(
|
||||
RenderPass const& pass) noexcept {
|
||||
|
||||
// find the first refractive object in channel 2
|
||||
RenderPass::Command const* const refraction = std::partition_point(pass.begin(), pass.end(),
|
||||
[](auto const& command) {
|
||||
constexpr uint64_t mask = RenderPass::CHANNEL_MASK | RenderPass::PASS_MASK;
|
||||
constexpr uint64_t channel = uint64_t(RenderableManager::Builder::DEFAULT_CHANNEL) << RenderPass::CHANNEL_SHIFT;
|
||||
constexpr uint64_t value = channel | uint64_t(RenderPass::Pass::REFRACT);
|
||||
return (command.key & mask) < value;
|
||||
});
|
||||
|
||||
const bool hasScreenSpaceRefraction =
|
||||
(refraction->key & RenderPass::PASS_MASK) == uint64_t(RenderPass::Pass::REFRACT);
|
||||
|
||||
return hasScreenSpaceRefraction ? refraction : nullptr;
|
||||
|
||||
}
|
||||
|
||||
RendererUtils::ColorPassOutput RendererUtils::refractionPass(
|
||||
std::optional<RendererUtils::ColorPassOutput> RendererUtils::refractionPass(
|
||||
FrameGraph& fg, FEngine& engine, FView const& view,
|
||||
ColorPassInput colorPassInput,
|
||||
ColorPassConfig config,
|
||||
PostProcessManager::ScreenSpaceRefConfig const& ssrConfig,
|
||||
PostProcessManager::ColorGradingConfig const colorGradingConfig,
|
||||
RenderPass const& pass, RenderPass::Command const* const firstRefractionCommand) noexcept {
|
||||
RenderPass const& pass) noexcept {
|
||||
|
||||
assert_invariant(firstRefractionCommand);
|
||||
RenderPass::Command const* const refraction = firstRefractionCommand;
|
||||
// find the first refractive object in channel 2
|
||||
RenderPass::Command const* const refraction = std::partition_point(pass.begin(), pass.end(),
|
||||
[](auto const& command) {
|
||||
constexpr uint64_t mask = RenderPass::CHANNEL_MASK | RenderPass::PASS_MASK;
|
||||
constexpr uint64_t channel = uint64_t(RenderableManager::Builder::DEFAULT_CHANNEL) << RenderPass::CHANNEL_SHIFT;
|
||||
constexpr uint64_t value = channel | uint64_t(RenderPass::Pass::REFRACT);
|
||||
return (command.key & mask) < value;
|
||||
});
|
||||
|
||||
const bool hasScreenSpaceRefraction =
|
||||
(refraction->key & RenderPass::PASS_MASK) == uint64_t(RenderPass::Pass::REFRACT);
|
||||
|
||||
// if there wasn't any refractive object, just skip everything below.
|
||||
assert_invariant(!colorPassInput.linearColor);
|
||||
assert_invariant(!colorPassInput.depth);
|
||||
config.hasScreenSpaceReflectionsOrRefractions = true;
|
||||
if (UTILS_UNLIKELY(hasScreenSpaceRefraction)) {
|
||||
assert_invariant(!colorPassInput.linearColor);
|
||||
assert_invariant(!colorPassInput.depth);
|
||||
config.hasScreenSpaceReflectionsOrRefractions = true;
|
||||
|
||||
PostProcessManager& ppm = engine.getPostProcessManager();
|
||||
auto const opaquePassOutput = colorPass(fg,
|
||||
"Color Pass (opaque)", engine, view, colorPassInput, {
|
||||
// When rendering the opaques, we need to conserve the sample buffer,
|
||||
// so create a config that specifies the sample count.
|
||||
.width = config.physicalViewport.width,
|
||||
.height = config.physicalViewport.height,
|
||||
.samples = config.msaa,
|
||||
.format = config.hdrFormat
|
||||
},
|
||||
config, { .asSubpass = false, .customResolve = false },
|
||||
pass.getExecutor(pass.begin(), refraction));
|
||||
PostProcessManager& ppm = engine.getPostProcessManager();
|
||||
auto const opaquePassOutput = colorPass(fg,
|
||||
"Color Pass (opaque)", engine, view, colorPassInput, {
|
||||
// When rendering the opaques, we need to conserve the sample buffer,
|
||||
// so create a config that specifies the sample count.
|
||||
.width = config.physicalViewport.width,
|
||||
.height = config.physicalViewport.height,
|
||||
.samples = config.msaa,
|
||||
.format = config.hdrFormat
|
||||
},
|
||||
config, { .asSubpass = false, .customResolve = false },
|
||||
pass.getExecutor(pass.begin(), refraction));
|
||||
|
||||
|
||||
// Generate the mipmap chain
|
||||
// Note: we can run some post-processing effects while the "color pass" descriptor set
|
||||
// in bound because only the descriptor 0 (frame uniforms) matters, and it's
|
||||
// present in both.
|
||||
PostProcessManager::generateMipmapSSR(ppm, fg,
|
||||
opaquePassOutput.linearColor,
|
||||
ssrConfig.refraction,
|
||||
true, ssrConfig);
|
||||
// Generate the mipmap chain
|
||||
// Note: we can run some post-processing effects while the "color pass" descriptor set
|
||||
// in bound because only the descriptor 0 (frame uniforms) matters, and it's
|
||||
// present in both.
|
||||
PostProcessManager::generateMipmapSSR(ppm, fg,
|
||||
opaquePassOutput.linearColor,
|
||||
ssrConfig.refraction,
|
||||
true, ssrConfig);
|
||||
|
||||
// Now we're doing the refraction pass proper.
|
||||
// This uses the same framebuffer (color and depth) used by the opaque pass.
|
||||
// For this reason, the `colorBufferDesc` parameter of colorPass() below is only used for
|
||||
// the width and height.
|
||||
colorPassInput.linearColor = opaquePassOutput.linearColor;
|
||||
colorPassInput.depth = opaquePassOutput.depth;
|
||||
// Now we're doing the refraction pass proper.
|
||||
// This uses the same framebuffer (color and depth) used by the opaque pass.
|
||||
// For this reason, the `colorBufferDesc` parameter of colorPass() below is only used for
|
||||
// the width and height.
|
||||
colorPassInput.linearColor = opaquePassOutput.linearColor;
|
||||
colorPassInput.depth = opaquePassOutput.depth;
|
||||
|
||||
// Since we're reusing the existing target we don't want to clear any of its buffer.
|
||||
// Important: if this target ended up being an imported target, then the clearFlags
|
||||
// specified here wouldn't apply (the clearFlags of the imported target take precedence),
|
||||
// and we'd end up clearing the opaque pass. This scenario never happens because it is
|
||||
// prevented in Renderer.cpp's final blit.
|
||||
config.clearFlags = TargetBufferFlags::NONE;
|
||||
auto transparentPassOutput = colorPass(fg, "Color Pass (transparent)",
|
||||
engine, view, colorPassInput, {
|
||||
.width = config.physicalViewport.width,
|
||||
.height = config.physicalViewport.height },
|
||||
config, colorGradingConfig,
|
||||
pass.getExecutor(refraction, pass.end()));
|
||||
// Since we're reusing the existing target we don't want to clear any of its buffer.
|
||||
// Important: if this target ended up being an imported target, then the clearFlags
|
||||
// specified here wouldn't apply (the clearFlags of the imported target take precedence),
|
||||
// and we'd end up clearing the opaque pass. This scenario never happens because it is
|
||||
// prevented in Renderer.cpp's final blit.
|
||||
config.clearFlags = TargetBufferFlags::NONE;
|
||||
auto transparentPassOutput = colorPass(fg, "Color Pass (transparent)",
|
||||
engine, view, colorPassInput, {
|
||||
.width = config.physicalViewport.width,
|
||||
.height = config.physicalViewport.height },
|
||||
config, colorGradingConfig,
|
||||
pass.getExecutor(refraction, pass.end()));
|
||||
|
||||
if (config.msaa > 1 && !colorGradingConfig.asSubpass) {
|
||||
// We need to do a resolve here because later passes (such as color grading or DoF) will
|
||||
// need to sample from 'output'. However, because we have MSAA, we know we're not
|
||||
// sampleable. And this is because in the SSR case, we had to use a renderbuffer to
|
||||
// conserve the multi-sample buffer.
|
||||
transparentPassOutput.linearColor = ppm.resolve(fg, "Resolved Color Buffer",
|
||||
transparentPassOutput.linearColor, { .levels = 1 });
|
||||
if (config.msaa > 1 && !colorGradingConfig.asSubpass) {
|
||||
// We need to do a resolve here because later passes (such as color grading or DoF) will
|
||||
// need to sample from 'output'. However, because we have MSAA, we know we're not
|
||||
// sampleable. And this is because in the SSR case, we had to use a renderbuffer to
|
||||
// conserve the multi-sample buffer.
|
||||
transparentPassOutput.linearColor = ppm.resolve(fg, "Resolved Color Buffer",
|
||||
transparentPassOutput.linearColor, { .levels = 1 });
|
||||
}
|
||||
return transparentPassOutput;
|
||||
}
|
||||
return transparentPassOutput;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
|
||||
@@ -26,18 +26,17 @@
|
||||
#include <filament/Viewport.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
#include <backend/PixelBufferDescriptor.h>
|
||||
|
||||
#include <math/vec2.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
namespace backend {
|
||||
class PixelBufferDescriptor;
|
||||
}
|
||||
namespace filament {
|
||||
|
||||
class FRenderTarget;
|
||||
class FrameGraph;
|
||||
@@ -100,20 +99,18 @@ public:
|
||||
PostProcessManager::ColorGradingConfig colorGradingConfig,
|
||||
RenderPass::Executor passExecutor) noexcept;
|
||||
|
||||
static ColorPassOutput refractionPass(
|
||||
static std::optional<ColorPassOutput> refractionPass(
|
||||
FrameGraph& fg, FEngine& engine, FView const& view,
|
||||
ColorPassInput colorPassInput,
|
||||
ColorPassConfig config,
|
||||
PostProcessManager::ScreenSpaceRefConfig const& ssrConfig,
|
||||
PostProcessManager::ColorGradingConfig colorGradingConfig,
|
||||
RenderPass const& pass, RenderPass::Command const* firstRefractionCommand) noexcept;
|
||||
RenderPass const& pass) noexcept;
|
||||
|
||||
static void readPixels(backend::DriverApi& driver,
|
||||
backend::Handle<backend::HwRenderTarget> renderTargetHandle,
|
||||
uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
|
||||
backend::PixelBufferDescriptor&& buffer);
|
||||
|
||||
static RenderPass::Command const* getFirstRefractionCommand(RenderPass const& pass) noexcept;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -141,7 +141,6 @@ Engine* FEngine::create(Builder const& builder) {
|
||||
.stereoscopicType = instance->getConfig().stereoscopicType,
|
||||
.assertNativeWindowIsValid = instance->features.backend.opengl.assert_native_window_is_valid,
|
||||
.metalDisablePanicOnDrawableFailure = instance->getConfig().metalDisablePanicOnDrawableFailure,
|
||||
.gpuContextPriority = instance->getConfig().gpuContextPriority,
|
||||
};
|
||||
instance->mDriver = platform->createDriver(sharedContext, driverConfig);
|
||||
|
||||
@@ -762,7 +761,6 @@ int FEngine::loop() {
|
||||
.stereoscopicType = mConfig.stereoscopicType,
|
||||
.assertNativeWindowIsValid = features.backend.opengl.assert_native_window_is_valid,
|
||||
.metalDisablePanicOnDrawableFailure = mConfig.metalDisablePanicOnDrawableFailure,
|
||||
.gpuContextPriority = mConfig.gpuContextPriority,
|
||||
};
|
||||
mDriver = mPlatform->createDriver(mSharedGLContext, driverConfig);
|
||||
|
||||
@@ -824,9 +822,9 @@ int FEngine::loop() {
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
if(debug.server) {
|
||||
delete debug.server;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if FILAMENT_ENABLE_FGVIEWER
|
||||
#if FILAMENT_ENABLE_FGVIEWER
|
||||
if(debug.fgviewerServer) {
|
||||
delete debug.fgviewerServer;
|
||||
}
|
||||
|
||||
@@ -98,14 +98,6 @@ namespace filament::fgviewer {
|
||||
} // namespace filament::fgviewer
|
||||
#endif
|
||||
|
||||
// We have added correctness assertions that breaks clients' projects. We add this define to allow
|
||||
// for the client's to address these assertions at a more gradual pace.
|
||||
#if defined(FILAMENT_RELAXED_CORRECTNESS_ASSERTIONS)
|
||||
#define CORRECTNESS_ASSERTION_DEFAULT false
|
||||
#else
|
||||
#define CORRECTNESS_ASSERTION_DEFAULT true
|
||||
#endif
|
||||
|
||||
namespace filament {
|
||||
|
||||
class Renderer;
|
||||
@@ -723,14 +715,10 @@ public:
|
||||
bool use_shadow_atlas = false;
|
||||
} shadows;
|
||||
struct {
|
||||
// TODO: clean-up the following flags (equivalent to setting them to true) when
|
||||
// clients have addressed their usages.
|
||||
bool assert_material_instance_in_use = CORRECTNESS_ASSERTION_DEFAULT;
|
||||
bool assert_destroy_material_before_material_instance =
|
||||
CORRECTNESS_ASSERTION_DEFAULT;
|
||||
bool assert_vertex_buffer_count_exceeds_8 = CORRECTNESS_ASSERTION_DEFAULT;
|
||||
bool assert_vertex_buffer_attribute_stride_mult_of_4 =
|
||||
CORRECTNESS_ASSERTION_DEFAULT;
|
||||
// TODO: default the following two flags to true.
|
||||
bool assert_material_instance_in_use = false;
|
||||
bool assert_destroy_material_before_material_instance = false;
|
||||
bool assert_vertex_buffer_count_exceeds_8 = false;
|
||||
} debug;
|
||||
} engine;
|
||||
struct {
|
||||
@@ -771,9 +759,6 @@ public:
|
||||
{ "features.engine.debug.assert_vertex_buffer_count_exceeds_8",
|
||||
"Assert when a client's number of buffers for a VertexBuffer exceeds 8.",
|
||||
&features.engine.debug.assert_vertex_buffer_count_exceeds_8, false },
|
||||
{ "features.engine.debug.assert_vertex_buffer_attribute_stride_mult_of_4",
|
||||
"Assert that the attribute stride of a vertex buffer is a multiple of 4.",
|
||||
&features.engine.debug.assert_vertex_buffer_attribute_stride_mult_of_4, false },
|
||||
}};
|
||||
|
||||
utils::Slice<const FeatureFlag> getFeatureFlags() const noexcept {
|
||||
|
||||
@@ -1125,6 +1125,16 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Color passes
|
||||
|
||||
// this makes the viewport relative to xvp
|
||||
// FIXME: we should use 'vp' when rendering directly into the swapchain, but that's hard to
|
||||
// know at this point. This will usually be the case when post-process is disabled.
|
||||
// FIXME: we probably should take the dynamic scaling into account too
|
||||
// if MSAA is enabled, we end-up rendering in an intermediate buffer. This is the only case where
|
||||
// "!hasPostProcess" doesn't guarantee rendering into the swapchain.
|
||||
const bool useIntermediateBuffer = hasPostProcess || msaaOptions.enabled ||
|
||||
(isRenderingMultiview && engine.debug.stereo.combine_multiview_images);
|
||||
passBuilder.scissorViewport(useIntermediateBuffer ? xvp : vp);
|
||||
|
||||
// This one doesn't need to be a FrameGraph pass because it always happens by construction
|
||||
// (i.e. it won't be culled, unless everything is culled), so no need to complexify things.
|
||||
passBuilder.variant(variant);
|
||||
@@ -1164,35 +1174,8 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
passBuilder.renderFlags(renderFlags);
|
||||
}
|
||||
|
||||
// create the pass, which generates all its commands (this is a heavy operation)
|
||||
RenderPass const pass{ passBuilder.build(engine, driver) };
|
||||
|
||||
// now that we have the commands we can figure out if we have refraction commands
|
||||
auto* const firstRefractionCommand = [&view](RenderPass const& pass) {
|
||||
RenderPass::Command const* p = nullptr;
|
||||
if (UTILS_UNLIKELY(view.isScreenSpaceRefractionEnabled() && !pass.empty())) {
|
||||
p = RendererUtils::getFirstRefractionCommand(pass);
|
||||
}
|
||||
return p;
|
||||
}(pass);
|
||||
|
||||
hasScreenSpaceRefraction = firstRefractionCommand != nullptr;
|
||||
|
||||
// this makes the viewport relative to xvp
|
||||
// FIXME: we should use 'vp' when rendering directly into the swapchain, but that's hard to
|
||||
// know at this point. This will usually be the case when post-process is disabled.
|
||||
// FIXME: we probably should take the dynamic scaling into account too
|
||||
// if MSAA is enabled, we end-up rendering in an intermediate buffer. This is the only case where
|
||||
// "!hasPostProcess" doesn't guarantee rendering into the swapchain.
|
||||
const bool useIntermediateBuffer = hasPostProcess || msaaOptions.enabled ||
|
||||
ssReflectionsOptions.enabled || hasScreenSpaceRefraction ||
|
||||
(isRenderingMultiview && engine.debug.stereo.
|
||||
combine_multiview_images);
|
||||
|
||||
// this is slightly ugly, but conceptually `pass` is const; it's just that we can't set
|
||||
// the scissor viewport during construction
|
||||
const_cast<RenderPass&>(pass).setScissorViewport(useIntermediateBuffer ? xvp : vp);
|
||||
|
||||
FrameGraphTexture::Descriptor colorBufferDesc = {
|
||||
.width = config.physicalViewport.width,
|
||||
.height = config.physicalViewport.height,
|
||||
@@ -1237,17 +1220,21 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
},
|
||||
colorBufferDesc, config, colorGradingConfigForColor, pass.getExecutor());
|
||||
|
||||
if (UTILS_UNLIKELY(hasScreenSpaceRefraction)) {
|
||||
if (view.isScreenSpaceRefractionEnabled() && !pass.empty()) {
|
||||
// This cancels the colorPass() call above if refraction is active.
|
||||
// The color pass + refraction + color-grading as subpass if needed
|
||||
colorPassOutput = RendererUtils::refractionPass(fg, mEngine, view, {
|
||||
auto const output = RendererUtils::refractionPass(fg, mEngine, view, {
|
||||
.shadows = blackboard.get<FrameGraphTexture>("shadows"),
|
||||
.ssao = blackboard.get<FrameGraphTexture>("ssao"),
|
||||
.ssr = ssrConfig.ssr,
|
||||
.structure = structure
|
||||
},
|
||||
config, ssrConfig, colorGradingConfigForColor,
|
||||
pass, firstRefractionCommand);
|
||||
config, ssrConfig, colorGradingConfigForColor, pass);
|
||||
|
||||
hasScreenSpaceRefraction = output.has_value();
|
||||
if (hasScreenSpaceRefraction) {
|
||||
colorPassOutput = output.value();
|
||||
}
|
||||
}
|
||||
|
||||
if (colorGradingConfig.customResolve) {
|
||||
|
||||
@@ -386,9 +386,8 @@ void FTexture::setImage(FEngine& engine, size_t const level,
|
||||
FILAMENT_CHECK_PRECONDITION(any(mUsage & Texture::Usage::UPLOADABLE))
|
||||
<< "Texture is not uploadable.";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(mSampleCount <= 1)
|
||||
<< "Operation not supported with multisample ("
|
||||
<< unsigned(mSampleCount) << ") texture.";
|
||||
FILAMENT_CHECK_PRECONDITION(mSampleCount <= 1) << "Operation not supported with multisample ("
|
||||
<< unsigned(mSampleCount) << ") texture.";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(xoffset + width <= valueForLevel(level, mWidth))
|
||||
<< "xoffset (" << unsigned(xoffset) << ") + width (" << unsigned(width)
|
||||
@@ -429,46 +428,28 @@ void FTexture::setImage(FEngine& engine, size_t const level,
|
||||
<< ") > texture depth (" << effectiveTextureDepthOrLayers << ") at level ("
|
||||
<< unsigned(level) << ")";
|
||||
|
||||
if (UTILS_UNLIKELY(!width || !height || !depth)) {
|
||||
// The operation is a no-op, return immediately. The PixelBufferDescriptor callback
|
||||
// should be called automatically when the object is destroyed.
|
||||
// The precondition check below assumes width, height, depth non null.
|
||||
return;
|
||||
}
|
||||
|
||||
if (p.type != PixelDataType::COMPRESSED) {
|
||||
using PBD = PixelBufferDescriptor;
|
||||
size_t const stride = p.stride ? p.stride : width;
|
||||
size_t const bpp = PBD::computeDataSize(p.format, p.type, 1, 1, 1);
|
||||
size_t const bpp = PBD::computePixelSize(p.format, p.type);
|
||||
size_t const bpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment);
|
||||
size_t const bpl = bpr * height; // TODO: PBD should have a "layer stride"
|
||||
size_t const pbdWidth = stride;
|
||||
// TODO: PBD should have a "layer stride" (using "depth" as a substitute).
|
||||
size_t const pbdDepth = depth;
|
||||
size_t const pbdHeight = p.size / bpr / pbdDepth;
|
||||
assert_invariant(p.size % bpr == 0 && (p.size / bpr) % pbdDepth == 0);
|
||||
|
||||
// TODO: PBD should have a p.depth (# layers to skip)
|
||||
|
||||
/* Calculates the byte offset of the last pixel in a 3D sub-region. */
|
||||
auto const calculateLastPixelOffset = [bpp, bpr, bpl](
|
||||
size_t xoff, size_t yoff, size_t zoff,
|
||||
size_t width, size_t height, size_t depth) {
|
||||
// The 0-indexed coordinates of the last pixel are:
|
||||
// x = xoff + width - 1
|
||||
// y = yoff + height - 1
|
||||
// z = zoff + depth - 1
|
||||
// The offset is calculated as: (z * bpl) + (y * bpr) + (x * bpp)
|
||||
return ((zoff + depth - 1) * bpl) +
|
||||
((yoff + height - 1) * bpr) +
|
||||
((xoff + width - 1) * bpp);
|
||||
};
|
||||
|
||||
size_t const lastPixelOffset = calculateLastPixelOffset(
|
||||
p.left, p.top, 0, width, height, depth);
|
||||
|
||||
// make sure the whole last pixel is in the buffer
|
||||
FILAMENT_CHECK_PRECONDITION(lastPixelOffset + bpp <= p.size)
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
bpp * (pbdWidth - p.left) * (pbdHeight - p.top) * (pbdDepth - 0) >=
|
||||
bpp * (width - xoffset) * (height - yoffset) * (depth - zoffset))
|
||||
<< "buffer overflow: (size=" << size_t(p.size) << ", stride=" << size_t(p.stride)
|
||||
<< ", left=" << unsigned(p.left) << ", top=" << unsigned(p.top)
|
||||
<< ") smaller than specified region "
|
||||
"{{"
|
||||
<< unsigned(xoffset) << "," << unsigned(yoffset) << "," << unsigned(zoffset) << "},{"
|
||||
<< unsigned(width) << "," << unsigned(height) << "," << unsigned(depth) << ")}}";
|
||||
<< unsigned(xoffset) << "," << unsigned(yoffset) << "," << unsigned(zoffset)
|
||||
<< "},{" << unsigned(width) << "," << unsigned(height) << "," << unsigned(depth)
|
||||
<< ")}}";
|
||||
}
|
||||
|
||||
engine.getDriverApi().update3DImage(mHandle, uint8_t(level), xoffset, yoffset, zoffset, width,
|
||||
|
||||
@@ -162,7 +162,8 @@ VertexBuffer* VertexBuffer::Builder::build(Engine& engine) {
|
||||
FILAMENT_CHECK_PRECONDITION(mImpl->mBufferCount <= DOCUMENTED_MAX_VERTEX_BUFFER_COUNT)
|
||||
<< "bufferCount cannot be more than " << DOCUMENTED_MAX_VERTEX_BUFFER_COUNT;
|
||||
} else if (mImpl->mBufferCount > DOCUMENTED_MAX_VERTEX_BUFFER_COUNT) {
|
||||
LOG(WARNING) << "bufferCount cannot be more than " << DOCUMENTED_MAX_VERTEX_BUFFER_COUNT;
|
||||
utils::slog.w << "bufferCount cannot be more than " << DOCUMENTED_MAX_VERTEX_BUFFER_COUNT
|
||||
<< utils::io::endl;
|
||||
}
|
||||
|
||||
// Next we check if any unused buffer slots have been allocated. This helps prevent errors
|
||||
@@ -177,15 +178,9 @@ VertexBuffer* VertexBuffer::Builder::build(Engine& engine) {
|
||||
<< "attribute " << j << " offset=" << attributes[j].offset
|
||||
<< " is not multiple of 4";
|
||||
|
||||
FEngine* fengine = static_cast<FEngine*>(&engine);
|
||||
if (fengine->features.engine.debug.assert_vertex_buffer_attribute_stride_mult_of_4) {
|
||||
FILAMENT_CHECK_PRECONDITION((attributes[j].stride & 0x3u) == 0)
|
||||
<< "attribute " << j << " stride=" << attributes[j].stride
|
||||
<< " is not multiple of 4";
|
||||
} else if ((attributes[j].stride & 0x3u) != 0) {
|
||||
LOG(WARNING) << "attribute " << j << " stride=" << attributes[j].stride
|
||||
<< " is not multiple of 4";
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION((attributes[j].stride & 0x3u) == 0)
|
||||
<< "attribute " << j << " stride=" << attributes[j].stride
|
||||
<< " is not multiple of 4";
|
||||
|
||||
if (engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0) {
|
||||
FILAMENT_CHECK_PRECONDITION(!(attributes[j].flags & Attribute::FLAG_INTEGER_TARGET))
|
||||
|
||||
@@ -8,8 +8,7 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high,
|
||||
unfilterable: true
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
|
||||
@@ -4,8 +4,7 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: medium,
|
||||
unfilterable: true
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
|
||||
@@ -9,8 +9,7 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high,
|
||||
unfilterable: true
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
@@ -78,3 +77,4 @@ fragment {
|
||||
postProcess.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,7 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: medium,
|
||||
unfilterable: true
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : float2,
|
||||
|
||||
@@ -9,8 +9,7 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: medium,
|
||||
unfilterable: true
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : float2,
|
||||
|
||||
@@ -5,8 +5,7 @@ material {
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high,
|
||||
multisample : true,
|
||||
unfilterable: true
|
||||
multisample : true
|
||||
}
|
||||
],
|
||||
outputs : [
|
||||
|
||||
@@ -4,8 +4,7 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high,
|
||||
unfilterable: true
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : mat4,
|
||||
|
||||
@@ -4,8 +4,7 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high,
|
||||
unfilterable: true
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : mat4,
|
||||
|
||||
@@ -4,8 +4,7 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high,
|
||||
unfilterable: true
|
||||
precision: high
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
|
||||
@@ -4,8 +4,7 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high,
|
||||
unfilterable: true
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : mat4,
|
||||
|
||||
@@ -4,8 +4,7 @@ material {
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high,
|
||||
unfilterable: true
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : mat4,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.62.0"
|
||||
spec.version = "1.61.1"
|
||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
spec.homepage = "https://google.github.io/filament"
|
||||
spec.authors = "Google LLC."
|
||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||
spec.platform = :ios, "11.0"
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.62.0/filament-v1.62.0-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.61.1/filament-v1.61.1-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
namespace filament {
|
||||
|
||||
// update this when a new version of filament wouldn't work with older materials
|
||||
static constexpr size_t MATERIAL_VERSION = 62;
|
||||
static constexpr size_t MATERIAL_VERSION = 61;
|
||||
|
||||
/**
|
||||
* Supported shading models
|
||||
|
||||
@@ -61,7 +61,6 @@ public:
|
||||
Type type; // type of this sampler
|
||||
Format format; // format of this sampler
|
||||
Precision precision; // precision of this sampler
|
||||
bool unfilterable; // whether the sampling should be unfiltered.
|
||||
bool multisample; // multisample capable
|
||||
ShaderStageFlags stages; // stages the sampler can be accessed from
|
||||
};
|
||||
@@ -84,7 +83,6 @@ public:
|
||||
Type type; // type of this sampler
|
||||
Format format; // format of this sampler
|
||||
Precision precision; // precision of this sampler
|
||||
bool unfilterable = false; // whether the sampling should be unfiltered.
|
||||
bool multisample = false; // multisample capable
|
||||
ShaderStageFlags stages =
|
||||
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS; // shader stages using this sampler
|
||||
@@ -97,8 +95,7 @@ public:
|
||||
|
||||
// Add a sampler
|
||||
Builder& add(std::string_view samplerName, Binding binding, Type type, Format format,
|
||||
Precision precision = Precision::MEDIUM, bool unfilterable = false,
|
||||
bool multisample = false,
|
||||
Precision precision = Precision::MEDIUM, bool multisample = false,
|
||||
ShaderStageFlags stages = ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) noexcept;
|
||||
|
||||
// Add multiple samplers
|
||||
|
||||
@@ -96,7 +96,7 @@ static constexpr std::initializer_list<DescriptorSetLayoutBinding> perRenderable
|
||||
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::OBJECT_UNIFORMS, DescriptorFlags::DYNAMIC_OFFSET },
|
||||
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::BONES_UNIFORMS, DescriptorFlags::DYNAMIC_OFFSET },
|
||||
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::MORPHING_UNIFORMS },
|
||||
{ DescriptorType::SAMPLER_2D_ARRAY_FLOAT, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_POSITIONS, DescriptorFlags::UNFILTERABLE },
|
||||
{ DescriptorType::SAMPLER_2D_ARRAY_FLOAT, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_POSITIONS },
|
||||
{ DescriptorType::SAMPLER_2D_ARRAY_INT, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_TANGENTS },
|
||||
{ DescriptorType::SAMPLER_2D_ARRAY_FLOAT, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::BONES_INDICES_AND_WEIGHTS },
|
||||
};
|
||||
|
||||
@@ -50,12 +50,12 @@ SamplerInterfaceBlock::Builder::stageFlags(backend::ShaderStageFlags stageFlags)
|
||||
}
|
||||
|
||||
SamplerInterfaceBlock::Builder& SamplerInterfaceBlock::Builder::add(std::string_view samplerName,
|
||||
Binding binding, Type type, Format format, Precision precision, bool unfilterable,
|
||||
bool multisample, ShaderStageFlags stages) noexcept {
|
||||
Binding binding, Type type, Format format, Precision precision, bool multisample,
|
||||
ShaderStageFlags stages) noexcept {
|
||||
mEntries.push_back({
|
||||
{ samplerName.data(), samplerName.size() }, // name
|
||||
{}, // uniform name
|
||||
binding, type, format, precision, unfilterable, multisample, stages });
|
||||
{ samplerName.data(), samplerName.size() }, // name
|
||||
{ }, // uniform name
|
||||
binding, type, format, precision, multisample, stages });
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -65,9 +65,8 @@ SamplerInterfaceBlock SamplerInterfaceBlock::Builder::build() {
|
||||
|
||||
SamplerInterfaceBlock::Builder& SamplerInterfaceBlock::Builder::add(
|
||||
std::initializer_list<ListEntry> list) noexcept {
|
||||
for (auto& e: list) {
|
||||
add(e.name, e.binding, e.type, e.format, e.precision, e.unfilterable, e.multisample,
|
||||
e.stages);
|
||||
for (auto& e : list) {
|
||||
add(e.name, e.binding, e.type, e.format, e.precision, e.multisample, e.stages);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/compiler.h>
|
||||
@@ -324,8 +323,8 @@ public:
|
||||
*/
|
||||
MaterialBuilder& parameter(const char* name, SamplerType samplerType,
|
||||
SamplerFormat format = SamplerFormat::FLOAT,
|
||||
ParameterPrecision precision = ParameterPrecision::DEFAULT, bool unfilterable = false,
|
||||
bool multisample = false, const char* transformName = "",
|
||||
ParameterPrecision precision = ParameterPrecision::DEFAULT, bool multisample = false,
|
||||
const char* transformName = "",
|
||||
ShaderStageFlags stages = ShaderStageFlags::ALL_SHADER_STAGE_FLAGS);
|
||||
|
||||
MaterialBuilder& buffer(filament::BufferInterfaceBlock bib);
|
||||
@@ -659,39 +658,17 @@ public:
|
||||
|
||||
// Sampler
|
||||
Parameter(const char* paramName, SamplerType t, SamplerFormat f, ParameterPrecision p,
|
||||
bool unfilterable, bool ms, const char* tn, ShaderStageFlags s)
|
||||
: name(paramName),
|
||||
size(1),
|
||||
precision(p),
|
||||
samplerType(t),
|
||||
format(f),
|
||||
unfilterable(unfilterable),
|
||||
multisample(ms),
|
||||
transformName(tn),
|
||||
stages(s),
|
||||
parameterType(SAMPLER) {}
|
||||
bool ms, const char* tn, ShaderStageFlags s)
|
||||
: name(paramName), size(1), precision(p), samplerType(t), format(f),
|
||||
parameterType(SAMPLER), multisample(ms), transformName(tn), stages(s) { }
|
||||
|
||||
// Uniform
|
||||
Parameter(const char* paramName, UniformType t, size_t typeSize, ParameterPrecision p)
|
||||
: name(paramName),
|
||||
size(typeSize),
|
||||
uniformType(t),
|
||||
precision(p),
|
||||
format{ 0 },
|
||||
unfilterable(false),
|
||||
multisample(false),
|
||||
parameterType(UNIFORM) {}
|
||||
: name(paramName), size(typeSize), uniformType(t), precision(p), parameterType(UNIFORM) { }
|
||||
|
||||
// Subpass
|
||||
Parameter(const char* paramName, SubpassType t, SamplerFormat f, ParameterPrecision p)
|
||||
: name(paramName),
|
||||
size(1),
|
||||
precision(p),
|
||||
subpassType(t),
|
||||
format(f),
|
||||
unfilterable(false),
|
||||
multisample(false),
|
||||
parameterType(SUBPASS) {}
|
||||
: name(paramName), size(1), precision(p), subpassType(t), format(f), parameterType(SUBPASS) { }
|
||||
|
||||
utils::CString name;
|
||||
size_t size;
|
||||
@@ -700,7 +677,6 @@ public:
|
||||
SamplerType samplerType;
|
||||
SubpassType subpassType;
|
||||
SamplerFormat format;
|
||||
bool unfilterable;
|
||||
bool multisample;
|
||||
utils::CString transformName;
|
||||
ShaderStageFlags stages;
|
||||
|
||||
@@ -300,19 +300,19 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, UniformType const
|
||||
return parameter(name, 1, type, precision);
|
||||
}
|
||||
|
||||
|
||||
MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType samplerType,
|
||||
SamplerFormat format, ParameterPrecision precision, bool unfilterable, bool multisample,
|
||||
SamplerFormat format, ParameterPrecision precision, bool multisample,
|
||||
const char* transformName, ShaderStageFlags stages) {
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
!multisample || (format != SamplerFormat::SHADOW &&
|
||||
(samplerType == SamplerType::SAMPLER_2D ||
|
||||
samplerType == SamplerType::SAMPLER_2D_ARRAY)))
|
||||
FILAMENT_CHECK_PRECONDITION(!multisample ||
|
||||
(format != SamplerFormat::SHADOW &&
|
||||
(samplerType == SamplerType::SAMPLER_2D ||
|
||||
samplerType == SamplerType::SAMPLER_2D_ARRAY)))
|
||||
<< "multisample samplers only possible with SAMPLER_2D or SAMPLER_2D_ARRAY,"
|
||||
" as long as type is not SHADOW";
|
||||
|
||||
FILAMENT_CHECK_POSTCONDITION(mParameterCount < MAX_PARAMETERS_COUNT) << "Too many parameters";
|
||||
mParameters[mParameterCount++] = { name, samplerType, format, precision, unfilterable,
|
||||
multisample, transformName, stages };
|
||||
mParameters[mParameterCount++] = { name, samplerType, format, precision, multisample, transformName, stages };
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -639,12 +639,10 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept {
|
||||
assert_invariant(!param.isSubpass());
|
||||
if (param.isSampler()) {
|
||||
sbb.add({ param.name.data(), param.name.size() }, binding, param.samplerType,
|
||||
param.format, param.precision, param.unfilterable, param.multisample,
|
||||
param.stages);
|
||||
param.format, param.precision, param.multisample, param.stages);
|
||||
if (!param.transformName.empty()) {
|
||||
ibb.add({ { { param.transformName.data(), param.transformName.size() },
|
||||
uint8_t(binding), 0, UniformType::MAT3, Precision::DEFAULT,
|
||||
FeatureLevel::FEATURE_LEVEL_0 } });
|
||||
ibb.add({{{ param.transformName.data(), param.transformName.size() }, uint8_t(binding),
|
||||
0, UniformType::MAT3, Precision::DEFAULT, FeatureLevel::FEATURE_LEVEL_0 }});
|
||||
}
|
||||
binding++;
|
||||
} else if (param.isUniform()) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user