Compare commits

..
Author SHA1 Message Date
bridgewaterrobbie c611b4df6f Fixup shader code 2025-06-10 18:04:41 -04:00
bridgewaterrobbie 6b91d7ca2d Reduce the number of prepared mipmap pipelines- this should be configurable really if we want to preserve this logic in the long run. 2025-06-10 16:54:28 -04:00
bridgewaterrobbie df264372d9 Add check for if the format (Or non srgb format) is storage compatible 2025-06-10 16:54:03 -04:00
bridgewaterrobbie bf9435d0fd Never merge: Arbitrarilly raise max storage texutres per stage for easier tinkering, though this s houldn't be relied upon, spec limit is 4 2025-06-10 16:18:17 -04:00
bridgewaterrobbie 726177fead Hacky version of playing with formats and usages. 2025-06-10 15:58:02 -04:00
bridgewaterrobbie 2ef01ae1e8 Try to go in multiple passes to avoid hitting limits 2025-06-10 15:57:48 -04:00
bridgewaterrobbie 1374e8acef tinker2 2025-06-10 14:20:27 -04:00
bridgewaterrobbie 622beb211a WIP: Use converted JS library for compute pass mipmap generation 2025-06-10 14:16:57 -04:00
Juan Caldas b310e3d24a webgpu: Add missing WEBGPU cases (#8839) 2025-06-10 13:56:48 -04:00
Juan Caldas b359d77669 webgpu: Adjust the wrap modes (#8844)
* Change the WebGPU wrap modes
2025-06-10 11:09:37 -04:00
Sungun Park e57a4061cc Add missing header (#8842) 2025-06-09 21:01:29 +00:00
Powei Feng 53f82e6b71 vk: rename selectMemory params (#8835)
Use more accurate parameter names. 'types' indicate the types
of memory we are considering.  'reqs' indicate the requirements
each type must meet.

BUGS=401579988
2025-06-09 20:33:08 +00:00
rafadevaiandSerge Metral e88bba5940 VK: Disable VMA internal synchronization (#8836)
The vulkan backend is currently single threads, so there's
no need for VMA to use synchronization internally. This
will improve the CPU performance when going through VMA.

Co-authored-by: Serge Metral <sergemetral@google.com>
2025-06-09 20:05:02 +00:00
Matthew Hoffman 0b83454d08 Fix backend tests on macos vulkan. (#8815) 2025-06-09 19:05:03 +00:00
Matthew Hoffman 1ef0d36f79 Improve and document the backend test helper script. (#8816) 2025-06-09 18:19:30 +00:00
Powei Feng e350737d1e Remove texture pbd check for compressed textures (#8837)
This will also fix the failing ktx test.
2025-06-09 17:47:55 +00:00
Andy Hovingh cb203b13de webgpu: refactor: sort CMake source files 2025-06-09 11:05:15 -05:00
Andy Hovingh 92a0d7bfc3 webgpu: refactor: move WebGPU render target class to its own source+header files. 2025-06-09 11:05:15 -05:00
Andy Hovingh c1a7ed2799 webgpu: refactor: move WebGPU texture class to its own source+header files. 2025-06-09 11:05:15 -05:00
Andy Hovingh 692301bdf1 webgpu: refactor: move WebGPU descriptor set related classes to their own source+header files. 2025-06-09 11:05:15 -05:00
Andy Hovingh 05b87b3c42 webgpu: refactor: move WebGPU buffer classes to their own source+header files. 2025-06-09 11:05:15 -05:00
Andy Hovingh 9825297e70 webgpu: refactor: move WebGPUProgram to its own source+header files. 2025-06-09 11:05:15 -05:00
Powei Feng 6a59c887de vk: implement proper offset when uploading to texture (#8830)
We didn't take into account the top/left/stride parameters of the
PixelBufferDescriptor.
2025-06-06 20:46:45 +00:00
bridgewaterrobbie 33b4b46220 Create simple gltf viewer based on gltf_instances, primarily to facilitate webgpu development 2025-06-06 14:51:17 -04:00
Powei Feng 06170a25f7 Fix PBD size check in Texture (#8826)
Bug found when running ./samples/heightfield and press add pixel
buffer padding.
2025-06-06 18:20:05 +00:00
Konrad Piascik 418dad883d webgpu: Fix Android material compilation
Instead of requiring GL_EXT_shader_non_constant_global_initializers
we can just make the new variables const

BUGS=[421457710]
2025-06-06 13:46:37 -04:00
Jeremy Nelson 447661efed Use colorInfos.layer 2025-06-05 22:18:34 -07:00
Jeremy Nelson bd61ab691c add Default miplevel and array layer 2025-06-05 22:18:34 -07:00
Jeremy Nelson 2a04cee97f rename member variables 2025-06-05 22:18:34 -07:00
Jeremy Nelson 5ae7760752 fix samplerType check 2025-06-05 22:18:34 -07:00
Jeremy Nelson 56b8bf5b4b noexcept to makeTextureView 2025-06-05 22:18:34 -07:00
Jeremy Nelson 6af38fa47f renaming param to samplerType 2025-06-05 22:18:34 -07:00
Jeremy Nelson 0c01799c50 overload getTextureView 2025-06-05 22:18:34 -07:00
Jeremy Nelson e2519e0eed Update WebGPUHandles.cpp 2025-06-05 22:18:34 -07:00
Jeremy Nelson 6178ae0459 Use colorInfos.layer
# Conflicts:
#	filament/backend/src/webgpu/WebGPUHandles.cpp
2025-06-05 22:18:34 -07:00
Eliza 8a1a0b0fd2 materials: introduce mutable spec constants (#8795)
* materials: introduce mutable spec constants

Rationale & design of this feature has been discussed internally.

The current implementation uses a `FixedCapacityVector` to store the new program
handles, but I wouldn't object to replacing it with a hasmap as discussed
offline.

I have compiled but not tested this yet on Android, so I'm not certain that the
API bindings are correctly wired up.

* materials: mutable spec constant feedback

* materials: address mutable spec constant comments
2025-06-06 03:52:20 +00:00
Powei Feng a7c3cf4173 Ensure maximum number of vertex buffer does not exceed 8 (#8824) 2025-06-05 21:16:38 +00:00
bridgewaterrobbie 57b68eeba3 webgpu: Add required feature RG11B10UfloatRenderable for gltf_viewer, with comment on why 2025-06-05 15:08:03 -04:00
Juan Caldas ad8c9ce4e0 Add common CLI Args parser for the samples (#8819) 2025-06-05 18:18:41 +00:00
Powei Feng 7dc0f2cc86 vk: fix two validation error (#8825)
On starting gltf_viewer on mac, there were validation errors on

 1) copying to a depth texture using the aspect COLOR.
 2) querying formats that required extensions without enabling those
    extensions.
2025-06-05 17:50:40 +00:00
bridgewaterrobbie 37110799ef webgpu: Correct stencil state handling for depth-only formats 2025-06-05 13:22:26 -04:00
Anish GoyalandSerge Metral 88a06ec8e7 Switch to block-based stage-pool for Vulkan (#8742)
* Switch to block-based stage-pool for Vulkan

Instead of allocating a staging buffer every time one is needed,
allocate a large (8mb) block of memory, and divvy it up as needed.
We will make this configurable in the future, to allow for tuning
for different apps as needed.

* Address PR comments: use fvkmemory::Resource

Instead of having the child block be a unique_ptr that we create a
separate container for within the command buffers, just have the
stage block segments be fvkmemory::Resource instances.

* Address PR comments for staging buff change

- As per discussion with @poweifeng, change the name of a variable
  called "stage" to "stageSegment" for clarity
- As per discussion with @rafadevai, change the order of terminate
  calls in VulkanDriver to better reflect cleanup order of some
  objects.

* Align stage pool to nonCoherentAtomSize

In order to prevent flushing more atoms than were modified when writing
data to host-mapped memory in a staging buffer, ensure that all segments
allocated are aligned to nonCoherentAtomSize.

Also - fix merge conflict compile errors.

---------

Co-authored-by: Serge Metral <sergemetral@google.com>
2025-06-05 09:15:26 -07:00
bridgewaterrobbie 38705d6226 Add todos for unimplemented funcs that we haven't yet started on 2025-06-05 10:48:59 -04:00
bridgewaterrobbie d325bb43cf Add destroy texture implementation 2025-06-05 10:48:59 -04:00
Sungun Park 0c52d3c9bf Fix: Submit callback handle on completion (#8818)
This reverts a behavioral regression introduced in commit c3542b135e,
which deferred callback submission until the program was first used.

This commit restores the correct behavior by submitting the callback
handle as soon as the token's work is complete. This occurs either upon
successful `gl.program` population or via cancellation, ensuring the
caller is properly notified that the resource loading operation has
concluded.
2025-06-04 16:44:48 +00:00
Konrad Piascik 770176a1e3 Fix Windows build error C2512 2025-06-04 11:17:05 -04:00
bridgewaterrobbie 672603f9b4 Generate mipmaps when requested, deferring as needed 2025-06-03 17:45:35 -04:00
Powei Feng 0ecf6c46e2 github: Fix commit message parsing (#8817) 2025-06-03 14:24:29 -07:00
Andy Hovingh 8b87a54c1a webgpu: fix vertex buffer info for hellopbr 2025-06-03 16:08:12 -05:00
Powei Feng cd1d3e8749 github: update windows runner due to 2019 being "stuck" (#8814) 2025-06-03 13:27:31 -07:00
rafadevai e88072cec0 VK: Introduce VulkanBufferCache (#8757)
This class will allow better tracking of memory
allocations and recycling of buffers. Currently
only the uniform buffers are recycled.

It will eventually allow us to dynamically change
the underlying GPU buffer of a VulkanBuffer when
updating an UBO for uniforms and also keep track
which of those buffers are still inflight and which
ones are ready to be reuse for an UBO.

Its the first step on moving towards by passing
the staging buffer in UMA.
2025-06-03 12:36:50 -07:00
Powei Feng 1d2e165d99 Release Filament 1.61.0 2025-06-03 12:02:10 -07:00
Powei Feng cfc4ac5511 Add missing include in JobSystem.cpp (#8812) 2025-06-03 11:44:42 -07:00
Powei Feng 73a03d7af0 Flip conditional for fixing missing samplers (#8811) 2025-06-03 11:19:29 -07:00
Powei Feng 56dc348cc8 utils: add additional guards for Tracing (#8810)
The addition JobSystem.cpp allows for defining
FILAMENT_TRACING_ENABLED across targets.

Addingin FILAMENT_TRACING_ENABLED to the #if in Tracing.h prevents
perfetto from being included.
2025-06-03 11:18:11 -07:00
Juan Caldas 1b46ddd8b6 Webgpu: Check for Shadows (#8807)
BUGS=[397432947]
2025-06-03 11:57:34 +00:00
Powei Feng a68aaa114e renderdiff: fix breakage in parsing commit message (#8808) 2025-06-02 15:55:35 -07:00
Powei Feng 3da7dabb2a renderdiff: enable update goldens on commit merge (#8771) 2025-06-02 14:12:26 -07:00
bridgewaterrobbie 1e2311da3d Fix the fact that isSample might be combined with the other flags 2025-06-02 16:18:08 -04:00
bridgewaterrobbie a38c55c82d Fix use accidental use of uninitalized HwTexture::target 2025-06-02 16:18:08 -04:00
Powei Feng cd22478e4f renderdiff: add viewer for image differences (#8768)
- Modify the compare script to output more details of a
   comparison. This will include the source/golden directory,
   the comparison directory (the new renderings), and a file
   path to difference images if the golden does not match
   the rendered image.
 - The image_diff script can now output a TIFF that is the
   difference of two input TIFFs.
 - Add a viewer for examining the differences between rendered
   output and golden images.
 - The viewer consists of a simple server of web API endpoints
   for querying difference results (along with rendered images
   in TIFF).
 - And a web-based (html + lit-element) UI for looking at the
   rendered images and differences.
2025-06-02 19:37:46 +00:00
bridgewaterrobbie 60036c75fe Remove asserts that throw on shadowtest. Investigate them later 2025-06-02 14:17:39 -04:00
Powei Feng 044f2aeb7c gl: keep external texture id in sync (#8803)
(Attributed to @dsternfeld7)
2025-06-02 17:18:00 +00:00
161 changed files with 7669 additions and 2979 deletions
+43
View File
@@ -0,0 +1,43 @@
name: 'Get commit message'
outputs:
msg:
value: ${{ steps.action_output.outputs.msg }}
runs:
using: "composite"
steps:
- name: Find commit message (on push)
if: github.event_name == 'push'
shell: bash
run: |
AUTHOR_NAME="${{ github.event.head_commit.author.name }}"
AUTHOR_EMAIL="${{ github.event.head_commit.author.email }}"
TSTAMP="${{ github.event.head_commit.timestamp }}"
echo "commit ${{ github.event.head_commit.id }}" >> /tmp/commit_msg.txt
echo "Author: ${AUTHOR_NAME}<${AUTHOR_EMAIL}>" >> /tmp/commit_msg.txt
echo "Date: ${TSTAMP}" >> /tmp/commit_msg.txt
echo "" >> /tmp/commit_msg.txt
echo "${{ github.event.head_commit.message }}" >> /tmp/commit_msg.txt
- name: Checkout code
shell: bash
id: checkout_code
if: github.event_name == 'pull_request'
run: |
echo "hash=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
git checkout ${{ github.event.pull_request.head.ref }}
COMMIT_MESSAGE=$(git log -1)
echo "$(git log -1)" >> /tmp/commit_msg.txt
- shell: bash
id: action_output
run: |
DELIMITER="EOF_FILE_CONTENT_$(date +%s)" # Using timestamp to make it more unique
echo "msg<<$DELIMITER" >> "$GITHUB_OUTPUT"
cat /tmp/commit_msg.txt >> "$GITHUB_OUTPUT"
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
echo "----- got commit message ---"
cat /tmp/commit_msg.txt
echo "----------------------------"
- name: Uncheckout code
shell: bash
if: github.event_name == 'pull_request'
run: |
git checkout ${{ steps.checkout_code.outputs.hash }}
+35
View File
@@ -0,0 +1,35 @@
name: 'Post-submit tasks'
on:
push:
branches:
- main
jobs:
update-renderdiff-goldens:
name: update-renderdiff-goldens
runs-on: 'ubuntu-24.04-4core'
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/linux-prereq
- id: get_commit_msg
uses: ./.github/actions/get-commit-msg
- name: Prerequisites
run: pip install tifffile numpy
- name: Run update script
env:
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
run: |
GOLDEN_BRANCH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | python3 test/renderdiff/src/commit_msg.py)
COMMIT_HASH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | head -n 1 | tr -d 'commit ')
if [[ "${GOLDEN_BRANCH}" != "main" ]]; then
git config --global user.email "filament.bot@gmail.com"
git config --global user.name "Filament Bot"
git config --global credential.helper cache
echo "branch==${GOLDEN_BRANCH}"
echo "hash==${COMMIT_HASH}"
python3 test/renderdiff/src/update_golden.py --branch=${GOLDEN_BRANCH} \
--merge-to-main --filament-tag=${COMMIT_HASH} --golden-repo-token=${GH_TOKEN}
fi
+13 -10
View File
@@ -3,10 +3,10 @@ name: Presubmit
on:
push:
branches:
- main
- main
pull_request:
branches:
- main
- main
jobs:
build-desktop-mac:
@@ -41,8 +41,7 @@ jobs:
build-windows:
name: build-windows
runs-on: win-2019-16core
runs-on: windows-2022-32core
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -110,31 +109,35 @@ jobs:
fetch-depth: 0
- name: Check for manual edits to /docs
run: |
echo "${{ github.event.pull_request.head.sha }} -- ${{ github.event.pull_request.head.sha }}"
# disable for now
# bash docs_src/build/presubmit_check.sh ${{ github.event.pull_request.head.sha }}
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
runs-on: macos-14-xlarge
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- id: get_commit_msg
uses: ./.github/actions/get-commit-msg
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/mac-prereq
- name: Cache Mesa and deps
id: mesa-cache
uses: actions/cache@v4
with:
path: mesa
key: ${{ runner.os }}-mesa-deps-2-${{ vars.MESA_VERSION }}
- name: Prerequisites
id: prereqs
run: |
bash build/common/get-mesa.sh
pip install tifffile numpy
- name: Run Test
run: bash test/renderdiff/test.sh
run: |
echo "${{ steps.get_commit_msg.outputs.msg }}" | bash test/renderdiff/test.sh
- uses: actions/upload-artifact@v4
with:
name: presubmit-renderdiff-result
+1 -1
View File
@@ -226,7 +226,7 @@ jobs:
build-windows:
name: build-windows
runs-on: windows-2019-32core
runs-on: windows-2022-32core
if: github.event_name == 'release' || github.event.inputs.platform == 'windows'
steps:
+1 -1
View File
@@ -10,7 +10,7 @@ on:
jobs:
build-windows:
name: build-windows
runs-on: windows-2019-32core
runs-on: windows-2022-32core
steps:
- uses: actions/checkout@v4.1.6
+5 -1
View File
@@ -1,9 +1,13 @@
# Filament Release Notes log
**If you are merging a PR into main**: please add the release note below, under the *Release notes
for next branch cut* header.
We are chaning the way Vulkan buffers are handled. We need to switch over to a managed (or view-based) model where the data stored inside the object is a proxy to a Vulkan object that can dynamically be swapped around.
**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
- samples: samples now have a CLI to select backend api
- materials: sampler now export their type in the material binary [⚠️ **New Material Version**]
- materials: new mutable specialization constants feature. See the [materials documentation](https://google.github.io/filament/Materials.html) for details. [⚠️ **New Material Version**]
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.60.1'
implementation 'com.google.android.filament:filament-android:1.61.0'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.60.1'
pod 'Filament', '~> 1.61.0'
```
## Documentation
+3
View File
@@ -7,6 +7,9 @@ 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.61.1
## v1.61.0
- materials: sampler now export their type in the material binary [⚠️ **New Material Version**]
@@ -60,6 +60,14 @@ static void setParameter(JNIEnv* env, jlong nativeMaterialInstance, jstring name
env->ReleaseStringUTFChars(name_, name);
}
template<typename T>
static void setConstant(JNIEnv* env, jlong nativeMaterialInstance, jstring name_, T v) {
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
const char *name = env->GetStringUTFChars(name_, 0);
instance->setConstant(name, v);
env->ReleaseStringUTFChars(name_, name);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetParameterBool(JNIEnv *env, jclass,
@@ -264,6 +272,13 @@ Java_com_google_android_filament_MaterialInstance_nSetParameterTexture(
env->ReleaseStringUTFChars(name_, name);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetConstantBool(JNIEnv *env, jclass,
jlong nativeMaterialInstance, jstring name_, jboolean x) {
setConstant(env, nativeMaterialInstance, name_, bool(x));
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetScissor(
@@ -402,6 +402,16 @@ public class MaterialInstance {
nSetParameterFloat4(getNativeObject(), name, color[0], color[1], color[2], color[3]);
}
/**
* Sets the value of a bool constant.
*
* @param name the name of the material constant
* @param x the value of the material constant
*/
public void setConstant(@NonNull String name, boolean x) {
nSetConstantBool(getNativeObject(), name, x);
}
/**
* Set-up a custom scissor rectangle; by default it is disabled.
*
@@ -921,6 +931,9 @@ public class MaterialInstance {
@NonNull String name, int element, @NonNull @Size(min = 1) float[] v,
@IntRange(from = 0) int offset, @IntRange(from = 1) int count);
private static native void nSetConstantBool(long nativeMaterialInstance,
@NonNull String name, boolean x);
private static native void nSetParameterTexture(long nativeMaterialInstance,
@NonNull String name, long nativeTexture, long sampler);
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.60.1
VERSION_NAME=1.61.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
+9 -2
View File
@@ -41,6 +41,8 @@ for cmd in "${NEEDED_PYTHON_DEPS[@]}"; do
done
deactivate
LOCAL_PKG_CONFIG_PATH=
# Install system deps
if [[ "$OS_NAME" == "Linux" ]]; then
if [[ "$GITHUB_WORKFLOW" ]]; then
@@ -82,6 +84,9 @@ elif [[ "$OS_NAME" == "Darwin" ]]; then
fi
fi
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=true brew install autoconf automake libx11 libxext libxrandr llvm@${LLVM_VERSION} ninja meson pkg-config libxshmfence
# For reasons unknown, this is necessary for pkg-config to find homebrew's packages
LOCAL_PKG_CONFIG_PATH="/opt/homebrew/lib/pkgconfig:$PKG_CONFIG_PATH"
fi # [[ "$OS_NAME" == x ]]
LOCAL_LDFLAGS=${LDFLAGS}
@@ -134,9 +139,11 @@ fi
# -Dgallium-drivers=swrast => builds GL software rasterizer
# -Dvulkan-drivers=swrast => builds VK software rasterizer
# -Dgallium-drivers=llvmpipe is needed for GL >= 4.1 pipe-screen (see src/gallium/auxiliary/target-helpers/inline_sw_helper.h)
CXX=${LOCAL_CXX} CC=${LOCAL_CC} PATH=${LOCAL_PATH} LDFLAGS=${LOCAL_LDFLAGS} CPPFLAGS=${LOCAL_CPPFLAGS} \
PKG_CONFIG_PATH=${LOCAL_PKG_CONFIG_PATH} PATH=${LOCAL_PATH} \
CXX=${LOCAL_CXX} CC=${LOCAL_CC} LDFLAGS=${LOCAL_LDFLAGS} CPPFLAGS=${LOCAL_CPPFLAGS} \
meson setup --wipe builddir/ -Dprefix="${MESA_DIR}/out" -Dglx=xlib -Dosmesa=true -Dgallium-drivers=llvmpipe,swrast -Dvulkan-drivers=swrast
CXX=${LOCAL_CXX} CC=${LOCAL_CC} PATH=${LOCAL_PATH} LDFLAGS=${LOCAL_LDFLAGS} CPPFLAGS=${LOCAL_CPPFLAGS} \
PKG_CONFIG_PATH=${LOCAL_PKG_CONFIG_PATH} PATH=${LOCAL_PATH} \
CXX=${LOCAL_CXX} CC=${LOCAL_CC} LDFLAGS=${LOCAL_LDFLAGS} CPPFLAGS=${LOCAL_CPPFLAGS} \
meson install -C builddir/
# Disable python venv
+6 -2
View File
@@ -47,7 +47,11 @@ if "%RUNNING_LOCALLY%" == "1" (
set "PATH=%PATH%;C:\Program Files\7-Zip"
)
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\%VISUAL_STUDIO_VERSION%\VC\Auxiliary\Build\vcvars64.bat"
:: Outdated windows-2019 pattern
:: call "C:\Program Files (x86)\Microsoft Visual Studio\2019\%VISUAL_STUDIO_VERSION%\VC\Auxiliary\Build\vcvars64.bat"
call "C:\Program Files\Microsoft Visual Studio\2022\%VISUAL_STUDIO_VERSION%\VC\Auxiliary\Build\vcvars64.bat"
echo Passed vcvars64.bat
if errorlevel 1 exit /b %errorlevel%
msbuild /version
@@ -107,7 +111,7 @@ cd out\cmake-%variant%
if errorlevel 1 exit /b %errorlevel%
cmake ..\.. ^
-G "Visual Studio 16 2019" ^
-G "Visual Studio 17 2022" ^
-A x64 ^
%flag% ^
-DCMAKE_INSTALL_PREFIX=..\%variant% ^
+31 -24
View File
@@ -211,7 +211,7 @@ when using textures.
This property can dramatically change the appearance of a surface. Non-metallic surfaces have
chromatic diffuse reflection and achromatic specular reflection (reflected light does not change
color). Metallic surfaces do not have any diffuse reflection and chromatic specular reflection
(reflected light takes on the color of the surfaced as defined by `baseColor`).
(reflected light takes on the color of the surfaced as defined by `baseColor`).
The effect of `metallic` is shown in figure [metallicProperty] (click on the image to see a
larger version).
@@ -247,7 +247,7 @@ The effect of `roughness` on metallic surfaces is shown in figure [roughnessCond
When refraction through an object is enabled (using a `refractonType` of `thin` or `solid`), the
`roughness` property will also affect the refractions, as shown in figure
[roughnessRefractionProperty] (click on the image to see a larger version).
![Figure [roughnessRefractionProperty]: Refractive sphere with `roughness` varying from 0.0
(left) to 1.0 (right)](images/materials/refraction_roughness.png)
@@ -306,7 +306,7 @@ The sheen color controls the color appearance and strength of an optional sheen
base layer described by the properties above. The sheen layer always sits below the clear coat layer
if such a layer is present.
The sheen layer can be used to represent cloth and fabric materials. Please refer to
The sheen layer can be used to represent cloth and fabric materials. Please refer to
section [Cloth model] for more information about cloth and fabric materials.
The effect of `sheenColor` is shown in figure [materialSheenColor]
@@ -519,14 +519,14 @@ light to bend further away from the initial path.
Table [commonMatIOR] describes acceptable refractive indices for various types of materials.
Material | IOR
Material | IOR
--------------------------:|:-----------------
Air | 1.0
Water | 1.33
Common liquids | 1.33 to 1.5
Common gemstones | 1.58 to 2.33
Plastics, glass | 1.5 to 1.58
Other dielectric materials | 1.33 to 1.58
Water | 1.33
Common liquids | 1.33 to 1.5
Common gemstones | 1.58 to 2.33
Plastics, glass | 1.5 to 1.58
Other dielectric materials | 1.33 to 1.58
[Table [commonMatIOR]: Index of refraction of common materials]
The appearance of a refractive material will greatly depend on the `refractionType` and
@@ -1091,30 +1091,37 @@ Value
`bool` or `number`, depending on the `type` of the constant. The type must be one of the types
described in table [materialConstantsTypes].
Type | Description | Default
:----------------------|:-----------------------------------------|:------------------
int | A signed, 32 bit GLSL int | 0
float | A single-precision GLSL float | 0.0
bool | A GLSL bool | false
Constants may also be specified as mutable by setting the `mutable` property to `true`. Only
`bool` spec constants may be specified as mutable.
| Type | Description | Default | May be mutable? |
|:------|:------------------------------|:--------|-----------------|
| int | A signed, 32 bit GLSL int | | no |
| float | A single-precision GLSL float | 0.0 | no |
| bool | A GLSL bool | false | yes |
[Table [materialConstantsTypes]: Material constants types]
Description
: Lists the constant parameters accepted by your material. These constants can be set, or
"specialized", at runtime when loading a material package. Multiple materials can be loaded from
the same material package with differing constant parameter specializations. Once a material is
loaded from a material package, its constant parameters cannot be changed. Compared to regular
parameters, constant parameters allow the compiler to generate more efficient code. Access
constant parameters from the shader by prefixing the name with `materialConstant_`. For example,
a constant parameter named `myConstant` is accessed in the shader as
`materialConstant_myConstant`. If a constant parameter is not set at runtime, the default is
used.
the same material package with differing constant parameter specializations. If a constant
parameter is specialized as mutable, it may be changed at any time on a per-instance basis via
`MaterialInstance::setConstant(name, value)`. Otherwise, once a material is loaded from a
material package, its constant parameters cannot be changed.
Compared to regular parameters, constant parameters allow the compiler to generate more
efficient code. Access constant parameters from the shader by prefixing the name with
`materialConstant_`. For example, a constant parameter named `myConstant` is accessed in the
shader as `materialConstant_myConstant`. If a constant parameter is not set at runtime, the
default is used.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
material {
constants : [
{
name : overrideAlpha,
type : bool
type : bool,
mutable : true,
},
{
name : customAlpha,
@@ -2553,7 +2560,7 @@ standard skybox material. It produces a list of 2 parameters, named `showSun` an
respectively a boolean and a cubemap texture.
```text
$ matc --reflect parameters filament/src/materials/skybox.mat
$ matc --reflect parameters filament/src/materials/skybox.mat
{
"parameters": [
{
@@ -2569,7 +2576,7 @@ $ matc --reflect parameters filament/src/materials/skybox.mat
}
]
}
```
```
### --variant-filter
+26 -4
View File
@@ -180,8 +180,11 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanAsyncHandles.h
src/vulkan/VulkanBlitter.cpp
src/vulkan/VulkanBlitter.h
src/vulkan/VulkanBuffer.cpp
src/vulkan/VulkanBuffer.h
src/vulkan/VulkanBufferCache.h
src/vulkan/VulkanBufferCache.cpp
src/vulkan/VulkanBufferProxy.h
src/vulkan/VulkanBufferProxy.cpp
src/vulkan/VulkanCommands.cpp
src/vulkan/VulkanCommands.h
src/vulkan/VulkanConstants.h
@@ -254,17 +257,36 @@ if (FILAMENT_SUPPORTS_WEBGPU)
list(APPEND SRCS
include/backend/platforms/WebGPUPlatform.h
src/webgpu/platform/WebGPUPlatform.cpp
src/webgpu/WebGPUBufferBase.cpp
src/webgpu/WebGPUBufferBase.h
src/webgpu/WebGPUBufferObject.cpp
src/webgpu/WebGPUBufferObject.h
src/webgpu/WebGPUConstants.h
src/webgpu/WebGPUDescriptorSet.cpp
src/webgpu/WebGPUDescriptorSet.h
src/webgpu/WebGPUDescriptorSetLayout.cpp
src/webgpu/WebGPUDescriptorSetLayout.h
src/webgpu/WebGPUDriver.cpp
src/webgpu/WebGPUDriver.h
src/webgpu/WebGPUHandles.cpp
src/webgpu/WebGPUHandles.h
src/webgpu/WebGPUIndexBuffer.cpp
src/webgpu/WebGPUIndexBuffer.h
src/webgpu/WebGPUPipelineCreation.cpp
src/webgpu/WebGPUPipelineCreation.h
src/webgpu/WebGPUProgram.cpp
src/webgpu/WebGPUProgram.h
src/webgpu/WebGPURenderPrimitive.h
src/webgpu/WebGPURenderTarget.cpp
src/webgpu/WebGPURenderTarget.h
src/webgpu/WebGPUStrings.h
src/webgpu/WebGPUSwapChain.cpp
src/webgpu/WebGPUSwapChain.h
src/webgpu/WGPUProgram.cpp
src/webgpu/WebGPUTexture.cpp
src/webgpu/WebGPUTexture.h
src/webgpu/WebGPUVertexBuffer.cpp
src/webgpu/WebGPUVertexBuffer.h
src/webgpu/WebGPUVertexBufferInfo.cpp
src/webgpu/WebGPUVertexBufferInfo.h
src/webgpu/SpdMipmapGenerator.cpp
)
if (WIN32)
list(APPEND SRCS src/webgpu/platform/WebGPUPlatformWindows.cpp)
+4 -1
View File
@@ -20,6 +20,7 @@
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Invocable.h>
#include <utils/bitset.h>
#include <backend/DriverEnums.h>
@@ -66,6 +67,7 @@ public:
using DescriptorBindingsInfo = utils::FixedCapacityVector<Descriptor>;
using DescriptorSetInfo = std::array<DescriptorBindingsInfo, MAX_DESCRIPTOR_SET_COUNT>;
using SpecializationConstantsInfo = utils::FixedCapacityVector<SpecializationConstant>;
using MutableSpecConstantsInfo = utils::bitset8;
using ShaderBlob = utils::FixedCapacityVector<uint8_t>;
using ShaderSource = std::array<ShaderBlob, SHADER_TYPE_COUNT>;
@@ -102,7 +104,8 @@ public:
Program& descriptorBindings(backend::descriptor_set_t set,
DescriptorBindingsInfo descriptorBindings) noexcept;
Program& specializationConstants(SpecializationConstantsInfo specConstants) noexcept;
Program& specializationConstants(SpecializationConstantsInfo specConstants,
uint32_t firstMutableId, MutableSpecConstantsInfo mutableSpecConstants) noexcept;
struct PushConstant {
utils::CString name;
@@ -222,56 +222,6 @@ public:
return static_cast<Dp>(p);
}
template<typename Dp, typename B>
inline std::enable_if_t<
std::is_pointer_v<Dp> && std::is_base_of_v<B, std::remove_pointer_t<Dp>>, int16_t>
handle_set_value(Handle<B>& handle, uint8_t v) {
assert_invariant(handle);
auto [p, tag] = handleToPointer(handle.getId());
if (isPoolHandle(handle.getId())) {
// check for pool handle use-after-free
if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) {
auto const pNode = static_cast<typename Allocator::Node*>(p);
pNode[-1].test_value = v;
return 1;
}
return 0;
} else {
// check for heap handle use-after-free
if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) {
return -1;
}
return -2;
}
}
template<typename Dp, typename B>
inline std::enable_if_t<
std::is_pointer_v<Dp> && std::is_base_of_v<B, std::remove_pointer_t<Dp>>, int16_t>
handle_get_value(Handle<B>& handle) {
assert_invariant(handle);
auto [p, tag] = handleToPointer(handle.getId());
if (isPoolHandle(handle.getId())) {
// check for pool handle use-after-free
if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) {
auto const pNode = static_cast<typename Allocator::Node*>(p);
uint8_t age = pNode[-1].age;
uint8_t test_value = pNode[-1].test_value;
int16_t ret = (int16_t) ((age << 8) | test_value);
return ret;
}
return 0;
} else {
// check for heap handle use-after-free
if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) {
return -1;
}
return -2;
}
}
utils::CString getHandleTag(HandleBase::HandleId key) const noexcept;
template<typename B>
@@ -326,10 +276,7 @@ private:
class Allocator {
friend class HandleAllocator;
static constexpr size_t MIN_ALIGNMENT = alignof(std::max_align_t);
struct Node {
uint8_t age;
uint8_t test_value = 0xff;
};
struct Node { uint8_t age; };
// Note: using the `extra` parameter of PoolAllocator<>, even with a 1-byte structure,
// generally increases all pool allocations by 8-bytes because of alignment restrictions.
template<size_t SIZE>
+11 -1
View File
@@ -80,8 +80,18 @@ Program& Program::attributes(AttributesInfo attributes) noexcept {
return *this;
}
Program& Program::specializationConstants(SpecializationConstantsInfo specConstants) noexcept {
Program& Program::specializationConstants(SpecializationConstantsInfo specConstants,
uint32_t firstMutableId, MutableSpecConstantsInfo mutableSpecConstants) noexcept {
// String the two lists together.
mSpecializationConstants = std::move(specConstants);
uint32_t firstMutableIndex = specConstants.size();
mSpecializationConstants.reserve(specConstants.size() + mutableSpecConstants.size());
for (uint32_t i = 0; i < mutableSpecConstants.size(); i++) {
mSpecializationConstants[i + firstMutableIndex] = SpecializationConstant {
.id = i + firstMutableId,
.value = mutableSpecConstants[i],
};
}
return *this;
}
+20 -78
View File
@@ -643,25 +643,6 @@ void OpenGLDriver::createVertexBufferInfoR(
AttributeArray attributes) {
DEBUG_MARKER()
construct<GLVertexBufferInfo>(vbih, bufferCount, attributeCount, attributes);
// test ----
int16_t returned_value = handle_set_value<GLVertexBufferInfo*>(vbih, 90);
uint8_t age = (uint8_t) (returned_value >> 8);
uint8_t test_value = (returned_value & 0xff);
switch (test_value) {
case -2:
case -1:
case 0:
utils::slog.w << "@@@ createVertexBufferInfoR: unexpected result, age:" << (int) age
<< ", test_value:" << (int) test_value << utils::io::endl;
break;
default:
FILAMENT_CHECK_POSTCONDITION(test_value == 1)
<< "@@@ createVertexBufferInfoR: shouldn't happen, age:" << (int) age
<< ", test_value:" << (int) test_value << utils::io::endl;
break;
}
//----------
}
void OpenGLDriver::createVertexBufferR(
@@ -780,25 +761,6 @@ void OpenGLDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
construct<OpenGLProgram>(ph, *this, std::move(program));
CHECK_GL_ERROR(utils::slog.e)
// test ----
int16_t returned_value = handle_set_value<OpenGLProgram*>(ph, 100);
uint8_t age = (uint8_t) (returned_value >> 8);
uint8_t test_value = (returned_value & 0xff);
switch (test_value) {
case -2:
case -1:
case 0:
utils::slog.w << "@@@ createProgramR: unexpected result, age:" << (int)age << ", test_value:" << (int)test_value
<< utils::io::endl;
break;
default:
FILAMENT_CHECK_POSTCONDITION(test_value == 1)
<< "@@@ createProgramR: shouldn't happen, age:" << (int)age
<< ", test_value:" << (int)test_value << utils::io::endl;
break;
}
//----------
}
UTILS_NOINLINE
@@ -1956,25 +1918,6 @@ void OpenGLDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
void OpenGLDriver::destroyProgram(Handle<HwProgram> ph) {
DEBUG_MARKER()
if (ph) {
// test ----
int16_t returned_value = handle_set_value<OpenGLProgram*>(ph, 101);
uint8_t age = (uint8_t) (returned_value >> 8);
uint8_t test_value = (returned_value & 0xff);
switch (test_value) {
case -2:
case -1:
case 0:
utils::slog.w << "@@@ destroyProgram: unexpected result, age:" << (int) age
<< ", test_value:" << (int) test_value << utils::io::endl;
break;
default:
FILAMENT_CHECK_POSTCONDITION(test_value == 1)
<< "@@@ destroyProgram: shouldn't happen, age:" << (int) age
<< ", test_value:" << (int) test_value << utils::io::endl;
break;
}
//----------
OpenGLProgram* p = handle_cast<OpenGLProgram*>(ph);
destruct(ph, p);
}
@@ -2599,7 +2542,12 @@ void OpenGLDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain>
[this](size_t index) {
for (auto t: mTexturesWithStreamsAttached) {
if (t->hwStream->streamType == StreamType::NATIVE) {
glGenTextures(1, &t->gl.id);
if (t->externalTexture) {
glGenTextures(1, &t->externalTexture->id);
t->gl.id = t->externalTexture->id;
} else {
glGenTextures(1, &t->gl.id);
}
mPlatform.attach(t->hwStream->stream, t->gl.id);
mContext.updateTexImage(GL_TEXTURE_EXTERNAL_OES, t->gl.id);
}
@@ -3084,6 +3032,7 @@ void OpenGLDriver::attachStream(GLTexture* t, GLStream* hwStream) noexcept {
switch (hwStream->streamType) {
case StreamType::NATIVE:
mPlatform.attach(hwStream->stream, t->gl.id);
mContext.updateTexImage(GL_TEXTURE_EXTERNAL_OES, t->gl.id);
break;
case StreamType::ACQUIRED:
break;
@@ -3112,7 +3061,12 @@ void OpenGLDriver::detachStream(GLTexture* t) noexcept {
break;
}
glGenTextures(1, &t->gl.id);
if (t->externalTexture) {
glGenTextures(1, &t->externalTexture->id);
t->gl.id = t->externalTexture->id;
} else {
glGenTextures(1, &t->gl.id);
}
t->hwStream = nullptr;
}
@@ -3136,8 +3090,14 @@ void OpenGLDriver::replaceStream(GLTexture* texture, GLStream* newStream) noexce
switch (newStream->streamType) {
case StreamType::NATIVE:
glGenTextures(1, &texture->gl.id);
if (texture->externalTexture) {
glGenTextures(1, &texture->externalTexture->id);
texture->gl.id = texture->externalTexture->id;
} else {
glGenTextures(1, &texture->gl.id);
}
mPlatform.attach(newStream->stream, texture->gl.id);
mContext.updateTexImage(GL_TEXTURE_EXTERNAL_OES, texture->gl.id);
break;
case StreamType::ACQUIRED:
// Just re-use the old texture id.
@@ -4123,24 +4083,6 @@ void OpenGLDriver::bindPipeline(PipelineState const& state) {
setRasterState(state.rasterState);
setStencilState(state.stencilState);
gl.polygonOffset(state.polygonOffset.slope, state.polygonOffset.constant);
// test ----
Handle<HwProgram> pro = state.program;
int16_t returned_value = handle_get_value<OpenGLProgram*>(pro);
uint8_t age = (uint8_t)(returned_value >> 8);
uint8_t test_value = (returned_value & 0xff);
switch (test_value) {
case -2:
case -1:
case 0:
utils::slog.w << "@@@ bindPipeline: unexpected result, age:" << (int)age << ", test_value:" << (int)test_value << utils::io::endl;
break;
default:
if (test_value != 100) {
utils::slog.w << "@@@ bindPipeline: program is not valid OpenGLProgram, age:" << (int)age << ", test_value:" << (int)test_value << utils::io::endl;
}
break;
}
//----------
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(state.program);
mValidProgram = useProgram(p);
(*mCurrentPushConstants) = p->getPushConstants();
@@ -282,20 +282,6 @@ private:
return mHandleAllocator.handle_cast<Dp, B>(handle);
}
template<typename Dp, typename B>
std::enable_if_t<std::is_pointer_v<Dp> && std::is_base_of_v<B, std::remove_pointer_t<Dp>>,
int16_t>
handle_set_value(Handle<B>& handle, uint8_t v) {
return mHandleAllocator.handle_set_value<Dp, B>(handle, v);
}
template<typename Dp, typename B>
std::enable_if_t<std::is_pointer_v<Dp> && std::is_base_of_v<B, std::remove_pointer_t<Dp>>,
int16_t>
handle_get_value(Handle<B>& handle) {
return mHandleAllocator.handle_get_value<Dp, B>(handle);
}
friend class OpenGLProgram;
friend class ShaderCompilerService;
@@ -23,6 +23,7 @@
#include "OpenGLDriver.h"
#include <iterator>
#include <optional>
#include <private/backend/BackendUtils.h>
#include <backend/DriverEnums.h>
@@ -110,7 +111,17 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
cond.wait(l, [this] { return signaled; });
}
CallbackManager::Handle handle{};
// This is invoked upon token completion, which occurs after a successful `gl.program`
// population or upon cancellation. In either scenario, the callback handle must be submitted
// to notify the caller that resource loading has concluded.
void trySubmittingCallback() noexcept {
if (handle) {
compiler.submitCallbackHandle(*handle);
handle = std::nullopt;
}
}
std::optional<CallbackManager::Handle> handle{};
BlobCacheKey key;
// Used for the `THREAD_POOL` mode.
@@ -120,7 +131,7 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
};
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() {
compiler.submitCallbackHandle(handle);
trySubmittingCallback();
}
/* static */ void ShaderCompilerService::setUserData(const program_token_t& token,
@@ -339,7 +350,7 @@ GLuint ShaderCompilerService::getProgram(program_token_t& token) {
// Cleanup the token.
token->compiler.cancelTickOp(token);
token = nullptr;// This will submit a callback condition (handle) to the callback manager.
token = nullptr; // This will try submitting a callback handle to the callback manager.
}
void ShaderCompilerService::tick() {
@@ -392,7 +403,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) {
// Cleanup the token.
token->compiler.cancelTickOp(token);
token = nullptr;// This will submit a callback condition (handle) to the callback manager.
token = nullptr;
return program;
}
@@ -659,6 +670,7 @@ void ShaderCompilerService::executeTickOps() noexcept {
}
glLinkProgram(program);
token->gl.program = program;
token->trySubmittingCallback();
}
/* static */ bool ShaderCompilerService::isLinkCompleted(program_token_t const& token) noexcept {
+22 -21
View File
@@ -17,35 +17,36 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANBUFFER_H
#define TNT_FILAMENT_BACKEND_VULKANBUFFER_H
#include "VulkanContext.h"
#include "VulkanStagePool.h"
#include "VulkanMemory.h"
#include "memory/Resource.h"
#include <functional>
namespace filament::backend {
// Encapsulates a Vulkan buffer, its attached DeviceMemory and a staging area.
class VulkanBuffer {
class VulkanBuffer : public fvkmemory::Resource {
public:
VulkanBuffer(VmaAllocator allocator, VulkanStagePool& stagePool, VkBufferUsageFlags usage,
uint32_t numBytes);
~VulkanBuffer();
void loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint32_t byteOffset,
uint32_t numBytes);
VkBuffer getGpuBuffer() const {
return mGpuBuffer;
// Because we need to recycle the unused `VulkanGpuBuffer`, we allow for a callback that the
// "Pool" can use to acquire the buffer back.
using OnRecycle = std::function<void(VulkanGpuBuffer const*)>;
VulkanBuffer(VulkanGpuBuffer const* gpuBuffer, OnRecycle&& onRecycleFn)
: mGpuBuffer(gpuBuffer),
mOnRecycleFn(onRecycleFn) {}
~VulkanBuffer() {
if (mOnRecycleFn) {
mOnRecycleFn(mGpuBuffer);
}
}
private:
VmaAllocator mAllocator;
VulkanStagePool& mStagePool;
VulkanGpuBuffer const* getGpuBuffer() const { return mGpuBuffer; }
VmaAllocation mGpuMemory = VK_NULL_HANDLE;
VkBuffer mGpuBuffer = VK_NULL_HANDLE;
VkBufferUsageFlags mUsage = {};
uint32_t mUpdatedOffset = 0;
uint32_t mUpdatedBytes = 0;
private:
VulkanGpuBuffer const* mGpuBuffer;
OnRecycle mOnRecycleFn;
};
} // namespace filament::backend
}// namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_VULKANBUFFER_H
#endif// TNT_FILAMENT_BACKEND_VULKANBUFFER_H
@@ -0,0 +1,207 @@
/*
* 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 "VulkanBufferCache.h"
#include "VulkanBuffer.h"
#include "VulkanConstants.h"
#include "VulkanMemory.h"
#include "memory/Resource.h"
#include "memory/ResourceManager.h"
#include <utility>
namespace filament::backend {
namespace {
VkBufferUsageFlags getVkBufferUsage(VulkanBufferUsage usage) {
switch (usage) {
case VulkanBufferUsage::VERTEX:
return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
case VulkanBufferUsage::INDEX:
return VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
case VulkanBufferUsage::UNIFORM:
return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
case VulkanBufferUsage::SHADER_STORAGE:
return VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
case VulkanBufferUsage::UNKNOWN:
return 0;
}
return 0;
}
}// namespace
VulkanBufferCache::VulkanBufferCache(VulkanContext const& context,
fvkmemory::ResourceManager& resourceManager, VmaAllocator allocator)
: mContext(context),
mResourceManager(resourceManager),
mAllocator(allocator) {}
fvkmemory::resource_ptr<VulkanBuffer> VulkanBufferCache::acquire(VulkanBufferUsage usage,
uint32_t numBytes) noexcept {
assert_invariant(usage != VulkanBufferUsage::UNKNOWN);
BufferPool& bufferPool = getPool(usage);
// First check if an allocation exists whose capacity is greater than or equal to the requested
// size.
auto iter = bufferPool.lower_bound(numBytes);
if (iter != bufferPool.end()) {
VulkanGpuBuffer const* gpuBuffer = iter->second.gpuBuffer;
bufferPool.erase(iter);
return fvkmemory::resource_ptr<VulkanBuffer>::construct(&mResourceManager, gpuBuffer,
[this](VulkanGpuBuffer const* gpuBuffer) { this->release(gpuBuffer); });
}
// We were not able to find a sufficiently large allocation, so create a new one that is
// recycled after being yielded.
VulkanGpuBuffer const* gpuBuffer = allocate(usage, numBytes);
return fvkmemory::resource_ptr<VulkanBuffer>::construct(&mResourceManager, gpuBuffer,
[this](VulkanGpuBuffer const* gpuBuffer) { this->release(gpuBuffer); });
}
void VulkanBufferCache::gc() noexcept {
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_START("VulkanBufferCache::gc");
// If this is one of the first few frames, return early to avoid wrapping unsigned integers.
constexpr uint32_t TIME_BEFORE_EVICTION = 3;
if (++mCurrentFrame <= TIME_BEFORE_EVICTION) {
return;
}
const uint64_t evictionTime = mCurrentFrame - TIME_BEFORE_EVICTION;
// Destroy buffers that have not been used for several frames.
for (auto& bufferPool: mGpuBufferPools) {
for (auto poolIter = bufferPool.begin(); poolIter != bufferPool.end();) {
if (poolIter->second.lastAccessed < evictionTime) {
#if FVK_ENABLED(FVK_DEBUG_VULKAN_BUFFER_CACHE)
FVK_LOGD << "VulkanBufferCache - Destroyed vkBuffer "
<< poolIter->second.gpuBuffer->vkbuffer << " with usage "
<< static_cast<int>(poolIter->second.gpuBuffer->usage) << utils::io::endl;
#endif// FVK_DEBUG_VULKAN_BUFFER_CACHE
destroy(poolIter->second.gpuBuffer);
poolIter = bufferPool.erase(poolIter);
} else {
++poolIter;
}
}
}
FVK_SYSTRACE_END();
}
void VulkanBufferCache::terminate() noexcept {
for (auto& bufferPool: mGpuBufferPools) {
for (auto& poolEntry: bufferPool) {
destroy(poolEntry.second.gpuBuffer);
}
bufferPool.clear();
}
}
void VulkanBufferCache::release(VulkanGpuBuffer const* gpuBuffer) noexcept {
assert_invariant(gpuBuffer != nullptr);
BufferPool& bufferPool = getPool(gpuBuffer->usage);
bufferPool.insert(std::make_pair(gpuBuffer->numBytes, UnusedGpuBuffer{
.lastAccessed = mCurrentFrame,
.gpuBuffer = gpuBuffer,
}));
}
VulkanGpuBuffer const* VulkanBufferCache::allocate(VulkanBufferUsage usage,
uint32_t numBytes) noexcept {
VkBufferCreateInfo const bufferInfo{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = numBytes,
// `VK_BUFFER_USAGE_TRANSFER_DST_BIT` is needed to allow updating the buffer through
// a staging using `vkCmdCopyBuffer`.
.usage = getVkBufferUsage(usage) | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
};
VmaAllocationCreateFlags vmaFlags = 0;
if (usage == VulkanBufferUsage::UNIFORM) {
// In the case of UMA, the uniform buffers will always be mappable
if (mContext.isUnifiedMemoryArchitecture()) {
vmaFlags |= VMA_ALLOCATION_CREATE_MAPPED_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
}
}
VulkanGpuBuffer* gpuBuffer = new VulkanGpuBuffer{
.numBytes = numBytes,
.usage = usage,
};
VmaAllocationCreateInfo const allocInfo{
.flags = vmaFlags,
.usage = VMA_MEMORY_USAGE_AUTO,
.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
};
UTILS_UNUSED_IN_RELEASE VkResult result = vmaCreateBuffer(mAllocator, &bufferInfo, &allocInfo,
&gpuBuffer->vkbuffer, &gpuBuffer->vmaAllocation, &gpuBuffer->allocationInfo);
#if FVK_ENABLED(FVK_DEBUG_VULKAN_BUFFER_CACHE)
if (result != VK_SUCCESS) {
FVK_LOGE << "VulkanBufferCache - failed to allocate a new vkBuffer of size " << numBytes
<< " and usage " << static_cast<int>(usage) << ", error: " << result
<< utils::io::endl;
} else {
FVK_LOGD << "VulkanBufferCache - allocated a vkBuffer " << gpuBuffer->vkbuffer
<< " of size " << numBytes << " and usage = " << static_cast<int>(usage)
<< " successfully" << utils::io::endl;
}
#endif// FVK_DEBUG_VULKAN_BUFFER_CACHE
return gpuBuffer;
}
void VulkanBufferCache::destroy(VulkanGpuBuffer const* gpuBuffer) noexcept {
vmaDestroyBuffer(mAllocator, gpuBuffer->vkbuffer, gpuBuffer->vmaAllocation);
delete gpuBuffer;
gpuBuffer = nullptr;
}
VulkanBufferCache::BufferPool& VulkanBufferCache::getPool(VulkanBufferUsage usage) noexcept {
int poolIndex = -1;
switch (usage) {
case VulkanBufferUsage::VERTEX:
poolIndex = 0;
break;
case VulkanBufferUsage::INDEX:
poolIndex = 1;
break;
case VulkanBufferUsage::UNIFORM:
poolIndex = 2;
break;
case VulkanBufferUsage::SHADER_STORAGE:
poolIndex = 3;
break;
case VulkanBufferUsage::UNKNOWN:
PANIC_LOG("There's no pool for buffers with unkown usage.");
break;
}
assert_invariant(poolIndex >= 0 && poolIndex < MAX_POOL_COUNT);
return mGpuBufferPools[poolIndex];
}
}// namespace filament::backend
@@ -0,0 +1,84 @@
/*
* 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_VULKANBUFFERCACHE_H
#define TNT_FILAMENT_BACKEND_VULKANBUFFERCACHE_H
#include "VulkanBuffer.h"
#include "VulkanContext.h"
#include "VulkanMemory.h"
#include "memory/Resource.h"
#include "memory/ResourceManager.h"
#include <map>
namespace filament::backend {
class VulkanBufferCache {
public:
VulkanBufferCache(VulkanContext const& context, fvkmemory::ResourceManager& resourceManager,
VmaAllocator allocator);
// `VulkanBufferCache` is not copyable.
VulkanBufferCache(const VulkanBufferCache&) = delete;
VulkanBufferCache& operator=(const VulkanBufferCache&) = delete;
// Allocates or reuse a new VkBuffer that is device local.
// In the case of Unified memory architecture, uniform buffers are also host visible.
fvkmemory::resource_ptr<VulkanBuffer> acquire(VulkanBufferUsage usage,
uint32_t numBytes) noexcept;
// Evicts old unused `VulkanGpuBuffer` and bumps the current frame number
void gc() noexcept;
// Destroys all unused `VulkanGpuBuffer`.
// This should be called while the context's VkDevice is still alive.
void terminate() noexcept;
private:
struct UnusedGpuBuffer {
uint64_t lastAccessed;
VulkanGpuBuffer const* gpuBuffer;
};
using BufferPool = std::multimap<uint32_t, UnusedGpuBuffer>;
// Return a `VulkanGpuBuffer` back to its corresponding pool
void release(VulkanGpuBuffer const* gpuBuffer) noexcept;
// Allocate a new VkBuffer from the VMA pool of the corresponding `numBytes` and `usage`.
VulkanGpuBuffer const* allocate(VulkanBufferUsage usage, uint32_t numBytes) noexcept;
// Destroy the corresponding VkBuffer and return the VkDeviceMemory to the VMA pool.
void destroy(VulkanGpuBuffer const* gpuBuffer) noexcept;
BufferPool& getPool(VulkanBufferUsage usage) noexcept;
VulkanContext const& mContext;
fvkmemory::ResourceManager& mResourceManager;
VmaAllocator mAllocator;
// Buffers can be recycled, after they are released. Each type of buffer have its own pool
static constexpr int MAX_POOL_COUNT = 4;
BufferPool mGpuBufferPools[MAX_POOL_COUNT];
// Store the current "time" (really just a frame count) and LRU eviction parameters.
uint64_t mCurrentFrame = 0;
};
}// namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_VULKANBUFFERCACHE_H
@@ -14,49 +14,35 @@
* limitations under the License.
*/
#include "VulkanBuffer.h"
#include "VulkanBufferProxy.h"
#include "VulkanCommands.h"
#include "VulkanMemory.h"
#include <utils/Panic.h>
#include "VulkanBufferCache.h"
#include "VulkanMemory.h"
using namespace bluevk;
namespace filament::backend {
VulkanBuffer::VulkanBuffer(VmaAllocator allocator, VulkanStagePool& stagePool,
VkBufferUsageFlags usage, uint32_t numBytes)
VulkanBufferProxy::VulkanBufferProxy(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanBufferCache& bufferCache, VulkanBufferUsage usage, uint32_t numBytes)
: mAllocator(allocator),
mStagePool(stagePool),
mUsage(usage),
mBufferCache(bufferCache),
mBuffer(mBufferCache.acquire(usage, numBytes)),
mUpdatedOffset(0),
mUpdatedBytes(0) {
// for now make sure that only 1 bit is set in usage
// (because loadFromCpu() assumes that somewhat)
assert_invariant(usage && !(usage & (usage - 1)));
mUpdatedBytes(0) {}
// Create the VkBuffer.
VkBufferCreateInfo bufferInfo {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = numBytes,
.usage = usage | VK_BUFFER_USAGE_TRANSFER_DST_BIT
};
VmaAllocationCreateInfo allocInfo { .usage = VMA_MEMORY_USAGE_GPU_ONLY };
vmaCreateBuffer(mAllocator, &bufferInfo, &allocInfo, &mGpuBuffer, &mGpuMemory, nullptr);
}
VulkanBuffer::~VulkanBuffer() {
vmaDestroyBuffer(mAllocator, mGpuBuffer, mGpuMemory);
}
void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint32_t byteOffset,
uint32_t numBytes) {
VulkanStage const* stage = mStagePool.acquireStage(numBytes);
void* mapped;
vmaMapMemory(mAllocator, stage->memory, &mapped);
memcpy(mapped, cpuData, numBytes);
vmaUnmapMemory(mAllocator, stage->memory);
vmaFlushAllocation(mAllocator, stage->memory, 0, numBytes);
void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* cpuData,
uint32_t byteOffset, uint32_t numBytes) {
// 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);
assert_invariant(stage->memory());
commands.acquire(stage);
memcpy(stage->mapping(), cpuData, numBytes);
vmaFlushAllocation(mAllocator, stage->memory(), stage->offset(), numBytes);
// If there was a previous update, then we need to make sure the following write is properly
// synced with the previous read.
@@ -64,13 +50,13 @@ void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint
(byteOffset >= mUpdatedOffset && byteOffset <= (mUpdatedOffset + mUpdatedBytes))) {
VkAccessFlags srcAccess = 0;
VkPipelineStageFlags srcStage = 0;
if (mUsage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) {
if (getUsage() == VulkanBufferUsage::UNIFORM) {
srcAccess = VK_ACCESS_SHADER_READ_BIT;
srcStage = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
} else if (mUsage & VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) {
} else if (getUsage() == VulkanBufferUsage::VERTEX) {
srcAccess = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
srcStage = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
} else if (mUsage & VK_BUFFER_USAGE_INDEX_BUFFER_BIT) {
} else if (getUsage() == VulkanBufferUsage::INDEX) {
srcAccess = VK_ACCESS_INDEX_READ_BIT;
srcStage = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
}
@@ -81,20 +67,20 @@ void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = mGpuBuffer,
.buffer = getVkBuffer(),
.offset = byteOffset,
.size = numBytes,
};
vkCmdPipelineBarrier(cmdbuf, srcStage, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1,
&barrier, 0, nullptr);
vkCmdPipelineBarrier(commands.buffer(), srcStage, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
nullptr, 1, &barrier, 0, nullptr);
}
VkBufferCopy region = {
.srcOffset = 0,
.srcOffset = stage->offset(),
.dstOffset = byteOffset,
.size = numBytes,
};
vkCmdCopyBuffer(cmdbuf, stage->buffer, mGpuBuffer, 1, &region);
vkCmdCopyBuffer(commands.buffer(), stage->buffer(), getVkBuffer(), 1, &region);
mUpdatedOffset = byteOffset;
mUpdatedBytes = numBytes;
@@ -106,16 +92,16 @@ void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint
VkAccessFlags dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
if (mUsage & VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) {
if (getUsage() == VulkanBufferUsage::VERTEX) {
dstAccessMask |= VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
dstStageMask |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
} else if (mUsage & VK_BUFFER_USAGE_INDEX_BUFFER_BIT) {
} else if (getUsage() == VulkanBufferUsage::INDEX) {
dstAccessMask |= VK_ACCESS_INDEX_READ_BIT;
dstStageMask |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
} else if (mUsage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) {
} else if (getUsage() == VulkanBufferUsage::UNIFORM) {
dstAccessMask |= VK_ACCESS_SHADER_READ_BIT;
dstStageMask |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
} else if (mUsage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) {
} else if (getUsage() == VulkanBufferUsage::SHADER_STORAGE) {
// TODO: implement me
}
@@ -125,13 +111,21 @@ void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint
.dstAccessMask = dstAccessMask,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = mGpuBuffer,
.buffer = getVkBuffer(),
.offset = byteOffset,
.size = numBytes,
};
vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask, 0, 0, nullptr, 1,
&barrier, 0, nullptr);
vkCmdPipelineBarrier(commands.buffer(), VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask, 0, 0,
nullptr, 1, &barrier, 0, nullptr);
}
} // namespace filament::backend
VkBuffer VulkanBufferProxy::getVkBuffer() const noexcept {
return mBuffer->getGpuBuffer()->vkbuffer;
}
VulkanBufferUsage VulkanBufferProxy::getUsage() const noexcept {
return mBuffer->getGpuBuffer()->usage;
}
}// namespace filament::backend
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2018 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_VULKANBUFFERPROXY_H
#define TNT_FILAMENT_BACKEND_VULKANBUFFERPROXY_H
#include "VulkanBufferCache.h"
#include "VulkanCommands.h"
#include "VulkanContext.h"
#include "VulkanMemory.h"
#include "VulkanStagePool.h"
namespace filament::backend {
// This class acts as a dynamic wrapper for a `VulkanBuffer`. It allows you to modify the
// `VulkanBuffer` it references at runtime, wihtout affecting any external objects.
class VulkanBufferProxy {
public:
VulkanBufferProxy(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanBufferCache& bufferCache, VulkanBufferUsage usage, uint32_t numBytes);
void loadFromCpu(VulkanCommandBuffer& commands, const void* cpuData, uint32_t byteOffset,
uint32_t numBytes);
VkBuffer getVkBuffer() const noexcept;
VulkanBufferUsage getUsage() const noexcept;
private:
VmaAllocator mAllocator;
VulkanStagePool& mStagePool;
VulkanBufferCache& mBufferCache;
fvkmemory::resource_ptr<VulkanBuffer> mBuffer;
uint32_t mUpdatedOffset = 0;
uint32_t mUpdatedBytes = 0;
};
}// namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_VULKANBUFFERPROXY_H
@@ -81,6 +81,8 @@
// All other debug features must be disabled.
#define FVK_DEBUG_PROFILING 0x00040000
#define FVK_DEBUG_VULKAN_BUFFER_CACHE 0x00080000
// Useful default combinations
#define FVK_DEBUG_EVERYTHING (0xFFFFFFFF & ~FVK_DEBUG_PROFILING)
#define FVK_DEBUG_PERFORMANCE \
+5 -5
View File
@@ -71,23 +71,23 @@ struct VulkanRenderPass {
struct VulkanContext {
public:
static uint32_t selectMemoryType(VkPhysicalDeviceMemoryProperties const& memoryProperties,
uint32_t flags, VkFlags reqs) {
uint32_t types, VkFlags reqs) {
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) {
if (flags & 1) {
if (types & 1) {
if ((memoryProperties.memoryTypes[i].propertyFlags & reqs) == reqs) {
return i;
}
}
flags >>= 1;
types >>= 1;
}
return (uint32_t) VK_MAX_MEMORY_TYPES;
}
inline uint32_t selectMemoryType(uint32_t flags, VkFlags reqs) const {
inline uint32_t selectMemoryType(uint32_t types, VkFlags reqs) const {
if ((reqs & VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0) {
assert_invariant(isProtectedMemorySupported());
}
return selectMemoryType(mMemoryProperties, flags, reqs);
return selectMemoryType(mMemoryProperties, types, reqs);
}
inline fvkutils::VkFormatList const& getAttachmentDepthStencilFormats() const {
@@ -320,7 +320,7 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
uint8_t binding, fvkmemory::resource_ptr<VulkanBufferObject> bufferObject,
VkDeviceSize offset, VkDeviceSize size) noexcept {
VkDescriptorBufferInfo const info = {
.buffer = bufferObject->buffer.getGpuBuffer(),
.buffer = bufferObject->buffer.getVkBuffer(),
.offset = offset,
.range = size,
};
+27 -12
View File
@@ -19,7 +19,8 @@
#include "CommandStreamDispatcher.h"
#include "SystraceProfile.h"
#include "VulkanAsyncHandles.h"
#include "VulkanBuffer.h"
#include "VulkanBufferCache.h"
#include "VulkanBufferProxy.h"
#include "VulkanCommands.h"
#include "VulkanDriverFactory.h"
#include "VulkanHandles.h"
@@ -84,7 +85,11 @@ VmaAllocator createAllocator(VkInstance instance, VkPhysicalDevice physicalDevic
.vkGetImageMemoryRequirements2KHR = vkGetImageMemoryRequirements2KHR
#endif
};
VmaAllocatorCreateInfo const allocatorInfo {
VmaAllocatorCreateInfo const allocatorInfo{
// Disable the internal VMA synchronization because the backend is singled threaded.
// Improve CPU performance when using VMA functions. The backend will guarantee that all
// access to VMA is done in a thread safe way.
.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT,
.physicalDevice = physicalDevice,
.device = device,
.pVulkanFunctions = &funcs,
@@ -209,7 +214,8 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
mPlatform->getProtectedGraphicsQueueFamilyIndex(), &mContext),
mPipelineLayoutCache(mPlatform->getDevice()),
mPipelineCache(mPlatform->getDevice()),
mStagePool(mAllocator, &mCommands),
mStagePool(mAllocator, &mResourceManager, &mCommands, &mContext.getPhysicalDeviceLimits()),
mBufferCache(context, mResourceManager, mAllocator),
mFramebufferCache(mPlatform->getDevice()),
mYcbcrConversionCache(mPlatform->getDevice()),
mSamplerCache(mPlatform->getDevice()),
@@ -328,7 +334,6 @@ void VulkanDriver::terminate() {
// descriptorSetLayoutCache
mExternalImageManager.terminate();
mStagePool.terminate();
mPipelineCache.terminate();
mFramebufferCache.terminate();
mSamplerCache.terminate();
@@ -339,6 +344,15 @@ void VulkanDriver::terminate() {
// Before terminating ResourceManager, we must make sure all of the resource_ptrs have been unset.
mResourceManager.terminate();
// Must come after `mResourceManager`.
// Before terminating the memory pool, we must make sure all the VulkanBufferMemory are yielded
// back to the pool.
mBufferCache.terminate();
// Before terminating stagePool, we need all resources to have been
// reclaimed, as they perform cleanup within the stage pool.
mStagePool.terminate();
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
mResourceManager.print();
#endif
@@ -371,6 +385,7 @@ void VulkanDriver::collectGarbage() {
mCommands.gc();
mDescriptorSetCache.gc();
mStagePool.gc();
mBufferCache.gc();
mFramebufferCache.gc();
mPipelineCache.gc();
@@ -514,7 +529,7 @@ void VulkanDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType ele
FVK_SYSTRACE_SCOPE();
auto elementSize = (uint8_t) getElementTypeSize(elementType);
auto ib = resource_ptr<VulkanIndexBuffer>::make(&mResourceManager, ibh, mAllocator, mStagePool,
elementSize, indexCount);
mBufferCache, elementSize, indexCount);
ib.inc();
}
@@ -531,7 +546,7 @@ void VulkanDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byte
BufferObjectBinding bindingType, BufferUsage usage) {
FVK_SYSTRACE_SCOPE();
auto bo = resource_ptr<VulkanBufferObject>::make(&mResourceManager, boh, mAllocator, mStagePool,
byteCount, bindingType);
mBufferCache, byteCount, bindingType);
bo.inc();
}
@@ -1223,7 +1238,7 @@ void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor
VulkanCommandBuffer& commands = mCommands.get();
auto ib = resource_ptr<VulkanIndexBuffer>::cast(&mResourceManager, ibh);
commands.acquire(ib);
ib->buffer.loadFromCpu(commands.buffer(), p.buffer, byteOffset, p.size);
ib->buffer.loadFromCpu(commands, p.buffer, byteOffset, p.size);
scheduleDestroy(std::move(p));
}
@@ -1238,7 +1253,7 @@ void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescript
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
commands.acquire(bo);
bo->buffer.loadFromCpu(commands.buffer(), bd.buffer, byteOffset, bd.size);
bo->buffer.loadFromCpu(commands, bd.buffer, byteOffset, bd.size);
scheduleDestroy(std::move(bd));
}
@@ -1249,7 +1264,7 @@ void VulkanDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> boh,
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
commands.acquire(bo);
// TODO: implement unsynchronized version
bo->buffer.loadFromCpu(commands.buffer(), bd.buffer, byteOffset, bd.size);
bo->buffer.loadFromCpu(commands, bd.buffer, byteOffset, bd.size);
scheduleDestroy(std::move(bd));
}
@@ -1622,8 +1637,8 @@ void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y
mReadPixels.run(
srcTarget, x, y, width, height, mPlatform->getGraphicsQueueFamilyIndex(),
std::move(pbd),
[&context = mContext](uint32_t reqs, VkFlags flags) {
return context.selectMemoryType(reqs, flags);
[&context = mContext](uint32_t types, VkFlags reqs) {
return context.selectMemoryType(types, reqs);
},
[this](PixelBufferDescriptor&& pbd) {
scheduleDestroy(std::move(pbd));
@@ -1898,7 +1913,7 @@ void VulkanDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
// avoid rebinding these if they are already bound, but since we do not (yet) support subranges
// it would be rare for a client to make consecutive draw calls with the same render primitive.
vkCmdBindVertexBuffers(cmdbuffer, 0, bufferCount, buffers, offsets);
vkCmdBindIndexBuffer(cmdbuffer, prim->indexBuffer->buffer.getGpuBuffer(), 0,
vkCmdBindIndexBuffer(cmdbuffer, prim->indexBuffer->buffer.getVkBuffer(), 0,
prim->indexBuffer->indexType);
}
+4 -1
View File
@@ -18,15 +18,17 @@
#define TNT_FILAMENT_BACKEND_VULKANDRIVER_H
#include "VulkanBlitter.h"
#include "VulkanBufferCache.h"
#include "VulkanConstants.h"
#include "VulkanContext.h"
#include "VulkanFboCache.h"
#include "VulkanHandles.h"
#include "VulkanMemory.h"
#include "VulkanPipelineCache.h"
#include "VulkanQueryManager.h"
#include "VulkanReadPixels.h"
#include "VulkanSamplerCache.h"
#include "VulkanStagePool.h"
#include "VulkanQueryManager.h"
#include "VulkanYcbcrConversionCache.h"
#include "vulkan/VulkanDescriptorSetCache.h"
#include "vulkan/VulkanDescriptorSetLayoutCache.h"
@@ -138,6 +140,7 @@ private:
VulkanPipelineLayoutCache mPipelineLayoutCache;
VulkanPipelineCache mPipelineCache;
VulkanStagePool mStagePool;
VulkanBufferCache mBufferCache;
VulkanFboCache mFramebufferCache;
VulkanYcbcrConversionCache mYcbcrConversionCache;
VulkanSamplerCache mSamplerCache;
@@ -583,22 +583,22 @@ void VulkanVertexBuffer::setBuffer(fvkmemory::resource_ptr<VulkanBufferObject> b
int8_t const* const attribToBuffer = vbi->getAttributeToBuffer();
for (uint8_t attribIndex = 0; attribIndex < count; attribIndex++) {
if (attribToBuffer[attribIndex] == static_cast<int8_t>(index)) {
vkbuffers[attribIndex] = bufferObject->buffer.getGpuBuffer();
vkbuffers[attribIndex] = bufferObject->buffer.getVkBuffer();
}
}
mResources.push_back(bufferObject);
}
VulkanBufferObject::VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
uint32_t byteCount, BufferObjectBinding bindingType)
VulkanBufferCache& bufferCache, uint32_t byteCount, BufferObjectBinding bindingType)
: HwBufferObject(byteCount),
buffer(allocator, stagePool, getBufferObjectUsage(bindingType), byteCount),
buffer(allocator, stagePool, bufferCache, getBufferObjectUsage(bindingType), byteCount),
bindingType(bindingType) {}
VulkanRenderPrimitive::VulkanRenderPrimitive(PrimitiveType pt,
fvkmemory::resource_ptr<VulkanVertexBuffer> vb,
fvkmemory::resource_ptr<VulkanIndexBuffer> ib)
: HwRenderPrimitive{.type = pt},
: HwRenderPrimitive{ .type = pt },
vertexBuffer(vb),
indexBuffer(ib) {}
+21 -18
View File
@@ -21,20 +21,21 @@
#include "DriverBase.h"
#include "VulkanAsyncHandles.h"
#include "VulkanBuffer.h"
#include "VulkanBufferCache.h"
#include "VulkanBufferProxy.h"
#include "VulkanFboCache.h"
#include "VulkanSwapChain.h"
#include "VulkanTexture.h"
#include "vulkan/memory/Resource.h"
#include "vulkan/utils/StaticVector.h"
#include "vulkan/utils/Definitions.h"
#include "vulkan/utils/StaticVector.h"
#include <backend/Program.h>
#include <utils/bitset.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Mutex.h>
#include <utils/StructureOfArrays.h>
#include <utils/bitset.h>
#include <array>
@@ -428,21 +429,22 @@ private:
};
struct VulkanIndexBuffer : public HwIndexBuffer, fvkmemory::Resource {
VulkanIndexBuffer(VmaAllocator allocator, VulkanStagePool& stagePool, uint8_t elementSize,
uint32_t indexCount)
VulkanIndexBuffer(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanBufferCache& bufferCache, uint8_t elementSize, uint32_t indexCount)
: HwIndexBuffer(elementSize, indexCount),
buffer(allocator, stagePool, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, elementSize * indexCount),
buffer(allocator, stagePool, bufferCache, VulkanBufferUsage::INDEX,
elementSize * indexCount),
indexType(elementSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32) {}
VulkanBuffer buffer;
VulkanBufferProxy buffer;
const VkIndexType indexType;
};
struct VulkanBufferObject : public HwBufferObject, fvkmemory::Resource {
VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool, uint32_t byteCount,
BufferObjectBinding bindingType);
VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanBufferCache& bufferCache, uint32_t byteCount, BufferObjectBinding bindingType);
VulkanBuffer buffer;
VulkanBufferProxy buffer;
const BufferObjectBinding bindingType;
};
@@ -455,18 +457,19 @@ struct VulkanRenderPrimitive : public HwRenderPrimitive, fvkmemory::Resource {
fvkmemory::resource_ptr<VulkanIndexBuffer> indexBuffer;
};
inline constexpr VkBufferUsageFlagBits getBufferObjectUsage(
BufferObjectBinding bindingType) noexcept {
switch(bindingType) {
inline constexpr VulkanBufferUsage getBufferObjectUsage(BufferObjectBinding bindingType) noexcept {
switch (bindingType) {
case BufferObjectBinding::VERTEX:
return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
return VulkanBufferUsage::VERTEX;
case BufferObjectBinding::UNIFORM:
return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
return VulkanBufferUsage::UNIFORM;
case BufferObjectBinding::SHADER_STORAGE:
return VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
// when adding more buffer-types here, make sure to update VulkanBuffer::loadFromCpu()
// if necessary.
return VulkanBufferUsage::SHADER_STORAGE;
// when adding more buffer-types here, make sure to update VulkanBuffer::loadFromCpu()
// if necessary.
}
return VulkanBufferUsage::UNKNOWN;
}
} // namespace filament::backend
@@ -37,4 +37,24 @@ VK_DEFINE_HANDLE(VmaAllocator)
VK_DEFINE_HANDLE(VmaAllocation)
VK_DEFINE_HANDLE(VmaPool)
namespace filament::backend {
enum class VulkanBufferUsage : uint8_t {
UNKNOWN,
VERTEX,
INDEX,
UNIFORM,
SHADER_STORAGE,
};
struct VulkanGpuBuffer {
VkBuffer vkbuffer = VK_NULL_HANDLE;
VmaAllocation vmaAllocation = VK_NULL_HANDLE;
VmaAllocationInfo allocationInfo;
uint32_t numBytes = 0;
VulkanBufferUsage usage = VulkanBufferUsage::UNKNOWN;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_VULKANMEMORY_H
@@ -143,7 +143,7 @@ void VulkanReadPixels::run(fvkmemory::resource_ptr<VulkanRenderTarget> srcTarget
mTaskHandler = std::make_unique<TaskHandler>();
}
VkCommandPool& cmdpool = mCommandPool;
VkCommandPool const cmdpool = mCommandPool;
fvkmemory::resource_ptr<VulkanTexture> srcTexture = srcTarget->getColor0().texture;
assert_invariant(srcTexture);
@@ -152,17 +152,17 @@ void VulkanReadPixels::run(fvkmemory::resource_ptr<VulkanRenderTarget> srcTarget
= srcFormat == VK_FORMAT_B8G8R8A8_UNORM || srcFormat == VK_FORMAT_B8G8R8A8_SRGB;
// Create a host visible, linearly tiled image as a staging area.
VkImageCreateInfo const imageInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = srcFormat,
.extent = {width, height, 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_LINEAR,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
VkImageCreateInfo const imageInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = srcFormat,
.extent = { width, height, 1 },
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_LINEAR,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
VkImage stagingImage;
@@ -196,20 +196,20 @@ void VulkanReadPixels::run(fvkmemory::resource_ptr<VulkanRenderTarget> srcTarget
<< "VulkanReadPixels: unable to find a memory type that meets requirements.";
VkMemoryAllocateInfo const allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memReqs.size,
.memoryTypeIndex = memoryTypeIndex,
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memReqs.size,
.memoryTypeIndex = memoryTypeIndex,
};
vkAllocateMemory(device, &allocInfo, VKALLOC, &stagingMemory);
vkBindImageMemory(device, stagingImage, stagingMemory, 0);
VkCommandBuffer cmdbuffer;
VkCommandBufferAllocateInfo const allocateInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = cmdpool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
VkCommandBufferAllocateInfo const allocateInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = cmdpool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
vkAllocateCommandBuffers(device, &allocateInfo, &cmdbuffer);
@@ -304,7 +304,6 @@ void VulkanReadPixels::run(fvkmemory::resource_ptr<VulkanRenderTarget> srcTarget
cmdpool, cmdbuffer, pUserBuffer,
fence = readCompleteFence]() mutable {
VkResult status = vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
// Fence hasn't been reached. Try waiting again.
if (status != VK_SUCCESS) {
FVK_LOGE << "Failed to wait for readPixels fence" << utils::io::endl;
return;
+120 -55
View File
@@ -28,46 +28,111 @@ static constexpr uint32_t TIME_BEFORE_EVICTION = 3;
namespace filament::backend {
VulkanStagePool::VulkanStagePool(VmaAllocator allocator, VulkanCommands* commands)
: mAllocator(allocator),
mCommands(commands) {}
namespace {
VulkanStage const* VulkanStagePool::acquireStage(uint32_t numBytes) {
// First check if a stage exists whose capacity is greater than or equal to the requested size.
auto iter = mFreeStages.lower_bound(numBytes);
if (iter != mFreeStages.end()) {
auto stage = iter->second;
mFreeStages.erase(iter);
stage->lastAccessed = mCurrentFrame;
mUsedStages.push_back(stage);
return stage;
}
// We were not able to find a sufficiently large stage, so create a new one.
VulkanStage* stage = new VulkanStage({
.memory = VK_NULL_HANDLE,
.buffer = VK_NULL_HANDLE,
.capacity = numBytes,
.lastAccessed = mCurrentFrame,
// Note: these are temporary values, they will be configurable.
static constexpr uint32_t MAX_EMPTY_STAGES_TO_RETAIN = 1;
constexpr uint32_t STAGE_SIZE = 1048576;
}// namespace
fvkmemory::resource_ptr<VulkanStage::Segment> VulkanStage::acquireSegment(
fvkmemory::ResourceManager* resManager, uint32_t numBytes) {
auto segment = fvkmemory::resource_ptr<Segment>::construct(
resManager, this, numBytes, mCurrentOffset, [this](uint32_t offset) {
mSegments.erase(offset);
});
mSegments.insert({mCurrentOffset, segment.get()});
mCurrentOffset += numBytes;
return segment;
}
// Create the VkBuffer.
mUsedStages.push_back(stage);
VkBufferCreateInfo bufferInfo {
VulkanStagePool::VulkanStagePool(VmaAllocator allocator, fvkmemory::ResourceManager* resManager,
VulkanCommands* commands, const VkPhysicalDeviceLimits* deviceLimits)
: mAllocator(allocator),
mResManager(resManager),
mCommands(commands),
mDeviceLimits(deviceLimits) {}
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.
numBytes = alignToNonCoherentAtomSize(numBytes);
// First check if a stage segment exists whose capacity is greater than or
// equal to the requested size.
auto iter = mStages.lower_bound(numBytes);
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, numBytes);
// Update the stage's metadata, and reinsert it with the remaining segment
// capacity.
uint32_t spaceRemaining = pStage->capacity() - pStage->currentOffset();
mStages.insert({ spaceRemaining, pStage });
return pSegment;
}
uint32_t VulkanStagePool::alignToNonCoherentAtomSize(uint32_t bytes) {
VkDeviceSize alignment = mDeviceLimits->nonCoherentAtomSize;
if (alignment == 0) {
return bytes;
}
uint32_t remainder = bytes % alignment;
return remainder == 0 ? bytes : bytes + (alignment - remainder);
}
VulkanStage* VulkanStagePool::allocateNewStage(uint32_t capacity) {
VkBufferCreateInfo bufferInfo{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = numBytes,
.size = alignToNonCoherentAtomSize(capacity),
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
};
VmaAllocationCreateInfo allocInfo { .usage = VMA_MEMORY_USAGE_CPU_ONLY };
UTILS_UNUSED_IN_RELEASE VkResult result = vmaCreateBuffer(mAllocator, &bufferInfo,
&allocInfo, &stage->buffer, &stage->memory, nullptr);
VkBuffer buffer;
VmaAllocation memory;
VkResult result =
vmaCreateBuffer(mAllocator, &bufferInfo, &allocInfo, &buffer, &memory, nullptr);
#if FVK_ENABLED(FVK_DEBUG_STAGING_ALLOCATION)
if (result != VK_SUCCESS) {
FVK_LOGE << "Allocation error: " << result << utils::io::endl;
} else {
FVK_LOGD << "Allocated stage with hndl " << buffer << utils::io::endl;
}
#endif
return stage;
void* pMapping = nullptr;
if (result == VK_SUCCESS) {
result = vmaMapMemory(mAllocator, memory, &pMapping);
#if FVK_ENABLED(FVK_DEBUG_STAGING_ALLOCATION)
if (result != VK_SUCCESS) {
FVK_LOGE << "Memory mapping erryr: " << result << utils::io::endl;
}
#endif
}
return new VulkanStage(memory, buffer, capacity, pMapping);
}
void VulkanStagePool::destroyStage(VulkanStage const*&& stage) {
assert(stage->isSafeToReset()); // Ensure all segments have been reset already.
vmaUnmapMemory(mAllocator, stage->memory());
vmaDestroyBuffer(mAllocator, stage->buffer(), stage->memory());
delete stage;
}
VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, PixelDataType type,
@@ -141,27 +206,34 @@ void VulkanStagePool::gc() noexcept {
}
const uint64_t evictionTime = mCurrentFrame - TIME_BEFORE_EVICTION;
// Destroy buffers that have not been used for several frames.
decltype(mFreeStages) freeStages;
freeStages.swap(mFreeStages);
for (auto pair : freeStages) {
if (pair.second->lastAccessed < evictionTime) {
vmaDestroyBuffer(mAllocator, pair.second->buffer, pair.second->memory);
delete pair.second;
} else {
mFreeStages.insert(pair);
}
}
decltype(mStages) freeStages;
freeStages.swap(mStages);
uint8_t freeStageCount = 0; // Assuming we'll never have > 255 free stages
for (auto& pair : freeStages) {
// First, find any stages that have no segments within them.
if (pair.second->isSafeToReset()) {
if (++freeStageCount > MAX_EMPTY_STAGES_TO_RETAIN) {
#if FVK_ENABLED(FVK_DEBUG_STAGING_ALLOCATION)
FVK_LOGD << "Destroying a staging buffer with hndl " << pair.second->buffer()
<< utils::io::endl;
#endif
destroyStage(std::move(pair.second));
continue;
}
// Reclaim buffers that are no longer being used by any command buffer.
decltype(mUsedStages) usedStages;
usedStages.swap(mUsedStages);
for (auto stage : usedStages) {
if (stage->lastAccessed < evictionTime) {
stage->lastAccessed = mCurrentFrame;
mFreeStages.insert(std::make_pair(stage->capacity, stage));
#if FVK_ENABLED(FVK_DEBUG_STAGING_ALLOCATION)
if (pair.first == 0) {
FVK_LOGD << "Recycling an unused staging buffer with hndl " << pair.second->buffer()
<< utils::io::endl;
}
#endif
// Note - this segment is free, make sure the structure is cleared
// and reinsert it into our free stage list.
pair.second->reset();
mStages.insert({ pair.second->capacity(), pair.second });
} else {
mUsedStages.push_back(stage);
mStages.insert(pair);
}
}
@@ -192,17 +264,10 @@ void VulkanStagePool::gc() noexcept {
}
void VulkanStagePool::terminate() noexcept {
for (auto stage : mUsedStages) {
vmaDestroyBuffer(mAllocator, stage->buffer, stage->memory);
delete stage;
for (auto& pair : mStages) {
destroyStage(std::move(pair.second));
}
mUsedStages.clear();
for (auto pair : mFreeStages) {
vmaDestroyBuffer(mAllocator, pair.second->buffer, pair.second->memory);
delete pair.second;
}
mFreeStages.clear();
mStages.clear();
for (auto image : mUsedImages) {
vmaDestroyImage(mAllocator, image->image, image->memory);
+126 -15
View File
@@ -17,8 +17,11 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANSTAGEPOOL_H
#define TNT_FILAMENT_BACKEND_VULKANSTAGEPOOL_H
#include "backend/DriverEnums.h"
#include "VulkanMemory.h"
#include "backend/DriverEnums.h"
#include "vulkan/memory/Resource.h"
#include "vulkan/memory/ResourceManager.h"
#include "vulkan/memory/ResourcePointer.h"
#include <map>
#include <unordered_set>
@@ -28,12 +31,96 @@ namespace filament::backend {
class VulkanCommands;
// Immutable POD representing a shared CPU-GPU staging area.
struct VulkanStage {
VmaAllocation memory;
VkBuffer buffer;
uint32_t capacity;
mutable uint64_t lastAccessed;
// Object representing a shared CPU-GPU staging area, which can be subdivided
// into smaller buffers as needed.
class VulkanStage {
public:
VulkanStage(VmaAllocation memory, VkBuffer buffer, uint32_t capacity, void* mapping)
: mMemory(memory),
mBuffer(buffer),
mCapacity(capacity),
mMapping(mapping) {}
~VulkanStage() = default;
VulkanStage(const VulkanStage& other) = delete;
VulkanStage(VulkanStage&& other) = delete;
VulkanStage& operator=(const VulkanStage& other) = delete;
VulkanStage& operator=(VulkanStage&& other) = delete;
class Segment : public fvkmemory::Resource {
public:
using OnRecycle = std::function<void(uint32_t offset)>;
Segment(VulkanStage* parentStage, uint32_t capacity, uint32_t offset,
OnRecycle&& onRecycleFn)
: mParentStage(parentStage),
mCapacity(capacity),
mOffset(offset),
mOnRecycleFn(onRecycleFn) {}
~Segment() {
if (mOnRecycleFn) {
mOnRecycleFn(offset());
}
}
// Should not be copying this around.
Segment(const Segment& other) = delete;
Segment(Segment&& other) = delete;
Segment& operator=(const Segment& other) = delete;
Segment& operator=(Segment&& other) = delete;
inline VulkanStage* parentStage() const { return mParentStage; }
inline VkBuffer buffer() const { return parentStage()->buffer(); }
inline VmaAllocation memory() const { return parentStage()->memory(); }
inline uint32_t capacity() const { return mCapacity; }
inline uint32_t offset() const { return mOffset; }
inline void* mapping() const {
return reinterpret_cast<void*>(
reinterpret_cast<char*>(mParentStage->mapping()) + offset());
}
private:
// Ensure parent class can access the terminate method.
friend class VulkanStage;
VulkanStage* const mParentStage;
const uint32_t mCapacity;
const uint32_t mOffset;
OnRecycle mOnRecycleFn;
};
inline VmaAllocation memory() const { return mMemory; }
inline VkBuffer buffer() const { return mBuffer; }
inline uint32_t capacity() const { return mCapacity; }
inline void* mapping() const { return mMapping; }
inline uint32_t currentOffset() { return mCurrentOffset; }
inline bool isSafeToReset() const { return mSegments.empty(); }
inline void reset() { mCurrentOffset = 0; }
// 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.
fvkmemory::resource_ptr<Segment> acquireSegment(fvkmemory::ResourceManager* resManager,
uint32_t numBytes);
private:
const VmaAllocation mMemory;
const VkBuffer mBuffer;
const uint32_t mCapacity;
void* mMapping;
uint32_t mCurrentOffset = 0;
// Maps the start offset of a vulkan stage block to the stage block,
// for easy deletions later. This is managed by the blocks themselves, in an
// RAII pattern, during construction and destruction.
std::unordered_map<uint32_t, Segment*> mSegments;
};
struct VulkanStageImage {
@@ -49,11 +136,15 @@ struct VulkanStageImage {
// This class manages two types of host-mappable staging areas: buffer stages and image stages.
class VulkanStagePool {
public:
VulkanStagePool(VmaAllocator allocator, VulkanCommands* commands);
VulkanStagePool(VmaAllocator allocator, fvkmemory::ResourceManager* resManager,
VulkanCommands* commands, const VkPhysicalDeviceLimits* deviceLimits);
// Finds or creates a stage whose capacity is at least the given number of bytes.
// The stage is automatically released back to the pool after TIME_BEFORE_EVICTION frames.
VulkanStage const* acquireStage(uint32_t numBytes);
// Finds or creates a stage block whose capacity is at least the given
// number of bytes. Internally, creates and manages and subdivides large
// buffers so that we have less objects around that we have to keep track
// of.
// This function is NOT thread-safe.
fvkmemory::resource_ptr<VulkanStage::Segment> acquireStage(uint32_t numBytes);
// Images have VK_IMAGE_LAYOUT_GENERAL and must not be transitioned to any other layout
VulkanStageImage const* acquireImage(PixelDataFormat format, PixelDataType type,
@@ -64,17 +155,37 @@ public:
// Destroys all unused stages and asserts that there are no stages currently in use.
// This should be called while the context's VkDevice is still alive.
// Note: it is expected that all resources have been reclaimed before this
// is called. It is also expected that this stage pool does not hold any
// resource_ptrs, as this would lead to undefined behavior.
void terminate() noexcept;
private:
VmaAllocator mAllocator;
fvkmemory::ResourceManager* mResManager;
VulkanCommands* mCommands;
const VkPhysicalDeviceLimits* mDeviceLimits;
// Takes a number of bytes, and aligns it to the non-coherent atom size.
// This allows us to ensure that when we flush buffers from the host, we
// never flush more atoms than we need to.
uint32_t alignToNonCoherentAtomSize(uint32_t numBytes);
// Allocates a new stage buffer, and optionally subdivides it into stage
// blocks. If subdivideBlocks is true, predefined divisions will be used.
// Otherwise, it's expected that capacity is defined to a value, and that
// is the size that will be used for the buffer (as well as the only block
// being created).
VulkanStage* allocateNewStage(uint32_t capacity);
// Performs any bookkeeping required to delete a VulkanStage object; namely,
// unmapping memory, freeing the allocation, and deleting the VulkanStage
// object. Note: takes an r-value because after this call, `stage` won't
// exist.
void destroyStage(VulkanStage const*&& stage);
// Use an ordered multimap for quick (capacity => stage) lookups using lower_bound().
std::multimap<uint32_t, VulkanStage const*> mFreeStages;
// Simple unordered set for stashing a list of in-use stages that can be reclaimed later.
std::vector<VulkanStage const*> mUsedStages;
std::multimap<uint32_t, VulkanStage*> mStages;
std::unordered_set<VulkanStageImage const*> mFreeImages;
std::vector<VulkanStageImage const*> mUsedImages;
+67 -17
View File
@@ -25,6 +25,7 @@
#include <backend/DriverEnums.h>
#include <private/backend/BackendUtils.h>
#include <utils/compiler.h>
#include <utils/Panic.h>
using namespace bluevk;
@@ -218,6 +219,40 @@ VkImageUsageFlags getUsage(VulkanContext const& context, uint8_t samples,
return usage;
}
void adjustedMemcpy(void* mapped, PixelBufferDescriptor const& p, size_t width, size_t height,
size_t depth) {
uint8_t* buf = (uint8_t*) p.buffer;
size_t const pixelSize = PixelBufferDescriptor::computeDataSize(p.format, p.type, 1, 1, 1);
size_t const pbdStride = p.stride ? p.stride : width;
// Slow path of copying row by row
assert_invariant(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;
size_t const pbdLayerSize = pbdRowSize * pbdHeight;
size_t const rowSize = width * pixelSize;
size_t const layerSize = width * height * pixelSize;
// Size of a row to write
size_t const writeSize = std::min(pbdStride - p.left, width) * pixelSize;
for (size_t z = 0; z < depth; z++) {
for (size_t y = p.top; y < pbdHeight; y++) {
uint8_t* buf = (uint8_t*) p.buffer +
((p.left * pixelSize) + (y * pbdRowSize) + (z * pbdLayerSize));
uint8_t* curMapped = (uint8_t*) mapped + ((y - p.top) * rowSize + z * layerSize);
memcpy(curMapped, buf, writeSize);
}
}
} else {
size_t const writeSize = pixelSize * (width * height * depth);
memcpy(mapped, buf, writeSize);
}
}
} // anonymous namespace
VulkanTextureState::VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands,
@@ -480,30 +515,40 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
assert_invariant(hostData->size > 0 && "Data is empty");
// Otherwise, use vkCmdCopyBufferToImage.
void* mapped = nullptr;
VulkanStage const* stage = mState->mStagePool.acquireStage(hostData->size);
assert_invariant(stage->memory);
vmaMapMemory(mState->mAllocator, stage->memory, &mapped);
memcpy(mapped, hostData->buffer, hostData->size);
vmaUnmapMemory(mState->mAllocator, stage->memory);
vmaFlushAllocation(mState->mAllocator, stage->memory, 0, hostData->size);
size_t const bpp =
PixelBufferDescriptor::computeDataSize(hostData->format, hostData->type, 1, 1, 1);
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.
fvkmemory::resource_ptr<VulkanStage::Segment> stageSegment =
mState->mStagePool.acquireStage(writeSize);
assert_invariant(stageSegment->memory());
adjustedMemcpy(stageSegment->mapping(), *hostData, width, height, depth);
vmaFlushAllocation(mState->mAllocator, stageSegment->memory(), stageSegment->offset(),
writeSize);
VulkanCommandBuffer& commands = mState->mCommands->get();
VkCommandBuffer const cmdbuf = commands.buffer();
commands.acquire(stageSegment);
commands.acquire(fvkmemory::resource_ptr<VulkanTexture>::cast(this));
bool const isDepth = getImageAspect() & VK_IMAGE_ASPECT_DEPTH_BIT;
VkBufferImageCopy copyRegion = {
.bufferOffset = {},
.bufferOffset = stageSegment->offset(),
.bufferRowLength = {},
.bufferImageHeight = {},
.imageSubresource = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.aspectMask = VkImageAspectFlags(
isDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT),
.mipLevel = miplevel,
.baseArrayLayer = 0,
.layerCount = 1
.layerCount = 1,
},
.imageOffset = { int32_t(xoffset), int32_t(yoffset), int32_t(zoffset) },
.imageExtent = { width, height, depth }
.imageExtent = { width, height, depth },
};
VkImageSubresourceRange transitionRange = {
@@ -511,7 +556,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
.baseMipLevel = miplevel,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1
.layerCount = 1,
};
// Vulkan specifies subregions for 3D textures differently than from 2D arrays.
@@ -536,20 +581,25 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
transitionLayout(&commands, transitionRange, newLayout);
vkCmdCopyBufferToImage(cmdbuf, stage->buffer, mState->mTextureImage, newVkLayout, 1, &copyRegion);
vkCmdCopyBufferToImage(cmdbuf, stageSegment->buffer(), mState->mTextureImage, newVkLayout, 1,
&copyRegion);
transitionLayout(&commands, transitionRange, nextLayout);
}
void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width,
void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& data, uint32_t width,
uint32_t height, uint32_t depth, uint32_t miplevel) {
// Otherwise, use vkCmdCopyBufferToImage.
size_t const bpp = PixelBufferDescriptor::computeDataSize(data.format, data.type, 1, 1, 1);
size_t const writeSize = width * height * depth * bpp;
void* mapped = nullptr;
VulkanStageImage const* stage
= mState->mStagePool.acquireImage(hostData.format, hostData.type, width, height);
= mState->mStagePool.acquireImage(data.format, data.type, width, height);
vmaMapMemory(mState->mAllocator, stage->memory, &mapped);
memcpy(mapped, hostData.buffer, hostData.size);
adjustedMemcpy(mapped, data, width, height, depth);
vmaUnmapMemory(mState->mAllocator, stage->memory);
vmaFlushAllocation(mState->mAllocator, stage->memory, 0, hostData.size);
vmaFlushAllocation(mState->mAllocator, stage->memory, 0, writeSize);
VulkanCommandBuffer& commands = mState->mCommands->get();
VkCommandBuffer const cmdbuf = commands.buffer();
@@ -26,6 +26,7 @@ template ResourceType getTypeEnum<VulkanIndexBuffer>() noexcept;
template ResourceType getTypeEnum<VulkanProgram>() noexcept;
template ResourceType getTypeEnum<VulkanRenderTarget>() noexcept;
template ResourceType getTypeEnum<VulkanSwapChain>() noexcept;
template ResourceType getTypeEnum<VulkanStage::Segment>() noexcept;
template ResourceType getTypeEnum<VulkanRenderPrimitive>() noexcept;
template ResourceType getTypeEnum<VulkanTexture>() noexcept;
template ResourceType getTypeEnum<VulkanTextureState>() noexcept;
@@ -35,6 +36,7 @@ template ResourceType getTypeEnum<VulkanVertexBufferInfo>() noexcept;
template ResourceType getTypeEnum<VulkanDescriptorSetLayout>() noexcept;
template ResourceType getTypeEnum<VulkanDescriptorSet>() noexcept;
template ResourceType getTypeEnum<VulkanFence>() noexcept;
template ResourceType getTypeEnum<VulkanBuffer>() noexcept;
template<typename D>
ResourceType getTypeEnum() noexcept {
@@ -53,6 +55,9 @@ ResourceType getTypeEnum() noexcept {
if constexpr (std::is_same_v<D, VulkanSwapChain>) {
return ResourceType::SWAP_CHAIN;
}
if constexpr (std::is_same_v<D, VulkanStage::Segment>) {
return ResourceType::STAGE_SEGMENT;
}
if constexpr (std::is_same_v<D, VulkanRenderPrimitive>) {
return ResourceType::RENDER_PRIMITIVE;
}
@@ -80,6 +85,9 @@ ResourceType getTypeEnum() noexcept {
if constexpr (std::is_same_v<D, VulkanFence>) {
return ResourceType::FENCE;
}
if constexpr (std::is_same_v<D, VulkanBuffer>) {
return ResourceType::VULKAN_BUFFER;
}
return ResourceType::UNDEFINED_TYPE;
}
@@ -95,6 +103,8 @@ std::string getTypeStr(ResourceType type) {
return "RenderTarget";
case ResourceType::SWAP_CHAIN:
return "SwapChain";
case ResourceType::STAGE_SEGMENT:
return "Stage::Segment";
case ResourceType::RENDER_PRIMITIVE:
return "RenderPrimitive";
case ResourceType::TEXTURE:
@@ -113,6 +123,8 @@ std::string getTypeStr(ResourceType type) {
return "DescriptorSet";
case ResourceType::FENCE:
return "Fence";
case ResourceType::VULKAN_BUFFER:
return "VulkanBuffer";
case ResourceType::UNDEFINED_TYPE:
return "";
}
@@ -49,7 +49,9 @@ enum class ResourceType : uint8_t {
DESCRIPTOR_SET_LAYOUT = 11,
DESCRIPTOR_SET = 12,
FENCE = 13,
UNDEFINED_TYPE = 14, // Must be the last enum because we use it for iterating over the enums.
VULKAN_BUFFER = 14,
STAGE_SEGMENT = 15,
UNDEFINED_TYPE = 16, // Must be the last enum because we use it for iterating over the enums.
};
template<typename D>
@@ -77,6 +77,9 @@ void ResourceManager::destroyWithType(ResourceType type, HandleId id) {
case ResourceType::SWAP_CHAIN:
destruct<VulkanSwapChain>(Handle<VulkanSwapChain>(id));
break;
case ResourceType::STAGE_SEGMENT:
destruct<VulkanStage::Segment>(Handle<VulkanStage::Segment>(id));
break;
case ResourceType::RENDER_PRIMITIVE:
destruct<VulkanRenderPrimitive>(Handle<VulkanRenderPrimitive>(id));
break;
@@ -104,6 +107,9 @@ void ResourceManager::destroyWithType(ResourceType type, HandleId id) {
case ResourceType::FENCE:
destruct<VulkanFence>(Handle<VulkanFence>(id));
break;
case ResourceType::VULKAN_BUFFER:
destruct<VulkanBuffer>(Handle<VulkanBuffer>(id));
break;
case ResourceType::UNDEFINED_TYPE:
break;
}
@@ -92,6 +92,16 @@ StructA* chainStruct(StructA* structA, StructB* structB) {
return structA;
}
bool shouldSkipFormat(VkFormat format) {
// Skip formats that require extensions.
for (VkFormat const extFormat: fvkutils::EXT_VK_FORMATS) {
if (format == extFormat) {
return true;
}
}
return false;
}
void printDeviceInfo(VkInstance instance, VkPhysicalDevice device) {
// Print some driver or MoltenVK information if it is available.
if (vkGetPhysicalDeviceProperties2) {
@@ -151,8 +161,14 @@ void printDepthFormats(VkPhysicalDevice device) {
constexpr VkFormatFeatureFlags required =
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
FVK_LOGI << "Sampleable depth formats: ";
for (VkFormat format : fvkutils::ALL_VK_FORMATS) {
for (VkFormat const format : fvkutils::ALL_VK_FORMATS) {
// Skip formats that require extensions.
if (shouldSkipFormat(format)) {
continue;
}
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(device, format, &props);
if ((props.optimalTilingFeatures & required) == required) {
FVK_LOGI << format << " ";
@@ -617,7 +633,13 @@ fvkutils::VkFormatList findBlittableDepthStencilFormats(VkPhysicalDevice device)
std::vector<VkFormat> selectedFormats;
constexpr VkFormatFeatureFlags required = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT |
VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
for (VkFormat format : fvkutils::ALL_VK_FORMATS) {
for (VkFormat const format : fvkutils::ALL_VK_FORMATS) {
// Skip formats that require extensions.
if (shouldSkipFormat(format)) {
continue;
}
if (fvkutils::isVkDepthFormat(format)) {
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(device, format, &props);
+319 -307
View File
@@ -32,313 +32,325 @@ using VkFormatList = utils::FixedCapacityVector<VkFormat>;
// Copied from
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkFormat.html
constexpr VkFormat ALL_VK_FORMATS[] = {
VK_FORMAT_UNDEFINED,
VK_FORMAT_R4G4_UNORM_PACK8,
VK_FORMAT_R4G4B4A4_UNORM_PACK16,
VK_FORMAT_B4G4R4A4_UNORM_PACK16,
VK_FORMAT_R5G6B5_UNORM_PACK16,
VK_FORMAT_B5G6R5_UNORM_PACK16,
VK_FORMAT_R5G5B5A1_UNORM_PACK16,
VK_FORMAT_B5G5R5A1_UNORM_PACK16,
VK_FORMAT_A1R5G5B5_UNORM_PACK16,
VK_FORMAT_R8_UNORM,
VK_FORMAT_R8_SNORM,
VK_FORMAT_R8_USCALED,
VK_FORMAT_R8_SSCALED,
VK_FORMAT_R8_UINT,
VK_FORMAT_R8_SINT,
VK_FORMAT_R8_SRGB,
VK_FORMAT_R8G8_UNORM,
VK_FORMAT_R8G8_SNORM,
VK_FORMAT_R8G8_USCALED,
VK_FORMAT_R8G8_SSCALED,
VK_FORMAT_R8G8_UINT,
VK_FORMAT_R8G8_SINT,
VK_FORMAT_R8G8_SRGB,
VK_FORMAT_R8G8B8_UNORM,
VK_FORMAT_R8G8B8_SNORM,
VK_FORMAT_R8G8B8_USCALED,
VK_FORMAT_R8G8B8_SSCALED,
VK_FORMAT_R8G8B8_UINT,
VK_FORMAT_R8G8B8_SINT,
VK_FORMAT_R8G8B8_SRGB,
VK_FORMAT_B8G8R8_UNORM,
VK_FORMAT_B8G8R8_SNORM,
VK_FORMAT_B8G8R8_USCALED,
VK_FORMAT_B8G8R8_SSCALED,
VK_FORMAT_B8G8R8_UINT,
VK_FORMAT_B8G8R8_SINT,
VK_FORMAT_B8G8R8_SRGB,
VK_FORMAT_R8G8B8A8_UNORM,
VK_FORMAT_R8G8B8A8_SNORM,
VK_FORMAT_R8G8B8A8_USCALED,
VK_FORMAT_R8G8B8A8_SSCALED,
VK_FORMAT_R8G8B8A8_UINT,
VK_FORMAT_R8G8B8A8_SINT,
VK_FORMAT_R8G8B8A8_SRGB,
VK_FORMAT_B8G8R8A8_UNORM,
VK_FORMAT_B8G8R8A8_SNORM,
VK_FORMAT_B8G8R8A8_USCALED,
VK_FORMAT_B8G8R8A8_SSCALED,
VK_FORMAT_B8G8R8A8_UINT,
VK_FORMAT_B8G8R8A8_SINT,
VK_FORMAT_B8G8R8A8_SRGB,
VK_FORMAT_A8B8G8R8_UNORM_PACK32,
VK_FORMAT_A8B8G8R8_SNORM_PACK32,
VK_FORMAT_A8B8G8R8_USCALED_PACK32,
VK_FORMAT_A8B8G8R8_SSCALED_PACK32,
VK_FORMAT_A8B8G8R8_UINT_PACK32,
VK_FORMAT_A8B8G8R8_SINT_PACK32,
VK_FORMAT_A8B8G8R8_SRGB_PACK32,
VK_FORMAT_A2R10G10B10_UNORM_PACK32,
VK_FORMAT_A2R10G10B10_SNORM_PACK32,
VK_FORMAT_A2R10G10B10_USCALED_PACK32,
VK_FORMAT_A2R10G10B10_SSCALED_PACK32,
VK_FORMAT_A2R10G10B10_UINT_PACK32,
VK_FORMAT_A2R10G10B10_SINT_PACK32,
VK_FORMAT_A2B10G10R10_UNORM_PACK32,
VK_FORMAT_A2B10G10R10_SNORM_PACK32,
VK_FORMAT_A2B10G10R10_USCALED_PACK32,
VK_FORMAT_A2B10G10R10_SSCALED_PACK32,
VK_FORMAT_A2B10G10R10_UINT_PACK32,
VK_FORMAT_A2B10G10R10_SINT_PACK32,
VK_FORMAT_R16_UNORM,
VK_FORMAT_R16_SNORM,
VK_FORMAT_R16_USCALED,
VK_FORMAT_R16_SSCALED,
VK_FORMAT_R16_UINT,
VK_FORMAT_R16_SINT,
VK_FORMAT_R16_SFLOAT,
VK_FORMAT_R16G16_UNORM,
VK_FORMAT_R16G16_SNORM,
VK_FORMAT_R16G16_USCALED,
VK_FORMAT_R16G16_SSCALED,
VK_FORMAT_R16G16_UINT,
VK_FORMAT_R16G16_SINT,
VK_FORMAT_R16G16_SFLOAT,
VK_FORMAT_R16G16B16_UNORM,
VK_FORMAT_R16G16B16_SNORM,
VK_FORMAT_R16G16B16_USCALED,
VK_FORMAT_R16G16B16_SSCALED,
VK_FORMAT_R16G16B16_UINT,
VK_FORMAT_R16G16B16_SINT,
VK_FORMAT_R16G16B16_SFLOAT,
VK_FORMAT_R16G16B16A16_UNORM,
VK_FORMAT_R16G16B16A16_SNORM,
VK_FORMAT_R16G16B16A16_USCALED,
VK_FORMAT_R16G16B16A16_SSCALED,
VK_FORMAT_R16G16B16A16_UINT,
VK_FORMAT_R16G16B16A16_SINT,
VK_FORMAT_R16G16B16A16_SFLOAT,
VK_FORMAT_R32_UINT,
VK_FORMAT_R32_SINT,
VK_FORMAT_R32_SFLOAT,
VK_FORMAT_R32G32_UINT,
VK_FORMAT_R32G32_SINT,
VK_FORMAT_R32G32_SFLOAT,
VK_FORMAT_R32G32B32_UINT,
VK_FORMAT_R32G32B32_SINT,
VK_FORMAT_R32G32B32_SFLOAT,
VK_FORMAT_R32G32B32A32_UINT,
VK_FORMAT_R32G32B32A32_SINT,
VK_FORMAT_R32G32B32A32_SFLOAT,
VK_FORMAT_R64_UINT,
VK_FORMAT_R64_SINT,
VK_FORMAT_R64_SFLOAT,
VK_FORMAT_R64G64_UINT,
VK_FORMAT_R64G64_SINT,
VK_FORMAT_R64G64_SFLOAT,
VK_FORMAT_R64G64B64_UINT,
VK_FORMAT_R64G64B64_SINT,
VK_FORMAT_R64G64B64_SFLOAT,
VK_FORMAT_R64G64B64A64_UINT,
VK_FORMAT_R64G64B64A64_SINT,
VK_FORMAT_R64G64B64A64_SFLOAT,
VK_FORMAT_B10G11R11_UFLOAT_PACK32,
VK_FORMAT_E5B9G9R9_UFLOAT_PACK32,
VK_FORMAT_D16_UNORM,
VK_FORMAT_X8_D24_UNORM_PACK32,
VK_FORMAT_D32_SFLOAT,
VK_FORMAT_S8_UINT,
VK_FORMAT_D16_UNORM_S8_UINT,
VK_FORMAT_D24_UNORM_S8_UINT,
VK_FORMAT_D32_SFLOAT_S8_UINT,
VK_FORMAT_BC1_RGB_UNORM_BLOCK,
VK_FORMAT_BC1_RGB_SRGB_BLOCK,
VK_FORMAT_BC1_RGBA_UNORM_BLOCK,
VK_FORMAT_BC1_RGBA_SRGB_BLOCK,
VK_FORMAT_BC2_UNORM_BLOCK,
VK_FORMAT_BC2_SRGB_BLOCK,
VK_FORMAT_BC3_UNORM_BLOCK,
VK_FORMAT_BC3_SRGB_BLOCK,
VK_FORMAT_BC4_UNORM_BLOCK,
VK_FORMAT_BC4_SNORM_BLOCK,
VK_FORMAT_BC5_UNORM_BLOCK,
VK_FORMAT_BC5_SNORM_BLOCK,
VK_FORMAT_BC6H_UFLOAT_BLOCK,
VK_FORMAT_BC6H_SFLOAT_BLOCK,
VK_FORMAT_BC7_UNORM_BLOCK,
VK_FORMAT_BC7_SRGB_BLOCK,
VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK,
VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK,
VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK,
VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK,
VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK,
VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK,
VK_FORMAT_EAC_R11_UNORM_BLOCK,
VK_FORMAT_EAC_R11_SNORM_BLOCK,
VK_FORMAT_EAC_R11G11_UNORM_BLOCK,
VK_FORMAT_EAC_R11G11_SNORM_BLOCK,
VK_FORMAT_ASTC_4x4_UNORM_BLOCK,
VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
VK_FORMAT_ASTC_5x4_UNORM_BLOCK,
VK_FORMAT_ASTC_5x4_SRGB_BLOCK,
VK_FORMAT_ASTC_5x5_UNORM_BLOCK,
VK_FORMAT_ASTC_5x5_SRGB_BLOCK,
VK_FORMAT_ASTC_6x5_UNORM_BLOCK,
VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
VK_FORMAT_ASTC_6x6_UNORM_BLOCK,
VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
VK_FORMAT_ASTC_8x5_UNORM_BLOCK,
VK_FORMAT_ASTC_8x5_SRGB_BLOCK,
VK_FORMAT_ASTC_8x6_UNORM_BLOCK,
VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
VK_FORMAT_ASTC_8x8_UNORM_BLOCK,
VK_FORMAT_ASTC_8x8_SRGB_BLOCK,
VK_FORMAT_ASTC_10x5_UNORM_BLOCK,
VK_FORMAT_ASTC_10x5_SRGB_BLOCK,
VK_FORMAT_ASTC_10x6_UNORM_BLOCK,
VK_FORMAT_ASTC_10x6_SRGB_BLOCK,
VK_FORMAT_ASTC_10x8_UNORM_BLOCK,
VK_FORMAT_ASTC_10x8_SRGB_BLOCK,
VK_FORMAT_ASTC_10x10_UNORM_BLOCK,
VK_FORMAT_ASTC_10x10_SRGB_BLOCK,
VK_FORMAT_ASTC_12x10_UNORM_BLOCK,
VK_FORMAT_ASTC_12x10_SRGB_BLOCK,
VK_FORMAT_ASTC_12x12_UNORM_BLOCK,
VK_FORMAT_ASTC_12x12_SRGB_BLOCK,
VK_FORMAT_G8B8G8R8_422_UNORM,
VK_FORMAT_B8G8R8G8_422_UNORM,
VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM,
VK_FORMAT_G8_B8R8_2PLANE_420_UNORM,
VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM,
VK_FORMAT_G8_B8R8_2PLANE_422_UNORM,
VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM,
VK_FORMAT_R10X6_UNORM_PACK16,
VK_FORMAT_R10X6G10X6_UNORM_2PACK16,
VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16,
VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16,
VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16,
VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16,
VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16,
VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16,
VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16,
VK_FORMAT_R12X4_UNORM_PACK16,
VK_FORMAT_R12X4G12X4_UNORM_2PACK16,
VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16,
VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16,
VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16,
VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16,
VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16,
VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16,
VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16,
VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16,
VK_FORMAT_G16B16G16R16_422_UNORM,
VK_FORMAT_B16G16R16G16_422_UNORM,
VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM,
VK_FORMAT_G16_B16R16_2PLANE_420_UNORM,
VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM,
VK_FORMAT_G16_B16R16_2PLANE_422_UNORM,
VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM,
VK_FORMAT_G8_B8R8_2PLANE_444_UNORM,
VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16,
VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16,
VK_FORMAT_G16_B16R16_2PLANE_444_UNORM,
VK_FORMAT_A4R4G4B4_UNORM_PACK16,
VK_FORMAT_A4B4G4R4_UNORM_PACK16,
VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK,
VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK,
VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK,
VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK,
VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK,
VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK,
VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK,
VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK,
VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK,
VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK,
VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK,
VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK,
VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK,
VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK,
VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG,
VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG,
VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG,
VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG,
VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG,
VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG,
VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG,
VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG,
// Not supported (yet) by bluevk
// VK_FORMAT_R16G16_SFIXED5_NV,
// VK_FORMAT_A1B5G5R5_UNORM_PACK16_KHR,
// VK_FORMAT_A8_UNORM_KHR,
// VK_FORMAT_A8_UNORM,
VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT,
VK_FORMAT_G8B8G8R8_422_UNORM_KHR,
VK_FORMAT_B8G8R8G8_422_UNORM_KHR,
VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM_KHR,
VK_FORMAT_G8_B8R8_2PLANE_420_UNORM_KHR,
VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM_KHR,
VK_FORMAT_G8_B8R8_2PLANE_422_UNORM_KHR,
VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM_KHR,
VK_FORMAT_R10X6_UNORM_PACK16_KHR,
VK_FORMAT_R10X6G10X6_UNORM_2PACK16_KHR,
VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16_KHR,
VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16_KHR,
VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16_KHR,
VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16_KHR,
VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16_KHR,
VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16_KHR,
VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16_KHR,
VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16_KHR,
VK_FORMAT_R12X4_UNORM_PACK16_KHR,
VK_FORMAT_R12X4G12X4_UNORM_2PACK16_KHR,
VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16_KHR,
VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16_KHR,
VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16_KHR,
VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16_KHR,
VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16_KHR,
VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16_KHR,
VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16_KHR,
VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16_KHR,
VK_FORMAT_G16B16G16R16_422_UNORM_KHR,
VK_FORMAT_B16G16R16G16_422_UNORM_KHR,
VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM_KHR,
VK_FORMAT_G16_B16R16_2PLANE_420_UNORM_KHR,
VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM_KHR,
VK_FORMAT_G16_B16R16_2PLANE_422_UNORM_KHR,
VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM_KHR,
VK_FORMAT_G8_B8R8_2PLANE_444_UNORM_EXT,
VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16_EXT,
VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16_EXT,
VK_FORMAT_G16_B16R16_2PLANE_444_UNORM_EXT,
VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT,
VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT,
VK_FORMAT_R16G16_S10_5_NV,
VK_FORMAT_UNDEFINED,
VK_FORMAT_R4G4_UNORM_PACK8,
VK_FORMAT_R4G4B4A4_UNORM_PACK16,
VK_FORMAT_B4G4R4A4_UNORM_PACK16,
VK_FORMAT_R5G6B5_UNORM_PACK16,
VK_FORMAT_B5G6R5_UNORM_PACK16,
VK_FORMAT_R5G5B5A1_UNORM_PACK16,
VK_FORMAT_B5G5R5A1_UNORM_PACK16,
VK_FORMAT_A1R5G5B5_UNORM_PACK16,
VK_FORMAT_R8_UNORM,
VK_FORMAT_R8_SNORM,
VK_FORMAT_R8_USCALED,
VK_FORMAT_R8_SSCALED,
VK_FORMAT_R8_UINT,
VK_FORMAT_R8_SINT,
VK_FORMAT_R8_SRGB,
VK_FORMAT_R8G8_UNORM,
VK_FORMAT_R8G8_SNORM,
VK_FORMAT_R8G8_USCALED,
VK_FORMAT_R8G8_SSCALED,
VK_FORMAT_R8G8_UINT,
VK_FORMAT_R8G8_SINT,
VK_FORMAT_R8G8_SRGB,
VK_FORMAT_R8G8B8_UNORM,
VK_FORMAT_R8G8B8_SNORM,
VK_FORMAT_R8G8B8_USCALED,
VK_FORMAT_R8G8B8_SSCALED,
VK_FORMAT_R8G8B8_UINT,
VK_FORMAT_R8G8B8_SINT,
VK_FORMAT_R8G8B8_SRGB,
VK_FORMAT_B8G8R8_UNORM,
VK_FORMAT_B8G8R8_SNORM,
VK_FORMAT_B8G8R8_USCALED,
VK_FORMAT_B8G8R8_SSCALED,
VK_FORMAT_B8G8R8_UINT,
VK_FORMAT_B8G8R8_SINT,
VK_FORMAT_B8G8R8_SRGB,
VK_FORMAT_R8G8B8A8_UNORM,
VK_FORMAT_R8G8B8A8_SNORM,
VK_FORMAT_R8G8B8A8_USCALED,
VK_FORMAT_R8G8B8A8_SSCALED,
VK_FORMAT_R8G8B8A8_UINT,
VK_FORMAT_R8G8B8A8_SINT,
VK_FORMAT_R8G8B8A8_SRGB,
VK_FORMAT_B8G8R8A8_UNORM,
VK_FORMAT_B8G8R8A8_SNORM,
VK_FORMAT_B8G8R8A8_USCALED,
VK_FORMAT_B8G8R8A8_SSCALED,
VK_FORMAT_B8G8R8A8_UINT,
VK_FORMAT_B8G8R8A8_SINT,
VK_FORMAT_B8G8R8A8_SRGB,
VK_FORMAT_A8B8G8R8_UNORM_PACK32,
VK_FORMAT_A8B8G8R8_SNORM_PACK32,
VK_FORMAT_A8B8G8R8_USCALED_PACK32,
VK_FORMAT_A8B8G8R8_SSCALED_PACK32,
VK_FORMAT_A8B8G8R8_UINT_PACK32,
VK_FORMAT_A8B8G8R8_SINT_PACK32,
VK_FORMAT_A8B8G8R8_SRGB_PACK32,
VK_FORMAT_A2R10G10B10_UNORM_PACK32,
VK_FORMAT_A2R10G10B10_SNORM_PACK32,
VK_FORMAT_A2R10G10B10_USCALED_PACK32,
VK_FORMAT_A2R10G10B10_SSCALED_PACK32,
VK_FORMAT_A2R10G10B10_UINT_PACK32,
VK_FORMAT_A2R10G10B10_SINT_PACK32,
VK_FORMAT_A2B10G10R10_UNORM_PACK32,
VK_FORMAT_A2B10G10R10_SNORM_PACK32,
VK_FORMAT_A2B10G10R10_USCALED_PACK32,
VK_FORMAT_A2B10G10R10_SSCALED_PACK32,
VK_FORMAT_A2B10G10R10_UINT_PACK32,
VK_FORMAT_A2B10G10R10_SINT_PACK32,
VK_FORMAT_R16_UNORM,
VK_FORMAT_R16_SNORM,
VK_FORMAT_R16_USCALED,
VK_FORMAT_R16_SSCALED,
VK_FORMAT_R16_UINT,
VK_FORMAT_R16_SINT,
VK_FORMAT_R16_SFLOAT,
VK_FORMAT_R16G16_UNORM,
VK_FORMAT_R16G16_SNORM,
VK_FORMAT_R16G16_USCALED,
VK_FORMAT_R16G16_SSCALED,
VK_FORMAT_R16G16_UINT,
VK_FORMAT_R16G16_SINT,
VK_FORMAT_R16G16_SFLOAT,
VK_FORMAT_R16G16B16_UNORM,
VK_FORMAT_R16G16B16_SNORM,
VK_FORMAT_R16G16B16_USCALED,
VK_FORMAT_R16G16B16_SSCALED,
VK_FORMAT_R16G16B16_UINT,
VK_FORMAT_R16G16B16_SINT,
VK_FORMAT_R16G16B16_SFLOAT,
VK_FORMAT_R16G16B16A16_UNORM,
VK_FORMAT_R16G16B16A16_SNORM,
VK_FORMAT_R16G16B16A16_USCALED,
VK_FORMAT_R16G16B16A16_SSCALED,
VK_FORMAT_R16G16B16A16_UINT,
VK_FORMAT_R16G16B16A16_SINT,
VK_FORMAT_R16G16B16A16_SFLOAT,
VK_FORMAT_R32_UINT,
VK_FORMAT_R32_SINT,
VK_FORMAT_R32_SFLOAT,
VK_FORMAT_R32G32_UINT,
VK_FORMAT_R32G32_SINT,
VK_FORMAT_R32G32_SFLOAT,
VK_FORMAT_R32G32B32_UINT,
VK_FORMAT_R32G32B32_SINT,
VK_FORMAT_R32G32B32_SFLOAT,
VK_FORMAT_R32G32B32A32_UINT,
VK_FORMAT_R32G32B32A32_SINT,
VK_FORMAT_R32G32B32A32_SFLOAT,
VK_FORMAT_R64_UINT,
VK_FORMAT_R64_SINT,
VK_FORMAT_R64_SFLOAT,
VK_FORMAT_R64G64_UINT,
VK_FORMAT_R64G64_SINT,
VK_FORMAT_R64G64_SFLOAT,
VK_FORMAT_R64G64B64_UINT,
VK_FORMAT_R64G64B64_SINT,
VK_FORMAT_R64G64B64_SFLOAT,
VK_FORMAT_R64G64B64A64_UINT,
VK_FORMAT_R64G64B64A64_SINT,
VK_FORMAT_R64G64B64A64_SFLOAT,
VK_FORMAT_B10G11R11_UFLOAT_PACK32,
VK_FORMAT_E5B9G9R9_UFLOAT_PACK32,
VK_FORMAT_D16_UNORM,
VK_FORMAT_X8_D24_UNORM_PACK32,
VK_FORMAT_D32_SFLOAT,
VK_FORMAT_S8_UINT,
VK_FORMAT_D16_UNORM_S8_UINT,
VK_FORMAT_D24_UNORM_S8_UINT,
VK_FORMAT_D32_SFLOAT_S8_UINT,
VK_FORMAT_BC1_RGB_UNORM_BLOCK,
VK_FORMAT_BC1_RGB_SRGB_BLOCK,
VK_FORMAT_BC1_RGBA_UNORM_BLOCK,
VK_FORMAT_BC1_RGBA_SRGB_BLOCK,
VK_FORMAT_BC2_UNORM_BLOCK,
VK_FORMAT_BC2_SRGB_BLOCK,
VK_FORMAT_BC3_UNORM_BLOCK,
VK_FORMAT_BC3_SRGB_BLOCK,
VK_FORMAT_BC4_UNORM_BLOCK,
VK_FORMAT_BC4_SNORM_BLOCK,
VK_FORMAT_BC5_UNORM_BLOCK,
VK_FORMAT_BC5_SNORM_BLOCK,
VK_FORMAT_BC6H_UFLOAT_BLOCK,
VK_FORMAT_BC6H_SFLOAT_BLOCK,
VK_FORMAT_BC7_UNORM_BLOCK,
VK_FORMAT_BC7_SRGB_BLOCK,
VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK,
VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK,
VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK,
VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK,
VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK,
VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK,
VK_FORMAT_EAC_R11_UNORM_BLOCK,
VK_FORMAT_EAC_R11_SNORM_BLOCK,
VK_FORMAT_EAC_R11G11_UNORM_BLOCK,
VK_FORMAT_EAC_R11G11_SNORM_BLOCK,
VK_FORMAT_ASTC_4x4_UNORM_BLOCK,
VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
VK_FORMAT_ASTC_5x4_UNORM_BLOCK,
VK_FORMAT_ASTC_5x4_SRGB_BLOCK,
VK_FORMAT_ASTC_5x5_UNORM_BLOCK,
VK_FORMAT_ASTC_5x5_SRGB_BLOCK,
VK_FORMAT_ASTC_6x5_UNORM_BLOCK,
VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
VK_FORMAT_ASTC_6x6_UNORM_BLOCK,
VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
VK_FORMAT_ASTC_8x5_UNORM_BLOCK,
VK_FORMAT_ASTC_8x5_SRGB_BLOCK,
VK_FORMAT_ASTC_8x6_UNORM_BLOCK,
VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
VK_FORMAT_ASTC_8x8_UNORM_BLOCK,
VK_FORMAT_ASTC_8x8_SRGB_BLOCK,
VK_FORMAT_ASTC_10x5_UNORM_BLOCK,
VK_FORMAT_ASTC_10x5_SRGB_BLOCK,
VK_FORMAT_ASTC_10x6_UNORM_BLOCK,
VK_FORMAT_ASTC_10x6_SRGB_BLOCK,
VK_FORMAT_ASTC_10x8_UNORM_BLOCK,
VK_FORMAT_ASTC_10x8_SRGB_BLOCK,
VK_FORMAT_ASTC_10x10_UNORM_BLOCK,
VK_FORMAT_ASTC_10x10_SRGB_BLOCK,
VK_FORMAT_ASTC_12x10_UNORM_BLOCK,
VK_FORMAT_ASTC_12x10_SRGB_BLOCK,
VK_FORMAT_ASTC_12x12_UNORM_BLOCK,
VK_FORMAT_ASTC_12x12_SRGB_BLOCK,
VK_FORMAT_G8B8G8R8_422_UNORM,
VK_FORMAT_B8G8R8G8_422_UNORM,
VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM,
VK_FORMAT_G8_B8R8_2PLANE_420_UNORM,
VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM,
VK_FORMAT_G8_B8R8_2PLANE_422_UNORM,
VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM,
VK_FORMAT_R10X6_UNORM_PACK16,
VK_FORMAT_R10X6G10X6_UNORM_2PACK16,
VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16,
VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16,
VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16,
VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16,
VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16,
VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16,
VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16,
VK_FORMAT_R12X4_UNORM_PACK16,
VK_FORMAT_R12X4G12X4_UNORM_2PACK16,
VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16,
VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16,
VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16,
VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16,
VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16,
VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16,
VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16,
VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16,
VK_FORMAT_G16B16G16R16_422_UNORM,
VK_FORMAT_B16G16R16G16_422_UNORM,
VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM,
VK_FORMAT_G16_B16R16_2PLANE_420_UNORM,
VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM,
VK_FORMAT_G16_B16R16_2PLANE_422_UNORM,
VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM,
VK_FORMAT_G8_B8R8_2PLANE_444_UNORM,
VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16,
VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16,
VK_FORMAT_G16_B16R16_2PLANE_444_UNORM,
VK_FORMAT_A4R4G4B4_UNORM_PACK16,
VK_FORMAT_A4B4G4R4_UNORM_PACK16,
VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK,
VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK,
VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK,
VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK,
VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK,
VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK,
VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK,
VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK,
VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK,
VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK,
VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK,
VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK,
VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK,
VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK,
VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG,
VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG,
VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG,
VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG,
VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG,
VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG,
VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG,
VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG,
// Not supported (yet) by bluevk
// VK_FORMAT_R16G16_SFIXED5_NV,
// VK_FORMAT_A1B5G5R5_UNORM_PACK16_KHR,
// VK_FORMAT_A8_UNORM_KHR,
// VK_FORMAT_A8_UNORM,
VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT,
VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT,
VK_FORMAT_G8B8G8R8_422_UNORM_KHR,
VK_FORMAT_B8G8R8G8_422_UNORM_KHR,
VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM_KHR,
VK_FORMAT_G8_B8R8_2PLANE_420_UNORM_KHR,
VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM_KHR,
VK_FORMAT_G8_B8R8_2PLANE_422_UNORM_KHR,
VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM_KHR,
VK_FORMAT_R10X6_UNORM_PACK16_KHR,
VK_FORMAT_R10X6G10X6_UNORM_2PACK16_KHR,
VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16_KHR,
VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16_KHR,
VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16_KHR,
VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16_KHR,
VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16_KHR,
VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16_KHR,
VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16_KHR,
VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16_KHR,
VK_FORMAT_R12X4_UNORM_PACK16_KHR,
VK_FORMAT_R12X4G12X4_UNORM_2PACK16_KHR,
VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16_KHR,
VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16_KHR,
VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16_KHR,
VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16_KHR,
VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16_KHR,
VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16_KHR,
VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16_KHR,
VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16_KHR,
VK_FORMAT_G16B16G16R16_422_UNORM_KHR,
VK_FORMAT_B16G16R16G16_422_UNORM_KHR,
VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM_KHR,
VK_FORMAT_G16_B16R16_2PLANE_420_UNORM_KHR,
VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM_KHR,
VK_FORMAT_G16_B16R16_2PLANE_422_UNORM_KHR,
VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM_KHR,
VK_FORMAT_G8_B8R8_2PLANE_444_UNORM_EXT,
VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16_EXT,
VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16_EXT,
VK_FORMAT_G16_B16R16_2PLANE_444_UNORM_EXT,
VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT,
VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT,
VK_FORMAT_R16G16_S10_5_NV,
};
constexpr VkFormat EXT_VK_FORMATS[] = {
// VK_EXT_ycbcr_2plane_444_formats
VK_FORMAT_G16_B16R16_2PLANE_444_UNORM,
VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16,
VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16,
VK_FORMAT_G8_B8R8_2PLANE_444_UNORM,
VK_FORMAT_G16_B16R16_2PLANE_444_UNORM,
// VK_NV_optical_flow
VK_FORMAT_R16G16_S10_5_NV,
};
using UniformBufferBitmask = utils::bitset64;
@@ -20,6 +20,7 @@
// An Array that will be statically fixed in capacity, but the "size" (as in user added elements) is
// variable. Note that this class is movable.
#include <utils/debug.h>
#include <utils/Panic.h>
#include <array>
@@ -0,0 +1,795 @@
#include "SpdMipmapGenerator.h"
#include <sstream>
#include <stdexcept>
// C++ port of https://github.com/JolifantoBambla/webgpu-spd for early experiments- do not merge like this
namespace spd {
// Helper to convert enums to strings for map keys or shader code
const char* to_string(wgpu::TextureFormat format) {
switch (format) {
case wgpu::TextureFormat::RGBA8Unorm: return "rgba8unorm";
case wgpu::TextureFormat::BGRA8Unorm: return "bgra8unorm";
case wgpu::TextureFormat::R32Float: return "r32float";
case wgpu::TextureFormat::RG32Float: return "rg32float";
case wgpu::TextureFormat::RGBA32Float: return "rgba32float";
case wgpu::TextureFormat::R16Float: return "r16float";
case wgpu::TextureFormat::RG16Float: return "rg16float";
case wgpu::TextureFormat::RGBA16Float: return "rgba16float";
// Add other formats as needed
default: return "rgba8unorm";
}
}
std::string MipmapGenerator::GetFilterCode(SPDFilter filter) {
switch (filter) {
case SPDFilter::Min:
return R"(
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
return min(min(v0, v1), min(v2, v3));
}
)";
case SPDFilter::Max:
return R"(
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
return max(max(v0, v1), max(v2, v3));
}
)";
case SPDFilter::MinMax:
return R"(
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
let max4 = max(max(v0.xy, v1.xy), max(v2.xy, v3.xy));
return vec4<SPDScalar>(min(min(v0.x, v1.x), min(v2.x, v3.x)), max(max4.x, max4.y), 0.0, 0.0);
}
)";
case SPDFilter::Average:
default:
return R"(
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
return (v0 + v1 + v2 + v3) * 0.25;
}
)";
}
}
MipmapGenerator::MipmapGenerator(const wgpu::Device& device) : m_device(device) {
wgpu::BindGroupLayoutEntry bglEntry{};
bglEntry.binding = 0;
bglEntry.visibility = wgpu::ShaderStage::Compute;
bglEntry.buffer.type = wgpu::BufferBindingType::Uniform;
bglEntry.buffer.minBindingSize = 16;
wgpu::BindGroupLayoutDescriptor bglDesc{};
bglDesc.entryCount = 1;
bglDesc.entries = &bglEntry;
m_internalResourcesBindGroupLayout = m_device.CreateBindGroupLayout(&bglDesc);
}
void MipmapGenerator::PreparePipelines(wgpu::TextureFormat format, SPDFilter filter, bool halfPrecision) {
SPDScalarType scalarType = SanitizeScalarType(format, halfPrecision);
// Prepare for a reasonable number of mips
for (uint32_t i = 1; i <= 4; ++i) {
GetOrCreatePipeline(format, filter, i, scalarType);
}
}
SPDScalarType MipmapGenerator::SanitizeScalarType(wgpu::TextureFormat format, bool halfPrecision) {
std::string formatStr = to_string(format);
std::transform(formatStr.begin(), formatStr.end(), formatStr.begin(), ::tolower);
SPDScalarType texelType = SPDScalarType::F32;
if (formatStr.find("sint") != std::string::npos) {
texelType = SPDScalarType::I32;
} else if (formatStr.find("uint") != std::string::npos) {
texelType = SPDScalarType::U32;
}
if (halfPrecision) {
bool hasF16 = false;
// In a real Dawn app, you would check device.GetSupportedFeatures()
// For now, let's assume it's available if requested.
hasF16 = true;
if (!hasF16) {
// Log warning: half precision requested but not supported
}
if (texelType != SPDScalarType::F32) {
// Log warning: half precision for non-float format
}
if (hasF16 && texelType == SPDScalarType::F32) {
return SPDScalarType::F16;
}
}
return texelType;
}
SPDPipeline& MipmapGenerator::GetOrCreatePipeline(wgpu::TextureFormat format, SPDFilter filter, uint32_t numMips, SPDScalarType scalarType) {
if (m_pipelines[format][scalarType][filter].count(numMips) == 0) {
// Create the pipeline
SPDPipeline spdPipeline;
std::vector<wgpu::BindGroupLayoutEntry> mipsBglEntries;
for (uint32_t i = 0; i <= numMips; ++i) {
wgpu::BindGroupLayoutEntry entry{};
entry.binding = i;
entry.visibility = wgpu::ShaderStage::Compute;
if (i == 0) {
entry.texture.sampleType = (scalarType == SPDScalarType::I32) ? wgpu::TextureSampleType::Sint :
(scalarType == SPDScalarType::U32) ? wgpu::TextureSampleType::Uint :
wgpu::TextureSampleType::UnfilterableFloat;
entry.texture.viewDimension = wgpu::TextureViewDimension::e2DArray;
} else {
entry.storageTexture.access = wgpu::StorageTextureAccess::WriteOnly;
entry.storageTexture.format = format;
entry.storageTexture.viewDimension = wgpu::TextureViewDimension::e2DArray;
}
mipsBglEntries.push_back(entry);
}
wgpu::BindGroupLayoutDescriptor mipsBglDesc{};
mipsBglDesc.entryCount = mipsBglEntries.size();
mipsBglDesc.entries = mipsBglEntries.data();
spdPipeline.mipsBindGroupLayout = m_device.CreateBindGroupLayout(&mipsBglDesc);
std::string shaderCode = MakeShaderCode(format, GetFilterCode(filter), numMips, scalarType);
wgpu::ShaderModuleWGSLDescriptor wgslDesc{};
wgslDesc.code = shaderCode.c_str();
wgpu::ShaderModuleDescriptor shaderModuleDesc{};
shaderModuleDesc.nextInChain = &wgslDesc;
wgpu::ShaderModule shaderModule = m_device.CreateShaderModule(&shaderModuleDesc);
wgpu::BindGroupLayout bgls[] = { spdPipeline.mipsBindGroupLayout, m_internalResourcesBindGroupLayout };
wgpu::PipelineLayoutDescriptor layoutDesc{};
layoutDesc.bindGroupLayoutCount = 2;
layoutDesc.bindGroupLayouts = bgls;
wgpu::ComputePipelineDescriptor pipelineDesc{};
pipelineDesc.layout = m_device.CreatePipelineLayout(&layoutDesc);
pipelineDesc.compute.module = shaderModule;
pipelineDesc.compute.entryPoint = "downsample";
spdPipeline.pipeline = m_device.CreateComputePipeline(&pipelineDesc);
m_pipelines[format][scalarType][filter][numMips] = std::move(spdPipeline);
}
return m_pipelines[format][scalarType][filter][numMips];
}
void MipmapGenerator::Generate(
wgpu::CommandEncoder& commandEncoder,
wgpu::Texture srcTexture,
const SPDPassConfig& config)
{
uint32_t width = srcTexture.GetWidth();
uint32_t height = srcTexture.GetHeight();
uint32_t arrayLayerCount = srcTexture.GetDepthOrArrayLayers();
wgpu::Texture target = config.targetTexture ? config.targetTexture : srcTexture;
uint32_t numMips = config.numMips > 0 ? config.numMips : target.GetMipLevelCount() - 1;
if (numMips == 0) return;
SPDScalarType scalarType = SanitizeScalarType(srcTexture.GetFormat(), config.halfPrecision);
SPDPipeline& spdPipeline = GetOrCreatePipeline(target.GetFormat(), config.filter, numMips, scalarType);
// --- Create Bind Group 0 (Mips) ---
std::vector<wgpu::BindGroupEntry> mipEntries;
wgpu::TextureViewDescriptor srcViewDesc{};
srcViewDesc.dimension = wgpu::TextureViewDimension::e2DArray;
srcViewDesc.baseMipLevel = config.sourceMipLevel;
srcViewDesc.mipLevelCount = 1;
srcViewDesc.baseArrayLayer = 0;
srcViewDesc.arrayLayerCount = arrayLayerCount;
wgpu::BindGroupEntry srcEntry{};
srcEntry.binding = 0;
srcEntry.textureView = srcTexture.CreateView(&srcViewDesc);
mipEntries.push_back(srcEntry);
for (uint32_t i = 0; i < numMips; ++i) {
wgpu::TextureViewDescriptor dstViewDesc{};
dstViewDesc.dimension = wgpu::TextureViewDimension::e2DArray;
dstViewDesc.baseMipLevel = config.sourceMipLevel + i + 1;
dstViewDesc.mipLevelCount = 1;
dstViewDesc.baseArrayLayer = 0;
dstViewDesc.arrayLayerCount = arrayLayerCount;
wgpu::BindGroupEntry dstEntry{};
dstEntry.binding = i + 1;
dstEntry.textureView = target.CreateView(&dstViewDesc);
mipEntries.push_back(dstEntry);
}
wgpu::BindGroupDescriptor mipBindGroupDesc{};
mipBindGroupDesc.layout = spdPipeline.mipsBindGroupLayout;
mipBindGroupDesc.entryCount = mipEntries.size();
mipBindGroupDesc.entries = mipEntries.data();
wgpu::BindGroup mipsBindGroup = m_device.CreateBindGroup(&mipBindGroupDesc);
// --- Create Bind Group 1 (Internal Resources) ---
uint32_t numWorkGroupsX = (width + 63) / 64;
uint32_t numWorkGroupsY = (height + 63) / 64;
uint32_t numWorkGroups = numWorkGroupsX * numWorkGroupsY;
struct DownsamplePassMeta {
uint32_t work_group_offset[2] = {0, 0};
uint32_t num_work_groups;
uint32_t mips;
uint32_t padding[12]; // Ensure size is multiple of 16
} meta;
meta.num_work_groups = numWorkGroups;
meta.mips = numMips;
wgpu::BufferDescriptor metaBufferDesc{};
metaBufferDesc.size = sizeof(DownsamplePassMeta);
metaBufferDesc.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst;
wgpu::Buffer metaBuffer = m_device.CreateBuffer(&metaBufferDesc);
m_device.GetQueue().WriteBuffer(metaBuffer, 0, &meta, sizeof(meta));
wgpu::BindGroupEntry metaEntry{};
metaEntry.binding = 0;
metaEntry.buffer = metaBuffer;
wgpu::BindGroupDescriptor internalBindGroupDesc{};
internalBindGroupDesc.layout = m_internalResourcesBindGroupLayout;
internalBindGroupDesc.entryCount = 1;
internalBindGroupDesc.entries = &metaEntry;
wgpu::BindGroup internalBindGroup = m_device.CreateBindGroup(&internalBindGroupDesc);
// --- Dispatch ---
wgpu::ComputePassEncoder pass = commandEncoder.BeginComputePass();
pass.SetPipeline(spdPipeline.pipeline);
pass.SetBindGroup(0, mipsBindGroup);
pass.SetBindGroup(1, internalBindGroup);
pass.DispatchWorkgroups(numWorkGroupsX, numWorkGroupsY, arrayLayerCount);
pass.End();
}
// Main shader generation logic
// Helper function to check if a string is in a vector of strings
bool includes(const std::vector<std::string>& vec, const std::string& str) {
for (const auto& s : vec) {
if (s == str) {
return true;
}
}
return false;
}
std::string MakeShaderCode(wgpu::TextureFormat outputFormat,
const std::string& filterOp,
unsigned int numMips, // Assuming a default value for numMips
SPDScalarType scalarType) { // Default scalarType
std::stringstream ss;
std::string texelType;
if (scalarType == SPDScalarType::I32) {
texelType = "i32";
} else if (scalarType == SPDScalarType::U32) {
texelType = "u32";
} else {
texelType = "f32";
}
bool useF16 = (scalarType == SPDScalarType::F16);
std::string filterCode = filterOp;
if (filterOp == SPD_FILTER_AVERAGE && !includes({"f32", "f16"}, texelType)) {
// Replace "* 0.25" with "/ 4"
size_t pos = filterCode.find("* 0.25");
if (pos != std::string::npos) {
filterCode.replace(pos, std::string("* 0.25").length(), "/ 4");
}
}
// Generate mipsBindings
std::string mipsBindings;
for (unsigned int i = 0; i < numMips; ++i) {
mipsBindings += "@group(0) @binding(" + std::to_string(i + 1) + ") var dst_mip_" + std::to_string(i + 1) + ": texture_storage_2d_array<" + to_string(outputFormat) + ", write>;\n";
}
// Generate mipsAccessorBody
std::string mipsAccessorBody;
for (unsigned int i = 0; i < numMips; ++i) {
if (i == 5 && numMips > 6) {
mipsAccessorBody += " else if mip == 6 {\n";
mipsAccessorBody += " textureStore(dst_mip_6, uv, slice, " + (useF16 ? "vec4<" + texelType + ">(value)" : "value") + ");\n";
mipsAccessorBody += " mip_dst_6_buffer[slice][uv.y][uv.x] = value;\n";
mipsAccessorBody += " }";
} else {
if (i != 0) {
mipsAccessorBody += " else ";
}
mipsAccessorBody += "if mip == " + std::to_string(i + 1) + " {\n";
mipsAccessorBody += " textureStore(dst_mip_" + std::to_string(i + 1) + ", uv, slice, " + (useF16 ? "vec4<" + texelType + ">(value)" : "value") + ");\n";
mipsAccessorBody += " }";
}
}
std::string mipsAccessor = "fn store_dst_mip(value: vec4<SPDScalar>, uv: vec2<u32>, slice: u32, mip: u32) {\n" + mipsAccessorBody + "\n}";
std::string midMipAccessor = "return mip_dst_6_buffer[slice][uv.y][uv.x];";
// Start building the final shader code string
ss << R"(
// This file is part of the FidelityFX SDK.
//
// Copyright (C) 2023 Advanced Micro Devices, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the “Software”), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Definitions --------------------------------------------------------------------------------------------------------
)";
if (useF16) {
ss << "enable f16;\n";
}
ss << "alias SPDScalar = " << texelType << ";\n\n"; // Using texelType here, assuming SPDScalar maps to it.
ss << R"(
// Helpers ------------------------------------------------------------------------------------------------------------
/**
* A helper function performing a remap 64x1 to 8x8 remapping which is necessary for 2D wave reductions.
* * The 64-wide lane indices to 8x8 remapping is performed as follows:
* 00 01 08 09 10 11 18 19
* 02 03 0a 0b 12 13 1a 1b
* 04 05 0c 0d 14 15 1c 1d
* 06 07 0e 0f 16 17 1e 1f
* 20 21 28 29 30 31 38 39
* 22 23 2a 2b 32 33 3a 3b
* 24 25 2c 2d 34 35 3c 3d
* 26 27 2e 2f 36 37 3e 3f
* * @param a: The input 1D coordinate to remap.
*
* @returns The remapped 2D coordinates.
*/
fn remap_for_wave_reduction(a: u32) -> vec2<u32> {
return vec2<u32>(
insertBits(extractBits(a, 2u, 3u), a, 0u, 1u),
insertBits(extractBits(a, 3u, 3u), extractBits(a, 1u, 2u), 0u, 2u)
);
}
fn map_to_xy(local_invocation_index: u32) -> vec2<u32> {
let sub_xy: vec2<u32> = remap_for_wave_reduction(local_invocation_index % 64);
return vec2<u32>(
sub_xy.x + 8 * ((local_invocation_index >> 6) % 2),
sub_xy.y + 8 * ((local_invocation_index >> 7))
);
}
/*
* Compute a linear value from a SRGB value.
* * @param value: The value to convert to linear from SRGB.
* * @returns A value in SRGB space.
*/
/*
fn srgb_to_linear(value: SPDScalar) -> SPDScalar {
let j = vec3<SPDScalar>(0.0031308 * 12.92, 12.92, 1.0 / 2.4);
let k = vec2<SPDScalar>(1.055, -0.055);
return clamp(j.x, value * j.y, pow(value, j.z) * k.x + k.y);
}
*/
// Resources & Accessors -----------------------------------------------------------------------------------------------
struct DownsamplePassMeta {
work_group_offset: vec2<u32>,
num_work_groups: u32,
mips: u32,
}
// In the original version dst_mip_i is an image2Darray [SPD_MAX_MIP_LEVELS+1], i.e., 12+1, but WGSL doesn't support arrays of textures yet
// Also these are read_write because for mips 7-13, the workgroup reads from mip level 6 - since most formats don't support read_write access in WGSL yet, we use a single read_write buffer in such cases instead
@group(0) @binding(0) var src_mip_0: texture_2d_array<)" << texelType << R"(>;
)" << mipsBindings << R"(
@group(1) @binding(0) var<uniform> downsample_pass_meta : DownsamplePassMeta;
@group(1) @binding(1) var<storage, read_write> spd_global_counter: array<atomic<u32>>;
@group(1) @binding(2) var<storage, read_write> mip_dst_6_buffer: array<array<array<vec4<f32>, 64>, 64>>;
fn get_mips() -> u32 {
return downsample_pass_meta.mips;
}
fn get_num_work_groups() -> u32 {
return downsample_pass_meta.num_work_groups;
}
fn get_work_group_offset() -> vec2<u32> {
return downsample_pass_meta.work_group_offset;
}
fn load_src_image(uv: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
return vec4<SPDScalar>(textureLoad(src_mip_0, uv, slice, 0));
}
fn load_mid_mip_image(uv: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
)";
if (numMips > 6) {
ss << midMipAccessor;
} else {
ss << "return vec4<SPDScalar>();";
}
ss << R"(
}
)" << mipsAccessor << R"(
// Workgroup -----------------------------------------------------------------------------------------------------------
var<workgroup> spd_intermediate: array<array<vec4<SPDScalar>, 16>, 16>;
var<workgroup> spd_counter: atomic<u32>;
fn spd_increase_atomic_counter(slice: u32) {
atomicStore(&spd_counter, atomicAdd(&spd_global_counter[slice], 1));
}
fn spd_get_atomic_counter() -> u32 {
return atomicLoad(&spd_counter);
}
fn spd_reset_atomic_counter(slice: u32) {
atomicStore(&spd_global_counter[slice], 0);
}
// Cotnrol flow --------------------------------------------------------------------------------------------------------
fn spd_barrier() {
// in glsl this does: groupMemoryBarrier(); barrier();
workgroupBarrier();
}
// Only last active workgroup should proceed
fn spd_exit_workgroup(num_work_groups: u32, local_invocation_index: u32, slice: u32) -> bool {
// global atomic counter
if (local_invocation_index == 0) {
spd_increase_atomic_counter(slice);
}
spd_barrier();
return spd_get_atomic_counter() != (num_work_groups - 1);
}
// Pixel access --------------------------------------------------------------------------------------------------------
)" << filterCode << R"(
fn spd_store(pix: vec2<u32>, out_value: vec4<SPDScalar>, mip: u32, slice: u32) {
store_dst_mip(out_value, pix, slice, mip + 1);
}
fn spd_load_intermediate(x: u32, y: u32) -> vec4<SPDScalar> {
return spd_intermediate[x][y];
}
fn spd_store_intermediate(x: u32, y: u32, value: vec4<SPDScalar>) {
spd_intermediate[x][y] = value;
}
fn spd_reduce_intermediate(i0: vec2<u32>, i1: vec2<u32>, i2: vec2<u32>, i3: vec2<u32>) -> vec4<SPDScalar> {
let v0 = spd_load_intermediate(i0.x, i0.y);
let v1 = spd_load_intermediate(i1.x, i1.y);
let v2 = spd_load_intermediate(i2.x, i2.y);
let v3 = spd_load_intermediate(i3.x, i3.y);
return spd_reduce_4(v0, v1, v2, v3);
}
fn spd_reduce_load_4(base: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
let v0 = load_src_image(base + vec2<u32>(0, 0), slice);
let v1 = load_src_image(base + vec2<u32>(0, 1), slice);
let v2 = load_src_image(base + vec2<u32>(1, 0), slice);
let v3 = load_src_image(base + vec2<u32>(1, 1), slice);
return spd_reduce_4(v0, v1, v2, v3);
}
fn spd_reduce_load_mid_mip_4(base: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
let v0 = load_mid_mip_image(base + vec2<u32>(0, 0), slice);
let v1 = load_mid_mip_image(base + vec2<u32>(0, 1), slice);
let v2 = load_mid_mip_image(base + vec2<u32>(1, 0), slice);
let v3 = load_mid_mip_image(base + vec2<u32>(1, 1), slice);
return spd_reduce_4(v0, v1, v2, v3);
}
// Main logic ---------------------------------------------------------------------------------------------------------
fn spd_downsample_mips_0_1(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
var v: array<vec4<SPDScalar>, 4>;
let workgroup64 = workgroup_id.xy * 64;
let workgroup32 = workgroup_id.xy * 32;
let workgroup16 = workgroup_id.xy * 16;
var tex = workgroup64 + vec2<u32>(x * 2, y * 2);
var pix = workgroup32 + vec2<u32>(x, y);
v[0] = spd_reduce_load_4(tex, slice);
spd_store(pix, v[0], 0, slice);
tex = workgroup64 + vec2<u32>(x * 2 + 32, y * 2);
pix = workgroup32 + vec2<u32>(x + 16, y);
v[1] = spd_reduce_load_4(tex, slice);
spd_store(pix, v[1], 0, slice);
tex = workgroup64 + vec2<u32>(x * 2, y * 2 + 32);
pix = workgroup32 + vec2<u32>(x, y + 16);
v[2] = spd_reduce_load_4(tex, slice);
spd_store(pix, v[2], 0, slice);
tex = workgroup64 + vec2<u32>(x * 2 + 32, y * 2 + 32);
pix = workgroup32 + vec2<u32>(x + 16, y + 16);
v[3] = spd_reduce_load_4(tex, slice);
spd_store(pix, v[3], 0, slice);
if mip <= 1 {
return;
}
for (var i = 0u; i < 4u; i++) {
spd_store_intermediate(x, y, v[i]);
spd_barrier();
if local_invocation_index < 64 {
v[i] = spd_reduce_intermediate(
vec2<u32>(x * 2 + 0, y * 2 + 0),
vec2<u32>(x * 2 + 1, y * 2 + 0),
vec2<u32>(x * 2 + 0, y * 2 + 1),
vec2<u32>(x * 2 + 1, y * 2 + 1)
);
spd_store(workgroup16 + vec2<u32>(x + (i % 2) * 8, y + (i / 2) * 8), v[i], 1, slice);
}
spd_barrier();
}
if local_invocation_index < 64 {
spd_store_intermediate(x + 0, y + 0, v[0]);
spd_store_intermediate(x + 8, y + 0, v[1]);
spd_store_intermediate(x + 0, y + 8, v[2]);
spd_store_intermediate(x + 8, y + 8, v[3]);
}
}
fn spd_downsample_mip_2(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
if local_invocation_index < 64u {
let v = spd_reduce_intermediate(
vec2<u32>(x * 2 + 0, y * 2 + 0),
vec2<u32>(x * 2 + 1, y * 2 + 0),
vec2<u32>(x * 2 + 0, y * 2 + 1),
vec2<u32>(x * 2 + 1, y * 2 + 1)
);
spd_store(workgroup_id.xy * 8 + vec2<u32>(x, y), v, mip, slice);
// store to LDS, try to reduce bank conflicts
// x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 x
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
// ...
// x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
spd_store_intermediate(x * 2 + y % 2, y * 2, v);
}
}
fn spd_downsample_mip_3(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
if local_invocation_index < 16u {
// x 0 x 0
// 0 0 0 0
// 0 x 0 x
// 0 0 0 0
let v = spd_reduce_intermediate(
vec2<u32>(x * 4 + 0 + 0, y * 4 + 0),
vec2<u32>(x * 4 + 2 + 0, y * 4 + 0),
vec2<u32>(x * 4 + 0 + 1, y * 4 + 2),
vec2<u32>(x * 4 + 2 + 1, y * 4 + 2)
);
spd_store(workgroup_id.xy * 4 + vec2<u32>(x, y), v, mip, slice);
// store to LDS
// x 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0 0
// ...
// 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0
// ...
// 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x
// ...
spd_store_intermediate(x * 4 + y, y * 4, v);
}
}
fn spd_downsample_mip_4(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
if local_invocation_index < 4u {
// x 0 0 0 x 0 0 0
// ...
// 0 x 0 0 0 x 0 0
let v = spd_reduce_intermediate(
vec2<u32>(x * 8 + 0 + 0 + y * 2, y * 8 + 0),
vec2<u32>(x * 8 + 4 + 0 + y * 2, y * 8 + 0),
vec2<u32>(x * 8 + 0 + 1 + y * 2, y * 8 + 4),
vec2<u32>(x * 8 + 4 + 1 + y * 2, y * 8 + 4)
);
spd_store(workgroup_id.xy * 2 + vec2<u32>(x, y), v, mip, slice);
// store to LDS
// x x x x 0 ...
// 0 ...
spd_store_intermediate(x + y * 2, 0, v);
}
}
fn spd_downsample_mip_5(workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
if local_invocation_index < 1u {
// x x x x 0 ...
// 0 ...
let v = spd_reduce_intermediate(vec2<u32>(0, 0), vec2<u32>(1, 0), vec2<u32>(2, 0), vec2<u32>(3, 0));
spd_store(workgroup_id.xy, v, mip, slice);
}
}
fn spd_downsample_next_four(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, base_mip: u32, mips: u32, slice: u32) {
if mips <= base_mip {
return;
}
spd_barrier();
spd_downsample_mip_2(x, y, workgroup_id, local_invocation_index, base_mip, slice);
if mips <= base_mip + 1 {
return;
}
spd_barrier();
spd_downsample_mip_3(x, y, workgroup_id, local_invocation_index, base_mip + 1, slice);
if mips <= base_mip + 2 {
return;
}
spd_barrier();
spd_downsample_mip_4(x, y, workgroup_id, local_invocation_index, base_mip + 2, slice);
if mips <= base_mip + 3 {
return;
}
spd_barrier();
spd_downsample_mip_5(workgroup_id, local_invocation_index, base_mip + 3, slice);
}
fn spd_downsample_last_four(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, base_mip: u32, mips: u32, slice: u32, exit: bool) {
if mips <= base_mip {
return;
}
spd_barrier();
if !exit {
spd_downsample_mip_2(x, y, workgroup_id, local_invocation_index, base_mip, slice);
}
if mips <= base_mip + 1 {
return;
}
spd_barrier();
if !exit {
spd_downsample_mip_3(x, y, workgroup_id, local_invocation_index, base_mip + 1, slice);
}
if mips <= base_mip + 2 {
return;
}
spd_barrier();
if !exit {
spd_downsample_mip_4(x, y, workgroup_id, local_invocation_index, base_mip + 2, slice);
}
if mips <= base_mip + 3 {
return;
}
spd_barrier();
if !exit {
spd_downsample_mip_5(workgroup_id, local_invocation_index, base_mip + 3, slice);
}
}
fn spd_downsample_mips_6_7(x: u32, y: u32, mips: u32, slice: u32) {
var tex = vec2<u32>(x * 4 + 0, y * 4 + 0);
var pix = vec2<u32>(x * 2 + 0, y * 2 + 0);
let v0 = spd_reduce_load_mid_mip_4(tex, slice);
spd_store(pix, v0, 6, slice);
tex = vec2<u32>(x * 4 + 2, y * 4 + 0);
pix = vec2<u32>(x * 2 + 1, y * 2 + 0);
let v1 = spd_reduce_load_mid_mip_4(tex, slice);
spd_store(pix, v1, 6, slice);
tex = vec2<u32>(x * 4 + 0, y * 4 + 2);
pix = vec2<u32>(x * 2 + 0, y * 2 + 1);
let v2 = spd_reduce_load_mid_mip_4(tex, slice);
spd_store(pix, v2, 6, slice);
tex = vec2<u32>(x * 4 + 2, y * 4 + 2);
pix = vec2<u32>(x * 2 + 1, y * 2 + 1);
let v3 = spd_reduce_load_mid_mip_4(tex, slice);
spd_store(pix, v3, 6, slice);
if mips <= 7 {
return;
}
// no barrier needed, working on values only from the same thread
let v = spd_reduce_4(v0, v1, v2, v3);
spd_store(vec2<u32>(x, y), v, 7, slice);
spd_store_intermediate(x, y, v);
}
fn spd_downsample_last_6(x: u32, y: u32, local_invocation_index: u32, mips: u32, num_work_groups: u32, slice: u32) {
if mips <= 6 {
return;
}
// increase the global atomic counter for the given slice and check if it's the last remaining thread group:
// terminate if not, continue if yes.
let exit = spd_exit_workgroup(num_work_groups, local_invocation_index, slice);
// can't exit directly because subsequent barrier calls break uniform control flow...
if !exit {
// reset the global atomic counter back to 0 for the next spd dispatch
spd_reset_atomic_counter(slice);
// After mip 5 there is only a single workgroup left that downsamples the remaining up to 64x64 texels.
// compute MIP level 6 and 7
spd_downsample_mips_6_7(x, y, mips, slice);
}
// compute MIP level 8, 9, 10, 11
spd_downsample_last_four(x, y, vec2<u32>(0, 0), local_invocation_index, 8, mips, slice, exit);
}
/// Downsamples a 64x64 tile based on the work group id.
/// If after downsampling it's the last active thread group, computes the remaining MIP levels.
///
/// @param [in] workGroupID index of the work group / thread group
/// @param [in] localInvocationIndex index of the thread within the thread group in 1D
/// @param [in] mips the number of total MIP levels to compute for the input texture
/// @param [in] numWorkGroups the total number of dispatched work groups / thread groups for this slice
/// @param [in] slice the slice of the input texture
fn spd_downsample(workgroup_id: vec2<u32>, local_invocation_index: u32, mips: u32, num_work_groups: u32, slice: u32) {
let xy = map_to_xy(local_invocation_index);
spd_downsample_mips_0_1(xy.x, xy.y, workgroup_id, local_invocation_index, mips, slice);
spd_downsample_next_four(xy.x, xy.y, workgroup_id, local_invocation_index, 2, mips, slice);
)";
if (numMips > 6) {
ss << " spd_downsample_last_6(xy.x, xy.y, local_invocation_index, mips, num_work_groups, slice);\n";
}
ss << R"(}
// Entry points -------------------------------------------------------------------------------------------------------
@compute
@workgroup_size(256, 1, 1)
fn downsample(@builtin(local_invocation_index) local_invocation_index: u32, @builtin(workgroup_id) workgroup_id: vec3<u32>) {
spd_downsample(
workgroup_id.xy + get_work_group_offset(),
local_invocation_index,
get_mips(),
get_num_work_groups(),
workgroup_id.z
);
}
)";
return ss.str();
}
} // namespace spd
@@ -0,0 +1,83 @@
#pragma once
#include <webgpu/webgpu_cpp.h>
#include <string>
#include <vector>
#include <unordered_map>
#include <optional>
// C++ port of https://github.com/JolifantoBambla/webgpu-spd for early experiments- do not merge like this
namespace spd {
// Enum for selecting the downsampling filter.
enum class SPDFilter {
Average,
Min,
Max,
MinMax
};
// Enum for shader scalar types.
enum class SPDScalarType {
F32,
F16,
I32,
U32
};
// Configuration for a single mipmap generation pass.
struct SPDPassConfig {
SPDFilter filter = SPDFilter::Average;
wgpu::Texture targetTexture = nullptr;
uint32_t numMips = 0;
bool halfPrecision = false;
uint32_t sourceMipLevel = 0;
};
// Holds a pipeline and its corresponding bind group layout.
struct SPDPipeline {
wgpu::BindGroupLayout mipsBindGroupLayout = nullptr;
wgpu::ComputePipeline pipeline = nullptr;
};
// Manages pipeline creation, caching, and execution for mipmap generation.
class MipmapGenerator {
public:
MipmapGenerator(const wgpu::Device& device);
// Pre-creates pipelines for specified formats and filters.
void PreparePipelines(wgpu::TextureFormat format, SPDFilter filter, bool halfPrecision = false);
// Generates a compute pass for creating mipmaps.
void Generate(
wgpu::CommandEncoder& commandEncoder,
wgpu::Texture srcTexture,
const SPDPassConfig& config
);
private:
wgpu::Device m_device;
wgpu::BindGroupLayout m_internalResourcesBindGroupLayout;
// Cached pipelines: Map<TextureFormat, Map<SPDScalarType, Map<Filter, Map<NumMips, Pipeline>>>>
std::unordered_map<wgpu::TextureFormat,
std::unordered_map<SPDScalarType,
std::unordered_map<SPDFilter,
std::unordered_map<uint32_t, SPDPipeline>>>> m_pipelines;
// Helper methods
SPDPipeline& GetOrCreatePipeline(wgpu::TextureFormat format, SPDFilter filter, uint32_t numMips, SPDScalarType scalarType);
SPDScalarType SanitizeScalarType(wgpu::TextureFormat format, bool halfPrecision);
std::string GetFilterCode(SPDFilter filter);
};
// Assuming SPD_FILTER_AVERAGE is a string constant
const std::string SPD_FILTER_AVERAGE = "value * 0.25"; // Original filter operation
// Generates the WGSL shader code dynamically.
std::string MakeShaderCode(
wgpu::TextureFormat outputFormat,
const std::string& filterOp = SPD_FILTER_AVERAGE,
uint32_t numMips = 0,
SPDScalarType scalarType = SPDScalarType::F32
);
} // namespace spd
@@ -0,0 +1,92 @@
/*
* 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 "WebGPUBufferBase.h"
#include "WebGPUConstants.h"
#include "DriverBase.h"
#include <backend/BufferDescriptor.h>
#include <utils/Panic.h>
#include <utils/debug.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
#include <cstring>
namespace filament::backend {
namespace {
[[nodiscard]] wgpu::Buffer createBuffer(wgpu::Device const& device, const wgpu::BufferUsage usage,
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 += (WEBGPU_BUFFER_SIZE_MODULUS - (size % WEBGPU_BUFFER_SIZE_MODULUS)) %
WEBGPU_BUFFER_SIZE_MODULUS;
wgpu::BufferDescriptor descriptor{
.label = label,
.usage = usage,
.size = size,
.mappedAtCreation = false };
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
FILAMENT_CHECK_POSTCONDITION(buffer) << "Failed to create buffer for " << label;
return buffer;
}
} // namespace
WebGPUBufferBase::WebGPUBufferBase(wgpu::Device const& device, const wgpu::BufferUsage usage,
const uint32_t size, char const* const label)
: mBuffer{ createBuffer(device, usage, size, label) } {}
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
const uint32_t byteOffset, wgpu::Queue const& queue) {
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
<< "updateGPUBuffer called with a null buffer";
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 % 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 % 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.
const size_t legalSize = bufferDescriptor.size - remainder;
queue.WriteBuffer(mBuffer, byteOffset, bufferDescriptor.buffer, legalSize);
if (remainder != 0) {
const uint8_t* remainderStart =
static_cast<const uint8_t*>(bufferDescriptor.buffer) + legalSize;
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, WEBGPU_BUFFER_SIZE_MODULUS - remainder);
queue.WriteBuffer(mBuffer, byteOffset + legalSize, &mRemainderChunk,
WEBGPU_BUFFER_SIZE_MODULUS);
}
}
}// 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_WEBGPUBUFFERBASE_H
#define TNT_FILAMENT_BACKEND_WEBGPUBUFFERBASE_H
#include "WebGPUConstants.h"
#include <webgpu/webgpu_cpp.h>
#include <array>
#include <cstdint>
namespace filament::backend {
class BufferDescriptor;
class WebGPUBufferBase /* intended to be extended */ {
public:
void updateGPUBuffer(BufferDescriptor const&, uint32_t byteOffset, wgpu::Queue const&);
[[nodiscard]] wgpu::Buffer const& getBuffer() const { return mBuffer; }
protected:
WebGPUBufferBase(wgpu::Device const&, wgpu::BufferUsage, uint32_t size, char const* label);
private:
const wgpu::Buffer mBuffer;
// 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
#endif // TNT_FILAMENT_BACKEND_WEBGPUBUFFERBASE_H
@@ -0,0 +1,49 @@
/*
* 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 "WebGPUBufferObject.h"
#include "WebGPUBufferBase.h"
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
namespace filament::backend {
namespace {
[[nodiscard]] constexpr wgpu::BufferUsage getBufferObjectUsage(
const BufferObjectBinding bindingType) noexcept {
switch (bindingType) {
case BufferObjectBinding::VERTEX: return wgpu::BufferUsage::Vertex;
case BufferObjectBinding::UNIFORM: return wgpu::BufferUsage::Uniform;
case BufferObjectBinding::SHADER_STORAGE: return wgpu::BufferUsage::Storage;
}
}
} // namespace
WebGPUBufferObject::WebGPUBufferObject(wgpu::Device const& device,
const BufferObjectBinding bindingType, const uint32_t byteCount)
: HwBufferObject{ byteCount },
WebGPUBufferBase{ device, wgpu::BufferUsage::CopyDst | getBufferObjectUsage(bindingType),
byteCount, "buffer_object" } {}
} // namespace filament::backend
@@ -0,0 +1,41 @@
/*
* 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_WEBGPUBUFFEROBJECT_H
#define TNT_FILAMENT_BACKEND_WEBGPUBUFFEROBJECT_H
#include "WebGPUBufferBase.h"
#include "DriverBase.h"
#include <cstdint>
namespace wgpu {
class Device;
}
namespace filament::backend {
enum class BufferObjectBinding : uint8_t;
class WebGPUBufferObject final : public HwBufferObject, public WebGPUBufferBase {
public:
WebGPUBufferObject(wgpu::Device const&, BufferObjectBinding, uint32_t byteCount);
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUBUFFEROBJECT_H
@@ -21,6 +21,8 @@
#include <cstdint>
constexpr size_t WEBGPU_BUFFER_SIZE_MODULUS = 4;
// FWGPU is short for Filament WebGPU
// turn on runtime validation, namely for debugging, that would normally not run (for release)
@@ -0,0 +1,106 @@
/*
* 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 "WebGPUDescriptorSet.h"
#include "WebGPUDescriptorSetLayout.h"
#include <backend/DriverEnums.h>
#include <utils/Panic.h>
#include <utils/debug.h>
#include <webgpu/webgpu_cpp.h>
#include <algorithm>
#include <cstdint>
#include <utility>
#include <vector>
namespace filament::backend {
namespace {
constexpr uint8_t INVALID_INDEX = MAX_DESCRIPTOR_COUNT + 1;
} // namespace
WebGPUDescriptorSet::WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout,
std::vector<WebGPUDescriptorSetLayout::BindGroupEntryInfo> const& bindGroupEntries)
: mLayout{ layout },
mEntriesWithDynamicOffsetsCount{ static_cast<size_t>(std::count_if(bindGroupEntries.begin(),
bindGroupEntries.end(), [](auto const& entry) { return entry.hasDynamicOffset; })) } {
mEntries.resize(bindGroupEntries.size());
for (size_t i = 0; i < bindGroupEntries.size(); ++i) {
mEntries[i].binding = bindGroupEntries[i].binding;
}
// Establish the size of entries based on the layout. This should be reliable and efficient.
assert_invariant(INVALID_INDEX > mEntryIndexByBinding.size());
for (size_t i = 0; i < mEntryIndexByBinding.size(); i++) {
mEntryIndexByBinding[i] = INVALID_INDEX;
}
for (size_t index = 0; index < mEntries.size(); index++) {
wgpu::BindGroupEntry const& entry = mEntries[index];
assert_invariant(entry.binding < mEntryIndexByBinding.size());
mEntryIndexByBinding[entry.binding] = static_cast<uint8_t>(index);
}
}
void WebGPUDescriptorSet::addEntry(const unsigned int index, wgpu::BindGroupEntry&& entry) {
if (mBindGroup) {
// We will keep getting hits from future updates, but shouldn't do anything
// Filament guarantees this won't change after things have locked.
return;
}
// TODO: Putting some level of trust that Filament is not going to reuse indexes or go past the
// layout index for efficiency. Add guards if wrong.
FILAMENT_CHECK_POSTCONDITION(index < mEntryIndexByBinding.size())
<< "impossible/invalid index for a descriptor/binding (our of range or >= "
"MAX_DESCRIPTOR_COUNT) "
<< index;
uint8_t entryIndex = mEntryIndexByBinding[index];
FILAMENT_CHECK_POSTCONDITION(entryIndex != INVALID_INDEX && entryIndex < mEntries.size())
<< "Invalid binding " << index;
entry.binding = index;
mEntries[entryIndex] = std::move(entry);
}
wgpu::BindGroup WebGPUDescriptorSet::lockAndReturn(wgpu::Device const& device) {
if (mBindGroup) {
return mBindGroup;
}
// TODO label? Should we just copy layout label?
const wgpu::BindGroupDescriptor descriptor{
.layout = mLayout,
.entryCount = mEntries.size(),
.entries = mEntries.data()
};
mBindGroup = device.CreateBindGroup(&descriptor);
FILAMENT_CHECK_POSTCONDITION(mBindGroup) << "Failed to create bind group?";
// once we have created the bind group itself we should no longer need any other state
mLayout = nullptr;
mEntries.clear();
mEntries.shrink_to_fit();
return mBindGroup;
}
WebGPUDescriptorSet::~WebGPUDescriptorSet() {
mBindGroup = nullptr;
mLayout = nullptr;
}
} // namespace filament::backend
@@ -0,0 +1,62 @@
/*
* 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_WEBGPUDESCRIPTORSET_H
#define TNT_FILAMENT_BACKEND_WEBGPUDESCRIPTORSET_H
#include "WebGPUDescriptorSetLayout.h"
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#include <webgpu/webgpu_cpp.h>
#include <array>
#include <cstdint>
#include <vector>
namespace filament::backend {
class WebGPUDescriptorSet final : public HwDescriptorSet {
public:
WebGPUDescriptorSet(wgpu::BindGroupLayout const&,
std::vector<WebGPUDescriptorSetLayout::BindGroupEntryInfo> const&);
~WebGPUDescriptorSet();
void addEntry(unsigned int index, wgpu::BindGroupEntry&& entry);
[[nodiscard]] wgpu::BindGroup lockAndReturn(wgpu::Device const&);
[[nodiscard]] bool getIsLocked() const { return mBindGroup != nullptr; }
[[nodiscard]] size_t getEntitiesWithDynamicOffsetsCount() const {
return mEntriesWithDynamicOffsetsCount;
}
// May be nullptr. Use lockAndReturn to create the bind group when appropriate
[[nodiscard]] wgpu::BindGroup const& getBindGroup() const { return mBindGroup; }
private:
wgpu::BindGroupLayout mLayout = nullptr;
std::array<uint8_t, MAX_DESCRIPTOR_COUNT> mEntryIndexByBinding{};
std::vector<wgpu::BindGroupEntry> mEntries;
const size_t mEntriesWithDynamicOffsetsCount;
wgpu::BindGroup mBindGroup = nullptr;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUDESCRIPTORSET_H
@@ -0,0 +1,186 @@
/*
* 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 "WebGPUDescriptorSetLayout.h"
#include <backend/DriverEnums.h>
#include <utils/BitmaskEnum.h>
#include <utils/CString.h>
#include <utils/Panic.h>
#include <utils/StaticString.h>
#include <utils/debug.h>
#include <webgpu/webgpu_cpp.h>
#include <algorithm>
#include <string>
#include <variant>
#include <vector>
namespace filament::backend {
namespace {
// Convert Filament Shader Stage Flags bitmask to webgpu equivalent
[[nodiscard]] wgpu::ShaderStage filamentStageToWGPUStage(const ShaderStageFlags fFlags) {
wgpu::ShaderStage retStages = wgpu::ShaderStage::None;
if (any(ShaderStageFlags::VERTEX & fFlags)) {
retStages |= wgpu::ShaderStage::Vertex;
}
if (any(ShaderStageFlags::FRAGMENT & fFlags)) {
retStages |= wgpu::ShaderStage::Fragment;
}
if (any(ShaderStageFlags::COMPUTE & fFlags)) {
retStages |= wgpu::ShaderStage::Compute;
}
return retStages;
}
} // namespace
WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout,
wgpu::Device const& device) {
assert_invariant(device);
std::string baseLabel;
if (std::holds_alternative<utils::StaticString>(layout.label)) {
const auto& temp = std::get_if<utils::StaticString>(&layout.label);
baseLabel = temp->c_str();
} else if (std::holds_alternative<utils::CString>(layout.label)) {
const auto& temp = std::get_if<utils::CString>(&layout.label);
baseLabel = temp->c_str();
}
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
// debugging. For now, hack an incrementing value.
static int layoutNum = 0;
const unsigned int samplerCount =
std::count_if(layout.bindings.begin(), layout.bindings.end(), [](auto& fEntry) {
return DescriptorSetLayoutBinding::isSampler(fEntry.type);
});
std::vector<wgpu::BindGroupLayoutEntry> wEntries;
wEntries.reserve(layout.bindings.size() + samplerCount);
mBindGroupEntries.reserve(wEntries.capacity());
for (auto fEntry: layout.bindings) {
auto& wEntry = wEntries.emplace_back();
auto& entryInfo = mBindGroupEntries.emplace_back();
wEntry.visibility = filamentStageToWGPUStage(fEntry.stageFlags);
wEntry.binding = fEntry.binding * 2;
entryInfo.binding = wEntry.binding;
switch (fEntry.type) {
case DescriptorType::SAMPLER_2D_FLOAT:
case DescriptorType::SAMPLER_2D_INT:
case DescriptorType::SAMPLER_2D_UINT:
case DescriptorType::SAMPLER_2D_DEPTH:
case DescriptorType::SAMPLER_2D_ARRAY_FLOAT:
case DescriptorType::SAMPLER_2D_ARRAY_INT:
case DescriptorType::SAMPLER_2D_ARRAY_UINT:
case DescriptorType::SAMPLER_2D_ARRAY_DEPTH:
case DescriptorType::SAMPLER_CUBE_FLOAT:
case DescriptorType::SAMPLER_CUBE_INT:
case DescriptorType::SAMPLER_CUBE_UINT:
case DescriptorType::SAMPLER_CUBE_DEPTH:
case DescriptorType::SAMPLER_CUBE_ARRAY_FLOAT:
case DescriptorType::SAMPLER_CUBE_ARRAY_INT:
case DescriptorType::SAMPLER_CUBE_ARRAY_UINT:
case DescriptorType::SAMPLER_CUBE_ARRAY_DEPTH:
case DescriptorType::SAMPLER_3D_FLOAT:
case DescriptorType::SAMPLER_3D_INT:
case DescriptorType::SAMPLER_3D_UINT:
case DescriptorType::SAMPLER_2D_MS_FLOAT:
case DescriptorType::SAMPLER_2D_MS_INT:
case DescriptorType::SAMPLER_2D_MS_UINT:
case DescriptorType::SAMPLER_2D_MS_ARRAY_FLOAT:
case DescriptorType::SAMPLER_2D_MS_ARRAY_INT:
case DescriptorType::SAMPLER_2D_MS_ARRAY_UINT: {
auto& samplerEntry = wEntries.emplace_back();
auto& samplerEntryInfo = mBindGroupEntries.emplace_back();
samplerEntry.binding = fEntry.binding * 2 + 1;
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)) {
samplerEntry.sampler.type = wgpu::SamplerBindingType::NonFiltering;
} else {
samplerEntry.sampler.type = wgpu::SamplerBindingType::Filtering;
}
break;
}
case DescriptorType::UNIFORM_BUFFER: {
wEntry.buffer.hasDynamicOffset =
any(fEntry.flags & DescriptorFlags::DYNAMIC_OFFSET);
entryInfo.hasDynamicOffset = wEntry.buffer.hasDynamicOffset;
wEntry.buffer.type = wgpu::BufferBindingType::Uniform;
// TODO: Ideally we fill minBindingSize
break;
}
case DescriptorType::INPUT_ATTACHMENT: {
PANIC_POSTCONDITION("Input Attachment is not supported");
break;
}
case DescriptorType::SHADER_STORAGE_BUFFER: {
PANIC_POSTCONDITION("Shader storage is not supported");
break;
}
case DescriptorType::SAMPLER_EXTERNAL: {
PANIC_POSTCONDITION("External Sampler is not supported");
break;
}
}
if (isDepthDescriptor(fEntry.type)) {
wEntry.texture.sampleType = wgpu::TextureSampleType::Depth;
} else if (isFloatDescriptor(fEntry.type)) {
// TODO: Set once we have the filtering values
wEntry.texture.sampleType = wgpu::TextureSampleType::Float;
} else if (isIntDescriptor(fEntry.type)) {
wEntry.texture.sampleType = wgpu::TextureSampleType::Sint;
} else if (isUnsignedIntDescriptor(fEntry.type)) {
wEntry.texture.sampleType = wgpu::TextureSampleType::Uint;
}
if (is3dTypeDescriptor(fEntry.type)) {
wEntry.texture.viewDimension = wgpu::TextureViewDimension::e3D;
} else if (is2dTypeDescriptor(fEntry.type)) {
wEntry.texture.viewDimension = wgpu::TextureViewDimension::e2D;
} else if (is2dArrayTypeDescriptor(fEntry.type)) {
wEntry.texture.viewDimension = wgpu::TextureViewDimension::e2DArray;
} else if (isCubeTypeDescriptor(fEntry.type)) {
wEntry.texture.viewDimension = wgpu::TextureViewDimension::Cube;
} else if (isCubeArrayTypeDescriptor(fEntry.type)) {
wEntry.texture.viewDimension = wgpu::TextureViewDimension::CubeArray;
}
// fEntry.count is unused currently
}
std::string label = "layout_" + baseLabel + std::to_string(++layoutNum) ;
const wgpu::BindGroupLayoutDescriptor layoutDescriptor{
.label{label.c_str()}, // Use .c_str() if label needs to be const char*
.entryCount = wEntries.size(),
.entries = wEntries.data()
};
mLayout = device.CreateBindGroupLayout(&layoutDescriptor);
FILAMENT_CHECK_POSTCONDITION(mLayout)
<< "Failed to create bind group layout with label " << label;
}
} // namespace filament::backend
@@ -0,0 +1,53 @@
/*
* 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_WEBGPUDESCRIPTORSETLAYOUT_H
#define TNT_FILAMENT_BACKEND_WEBGPUDESCRIPTORSETLAYOUT_H
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
#include <vector>
namespace filament::backend {
class WebGPUDescriptorSetLayout final : public HwDescriptorSetLayout {
public:
struct BindGroupEntryInfo final {
uint8_t binding = 0;
bool hasDynamicOffset = false;
};
WebGPUDescriptorSetLayout(DescriptorSetLayout const&, wgpu::Device const&);
~WebGPUDescriptorSetLayout() = default;
[[nodiscard]] wgpu::BindGroupLayout const& getLayout() const { return mLayout; }
[[nodiscard]] std::vector<BindGroupEntryInfo> const& getBindGroupEntries() const {
return mBindGroupEntries;
}
private:
std::vector<BindGroupEntryInfo> mBindGroupEntries;
wgpu::BindGroupLayout mLayout = nullptr;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUDESCRIPTORSETLAYOUT_H
File diff suppressed because it is too large Load Diff
+5 -4
View File
@@ -17,7 +17,7 @@
#ifndef TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
#define TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
#include "WebGPUHandles.h"
#include "WebGPURenderTarget.h"
#include "webgpu/WebGPUConstants.h"
#include <backend/platforms/WebGPUPlatform.h>
@@ -64,17 +64,18 @@ private:
WebGPUPlatform& mPlatform;
wgpu::Adapter mAdapter = nullptr;
wgpu::Device mDevice = nullptr;
uint32_t mMinUniformBufferOffsetAlignment;
wgpu::Limits mDeviceLimits = {};
wgpu::Queue mQueue = nullptr;
void* mNativeWindow = nullptr;
WebGPUSwapChain* mSwapChain = nullptr;
uint64_t mNextFakeHandle = 1;
wgpu::CommandEncoder mCommandEncoder = nullptr;
std::vector<Handle<HwTexture>> mMipQueue;
wgpu::TextureView mTextureView = nullptr;
wgpu::RenderPassEncoder mRenderPassEncoder = nullptr;
wgpu::CommandBuffer mCommandBuffer = nullptr;
WGPURenderTarget* mDefaultRenderTarget = nullptr;
WGPURenderTarget* mCurrentRenderTarget = nullptr;
WebGPURenderTarget* mDefaultRenderTarget = nullptr;
WebGPURenderTarget* mCurrentRenderTarget = nullptr;
tsl::robin_map<uint32_t, wgpu::RenderPipeline> mPipelineMap;
File diff suppressed because it is too large Load Diff
-272
View File
@@ -1,272 +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_WEBGPUHANDLES_H
#define TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <utils/FixedCapacityVector.h>
#include <webgpu/webgpu_cpp.h>
#include <array>
#include <cstdint>
#include <vector>
namespace filament::backend {
class WGPUProgram final : public HwProgram {
public:
WGPUProgram(wgpu::Device&, Program&);
wgpu::ShaderModule vertexShaderModule = nullptr;
wgpu::ShaderModule fragmentShaderModule = nullptr;
wgpu::ShaderModule computeShaderModule = nullptr;
std::vector<wgpu::ConstantEntry> constants;
};
// WGPUVertexBufferInfo maps Filament vertex attributes to WebGPU buffer binding model.
class WGPUVertexBufferInfo : public HwVertexBufferInfo {
public:
WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
AttributeArray const& attributes);
inline wgpu::VertexBufferLayout const* getVertexBufferLayouts() const {
return mVertexBufferLayouts.data();
}
inline uint32_t getVertexBufferLayoutCount() const {
return static_cast<uint32_t>(mVertexBufferLayouts.size());
}
inline wgpu::VertexAttribute const* getVertexAttributes(uint32_t i) const {
return mVertexAttributes[i].data();
}
inline uint32_t getVertexAttributeCount(uint32_t i) const {
return static_cast<uint32_t>(mVertexAttributes[i].size());
}
struct WebGPUSlotBindingInfo {
uint8_t sourceBuffer;
uint32_t slot;
uint64_t bufferOffset;
uint32_t stride;
};
inline const std::vector<WebGPUSlotBindingInfo>& getWebGPUSlotBindingInfos() const {
return mWebGPUSlotBindingInfos;
}
private:
// This stores the final wgpu::VertexBufferLayout objects, one per WebGPU slot.
std::vector<wgpu::VertexBufferLayout> mVertexBufferLayouts;
// This stores all wgpu::VertexAttribute structs, indexed by their original
// Filament attributeIndex.
std::vector<std::vector<wgpu::VertexAttribute>> mVertexAttributes;
// Stores information for the driver to perform setVertexBuffer calls
std::vector<WebGPUSlotBindingInfo> mWebGPUSlotBindingInfos;
};
struct WGPUVertexBuffer : public HwVertexBuffer {
WGPUVertexBuffer(wgpu::Device const &device, uint32_t vertexCount, uint32_t bufferCount,
Handle<HwVertexBufferInfo> vbih);
Handle<HwVertexBufferInfo> vbih;
utils::FixedCapacityVector<wgpu::Buffer> buffers;
};
class WGPUBufferBase {
public:
void createBuffer(wgpu::Device const& device, wgpu::BufferUsage usage, uint32_t size,
char const* label);
void updateGPUBuffer(BufferDescriptor& bufferDescriptor, uint32_t byteOffset,
wgpu::Queue queue);
const wgpu::Buffer& getBuffer() const { return buffer; }
protected:
wgpu::Buffer buffer;
private:
// 4 bytes to hold any extra chunk we need.
std::array<uint8_t,4> mRemainderChunk;
};
class WGPUIndexBuffer : public HwIndexBuffer, public WGPUBufferBase {
public:
WGPUIndexBuffer(wgpu::Device const &device, uint8_t elementSize,
uint32_t indexCount);
wgpu::IndexFormat indexFormat;
};
class WGPUBufferObject : public HwBufferObject, public WGPUBufferBase {
public:
WGPUBufferObject(wgpu::Device const &device, BufferObjectBinding bindingType, uint32_t byteCount);
};
class WebGPUDescriptorSetLayout final : public HwDescriptorSetLayout {
public:
struct BindGroupEntryInfo final {
uint8_t binding = 0;
bool hasDynamicOffset = false;
};
WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout, wgpu::Device const& device);
~WebGPUDescriptorSetLayout();
[[nodiscard]] const wgpu::BindGroupLayout& getLayout() const { return mLayout; }
[[nodiscard]] std::vector<BindGroupEntryInfo> const& getBindGroupEntries() const {
return mBindGroupEntries;
}
private:
// TODO: If this is useful elsewhere, remove it from this class
// Convert Filament Shader Stage Flags bitmask to webgpu equivalent
static wgpu::ShaderStage filamentStageToWGPUStage(ShaderStageFlags fFlags);
std::vector<BindGroupEntryInfo> mBindGroupEntries;
wgpu::BindGroupLayout mLayout;
};
class WebGPUDescriptorSet final : public HwDescriptorSet {
public:
WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout,
std::vector<WebGPUDescriptorSetLayout::BindGroupEntryInfo> const& bindGroupEntries);
~WebGPUDescriptorSet();
wgpu::BindGroup lockAndReturn(wgpu::Device const&);
void addEntry(unsigned int index, wgpu::BindGroupEntry&& entry);
[[nodiscard]] bool getIsLocked() const { return mBindGroup != nullptr; }
[[nodiscard]] size_t countEntitiesWithDynamicOffsets() const;
// May be nullptr. Use lockAndReturn to create the bind group when appropriate
[[nodiscard]] const wgpu::BindGroup& getBindGroup() const { return mBindGroup; }
private:
wgpu::BindGroupLayout mLayout = nullptr;
static constexpr uint8_t INVALID_INDEX = MAX_DESCRIPTOR_COUNT + 1;
std::array<uint8_t, MAX_DESCRIPTOR_COUNT> mEntryIndexByBinding{};
std::vector<wgpu::BindGroupEntry> mEntries;
const size_t mEntriesWithDynamicOffsetsCount;
wgpu::BindGroup mBindGroup = nullptr;
};
class WGPUTexture : public HwTexture {
public:
WGPUTexture(SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples,
uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage,
wgpu::Device const& device) noexcept;
WGPUTexture(WGPUTexture* src, uint8_t baseLevel, uint8_t levelCount) noexcept;
wgpu::TextureAspect getAspect() const { return mAspect; }
size_t getBlockWidth() const { return mBlockWidth; }
size_t getBlockHeight() const { return mBlockHeight; }
[[nodiscard]] const wgpu::Texture& getTexture() const { return mTexture; }
[[nodiscard]] const wgpu::TextureView& getTextureView() const { return mTexureView; }
[[nodiscard]] wgpu::TextureFormat getFormat() const { return mFormat; }
static wgpu::TextureFormat fToWGPUTextureFormat(
filament::backend::TextureFormat const& fFormat);
static wgpu::TextureAspect fToWGPUTextureViewAspect(
filament::backend::TextureUsage const& fUsage,
filament::backend::TextureFormat const& fFormat);
private:
wgpu::TextureView makeTextureView(const uint8_t& baseLevel, const uint8_t& levelCount,
SamplerType target);
// CreateTextureR has info for a texture and sampler. Texture Views are needed for binding,
// along with a sampler Current plan: Inherit the sampler and Texture to always exist (It is a
// ref counted pointer) when making views. View is optional
wgpu::Texture mTexture = nullptr;
wgpu::TextureUsage mUsage = wgpu::TextureUsage::None;
wgpu::TextureFormat mFormat = wgpu::TextureFormat::Undefined;
wgpu::TextureAspect mAspect = wgpu::TextureAspect::Undefined;
uint32_t mArrayLayerCount = 1;
wgpu::TextureView mTexureView = nullptr;
wgpu::TextureUsage fToWGPUTextureUsage(filament::backend::TextureUsage const& fUsage);
size_t mBlockWidth;
size_t mBlockHeight;
};
struct WGPURenderPrimitive : public HwRenderPrimitive {
WGPUVertexBuffer* vertexBuffer = nullptr;
WGPUIndexBuffer* indexBuffer = nullptr;
};
class WGPURenderTarget : public HwRenderTarget {
public:
using Attachment = TargetBufferInfo; // Using TargetBufferInfo directly for attachments
WGPURenderTarget(uint32_t width, uint32_t height, uint8_t samples,
const MRT& colorAttachments,
const Attachment& depthAttachment,
const Attachment& stencilAttachment);
// Default constructor for the default render target
WGPURenderTarget()
: HwRenderTarget(0, 0),
defaultRenderTarget(true),
samples(1) {}
// Updated signature: takes resolved views for custom RTs, and default views for default RT
void setUpRenderPassAttachments(
wgpu::RenderPassDescriptor& descriptor,
RenderPassParams const& params,
// For default render target:
wgpu::TextureView const& defaultColorTextureView,
wgpu::TextureView const& defaultDepthStencilTextureView,
// For custom render targets:
wgpu::TextureView const* customColorTextureViews, // Array of views
uint32_t customColorTextureViewCount,
wgpu::TextureView const& customDepthTextureView,
wgpu::TextureView const& customStencilTextureView,
wgpu::TextureFormat customDepthFormat,
wgpu::TextureFormat customStencilFormat);
bool isDefaultRenderTarget() const { return defaultRenderTarget; }
uint8_t getSamples() const { return samples; }
// Accessors for the driver to get stored attachment info
const MRT& getColorAttachmentInfos() const { return mColorAttachments; }
const Attachment& getDepthAttachmentInfo() const { return mDepthAttachment; }
const Attachment& getStencilAttachmentInfo() const { return mStencilAttachment; }
// Static helpers for load/store operations
static wgpu::LoadOp getLoadOperation(const RenderPassParams& params, TargetBufferFlags buffer);
static wgpu::StoreOp getStoreOperation(const RenderPassParams& params, TargetBufferFlags buffer);
private:
bool defaultRenderTarget = false;
uint8_t samples = 1;
MRT mColorAttachments{};
// TODO WebGPU only supports a DepthStencil attachment, should this be just mDepthStencilAttachment?
Attachment mDepthAttachment{};
Attachment mStencilAttachment{};
// Cached descriptors for the render pass
std::vector<wgpu::RenderPassColorAttachment> mColorAttachmentDescriptors;
wgpu::RenderPassDepthStencilAttachment mDepthStencilAttachmentDescriptor{};
bool mHasDepthStencilAttachment = false;
};
}// namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
@@ -0,0 +1,36 @@
/*
* 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 "WebGPUIndexBuffer.h"
#include "WebGPUBufferBase.h"
#include "DriverBase.h"
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
namespace filament::backend {
WebGPUIndexBuffer::WebGPUIndexBuffer(wgpu::Device const& device, const uint8_t elementSize,
const uint32_t indexCount)
: HwIndexBuffer{ elementSize, indexCount },
WebGPUBufferBase{ device, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Index,
elementSize * indexCount, "index_buffer" },
mIndexFormat{ elementSize == 2 ? wgpu::IndexFormat::Uint16 : wgpu::IndexFormat::Uint32 } {}
} // namespace filament::backend
@@ -0,0 +1,45 @@
/*
* 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_WEBGPUINDEXBUFFER_H
#define TNT_FILAMENT_BACKEND_WEBGPUINDEXBUFFER_H
#include "WebGPUBufferBase.h"
#include "DriverBase.h"
#include <cstdint>
namespace wgpu {
class Device;
enum class IndexFormat : uint32_t;
} // namespace wgpu
namespace filament::backend {
class WebGPUIndexBuffer final : public HwIndexBuffer, public WebGPUBufferBase {
public:
WebGPUIndexBuffer(wgpu::Device const&, uint8_t elementSize, uint32_t indexCount);
[[nodiscard]] wgpu::IndexFormat getIndexFormat() const { return mIndexFormat; }
private:
const wgpu::IndexFormat mIndexFormat;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUINDEXBUFFER_H
@@ -16,7 +16,9 @@
#include "WebGPUPipelineCreation.h"
#include "WebGPUHandles.h"
#include "WebGPUProgram.h"
#include "WebGPURenderTarget.h"
#include "WebGPUVertexBufferInfo.h"
#include <backend/DriverEnums.h>
#include <backend/TargetBufferInfo.h>
@@ -65,6 +67,17 @@ constexpr wgpu::CullMode toWebGPU(CullingMode cullMode) {
}
}
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:
@@ -152,45 +165,70 @@ constexpr wgpu::BlendFactor toWebGPU(BlendFunction blendFunction) {
}// namespace
wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device,
WGPUProgram const& program, WGPUVertexBufferInfo const& vertexBufferInfo,
WebGPUProgram const& program, WebGPUVertexBufferInfo const& vertexBufferInfo,
wgpu::PipelineLayout const& layout, RasterState const& rasterState,
StencilState const& stencilState, PolygonOffset const& polygonOffset, PrimitiveType primitiveType,
std::vector<wgpu::TextureFormat> const& colorFormats,
wgpu::TextureFormat depthFormat, uint8_t samplesCount) {
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);
const wgpu::DepthStencilState depthStencilState {
.format = depthFormat,
.depthWriteEnabled = rasterState.depthWrite,
.depthCompare = toWebGPU(rasterState.depthFunc),
.stencilFront = {
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),
},
.stencilBack = {
};
depthStencilState.stencilBack = {
.compare = toWebGPU(stencilState.back.stencilFunc),
.failOp = toWebGPU(stencilState.back.stencilOpStencilFail),
.depthFailOp = toWebGPU(stencilState.back.stencilOpDepthFail),
.passOp = toWebGPU(stencilState.back.stencilOpDepthStencilPass),
},
.stencilReadMask = 0,
.stencilWriteMask = stencilState.stencilWrite ? 0xFFFFFFFF : 0,
.depthBias = static_cast<int32_t>(polygonOffset.constant),
.depthBiasSlopeScale = polygonOffset.slope,
.depthBiasClamp = 0.0f
};
};
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,
.vertex = { .module = program.vertexShaderModule,
.entryPoint = "main",
.constantCount = program.constants.size(),
.constants = program.constants.data(),
// 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()
},
@@ -232,14 +270,17 @@ wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device,
if (program.fragmentShaderModule != nullptr) {
fragmentState.module = program.fragmentShaderModule;
fragmentState.entryPoint = "main";
fragmentState.constantCount = program.constants.size(),
fragmentState.constants = program.constants.data(),
// 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);
// A fragment shader implies at least one color target if it outputs color.
// 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.
assert_invariant(fragmentState.targetCount > 0);
// 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];
@@ -34,14 +34,14 @@ enum class PrimitiveType : uint8_t;
struct RasterState;
struct StencilState;
class WGPUVertexBufferInfo;
class WGPUProgram;
class WebGPUVertexBufferInfo;
class WebGPUProgram;
[[nodiscard]] wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const&,
WGPUProgram const&, WGPUVertexBufferInfo const&, wgpu::PipelineLayout const&,
WebGPUProgram const&, WebGPUVertexBufferInfo const&, wgpu::PipelineLayout const&,
RasterState const&, StencilState const&, PolygonOffset const&, PrimitiveType primitiveType,
std::vector<wgpu::TextureFormat> const& colorFormats,
wgpu::TextureFormat depthFormat, uint8_t samplesCount);
std::vector<wgpu::TextureFormat> const& colorFormats, wgpu::TextureFormat depthFormat,
uint8_t samplesCount);
}// namespace filament::backend
@@ -14,7 +14,7 @@
* limitations under the License.
*/
#include "WebGPUHandles.h"
#include "WebGPUProgram.h"
#include "WebGPUConstants.h"
#include "WebGPUStrings.h"
@@ -23,14 +23,17 @@
#include <backend/DriverEnums.h>
#include <backend/Program.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Panic.h>
#include <utils/debug.h>
#include <utils/ostream.h>
#include <webgpu/webgpu_cpp.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
@@ -40,22 +43,34 @@ namespace filament::backend {
namespace {
/**
* Given the source code of a [WGSL] WebGPU shader in text (string view to it) and the constants
* to be overwritten now (at runtime), returns updated [WGSL] shader code text, where the constants
* have been replaced.
* @param shaderLabel Something to call this shader for troubleshooting, error messaging, etc.
* @param shaderSource The original WGSL WebGPU shader code as text to be processed. This is a
* view to that text and this function does not change it; it is immutable.
* Instead, the processed version of this source is returned by the function.
* @param specConstants The constants to replace in the shader code, indexed by constant id.
* @return Processed version of the WGSL WebGPU shader code provided, where the constants have
* been replaced with the values provided by the specConstants parameter.
*/
[[nodiscard]] std::string replaceSpecConstants(std::string_view shaderLabel,
std::string_view shaderSource,
std::unordered_map<uint32_t, std::variant<int32_t, float, bool>> const&
specConstants) {
// this function is not expected to be called at all if no spec constants are to be replaced
assert_invariant(!specConstants.empty());
constexpr std::string_view specConstantPrefix = "FILAMENT_SPEC_CONST_";
constexpr size_t specConstantPrefixSize = specConstantPrefix.size();
static constexpr std::string_view specConstantPrefix = "FILAMENT_SPEC_CONST_";
static constexpr size_t specConstantPrefixSize = specConstantPrefix.size();
const char* const sourceData = shaderSource.data();
std::stringstream processedShaderSourceStr{};
std::stringstream processedShaderSource{};
size_t pos = 0;
while (pos < shaderSource.size()) {
const size_t posOfNextSpecConstant = shaderSource.find(specConstantPrefix, pos);
if (posOfNextSpecConstant == std::string::npos) {
// no more spec constants, so just stream the rest of the source code string
processedShaderSourceStr
<< std::string_view(sourceData + pos, shaderSource.size() - pos);
processedShaderSource << std::string_view(sourceData + pos, shaderSource.size() - pos);
break;
}
const size_t posOfId = posOfNextSpecConstant + specConstantPrefixSize;
@@ -69,20 +84,21 @@ namespace {
FILAMENT_CHECK_POSTCONDITION(posEndOfStatement != std::string::npos)
<< "malformed " << shaderLabel << ". Found spec constant assignment with id "
<< idStr << " without a terminating ';' character?";
// this is a view into part of the statement, from after the id to the ';'
const std::string_view statementSegment =
std::string_view(sourceData + posAfterId, posEndOfStatement - posAfterId);
size_t posOfEqual = statementSegment.find('=');
if (posOfEqual == std::string::npos) {
// not an assignment statement, so stream to the end of the statement and continue...
processedShaderSourceStr
<< std::string_view(sourceData + pos, posEndOfStatement + 1 - pos);
processedShaderSource << std::string_view(sourceData + pos,
posEndOfStatement + 1 - pos);
pos = posEndOfStatement + 1;
continue;
}
posOfEqual += posAfterId; // position in original source, not just the segment
int id = 0;
posOfEqual += posAfterId; // position in original source overall, not just the segment
int constantId = 0;
try {
id = std::stoi(idStr.data());
constantId = std::stoi(idStr.data());
} catch (const std::invalid_argument& e) {
PANIC_POSTCONDITION("Invalid spec constant id '%s' in %s (not a valid integer?): %s",
idStr.data(), shaderLabel.data(), e.what());
@@ -91,35 +107,53 @@ namespace {
"Invalid spec constant id '%s' in %s (not an integer? out of range?): %s",
idStr.data(), shaderLabel.data(), e.what());
}
const auto newValueItr = specConstants.find(static_cast<uint32_t>(id));
const auto newValueItr = specConstants.find(static_cast<uint32_t>(constantId));
if (newValueItr == specConstants.end()) {
// not going to override the constant (stream to the end of the statement)...
processedShaderSourceStr
<< std::string_view(sourceData + pos, posEndOfStatement + 1 - pos);
// not going to override the constant,
// as the specConstants parameter doesn't specify it. So, we will keep the default
// already in the source text
// (stream to the end of the statement)...
processedShaderSource << std::string_view(sourceData + pos,
posEndOfStatement + 1 - pos);
pos = posEndOfStatement + 1;
continue;
}
// need to override the constant...
const std::variant<int32_t, float, bool> newValue = newValueItr->second;
// stream up to the equal sign...
processedShaderSourceStr << std::string_view(sourceData + pos, posOfEqual + 1 - pos);
processedShaderSource << std::string_view(sourceData + pos, posOfEqual + 1 - pos);
// stream the new value...
if (auto* v = std::get_if<int32_t>(&newValue)) {
processedShaderSourceStr << " " << *v << "i";
processedShaderSource << " " << *v << "i";
} else if (auto* f = std::get_if<float>(&newValue)) {
processedShaderSourceStr << " " << *f << "f";
processedShaderSource << " " << *f << "f";
} else if (auto* b = std::get_if<bool>(&newValue)) {
processedShaderSourceStr << " " << ((*b) ? "true" : "false");
processedShaderSource << " " << ((*b) ? "true" : "false");
}
processedShaderSourceStr << ";";
// and skip to after the end of the statement in the original source...
// end the statement...
processedShaderSource << ";";
// and skip to after the end of the statement in the original source and continue...
pos = posEndOfStatement + 1;
}
return processedShaderSourceStr.str();
return processedShaderSource.str();
}
[[nodiscard]] wgpu::ShaderModule createShaderModule(wgpu::Device& device, Program& program,
ShaderStage stage,
/**
* Creates a WebGPU shader module for a given "program" "stage", accounting for override constants.
* Effectively, this function is responsible for preprocessing the shader source and compiling it.
* @param device The WebGPU device, which is the WebGPU API entry point for creating/registering
* a shader module
* @param program The "program" to compile/create the shader, which includes the shader source
* @param stage The stage (e.g. vertex, fragment, etc.) to create the shader module
* @param specConstants Override constants to apply when creating/compiling the shader module.
* The expectation is that this is consistent with the program's spec constants, just in a map
* format for quick access
* @return the proper WebGPU shader module compiled/created from the input parameters. This might
* wrap a null handle if the shader is not present (if the shader source is empty), such as
* a missing fragment or compute shader.
*/
[[nodiscard]] wgpu::ShaderModule createShaderModule(wgpu::Device const& device,
Program const& program, const ShaderStage stage,
std::unordered_map<uint32_t, std::variant<int32_t, float, bool>> const& specConstants) {
const char* const programName = program.getName().c_str_safe();
std::array<utils::FixedCapacityVector<uint8_t>, Program::SHADER_TYPE_COUNT> const&
@@ -127,11 +161,11 @@ namespace {
utils::FixedCapacityVector<uint8_t> const& sourceBytes =
shaderSource[static_cast<size_t>(stage)];
if (sourceBytes.empty()) {
return nullptr;// nothing to compile, the shader was not provided
return nullptr;// nothing to compile/create, the shader was not provided
}
std::stringstream labelStream;
labelStream << programName << " " << filamentShaderStageToString(stage) << " shader";
auto label = labelStream.str();
const auto label = labelStream.str();
const std::string processedShaderSource =
specConstants.empty()
? reinterpret_cast<const char*>(sourceBytes.data())
@@ -139,16 +173,15 @@ namespace {
specConstants);
wgpu::ShaderModuleWGSLDescriptor wgslDescriptor{};
wgslDescriptor.code = wgpu::StringView(processedShaderSource);
wgpu::ShaderModuleDescriptor descriptor{
const wgpu::ShaderModuleDescriptor descriptor{
.nextInChain = &wgslDescriptor,
.label = label.data()
};
wgpu::ShaderModule module = device.CreateShaderModule(&descriptor);
FILAMENT_CHECK_POSTCONDITION(module != nullptr) << "Failed to create " << descriptor.label;
wgpu::Instance instance = device.GetAdapter().GetInstance();
instance.WaitAny(
module.GetCompilationInfo(wgpu::CallbackMode::WaitAnyOnly,
const wgpu::ShaderModule shaderModule = device.CreateShaderModule(&descriptor);
const wgpu::Instance instance = device.GetAdapter().GetInstance();
// synchronously creates the shader module...
const wgpu::WaitStatus waitResult = instance.WaitAny(
shaderModule.GetCompilationInfo(wgpu::CallbackMode::WaitAnyOnly,
[&descriptor](auto const& status,
wgpu::CompilationInfo const* info) {
switch (status) {
@@ -203,23 +236,42 @@ namespace {
#endif
}),
SHADER_COMPILATION_TIMEOUT_NANOSECONDS);
return module;
switch (waitResult) {
case wgpu::WaitStatus::Success:
break;
case wgpu::WaitStatus::Error:
PANIC_POSTCONDITION("Error creating/compiling shader %s (detected after wait).",
descriptor.label.data);
break;
case wgpu::WaitStatus::TimedOut:
PANIC_POSTCONDITION("Timed out creating/compiling shader %s", descriptor.label.data);
break;
}
FILAMENT_CHECK_POSTCONDITION(shaderModule) << "Failed to create " << descriptor.label;
return shaderModule;
}
/**
* Convenience function to convert the array structure of constants to a map indexed by constant
* id.
* @param specConstants Original spec constant structure (immutable)
* @param outConstantById Output map of spec constants indexed by constant id
*/
void toMap(utils::FixedCapacityVector<Program::SpecializationConstant> const& specConstants,
std::unordered_map<uint32_t, std::variant<int32_t, float, bool>>& constantById) {
constantById.reserve(specConstants.size());
std::unordered_map<uint32_t, std::variant<int32_t, float, bool>>& outConstantById) {
outConstantById.reserve(specConstants.size());
for (auto const& specConstant: specConstants) {
constantById.emplace(specConstant.id, specConstant.value);
outConstantById.emplace(specConstant.id, specConstant.value);
}
}
}// namespace
WGPUProgram::WGPUProgram(wgpu::Device& device, Program& program)
: HwProgram(program.getName()) {
WebGPUProgram::WebGPUProgram(wgpu::Device const& device, Program const& program)
: HwProgram{ program.getName() } {
std::unordered_map<uint32_t, std::variant<int32_t, float, bool>> specConstants;
toMap(program.getSpecializationConstants(), specConstants);
// TODO consider creating/compiling these shaders in parallel
vertexShaderModule = createShaderModule(device, program, ShaderStage::VERTEX, specConstants);
fragmentShaderModule =
createShaderModule(device, program, ShaderStage::FRAGMENT, specConstants);
@@ -0,0 +1,39 @@
/*
* 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_WEBGPUPROGRAM_H
#define TNT_FILAMENT_BACKEND_WEBGPUPROGRAM_H
#include "DriverBase.h"
#include <webgpu/webgpu_cpp.h>
namespace filament::backend {
class Program;
class WebGPUProgram final : public HwProgram {
public:
WebGPUProgram(wgpu::Device const&, Program const&);
wgpu::ShaderModule vertexShaderModule = nullptr;
wgpu::ShaderModule fragmentShaderModule = nullptr;
wgpu::ShaderModule computeShaderModule = nullptr;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUPROGRAM_H
@@ -0,0 +1,34 @@
/*
* 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_WEBGPURENDERPRIMITIVE_H
#define TNT_FILAMENT_BACKEND_WEBGPURENDERPRIMITIVE_H
#include "DriverBase.h"
namespace filament::backend {
class WebGPUVertexBuffer;
class WebGPUIndexBuffer;
struct WebGPURenderPrimitive : public HwRenderPrimitive {
WebGPUVertexBuffer* vertexBuffer = nullptr;
WebGPUIndexBuffer* indexBuffer = nullptr;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPURENDERPRIMITIVE_H
@@ -0,0 +1,190 @@
/*
* 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 "WebGPURenderTarget.h"
#include <backend/DriverEnums.h>
#include <backend/TargetBufferInfo.h>
#include <private/backend/BackendUtils.h>
#include <utils/BitmaskEnum.h>
#include <utils/Panic.h>
#include <utils/debug.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
namespace filament::backend {
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)
: HwRenderTarget{ width, height },
mDefaultRenderTarget{ false },
mSamples{ samples },
mLayerCount{ layerCount },
mColorAttachments{ colorAttachmentsMRT },
mDepthAttachment{ depthAttachmentInfo },
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 },
mSamples{ 1 },
mLayerCount{ 1 } {}
wgpu::LoadOp WebGPURenderTarget::getLoadOperation(RenderPassParams const& params,
const TargetBufferFlags bufferToOperateOn) {
if (any(params.flags.clear & bufferToOperateOn)) {
return wgpu::LoadOp::Clear;
}
if (any(params.flags.discardStart & bufferToOperateOn)) {
return wgpu::LoadOp::Clear; // Or wgpu::LoadOp::Undefined if clear is not desired on discard
}
return wgpu::LoadOp::Load;
}
wgpu::StoreOp WebGPURenderTarget::getStoreOperation(RenderPassParams const& params,
const TargetBufferFlags bufferToOperateOn) {
if (any(params.flags.discardEnd & bufferToOperateOn)) {
return wgpu::StoreOp::Discard;
}
return wgpu::StoreOp::Store;
}
void WebGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor& outDescriptor,
RenderPassParams const& params, wgpu::TextureView const& defaultColorTextureView,
wgpu::TextureView const& defaultDepthStencilTextureView,
wgpu::TextureView const* customColorTextureViews, uint32_t customColorTextureViewCount,
wgpu::TextureView const& customDepthTextureView,
wgpu::TextureView const& customStencilTextureView,
const wgpu::TextureFormat customDepthFormat,
const wgpu::TextureFormat customStencilFormat) {
mColorAttachmentDescriptors.clear();
mHasDepthStencilAttachment = false;
if (mDefaultRenderTarget) {
assert_invariant(defaultColorTextureView);
mColorAttachmentDescriptors.push_back({ .view = defaultColorTextureView,
.resolveTarget = nullptr,
.loadOp = WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::COLOR0),
.storeOp = WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::COLOR0),
.clearValue = { params.clearColor.r, params.clearColor.g, params.clearColor.b,
params.clearColor.a } });
if (defaultDepthStencilTextureView) {
mDepthStencilAttachmentDescriptor = {
.view = defaultDepthStencilTextureView,
.depthLoadOp = WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::DEPTH),
.depthStoreOp =
WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::DEPTH),
.depthClearValue = static_cast<float>(params.clearDepth),
.depthReadOnly =
(params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0,
.stencilLoadOp =
WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::STENCIL),
.stencilStoreOp =
WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::STENCIL),
.stencilClearValue = params.clearStencil,
.stencilReadOnly =
(params.readOnlyDepthStencil & RenderPassParams::READONLY_STENCIL) > 0,
};
mHasDepthStencilAttachment = true;
}
} else {// Custom Render Target
for (uint32_t i = 0; i < customColorTextureViewCount; ++i) {
if (customColorTextureViews[i]) {
mColorAttachmentDescriptors.push_back({ .view = customColorTextureViews[i],
// .resolveTarget = nullptr; // TODO: MSAA resolve for custom RT
.loadOp = WebGPURenderTarget::getLoadOperation(params, getTargetBufferFlagsAt(i)),
.storeOp =
WebGPURenderTarget::getStoreOperation(params, getTargetBufferFlagsAt(i)),
.clearValue = { .r = params.clearColor.r,
.g = params.clearColor.g,
.b = params.clearColor.b,
.a = params.clearColor.a } });
}
}
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) {
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 {
mDepthStencilAttachmentDescriptor.depthLoadOp = wgpu::LoadOp::Undefined;
mDepthStencilAttachmentDescriptor.depthStoreOp = wgpu::StoreOp::Undefined;
mDepthStencilAttachmentDescriptor.depthReadOnly = true;
}
if (hasStencil) {
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 {
mDepthStencilAttachmentDescriptor.stencilLoadOp = wgpu::LoadOp::Undefined;
mDepthStencilAttachmentDescriptor.stencilStoreOp = wgpu::StoreOp::Undefined;
mDepthStencilAttachmentDescriptor.stencilReadOnly = true;
}
mHasDepthStencilAttachment = true;
}
}
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
@@ -0,0 +1,91 @@
/*
* 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_WEBGPUHANDLES_H
#define TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#include <backend/TargetBufferInfo.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
#include <vector>
namespace filament::backend {
class WebGPURenderTarget : public HwRenderTarget {
public:
using Attachment = TargetBufferInfo; // Using TargetBufferInfo directly for attachments
WebGPURenderTarget(uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount,
MRT const& colorAttachments, Attachment const& depthAttachment,
Attachment const& stencilAttachment);
// Default constructor for the default render target
WebGPURenderTarget();
// Updated signature: takes resolved views for custom RTs, and default views for default RT
void setUpRenderPassAttachments(
wgpu::RenderPassDescriptor& outDescriptor,
RenderPassParams const& params,
// For default render target:
wgpu::TextureView const& defaultColorTextureView,
wgpu::TextureView const& defaultDepthStencilTextureView,
// For custom render targets:
wgpu::TextureView const* customColorTextureViews, // Array of views
uint32_t customColorTextureViewCount,
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 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);
private:
bool mDefaultRenderTarget = false;
uint8_t mSamples = 1;
uint8_t mLayerCount = 1;
MRT mColorAttachments{};
// TODO WebGPU only supports a DepthStencil attachment, should this be just
// mDepthStencilAttachment?
Attachment mDepthAttachment{};
Attachment mStencilAttachment{};
// Cached descriptors for the render pass
std::vector<wgpu::RenderPassColorAttachment> mColorAttachmentDescriptors;
wgpu::RenderPassDepthStencilAttachment mDepthStencilAttachmentDescriptor{};
bool mHasDepthStencilAttachment = false;
};
}// namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
@@ -0,0 +1,568 @@
/*
* 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 "WebGPUTexture.h"
#include "WebGPUConstants.h"
#include "DriverBase.h"
#include "private/backend/BackendUtils.h"
#include <backend/DriverEnums.h>
#include <utils/BitmaskEnum.h>
#include <utils/Panic.h>
#include <utils/debug.h>
#include <utils/ostream.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
namespace filament::backend {
namespace {
[[nodiscard]] bool IsFormatStorageCompatible(wgpu::TextureFormat format) {
switch (format) {
// List of formats that support storage binding
case wgpu::TextureFormat::R32Float:
case wgpu::TextureFormat::R32Sint:
case wgpu::TextureFormat::R32Uint:
case wgpu::TextureFormat::RG32Float:
case wgpu::TextureFormat::RG32Sint:
case wgpu::TextureFormat::RG32Uint:
case wgpu::TextureFormat::RGBA16Float:
case wgpu::TextureFormat::RGBA16Sint:
case wgpu::TextureFormat::RGBA16Uint:
case wgpu::TextureFormat::RGBA32Float:
case wgpu::TextureFormat::RGBA32Sint:
case wgpu::TextureFormat::RGBA32Uint:
case wgpu::TextureFormat::RGBA8Unorm:
case wgpu::TextureFormat::RGBA8Snorm:
case wgpu::TextureFormat::RGBA8Uint:
case wgpu::TextureFormat::RGBA8Sint:
return true;
default:
// All other formats, including packed floats (RG11B10Ufloat),
// depth/stencil, and sRGB formats do not support storage.
return false;
}
}
wgpu::TextureFormat GetStorageCompatibleFormat(wgpu::TextureFormat srgbFormat) {
switch (srgbFormat) {
case wgpu::TextureFormat::RGBA8UnormSrgb:
return wgpu::TextureFormat::RGBA8Unorm;
case wgpu::TextureFormat::BGRA8UnormSrgb:
return wgpu::TextureFormat::BGRA8Unorm;
case wgpu::TextureFormat::BC1RGBAUnormSrgb:
return wgpu::TextureFormat::BC1RGBAUnorm;
case wgpu::TextureFormat::BC2RGBAUnormSrgb:
return wgpu::TextureFormat::BC2RGBAUnorm;
case wgpu::TextureFormat::BC3RGBAUnormSrgb:
return wgpu::TextureFormat::BC3RGBAUnorm;
case wgpu::TextureFormat::BC7RGBAUnormSrgb:
return wgpu::TextureFormat::BC7RGBAUnorm;
case wgpu::TextureFormat::ETC2RGB8UnormSrgb:
return wgpu::TextureFormat::ETC2RGB8Unorm;
case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb:
return wgpu::TextureFormat::ETC2RGB8A1Unorm;
case wgpu::TextureFormat::ETC2RGBA8UnormSrgb:
return wgpu::TextureFormat::ETC2RGBA8Unorm;
case wgpu::TextureFormat::ASTC4x4UnormSrgb:
return wgpu::TextureFormat::ASTC4x4Unorm;
case wgpu::TextureFormat::ASTC5x4UnormSrgb:
return wgpu::TextureFormat::ASTC5x4Unorm;
case wgpu::TextureFormat::ASTC5x5UnormSrgb:
return wgpu::TextureFormat::ASTC5x5Unorm;
case wgpu::TextureFormat::ASTC6x5UnormSrgb:
return wgpu::TextureFormat::ASTC6x5Unorm;
case wgpu::TextureFormat::ASTC6x6UnormSrgb:
return wgpu::TextureFormat::ASTC6x6Unorm;
case wgpu::TextureFormat::ASTC8x5UnormSrgb:
return wgpu::TextureFormat::ASTC8x5Unorm;
case wgpu::TextureFormat::ASTC8x6UnormSrgb:
return wgpu::TextureFormat::ASTC8x6Unorm;
case wgpu::TextureFormat::ASTC8x8UnormSrgb:
return wgpu::TextureFormat::ASTC8x8Unorm;
case wgpu::TextureFormat::ASTC10x5UnormSrgb:
return wgpu::TextureFormat::ASTC10x5Unorm;
case wgpu::TextureFormat::ASTC10x6UnormSrgb:
return wgpu::TextureFormat::ASTC10x6Unorm;
case wgpu::TextureFormat::ASTC10x8UnormSrgb:
return wgpu::TextureFormat::ASTC10x8Unorm;
case wgpu::TextureFormat::ASTC10x10UnormSrgb:
return wgpu::TextureFormat::ASTC10x10Unorm;
case wgpu::TextureFormat::ASTC12x10UnormSrgb:
return wgpu::TextureFormat::ASTC12x10Unorm;
case wgpu::TextureFormat::ASTC12x12UnormSrgb:
return wgpu::TextureFormat::ASTC12x12Unorm;
default:
return srgbFormat; // If not an sRGB format, return the same input
}
}
[[nodiscard]] constexpr wgpu::StringView getUserTextureLabel(const SamplerType target) {
// TODO will be helpful to get more useful info than this
switch (target) {
case SamplerType::SAMPLER_2D: return "a_2D_user_texture";
case SamplerType::SAMPLER_2D_ARRAY: return "a_2D_array_user_texture";
case SamplerType::SAMPLER_CUBEMAP: return "a_cube_map_user_texture";
case SamplerType::SAMPLER_EXTERNAL: return "an_external_user_texture";
case SamplerType::SAMPLER_3D: return "a_3D_user_texture";
case SamplerType::SAMPLER_CUBEMAP_ARRAY: return "a_cube_map_array_user_texture";
}
}
[[nodiscard]] constexpr wgpu::StringView getUserTextureViewLabel(const SamplerType target) {
// TODO will be helpful to get more useful info than this
switch (target) {
case SamplerType::SAMPLER_2D: return "a_2D_user_texture_view";
case SamplerType::SAMPLER_2D_ARRAY: return "a_2D_array_user_texture_view";
case SamplerType::SAMPLER_CUBEMAP: return "a_cube_map_user_texture_view";
case SamplerType::SAMPLER_EXTERNAL: return "an_external_user_texture_view";
case SamplerType::SAMPLER_3D: return "a_3D_user_texture_view";
case SamplerType::SAMPLER_CUBEMAP_ARRAY: return "a_cube_map_array_user_texture_view";
}
}
[[nodiscard]] wgpu::TextureUsage fToWGPUTextureUsage(TextureUsage const& fUsage,
const uint8_t samples) {
wgpu::TextureUsage retUsage = wgpu::TextureUsage::None;
// Basing this mapping off of VulkanTexture.cpp's getUsage func and suggestions from Gemini
// TODO Validate assumptions, revisit if issues.
if (any(TextureUsage::BLIT_SRC & fUsage)) {
retUsage |= wgpu::TextureUsage::CopySrc;
}
if (any((TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE) & fUsage)) {
retUsage |= wgpu::TextureUsage::CopyDst;
}
if (any(TextureUsage::SAMPLEABLE & fUsage)) {
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.
if (any((TextureUsage::COLOR_ATTACHMENT | TextureUsage::STENCIL_ATTACHMENT |
TextureUsage::DEPTH_ATTACHMENT) &
fUsage)) {
retUsage |= wgpu::TextureUsage::RenderAttachment;
}
// This is from Vulkan logic- if there are any issues try disabling this first, allows perf
// benefit though
const bool useTransientAttachment =
// Usage consists of attachment flags only.
none(fUsage & ~TextureUsage::ALL_ATTACHMENTS) &&
// Usage contains at least one attachment flag.
any(fUsage & TextureUsage::ALL_ATTACHMENTS) &&
// Depth resolve cannot use transient attachment because it uses a custom shader.
// TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this
// restriction.
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
// and above to be able to support stencil resolve (along with depth).
!(any(fUsage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1);
if (useTransientAttachment) {
retUsage |= wgpu::TextureUsage::TransientAttachment;
}
// NOTE: Unused wgpu flags:
// StorageBinding
// StorageAttachment
// NOTE: Unused Filament flags:
// SUBPASS_INPUT VK goes to input attachment which we don't support right now
// PROTECTED
return retUsage;
}
[[nodiscard]] wgpu::TextureAspect fToWGPUTextureViewAspect(TextureUsage const& fUsage,
TextureFormat const& fFormat) {
const bool isDepth = any(fUsage & TextureUsage::DEPTH_ATTACHMENT);
const bool isStencil = any(fUsage & TextureUsage::STENCIL_ATTACHMENT);
const bool isColor = any(fUsage & TextureUsage::COLOR_ATTACHMENT);
const bool isSample = any(fUsage & TextureUsage::SAMPLEABLE);
if (isDepth && !isColor && !isStencil) {
return wgpu::TextureAspect::DepthOnly;
}
if (isStencil && !isColor && !isDepth) {
return wgpu::TextureAspect::StencilOnly;
}
if (fFormat == filament::backend::TextureFormat::DEPTH32F ||
fFormat == filament::backend::TextureFormat::DEPTH24 ||
fFormat == filament::backend::TextureFormat::DEPTH16) {
return wgpu::TextureAspect::DepthOnly;
}
if (fFormat == filament::backend::TextureFormat::STENCIL8) {
return wgpu::TextureAspect::StencilOnly;
}
if (fFormat == filament::backend::TextureFormat::DEPTH24_STENCIL8 ||
fFormat == filament::backend::TextureFormat::DEPTH32F_STENCIL8) {
if (isSample) {
return wgpu::TextureAspect::DepthOnly;
}
}
return wgpu::TextureAspect::All;
}
[[nodiscard]] constexpr wgpu::TextureViewDimension toWebGPUTextureViewDimension(
const SamplerType samplerType) {
switch (samplerType) {
case SamplerType::SAMPLER_2D: return wgpu::TextureViewDimension::e2D;
case SamplerType::SAMPLER_2D_ARRAY: return wgpu::TextureViewDimension::e2DArray;
case SamplerType::SAMPLER_CUBEMAP: return wgpu::TextureViewDimension::Cube;
case SamplerType::SAMPLER_EXTERNAL: return wgpu::TextureViewDimension::e2D;
case SamplerType::SAMPLER_3D: return wgpu::TextureViewDimension::e3D;
case SamplerType::SAMPLER_CUBEMAP_ARRAY: return wgpu::TextureViewDimension::CubeArray;
}
}
[[nodiscard]] constexpr wgpu::TextureDimension toWebGPUTextureDimension(
const SamplerType samplerType) {
switch (samplerType) {
case SamplerType::SAMPLER_2D: return wgpu::TextureDimension::e2D;
case SamplerType::SAMPLER_2D_ARRAY: return wgpu::TextureDimension::e2D;
case SamplerType::SAMPLER_CUBEMAP: return wgpu::TextureDimension::e2D;
case SamplerType::SAMPLER_EXTERNAL: return wgpu::TextureDimension::e2D;
case SamplerType::SAMPLER_3D: return wgpu::TextureDimension::e3D;
case SamplerType::SAMPLER_CUBEMAP_ARRAY: return wgpu::TextureDimension::e2D;
}
}
[[nodiscard]] constexpr wgpu::Extent3D toTextureSize(const SamplerType samplerType,
const uint32_t width, const uint32_t height, const uint32_t depth) {
switch (samplerType) {
case SamplerType::SAMPLER_2D:
case SamplerType::SAMPLER_2D_ARRAY:
case SamplerType::SAMPLER_EXTERNAL:
case SamplerType::SAMPLER_3D:
return { .width = width, .height = height, .depthOrArrayLayers = depth };
case SamplerType::SAMPLER_CUBEMAP:
return { .width = width, .height = height, .depthOrArrayLayers = 6 };
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
return { .width = width, .height = height, .depthOrArrayLayers = depth * 6 };
}
}
[[nodiscard]] constexpr uint32_t toArrayLayerCount(const SamplerType samplerType,
const uint32_t depthOrArrayLayers) {
switch (samplerType) {
case SamplerType::SAMPLER_2D:
case SamplerType::SAMPLER_EXTERNAL:
case SamplerType::SAMPLER_3D:
return 1;
case SamplerType::SAMPLER_2D_ARRAY:
case SamplerType::SAMPLER_CUBEMAP:
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
return depthOrArrayLayers;
}
}
} // namespace
WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels,
const TextureFormat format, const uint8_t samples, const uint32_t width,
const uint32_t height, const uint32_t depth, const TextureUsage usage,
wgpu::Device const& device) noexcept
: HwTexture{ samplerType, levels, samples, width, height, depth, format, usage },
mWebGPUFormat{ fToWGPUTextureFormat(format) },
mAspect{ fToWGPUTextureViewAspect(usage, format) },
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples) },
mBlockWidth{ filament::backend::getBlockWidth(format) },
mBlockHeight{ filament::backend::getBlockHeight(format) } {
mCompatFormat = GetStorageCompatibleFormat(mWebGPUFormat);
if(levels > 1 && (mWebGPUUsage & wgpu::TextureUsage::TextureBinding) && (mWebGPUUsage & wgpu::TextureUsage::CopyDst) && IsFormatStorageCompatible(mCompatFormat)){
mWebGPUUsage |= wgpu::TextureUsage::StorageBinding;
}
assert_invariant(
samples == 1 ||
samples == 4 &&
"An invalid number of samples were requested, as WGPU requires the sample "
"count to either be 1 (no multisampling) or 4, at least as of April 2025 of "
"the spec. See https://www.w3.org/TR/webgpu/#texture-creation or "
"https://gpuweb.github.io/gpuweb/#multisample-state");
wgpu::TextureDescriptor textureDescriptor{
.label = getUserTextureLabel(samplerType),
.usage = mWebGPUUsage,
.dimension = toWebGPUTextureDimension(samplerType),
.size = toTextureSize(samplerType, width, height, depth),
.format = mWebGPUFormat,
.mipLevelCount = levels,
.sampleCount = samples,
// TODO Is this fine? Could do all-the-things, a naive mapping or get something from
// Filament
.viewFormatCount = 0,
.viewFormats = nullptr,
};
//If our main format is not srgb, make the srgb a view into a non-srgb format
if(mCompatFormat != mWebGPUFormat){
textureDescriptor.viewFormatCount = 1;
std::swap(mCompatFormat, mWebGPUFormat);
textureDescriptor.format = mWebGPUFormat;
textureDescriptor.viewFormats = &mCompatFormat;
mWebGPUUsage|=wgpu::TextureUsage::StorageBinding;
textureDescriptor.usage = mWebGPUUsage;
}
mArrayLayerCount = toArrayLayerCount(samplerType, textureDescriptor.size.depthOrArrayLayers);
assert_invariant(textureDescriptor.format != wgpu::TextureFormat::Undefined &&
"Could not find appropriate WebGPU format");
mTexture = device.CreateTexture(&textureDescriptor);
FILAMENT_CHECK_POSTCONDITION(mTexture)
<< "Failed to create texture for " << textureDescriptor.label;
mDefaultTextureView = makeTextureView(mDefaultMipLevel, levels, mDefaultBaseArrayLayer,
mArrayLayerCount, samplerType);
FILAMENT_CHECK_POSTCONDITION(mDefaultTextureView)
<< "Failed to create default texture view for " << textureDescriptor.label;
}
WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
const uint8_t levelCount) noexcept
: HwTexture{ src->target, levelCount, src->samples, src->width, src->height, src->depth,
src->format, src->usage },
mTexture{ src->mTexture },
mWebGPUFormat{ src->mWebGPUFormat },
mAspect{ src->mAspect },
mWebGPUUsage{ src->mWebGPUUsage },
mArrayLayerCount{ src->mArrayLayerCount },
mBlockWidth{ src->mBlockWidth },
mBlockHeight{ src->mBlockHeight },
mDefaultTextureView{ makeTextureView(baseLevel, levelCount, 0, src->mArrayLayerCount,
src->target) },
mDefaultMipLevel{ baseLevel },
mDefaultBaseArrayLayer{ src->mArrayLayerCount } {}
wgpu::TextureView WebGPUTexture::getOrMakeTextureView(const uint8_t mipLevel,
const uint32_t arrayLayer) {
// TODO: there's an optimization to be made here to return mDefaultTextureView.
// Problem: mDefaultTextureView is a view of the entire texture,
// but this function (and its callers) expects a single-slice view.
// Returning the whole texture view for a single-slice request seems wrong.
return makeTextureView(mipLevel, 1, arrayLayer, 1, target);
}
wgpu::TextureFormat WebGPUTexture::fToWGPUTextureFormat(TextureFormat const& fFormat) {
switch (fFormat) {
case TextureFormat::R8: return wgpu::TextureFormat::R8Unorm;
case TextureFormat::R8_SNORM: return wgpu::TextureFormat::R8Snorm;
case TextureFormat::R8UI: return wgpu::TextureFormat::R8Uint;
case TextureFormat::R8I: return wgpu::TextureFormat::R8Sint;
case TextureFormat::STENCIL8: return wgpu::TextureFormat::Stencil8;
case TextureFormat::R16F: return wgpu::TextureFormat::R16Float;
case TextureFormat::R16UI: return wgpu::TextureFormat::R16Uint;
case TextureFormat::R16I: return wgpu::TextureFormat::R16Sint;
case TextureFormat::RG8: return wgpu::TextureFormat::RG8Unorm;
case TextureFormat::RG8_SNORM: return wgpu::TextureFormat::RG8Snorm;
case TextureFormat::RG8UI: return wgpu::TextureFormat::RG8Uint;
case TextureFormat::RG8I: return wgpu::TextureFormat::RG8Sint;
case TextureFormat::R32F: return wgpu::TextureFormat::R32Float;
case TextureFormat::R32UI: return wgpu::TextureFormat::R32Uint;
case TextureFormat::R32I: return wgpu::TextureFormat::R32Sint;
case TextureFormat::RG16F: return wgpu::TextureFormat::RG16Float;
case TextureFormat::RG16UI: return wgpu::TextureFormat::RG16Uint;
case TextureFormat::RG16I: return wgpu::TextureFormat::RG16Sint;
case TextureFormat::RGBA8: return wgpu::TextureFormat::RGBA8Unorm;
case TextureFormat::SRGB8_A8: return wgpu::TextureFormat::RGBA8UnormSrgb;
case TextureFormat::RGBA8_SNORM: return wgpu::TextureFormat::RGBA8Snorm;
case TextureFormat::RGBA8UI: return wgpu::TextureFormat::RGBA8Uint;
case TextureFormat::RGBA8I: return wgpu::TextureFormat::RGBA8Sint;
case TextureFormat::DEPTH16: return wgpu::TextureFormat::Depth16Unorm;
case TextureFormat::DEPTH24: return wgpu::TextureFormat::Depth24Plus;
case TextureFormat::DEPTH32F: return wgpu::TextureFormat::Depth32Float;
case TextureFormat::DEPTH24_STENCIL8: return wgpu::TextureFormat::Depth24PlusStencil8;
case TextureFormat::DEPTH32F_STENCIL8: return wgpu::TextureFormat::Depth32FloatStencil8;
case TextureFormat::RG32F: return wgpu::TextureFormat::RG32Float;
case TextureFormat::RG32UI: return wgpu::TextureFormat::RG32Uint;
case TextureFormat::RG32I: return wgpu::TextureFormat::RG32Sint;
case TextureFormat::RGBA16F: return wgpu::TextureFormat::RGBA16Float;
case TextureFormat::RGBA16UI: return wgpu::TextureFormat::RGBA16Uint;
case TextureFormat::RGBA16I: return wgpu::TextureFormat::RGBA16Sint;
case TextureFormat::RGBA32F: return wgpu::TextureFormat::RGBA32Float;
case TextureFormat::RGBA32UI: return wgpu::TextureFormat::RGBA32Uint;
case TextureFormat::RGBA32I: return wgpu::TextureFormat::RGBA32Sint;
case TextureFormat::EAC_R11: return wgpu::TextureFormat::EACR11Unorm;
case TextureFormat::EAC_R11_SIGNED: return wgpu::TextureFormat::EACR11Snorm;
case TextureFormat::EAC_RG11: return wgpu::TextureFormat::EACRG11Unorm;
case TextureFormat::EAC_RG11_SIGNED: return wgpu::TextureFormat::EACRG11Snorm;
case TextureFormat::ETC2_RGB8: return wgpu::TextureFormat::ETC2RGB8Unorm;
case TextureFormat::ETC2_SRGB8: return wgpu::TextureFormat::ETC2RGB8UnormSrgb;
case TextureFormat::ETC2_RGB8_A1: return wgpu::TextureFormat::ETC2RGB8A1Unorm;
case TextureFormat::ETC2_SRGB8_A1: return wgpu::TextureFormat::ETC2RGB8A1UnormSrgb;
case TextureFormat::ETC2_EAC_RGBA8: return wgpu::TextureFormat::ETC2RGBA8Unorm;
case TextureFormat::ETC2_EAC_SRGBA8: return wgpu::TextureFormat::ETC2RGBA8UnormSrgb;
case TextureFormat::RGBA_ASTC_4x4: return wgpu::TextureFormat::ASTC4x4Unorm;
case TextureFormat::SRGB8_ALPHA8_ASTC_4x4: return wgpu::TextureFormat::ASTC4x4UnormSrgb;
case TextureFormat::RGBA_ASTC_5x4: return wgpu::TextureFormat::ASTC5x4Unorm;
case TextureFormat::SRGB8_ALPHA8_ASTC_5x4: return wgpu::TextureFormat::ASTC5x4UnormSrgb;
case TextureFormat::RGBA_ASTC_5x5: return wgpu::TextureFormat::ASTC5x5Unorm;
case TextureFormat::SRGB8_ALPHA8_ASTC_5x5: return wgpu::TextureFormat::ASTC5x5UnormSrgb;
case TextureFormat::RGBA_ASTC_6x5: return wgpu::TextureFormat::ASTC6x5Unorm;
case TextureFormat::SRGB8_ALPHA8_ASTC_6x5: return wgpu::TextureFormat::ASTC6x5UnormSrgb;
case TextureFormat::RGBA_ASTC_6x6: return wgpu::TextureFormat::ASTC6x6Unorm;
case TextureFormat::SRGB8_ALPHA8_ASTC_6x6: return wgpu::TextureFormat::ASTC6x6UnormSrgb;
case TextureFormat::RGBA_ASTC_8x5: return wgpu::TextureFormat::ASTC8x5Unorm;
case TextureFormat::SRGB8_ALPHA8_ASTC_8x5: return wgpu::TextureFormat::ASTC8x5UnormSrgb;
case TextureFormat::RGBA_ASTC_8x6: return wgpu::TextureFormat::ASTC8x6Unorm;
case TextureFormat::SRGB8_ALPHA8_ASTC_8x6: return wgpu::TextureFormat::ASTC8x6UnormSrgb;
case TextureFormat::RGBA_ASTC_8x8: return wgpu::TextureFormat::ASTC8x8Unorm;
case TextureFormat::SRGB8_ALPHA8_ASTC_8x8: return wgpu::TextureFormat::ASTC8x8UnormSrgb;
case TextureFormat::RGBA_ASTC_10x5: return wgpu::TextureFormat::ASTC10x5Unorm;
case TextureFormat::SRGB8_ALPHA8_ASTC_10x5: return wgpu::TextureFormat::ASTC10x5UnormSrgb;
case TextureFormat::RGBA_ASTC_10x6: return wgpu::TextureFormat::ASTC10x6Unorm;
case TextureFormat::SRGB8_ALPHA8_ASTC_10x6: return wgpu::TextureFormat::ASTC10x6UnormSrgb;
case TextureFormat::RGBA_ASTC_10x8: return wgpu::TextureFormat::ASTC10x8Unorm;
case TextureFormat::SRGB8_ALPHA8_ASTC_10x8: return wgpu::TextureFormat::ASTC10x8UnormSrgb;
case TextureFormat::RGBA_ASTC_10x10: return wgpu::TextureFormat::ASTC10x10Unorm;
case TextureFormat::SRGB8_ALPHA8_ASTC_10x10: return wgpu::TextureFormat::ASTC10x10UnormSrgb;
case TextureFormat::RGBA_ASTC_12x10: return wgpu::TextureFormat::ASTC12x10Unorm;
case TextureFormat::SRGB8_ALPHA8_ASTC_12x10: return wgpu::TextureFormat::ASTC12x10UnormSrgb;
case TextureFormat::RGBA_ASTC_12x12: return wgpu::TextureFormat::ASTC12x12Unorm;
case TextureFormat::SRGB8_ALPHA8_ASTC_12x12: return wgpu::TextureFormat::ASTC12x12UnormSrgb;
case TextureFormat::RED_RGTC1: return wgpu::TextureFormat::BC4RUnorm;
case TextureFormat::SIGNED_RED_RGTC1: return wgpu::TextureFormat::BC4RSnorm;
case TextureFormat::RED_GREEN_RGTC2: return wgpu::TextureFormat::BC5RGUnorm;
case TextureFormat::SIGNED_RED_GREEN_RGTC2: return wgpu::TextureFormat::BC5RGSnorm;
case TextureFormat::RGB_BPTC_UNSIGNED_FLOAT: return wgpu::TextureFormat::BC6HRGBUfloat;
case TextureFormat::RGB_BPTC_SIGNED_FLOAT: return wgpu::TextureFormat::BC6HRGBFloat;
case TextureFormat::RGBA_BPTC_UNORM: return wgpu::TextureFormat::BC7RGBAUnorm;
case TextureFormat::SRGB_ALPHA_BPTC_UNORM: return wgpu::TextureFormat::BC7RGBAUnormSrgb;
case TextureFormat::RGB565:
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
// and discard the alpha and lower precision.
FWGPU_LOGW << "Requested Filament texture format RGB565 but getting "
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)"
<< utils::io::endl;
return wgpu::TextureFormat::Undefined;
case TextureFormat::RGB9_E5: return wgpu::TextureFormat::RGB9E5Ufloat;
case TextureFormat::RGB5_A1:
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
// and handle the packing/unpacking in shaders.
FWGPU_LOGW << "Requested Filament texture format RGB5_A1 but getting "
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)"
<< utils::io::endl;
return wgpu::TextureFormat::Undefined;
case TextureFormat::RGBA4:
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
// and handle the packing/unpacking in shaders.
FWGPU_LOGW << "Requested Filament texture format RGBA4 but getting "
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)"
<< utils::io::endl;
return wgpu::TextureFormat::Undefined;
case TextureFormat::RGB8:
FWGPU_LOGW << "Requested Filament texture format RGB8 but getting "
"wgpu::TextureFormat::RGBA8Unorm (no direct sRGB equivalent in wgpu "
"without alpha)"
<< utils::io::endl;
return wgpu::TextureFormat::RGBA8Unorm;
case TextureFormat::SRGB8:
FWGPU_LOGW << "Requested Filament texture format SRGB8 but getting "
"wgpu::TextureFormat::RGBA8UnormSrgb (no direct sRGB equivalent in wgpu "
"without alpha)"
<< utils::io::endl;
return wgpu::TextureFormat::RGBA8UnormSrgb;
case TextureFormat::RGB8_SNORM:
FWGPU_LOGW
<< "Requested Filament texture format RGB8_SNORM but getting "
"wgpu::TextureFormat::RGBA8Snorm (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
return wgpu::TextureFormat::RGBA8Snorm;
case TextureFormat::RGB8UI:
FWGPU_LOGW << "Requested Filament texture format RGB8UI but getting "
"wgpu::TextureFormat::RGBA8Uint (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
return wgpu::TextureFormat::RGBA8Uint;
case TextureFormat::RGB8I:
FWGPU_LOGW << "Requested Filament texture format RGB8I but getting "
"wgpu::TextureFormat::RGBA8Sint (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
return wgpu::TextureFormat::RGBA8Sint;
case TextureFormat::R11F_G11F_B10F: return wgpu::TextureFormat::RG11B10Ufloat;
case TextureFormat::UNUSED: return wgpu::TextureFormat::Undefined;
case TextureFormat::RGB10_A2: return wgpu::TextureFormat::RGB10A2Unorm;
case TextureFormat::RGB16F:
FWGPU_LOGW
<< "Requested Filament texture format RGB16F but getting "
"wgpu::TextureFormat::RGBA16Float (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
return wgpu::TextureFormat::RGBA16Float;
case TextureFormat::RGB16UI:
FWGPU_LOGW
<< "Requested Filament texture format RGB16UI but getting "
"wgpu::TextureFormat::RGBA16Uint (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
return wgpu::TextureFormat::RGBA16Uint;
case TextureFormat::RGB16I:
FWGPU_LOGW
<< "Requested Filament texture format RGB16I but getting "
"wgpu::TextureFormat::RGBA16Sint (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
return wgpu::TextureFormat::RGBA16Sint;
case TextureFormat::RGB32F:
FWGPU_LOGW
<< "Requested Filament texture format RGB32F but getting "
"wgpu::TextureFormat::RGBA32Float (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
return wgpu::TextureFormat::RGBA32Float;
case TextureFormat::RGB32UI:
FWGPU_LOGW
<< "Requested Filament texture format RGB32UI but getting "
"wgpu::TextureFormat::RGBA32Uint (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
return wgpu::TextureFormat::RGBA32Uint;
case TextureFormat::RGB32I:
FWGPU_LOGW
<< "Requested Filament texture format RGB32I but getting "
"wgpu::TextureFormat::RGBA32Sint (no direct mapping in wgpu without alpha)"
<< utils::io::endl;
return wgpu::TextureFormat::RGBA32Sint;
case TextureFormat::DXT1_RGB: return wgpu::TextureFormat::BC1RGBAUnorm;
case TextureFormat::DXT1_RGBA: return wgpu::TextureFormat::BC1RGBAUnorm;
case TextureFormat::DXT3_RGBA: return wgpu::TextureFormat::BC2RGBAUnorm;
case TextureFormat::DXT5_RGBA: return wgpu::TextureFormat::BC3RGBAUnorm;
case TextureFormat::DXT1_SRGB: return wgpu::TextureFormat::BC1RGBAUnormSrgb;
case TextureFormat::DXT1_SRGBA: return wgpu::TextureFormat::BC1RGBAUnormSrgb;
case TextureFormat::DXT3_SRGBA: return wgpu::TextureFormat::BC2RGBAUnormSrgb;
case TextureFormat::DXT5_SRGBA: return wgpu::TextureFormat::BC3RGBAUnormSrgb;
}
}
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 {
wgpu::TextureViewDescriptor textureViewDescriptor{
.label = getUserTextureViewLabel(target),
.format = mCompatFormat,
.dimension = toWebGPUTextureViewDimension(samplerType),
.baseMipLevel = baseLevel,
.mipLevelCount = levelCount,
.baseArrayLayer = baseArrayLayer,
.arrayLayerCount = arrayLayerCount,
.aspect = mAspect,
.usage = mWebGPUUsage };
textureViewDescriptor.usage &= (~wgpu::TextureUsage::StorageBinding);
wgpu::TextureView textureView = mTexture.CreateView(&textureViewDescriptor);
FILAMENT_CHECK_POSTCONDITION(textureView)
<< "Failed to create texture view " << textureViewDescriptor.label;
return textureView;
}
}// namespace filament::backend
@@ -0,0 +1,82 @@
/*
* 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_WEBGPUTEXTURE_H
#define TNT_FILAMENT_BACKEND_WEBGPUTEXTURE_H
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
namespace filament::backend {
class WebGPUTexture : public HwTexture {
public:
WebGPUTexture(SamplerType, uint8_t levels, TextureFormat, uint8_t samples, uint32_t width,
uint32_t height, uint32_t depth, TextureUsage, wgpu::Device const&) noexcept;
WebGPUTexture(WebGPUTexture const* src, uint8_t baseLevel, uint8_t levelCount) noexcept;
[[nodiscard]] wgpu::TextureAspect getAspect() const { return mAspect; }
[[nodiscard]] size_t getBlockWidth() const { return mBlockWidth; }
[[nodiscard]] size_t getBlockHeight() const { return mBlockHeight; }
[[nodiscard]] wgpu::Texture const& getTexture() const { return mTexture; }
[[nodiscard]] wgpu::TextureView const& getDefaultTextureView() const {
return mDefaultTextureView;
}
[[nodiscard]] wgpu::TextureView getOrMakeTextureView(uint8_t mipLevel, uint32_t arrayLayer);
[[nodiscard]] wgpu::TextureFormat getWebGPUFormat() const { return mWebGPUFormat; }
[[nodiscard]] uint32_t getArrayLayerCount() const { return mArrayLayerCount; }
[[nodiscard]] static wgpu::TextureFormat fToWGPUTextureFormat(
filament::backend::TextureFormat const& fFormat);
private:
// CreateTextureR has info for a texture and sampler. Texture Views are needed for binding,
// along with a sampler Current plan: Inherit the sampler and Texture to always exist (It is a
// ref counted pointer) when making views. View is optional
wgpu::Texture mTexture = nullptr;
// format is inherited from HwTexture. This naming is to distinguish it from Filament's format
wgpu::TextureFormat mWebGPUFormat = wgpu::TextureFormat::Undefined;
wgpu::TextureFormat mCompatFormat = wgpu::TextureFormat::Undefined;
wgpu::TextureAspect mAspect = wgpu::TextureAspect::Undefined;
// usage is inherited from HwTexture. This naming is to distinguish it from Filament's usage
wgpu::TextureUsage mWebGPUUsage = wgpu::TextureUsage::None;
uint32_t mArrayLayerCount = 1;
size_t mBlockWidth = 0;
size_t mBlockHeight = 0;
wgpu::TextureView mDefaultTextureView = nullptr;
uint32_t mDefaultMipLevel = 0;
uint32_t mDefaultBaseArrayLayer = 0;
[[nodiscard]] wgpu::TextureView makeTextureView(const uint8_t& baseLevel,
const uint8_t& levelCount, const uint32_t& baseArrayLayer,
const uint32_t& arrayLayerCount, SamplerType samplerType) const noexcept;
};
}// namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUTEXTURE_H
@@ -0,0 +1,35 @@
/*
* 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 "WebGPUVertexBuffer.h"
#include "DriverBase.h"
#include <backend/Handle.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
namespace filament::backend {
WebGPUVertexBuffer::WebGPUVertexBuffer(const uint32_t vertexCount, const uint32_t bufferCount,
Handle<HwVertexBufferInfo> vertexBufferInfoHandle)
: HwVertexBuffer{ vertexCount },
mVertexBufferInfoHandle{ vertexBufferInfoHandle } {
mBuffers.resize(bufferCount);
}
} // namespace filament::backend
@@ -0,0 +1,49 @@
/*
* 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_WEBGPUVERTEXBUFFER_H
#define TNT_FILAMENT_BACKEND_WEBGPUVERTEXBUFFER_H
#include "DriverBase.h"
#include <backend/Handle.h>
#include <cstdint>
#include <vector>
namespace wgpu {
class Buffer;
} // namespace wgpu
namespace filament::backend {
class WebGPUVertexBuffer final : public HwVertexBuffer {
public:
WebGPUVertexBuffer(uint32_t vertexCount, uint32_t bufferCount, Handle<HwVertexBufferInfo>);
[[nodiscard]] Handle<HwVertexBufferInfo>& getVertexBufferInfoHandle() {
return mVertexBufferInfoHandle;
}
[[nodiscard]] std::vector<wgpu::Buffer>& getBuffers() { return mBuffers; }
private:
Handle<HwVertexBufferInfo> mVertexBufferInfoHandle;
std::vector<wgpu::Buffer> mBuffers;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUVERTEXBUFFER_H
@@ -0,0 +1,383 @@
/*
* 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 "WebGPUVertexBufferInfo.h"
#include "WebGPUConstants.h"
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#include <utils/Panic.h>
#include <utils/ostream.h>
#include <webgpu/webgpu_cpp.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <vector>
namespace filament::backend {
namespace {
[[nodiscard]] wgpu::VertexFormat getVertexFormat(const ElementType type, const bool normalized,
const bool integer) {
using VertexFormat = wgpu::VertexFormat;
if (normalized) {
switch (type) {
// Single Component Types
case ElementType::BYTE: return VertexFormat::Snorm8;
case ElementType::UBYTE: return VertexFormat::Unorm8;
case ElementType::SHORT: return VertexFormat::Snorm16;
case ElementType::USHORT: return VertexFormat::Unorm16;
// Two Component Types
case ElementType::BYTE2: return VertexFormat::Snorm8x2;
case ElementType::UBYTE2: return VertexFormat::Unorm8x2;
case ElementType::SHORT2: return VertexFormat::Snorm16x2;
case ElementType::USHORT2: return VertexFormat::Unorm16x2;
// Three Component Types
// There is no vertex format type for 3 byte data in webgpu. Use
// 4 byte signed normalized type and ignore the last byte.
// TODO: This is to be verified.
case ElementType::BYTE3:
FWGPU_LOGW
<< "Requested Filament vertex format BYTE3 (normalized) but getting "
"wgpu::VertexFormat::Snorm8x4 (no direct mapping in wgpu for x3 byte)"
<< utils::io::endl;
return VertexFormat::Snorm8x4; // NOT MINSPEC
case ElementType::UBYTE3:
FWGPU_LOGW
<< "Requested Filament vertex format UBYTE3 (normalized) but getting "
"wgpu::VertexFormat::Unorm8x4 (no direct mapping in wgpu for x3 byte)"
<< utils::io::endl;
return VertexFormat::Unorm8x4; // NOT MINSPEC
case ElementType::SHORT3:
FWGPU_LOGW << "Requested Filament vertex format SHORT3 (normalized) but getting "
"wgpu::VertexFormat::Snorm16x4 (no direct mapping in wgpu for x3 "
"half/short)"
<< utils::io::endl;
return VertexFormat::Snorm16x4; // NOT MINSPEC
case ElementType::USHORT3:
FWGPU_LOGW << "Requested Filament vertex format USHORT3 (normalized) but getting "
"wgpu::VertexFormat::Unorm16x4 (no direct mapping in wgpu for x3 "
"half/short)"
<< utils::io::endl;
return VertexFormat::Unorm16x4; // NOT MINSPEC
// Four Component Types
case ElementType::BYTE4: return VertexFormat::Snorm8x4;
case ElementType::UBYTE4: return VertexFormat::Unorm8x4;
case ElementType::SHORT4: return VertexFormat::Snorm16x4;
case ElementType::USHORT4: return VertexFormat::Unorm16x4;
default:
PANIC_POSTCONDITION("Normalized format (%d enum value) does not exist in webgpu.",
type);
return VertexFormat::Float32x3;
}
}
switch (type) {
// Single Component Types
// There is no direct alternative for SSCALED in webgpu. Convert them to Float32 directly.
// This will result in increased memory on the cpu side.
// TODO: Is Float16 acceptable instead with some potential accuracy errors?
case ElementType::BYTE:
if (integer) return VertexFormat::Sint8;
FWGPU_LOGW << "Requested Filament vertex format BYTE (float) but getting "
"wgpu::VertexFormat::Float16 (no direct mapping in wgpu for 8 bit float)"
<< utils::io::endl;
return wgpu::VertexFormat::Float16;
case ElementType::UBYTE:
if (integer) return VertexFormat::Uint8;
FWGPU_LOGW << "Requested Filament vertex format UBYTE (float) but getting "
"wgpu::VertexFormat::Float16 (no direct mapping in wgpu for 8 bit float)"
<< utils::io::endl;
return VertexFormat::Float16;
case ElementType::SHORT:
if (integer) return VertexFormat::Sint16;
FWGPU_LOGW << "Requested Filament vertex format SHORT (float) and getting "
"wgpu::VertexFormat::Float16 (potential loss of precision?)"
<< utils::io::endl;
return VertexFormat ::Float16;
case ElementType::USHORT:
if (integer) return VertexFormat::Uint16;
FWGPU_LOGW << "Requested Filament vertex format USHORT (float) and getting "
"wgpu::VertexFormat::Float16 (potential loss of precision?)"
<< utils::io::endl;
return VertexFormat::Float16;
case ElementType::HALF: return VertexFormat::Float16;
case ElementType::INT: return VertexFormat::Sint32;
case ElementType::UINT: return VertexFormat::Uint32;
case ElementType::FLOAT: return VertexFormat::Float32;
// Two Component Types
case ElementType::BYTE2:
if (integer) return VertexFormat::Sint8x2;
FWGPU_LOGW
<< "Requested Filament vertex format BYTE2 (float) but getting "
"wgpu::VertexFormat::Float16x2 (no direct mapping in wgpu for 8 bit float)"
<< utils::io::endl;
return VertexFormat::Float16x2;
case ElementType::UBYTE2:
if (integer) return VertexFormat::Uint8x2;
FWGPU_LOGW
<< "Requested Filament vertex format UBYTE2 (float) but getting "
"wgpu::VertexFormat::Float16x2 (no direct mapping in wgpu for 8 bit float)"
<< utils::io::endl;
return VertexFormat::Float16x2;
case ElementType::SHORT2:
if (integer) return VertexFormat::Sint16x2;
FWGPU_LOGW << "Requested Filament vertex format SHORT2 (float) but getting "
"wgpu::VertexFormat::Float16x2 (potential loss of precision?)"
<< utils::io::endl;
return VertexFormat::Float16x2;
case ElementType::USHORT2:
if (integer) return VertexFormat::Uint16x2;
FWGPU_LOGW << "Requested Filament vertex format USHORT2 (float) but getting "
"wgpu::VertexFormat::Float16x2 (potential loss of precision?)"
<< utils::io::endl;
return VertexFormat::Float16x2;
case ElementType::HALF2: return VertexFormat::Float16x2;
case ElementType::FLOAT2: return VertexFormat::Float32x2;
// Three Component Types
case ElementType::BYTE3:
FWGPU_LOGW << "Requested Filament vertex format BYTE3 but getting "
"wgpu::VertexFormat::Sint8x4 (no direct mapping in wgpu for x3 byte)"
<< utils::io::endl;
return VertexFormat::Sint8x4; // NOT MINSPEC
case ElementType::UBYTE3:
FWGPU_LOGW << "Requested Filament vertex format UBYTE3 but getting "
"wgpu::VertexFormat::Uint8x4 (no direct mapping in wgpu for x3 byte)"
<< utils::io::endl;
return VertexFormat::Uint8x4; // NOT MINSPEC
case ElementType::SHORT3:
FWGPU_LOGW
<< "Requested Filament vertex format SHORT3 but getting "
"wgpu::VertexFormat::Sint16x4 (no direct mapping in wgpu for x3 half/short)"
<< utils::io::endl;
return VertexFormat::Sint16x4; // NOT MINSPEC
case ElementType::USHORT3:
FWGPU_LOGW
<< "Requested Filament vertex format USHORT3 but getting "
"wgpu::VertexFormat::Uint16x4 (no direct mapping in wgpu for x3 half/short)"
<< utils::io::endl;
return VertexFormat::Uint16x4; // NOT MINSPEC
case ElementType::HALF3:
FWGPU_LOGW
<< "Requested Filament vertex format HALF3 but getting "
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for x3 half/short)"
<< utils::io::endl;
return VertexFormat::Float16x4; // NOT MINSPEC
case ElementType::FLOAT3: return VertexFormat::Float32x3;
// Four Component Types
case ElementType::BYTE4:
if (integer) return VertexFormat::Sint8x4;
FWGPU_LOGW
<< "Requested Filament vertex format BYTE4 (float) but getting "
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for 8 bit float)"
<< utils::io::endl;
return VertexFormat::Float16x4;
case ElementType::UBYTE4:
if (integer) return VertexFormat::Uint8x4;
FWGPU_LOGW
<< "Requested Filament vertex format UBYTE4 (float) but getting "
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for 8 bit float)"
<< utils::io::endl;
return VertexFormat::Float16x4;
case ElementType::SHORT4:
if (integer) return VertexFormat::Sint16x4;
FWGPU_LOGW << "Requested Filament vertex format SHORT4 (float) but getting "
"wgpu::VertexFormat::Float16x4 (potential loss of precision?)"
<< utils::io::endl;
return VertexFormat::Float16x4;
case ElementType::USHORT4:
if (integer) return VertexFormat::Uint16x4;
FWGPU_LOGW << "Requested Filament vertex format USHORT4 (float) but getting "
"wgpu::VertexFormat::Float16x4 (potential loss of precision?)"
<< utils::io::endl;
return VertexFormat::Float16x4;
case ElementType::HALF4: return VertexFormat::Float16x4;
case ElementType::FLOAT4: return VertexFormat::Float32x4;
}
}
constexpr uint32_t INVALID_SLOT_INDEX = MAX_VERTEX_BUFFER_COUNT;
struct AttributeInfo final {
uint8_t slot = INVALID_SLOT_INDEX;
wgpu::VertexAttribute attribute = {};
AttributeInfo()
: slot{ INVALID_SLOT_INDEX },
attribute({}) {}
AttributeInfo(uint8_t slot, wgpu::VertexAttribute attribute)
: slot{ slot },
attribute{ attribute } {}
};
/**
* Creates the necessary vertex buffer layouts (+ a WebGPU slot per layout) given the input
* attributes provided by Filament, as well as populates the outAttributeInfos, which associates
* attributes to slots.
*
* NOTE: At this point, the vertex buffer layouts do not have attribute information. That needs
* to get updated in a subsequent step
*
* @param attributes Input vertex attribute information from Filament
* @param attributeCount The expected number of "used" vertex attributes from Filament, the number
* of attributes associated with a vertex buffer
* @param deviceMaxVertexBuffers The device's limit of vertex buffers
* @param outWebGPUSlotBindingInfos The WebGPU slot bindings to be used by the WebGPU driver to set
* vertex buffers in the WebGPU API.
* @param outVertexBufferLayouts The partially populated vertex buffer layouts to eventually pass to
* WebGPU. The attribute count and attributes pointer is not yet populated at this time. This must
* be done after this function.
* @param outAttributeInfos Information about all the vertex attributes to be used, actually
* associated with a vertex buffer, and the slot each one belongs
*/
void createBufferLayoutsBindingSlotsAndAttributeInfos(AttributeArray const& attributes,
const uint8_t attributeCount, const uint32_t deviceMaxVertexBuffers,
std::vector<WebGPUVertexBufferInfo::WebGPUSlotBindingInfo>& outWebGPUSlotBindingInfos,
std::vector<wgpu::VertexBufferLayout>& outVertexBufferLayouts,
std::array<AttributeInfo, MAX_VERTEX_ATTRIBUTE_COUNT>& outAttributeInfos) {
uint8_t currentWebGPUSlotIndex = 0;
uint8_t currentAttributeIndex = 0;
outVertexBufferLayouts.reserve(MAX_VERTEX_BUFFER_COUNT);
outWebGPUSlotBindingInfos.reserve(outVertexBufferLayouts.capacity());
for (uint32_t attributeIndex = 0; attributeIndex < attributes.size(); ++attributeIndex) {
const auto& attribute = attributes[attributeIndex];
if (attribute.buffer == Attribute::BUFFER_UNUSED) {
// We ignore "unused" attributes, ones not associated with a buffer. If a shader
// references such an attribute we have a bug one way or another. Either the API/CPU
// will produce an error (best case scenario), where an attribute is referenced in the
// shader but not provided by the backend API (CPU-side), or the shader would be getting
// junk/undefined data from the GPU, since we do not have a valid buffer of data to
// provide to the shader/GPU.
continue;
}
const bool isInteger = attribute.flags & Attribute::FLAG_INTEGER_TARGET;
const bool isNormalized = attribute.flags & Attribute::FLAG_NORMALIZED;
const wgpu::VertexFormat vertexFormat =
getVertexFormat(attribute.type, isNormalized, isInteger);
uint8_t existingSlot = INVALID_SLOT_INDEX;
for (uint32_t slot = 0; slot < currentWebGPUSlotIndex; slot++) {
WebGPUVertexBufferInfo::WebGPUSlotBindingInfo const& info =
outWebGPUSlotBindingInfos[slot];
// We consider attributes to be in the same slot/layout only if they belong to the
// same buffer and are interleaved; they cannot belong to separate partitions in the
// same buffer, for example.
// For the attributes to be interleaved, the stride must match (among other things).
// The attribute offset being less than the slot's/layout's buffer offset indicates
// that it is in a separate partition before this slot/layout, thus not part of it.
// The difference from the attribute's offset and this slot's/layout's buffer offset
// being greater than the stride indicates it is in a separate partition after this
// slot/layout, thus not part of it.
if (info.sourceBufferIndex == attribute.buffer && info.stride == attribute.stride &&
attribute.offset >= info.bufferOffset &&
((attribute.offset - info.bufferOffset) < attribute.stride)) {
existingSlot = slot;
break;
}
}
if (existingSlot == INVALID_SLOT_INDEX) {
// New combination, use a new WebGPU slot
FILAMENT_CHECK_PRECONDITION(currentWebGPUSlotIndex < MAX_VERTEX_BUFFER_COUNT &&
currentWebGPUSlotIndex < deviceMaxVertexBuffers)
<< "Number of vertex buffer layouts must not exceed MAX_VERTEX_BUFFER_COUNT ("
<< MAX_VERTEX_BUFFER_COUNT << ") or the device limit ("
<< deviceMaxVertexBuffers << ")";
existingSlot = currentWebGPUSlotIndex++;
outWebGPUSlotBindingInfos.push_back({
.sourceBufferIndex = attribute.buffer,
.bufferOffset = attribute.offset,
.stride = attribute.stride });
// we need to have a vertex buffer layout for each slot
outVertexBufferLayouts.push_back({
.stepMode = wgpu::VertexStepMode::Vertex,
.arrayStride = attribute.stride
// we do not know attributeCount or attributes at this time. We get those
// in a subsequent pass over the attributeInfos we collect in this loop.
});
}
// we don't use a designated initializer here because certain compilers can't handle it
outAttributeInfos[currentAttributeIndex++] = AttributeInfo(
existingSlot,
{
.format = vertexFormat,
.offset =
attribute.offset - outWebGPUSlotBindingInfos[existingSlot].bufferOffset,
.shaderLocation = attributeIndex });
}
FILAMENT_CHECK_POSTCONDITION(currentAttributeIndex == attributeCount)
<< "Using " << currentAttributeIndex << " attributes, but " << attributeCount
<< " where indicated.";
outVertexBufferLayouts.shrink_to_fit();
outWebGPUSlotBindingInfos.shrink_to_fit();
}
} // namespace
WebGPUVertexBufferInfo::WebGPUVertexBufferInfo(const uint8_t bufferCount,
const uint8_t attributeCount, AttributeArray const& attributes,
wgpu::Limits const& deviceLimits)
: HwVertexBufferInfo{ bufferCount, attributeCount } {
FILAMENT_CHECK_PRECONDITION(attributeCount <= MAX_VERTEX_ATTRIBUTE_COUNT &&
attributeCount <= deviceLimits.maxVertexAttributes)
<< "The number of vertex attributes requested (" << attributeCount
<< ") exceeds Filament's MAX_VERTEX_ATTRIBUTE_COUNT limit ("
<< MAX_VERTEX_ATTRIBUTE_COUNT << ") and/or the device's limit ("
<< deviceLimits.maxVertexAttributes << ")";
mVertexAttributes.reserve(attributeCount);
if (attributeCount == 0) {
mVertexBufferLayouts.resize(0);
mWebGPUSlotBindingInfos.resize(0);
return; // should not be possible, but being defensive. nothing to do otherwise
}
std::array<AttributeInfo, MAX_VERTEX_ATTRIBUTE_COUNT> attributeInfos{};
createBufferLayoutsBindingSlotsAndAttributeInfos(attributes, attributeCount,
deviceLimits.maxVertexBuffers, mWebGPUSlotBindingInfos, mVertexBufferLayouts,
attributeInfos);
// sort attribute infos by increasing slot (by increasing offset within each slot).
// We do this to ensure that attributes for the same slot/layout are contiguous in
// the vector, so the vertex buffer layout associated with these contiguous attributes
// can directly reference them in the mVertexAttributes vector below.
std::sort(attributeInfos.data(), attributeInfos.data() + attributeCount,
[](AttributeInfo const& first, AttributeInfo const& second) {
if (first.slot < second.slot) {
return true;
}
if (first.slot == second.slot) {
if (first.attribute.offset < second.attribute.offset) {
return true;
}
}
return false;
});
// populate mVertexAttributes and update mVertexBufferLayouts to reference the correct
// attributes in it (which will be contiguous in memory as ensured by the sorting above)...
for (uint32_t attributeIndex = 0; attributeIndex < attributeCount; ++attributeIndex) {
AttributeInfo const& info = attributeInfos[attributeIndex];
mVertexAttributes.push_back(info.attribute);
if (mVertexBufferLayouts[info.slot].attributeCount == 0) {
mVertexBufferLayouts[info.slot].attributes = &mVertexAttributes[attributeIndex];
}
mVertexBufferLayouts[info.slot].attributeCount++;
}
}
size_t WebGPUVertexBufferInfo::getVertexBufferLayoutCount() const {
return mVertexBufferLayouts.size();
}
}// namespace filament::backend
@@ -0,0 +1,78 @@
/*
* 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_WEBGPUVERTEXBUFFERINFO_H
#define TNT_FILAMENT_BACKEND_WEBGPUVERTEXBUFFERINFO_H
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#include <cstdint>
#include <vector>
namespace wgpu {
struct Limits;
struct VertexAttribute;
struct VertexBufferLayout;
} // namespace wgpu
namespace filament::backend {
// Maps Filament vertex attributes to WebGPU vertex buffer bindings.
class WebGPUVertexBufferInfo final : public HwVertexBufferInfo {
public:
WebGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
AttributeArray const& attributes, wgpu::Limits const& deviceLimits);
[[nodiscard]] wgpu::VertexBufferLayout const* getVertexBufferLayouts() const {
return mVertexBufferLayouts.data();
}
[[nodiscard]] size_t getVertexBufferLayoutCount() const;
struct WebGPUSlotBindingInfo final {
uint8_t sourceBufferIndex = 0; // limited by filament::backend::Attribute::buffer
uint32_t bufferOffset = 0; // limited by filament::backend::Attribute::offset
uint8_t stride = 0; // limited by filament::backend::Attribute::stride
};
[[nodiscard]] std::vector<WebGPUSlotBindingInfo> const& getWebGPUSlotBindingInfos() const {
return mWebGPUSlotBindingInfos;
}
private:
// This stores the final wgpu::VertexBufferLayout objects, one per WebGPU slot.
// (this is a vector and not an array due to size limitations of the handle in the handle
// allocator)
std::vector<wgpu::VertexBufferLayout> mVertexBufferLayouts;
// This stores all the vertex attributes across all the vertex buffer layouts, where
// the attributes are contiguous for each buffer layout.
// Each buffer layout references the first in a contiguous group of attributes in this array,
// as well as the number of such attributes in this vector.
// (this is a vector and not an array due to size limitations of the handle in the handle
// allocator)
std::vector<wgpu::VertexAttribute> mVertexAttributes;
// Stores information for the driver to perform setVertexBuffer calls
// (this is a vector and not an array due to size limitations of the handle in the handle
// allocator)
std::vector<WebGPUSlotBindingInfo> mWebGPUSlotBindingInfos;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUVERTEXBUFFERINFO_H
@@ -56,7 +56,12 @@ namespace {
constexpr std::array REQUIRED_FEATURES = { wgpu::FeatureName::TransientAttachments,
/*To make filtering assumptions like we want while waiting for Filament to provide that info,
float 32 needs to be filterable*/
wgpu::FeatureName::Float32Filterable };
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
wgpu::FeatureName::RG11B10UfloatRenderable
};
constexpr std::array OPTIONAL_FEATURES = {
wgpu::FeatureName::DepthClipControl,
@@ -82,6 +87,7 @@ constexpr wgpu::Limits REQUIRED_LIMITS = {
.maxBindingsPerBindGroup = filament::backend::MAX_DESCRIPTOR_COUNT,
.maxSamplersPerShaderStage = 16, // TODO should be set to filament::backend::MAX_SAMPLER_COUNT,
.maxStorageBuffersPerShaderStage = filament::backend::MAX_SSBO_COUNT,
.maxStorageTexturesPerShaderStage = 8,
.maxVertexBuffers = 8, // TODO should be set to filament::backend::MAX_VERTEX_BUFFER_COUNT,
.maxVertexAttributes = filament::backend::MAX_VERTEX_ATTRIBUTE_COUNT,
};
+63
View File
@@ -0,0 +1,63 @@
# Backend Unit Tests
These are tests that ensure the Filament backend is working properly on various operating systems
and graphics backends.
The majority of these tests generate images that are then compared against a known golden image.
## Running with a specific graphics library
Run with `-a<backend>` to run with a specific backend, such as `-avulkan` for vulkan or `-ametal`
for metal.
## Image comparisons
The expected images are stored as PNG files in the `expected_images` subdirectory of the test source
code. When cmake is run it will copy this directory to the build output creating
`images/expected_images` inside the same directory as the unit test binary.
The unit tests will then write their resulting images into `images/actual_images` in order to make
the tests easier to debug.
### Python utility for updating golden images and comparing results
Inside the unit test source code directory there is a python script called
`move_actual_images_to_expected.py`.
It will display the actual and expected images side-by-side and copy the actual image into the
source tree's `expected_images` directory if the user approves the change.
#### Directories
The `-r` flag is required and should be the directory where the test binary is.
If not running the script from the directory it's in, `-s` should be the source code's
`expected_images` directory.
#### Configuring compare/move behavior
The `-c` flag has the tool display the actual and expected images side-by-side. By default it
does this by opening both with the OS's default behavior. `-p` can be used to specify a different
terminal program.
The `-m` flag has the tool move the actual image to overwrite the expected image in the source
tree. If the images are also being compared then the move will only happen if the user approves the
change.
After updating the expected images, cmake will need to be run again to copy them to the binary's
directory. Also, currently some platforms can't load images to compare and so have the hash
hardcoded into the test source code, so for now those need to be updated by running the test and
copying the value manually.
#### Picking which tests to compare
The `-x` flag causes the tool to check the `test_detail.xml` file and only process the images who
failed a comparison. `--gtest_output=xml` needs to be passed to the test binary to generate this
file.
The `-t` argument takes a list of images to compare, provided as the file name without the `.png`
suffix.
## Unsupported tests
If a test depends on a feature that is unsupported by the current environment, it will start with
the `SKIP_IF` macro. This will cause the test to not be run.
@@ -25,26 +25,40 @@ class TestResults(object):
return failed_images
def handle_failed_image(self, failed_image):
self.show_images(failed_image)
print(f'Update {failed_image}\'s expected image? y/n')
while True:
user_input = input()
if user_input == 'y':
self.move_actual_to_source([failed_image])
break
elif user_input == 'n':
break
def process(self, test_prefixes: typing.List[str], compare: bool, move: bool,
compare_program: str = ''):
for test in test_prefixes:
self.handle_image(test, compare, move, compare_program)
def handle_all_failed_images(self):
for failed_image in self.get_latest_failed_images():
self.handle_failed_image(failed_image)
def handle_image(self, failed_image: str, compare: bool, move: bool, compare_program: str):
if compare:
self.show_images(failed_image, compare_program)
def show_images(self, failed_image):
# If the image can be moved, prompt the user after the comparison. But always wait for
# user input to not spam them with images.
if move:
print(f'Update {failed_image}\'s expected image? y/n')
while True:
user_input = input()
if user_input == 'y':
break
elif user_input == 'n':
move = False
break
else:
print(f'Move to next?')
input()
if move:
self.move_actual_to_source([failed_image])
def show_images(self, failed_image: str, command: str):
# TODO: Test more on non-mac systems
open_command: str
os_name = platform.system().lower()
if 'windows' in os_name:
if command:
open_command = command
elif 'windows' in os_name:
open_command = 'start'
elif 'osx' in os_name or 'darwin' in os_name:
open_command = 'open'
@@ -63,12 +77,6 @@ class TestResults(object):
replacement=TestResults.EXPECTED_SUFFIX,
output_path=self.source_expected_directory, prefixes=file_prefixes)
def batch_move(self, prefixes: typing.Optional[typing.List[str]] = None):
replace_file_names(path=self.actual_directory, removed=TestResults.ACTUAL_SUFFIX,
replacement=TestResults.EXPECTED_SUFFIX,
output_path=self.source_expected_directory, prefixes=prefixes)
def match_suffix(file_name: str, suffix: str, accepted_prefixes: typing.List[str]) -> str:
"""
Check if the file name is one of the searched for ones with the given suffix and if so return
@@ -105,52 +113,67 @@ def replace_file_names(path: str, removed: str, replacement: str = '', output_pa
print(f'Failed to find {unfound_prefix}_actual.png')
if __name__ == "__main__":
def main():
parser = argparse.ArgumentParser(prog='Backend Test File Renamer',
description='Moves actual generated test images to the '
'expected images directory, to update the test '
'requirements. test_cases accepts multiple '
'arguments that should be the name of the '
'expected image file without the .png suffix. '
'Also --all can be passed to copy all images.\n'
'Remember to sync CMake after running this to '
'move the new expected images to the binary '
'directory.')
description=
'Compares and moves actual generated test images to the expected images directory to '
'update the test requirements. You need to use -r to specify the directory with the test '
'binary.\nYou likely want the flags -cmx to compare and prompt to move every failed image'
'\nRemember to sync CMake after running this to move the new expected images to the binary '
'directory.')
parser.add_argument('-r', '--results_path',
help='The path with the generated images directory, which should be where '
'the test binary was run.')
parser.add_argument('-s', '--source_expected_path', default="./expected_images",
help='The directory that updated expected images should be written to, '
'which should be the source directory copy.')
# The mutually exclusive options for how to process the actual images
parser.add_argument('-b', '--batch', action='extend', nargs='*',
help='If true copy all actual images to the source expected image '
'directory.')
parser.add_argument('-a', '--all', action='store_true',
help='If true, visually compare all generated images.')
parser.add_argument('-t', '--tests', action='store_true',
help='If true use a test_detail.xml file that exists in the results_path '
'directory to visually compare all images that failed a test.')
parser.add_argument('-c', '--compare', action='extend', nargs='*',
help='A list of image names to visually compare (without the .png suffix).')
parser.add_argument('-c', '--compare', action='store_true',
help='If true, actual and expected images will be displayed to the user '
'for comparison.')
parser.add_argument('-p', '--compare_program', default=None,
help='The terminal program that should be used to open images when '
'comparing them')
parser.add_argument('-m', '--move_files', action='store_true',
help='If true, the actual images will be copied to overwrite the source '
'tree\'s expected image for that test. If the --compare flag is also '
'present the user will be prompted for each copy.')
# The mutually exclusive options for what images to compare.
parser.add_argument('-t', '--tests', action='extend', nargs='*',
help='The list of test images to compare, provided as the name of the '
'actual image file without the .png suffix.')
parser.add_argument('-x', '--xml', action='store_true',
help='If true use a test_detail.xml file generated by the test binary to '
'compare all images that failed a test. Remember to pass '
'`--gtest_output=xml` to the test binary to generate this file.')
args = parser.parse_args()
if not args.results_path:
raise AssertionError("No result path provided")
results_path = args.results_path
# Catch argument mistakes
if not args.move_files and not args.compare:
print("Neither instructed to move or compare images")
return
if args.compare_program and not args.compare:
print("Compare program provided but not comparing images")
return
results_path = args.results_path
results = TestResults(results_directory=results_path,
source_expected_directory=args.source_expected_path)
if args.all:
results.batch_move()
elif args.tests:
results.handle_all_failed_images()
elif args.compare:
for file_prefix in args.compare:
results.show_images(file_prefix)
else:
results.batch_move(args.batch)
print("--------------------------------------------------------")
print("REMEMBER TO RESYNC CMAKE AND UPDATE HASHES IN TEST FILES")
print("--------------------------------------------------------")
test_prefixes = args.tests
if args.xml:
test_prefixes = results.get_latest_failed_images()
results.process(test_prefixes, compare=args.compare, move=args.move_files,
compare_program=args.compare_program)
if args.move_files:
print("--------------------------------------------------------")
print("REMEMBER TO RESYNC CMAKE AND UPDATE HASHES IN TEST FILES")
print("--------------------------------------------------------")
if __name__ == "__main__":
main()
+11
View File
@@ -385,6 +385,10 @@ TEST_F(LoadImageTest, UpdateImage2D) {
}
TEST_F(LoadImageTest, UpdateImageSRGB) {
FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Crashing when reading pixels without a redundant call to makeCurrent right before the"
"render pass. b/422798473");
auto& api = getDriverApi();
Cleanup cleanup(api);
api.startCapture();
@@ -469,6 +473,10 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
}
TEST_F(LoadImageTest, UpdateImageMipLevel) {
FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Crashing when reading pixels without a redundant call to makeCurrent right before the"
"render pass. b/422798473");
auto& api = getDriverApi();
Cleanup cleanup(api);
api.startCapture();
@@ -541,6 +549,9 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
}
TEST_F(LoadImageTest, UpdateImage3D) {
FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Crashing when reading pixels without a redundant call to makeCurrent right before the"
"render pass. b/422798473");
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Checkerboard not drawn, possibly due to using wrong z value of 3d texture, "
"see b/417254499");
+53 -3
View File
@@ -86,6 +86,9 @@ public:
std::is_same_v<math::mat3f, T>
>;
template<typename T>
using is_supported_constant_parameter_t = std::enable_if_t<std::is_same_v<bool, T>>;
/**
* Creates a new MaterialInstance using another MaterialInstance as a template for initialization.
* The new MaterialInstance is an instance of the same Material of the template instance and
@@ -237,13 +240,13 @@ public:
/**
* Gets the value of a parameter by name.
*
*
* Note: Only supports non-texture parameters such as numeric and math types.
*
*
* @param name Name of the parameter as defined by Material. Cannot be nullptr.
* @param nameLength Length in `char` of the name parameter.
* @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled.
*
*
* @see Material::hasParameter
*/
template<typename T>
@@ -261,6 +264,53 @@ public:
return getParameter<T>(name, strlen(name));
}
/**
* Set a mutable constant parameter by name.
*
* @param name Name of the parameter as defined by Material. Cannot be nullptr.
* @param nameLength Length in `char` of the name parameter.
* @param value Value of the parameter to set.
* @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled.
*/
template<typename T, typename = is_supported_constant_parameter_t<T>>
void setConstant(const char* UTILS_NONNULL name, size_t nameLength, T const& value);
/** inline helper to provide the name as a null-terminated string literal */
template<typename T, typename = is_supported_constant_parameter_t<T>>
void setConstant(StringLiteral const name, T const& value) {
setConstant<T>(name.data, name.size, value);
}
/** inline helper to provide the name as a null-terminated C string */
template<typename T, typename = is_supported_constant_parameter_t<T>>
void setConstant(const char* UTILS_NONNULL name, T const& value) {
setConstant<T>(name, strlen(name), value);
}
/**
* Gets the value of a mutable constant parameter by name.
*
* @param name Name of the parameter as defined by Material. Cannot be nullptr.
* @param nameLength Length in `char` of the name parameter.
* @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled.
*
* @see Material::hasConstant
*/
template<typename T, typename = is_supported_constant_parameter_t<T>>
T getConstant(const char* UTILS_NONNULL name, size_t nameLength) const;
/** inline helper to provide the name as a null-terminated C string */
template<typename T, typename = is_supported_constant_parameter_t<T>>
T getConstant(StringLiteral const name) const {
return getConstant<T>(name.data, name.size);
}
/** inline helper to provide the name as a null-terminated C string */
template<typename T, typename = is_supported_constant_parameter_t<T>>
T getConstant(const char* UTILS_NONNULL name) const {
return getConstant<T>(name, strlen(name));
}
/**
* Set-up a custom scissor rectangle; by default it is disabled.
*
+40
View File
@@ -232,6 +232,46 @@ template UTILS_PUBLIC mat3f MaterialInstance::getParameter<mat3f> (const ch
// ------------------------------------------------------------------------------------------------
template<>
inline void FMaterialInstance::setConstantImpl<bool>(std::string_view const name, bool const& value) {
std::optional<uint32_t> id = mMaterial->getMutableConstantId(name);
FILAMENT_CHECK_PRECONDITION(id.has_value()) << "No mutable constant with name " << name;
mConstants.set(*id, value);
}
template<typename T, typename>
void MaterialInstance::setConstant(const char* name, size_t nameLength, T const& value) {
downcast(this)->setConstantImpl({ name, nameLength }, value);
}
// Explicit template instantiation of our supported types.
//
// Mutable spec constants will probably only ever allow bools, but it's nice to keep the API
// forwards-compatible.
template UTILS_PUBLIC void MaterialInstance::setConstant<bool>(const char* name, size_t nameLength, bool const& v);
// ------------------------------------------------------------------------------------------------
template<>
inline bool FMaterialInstance::getConstantImpl<bool>(std::string_view const name) const {
std::optional<uint32_t> id = mMaterial->getMutableConstantId(name);
FILAMENT_CHECK_PRECONDITION(id.has_value()) << "No mutable constant with name " << name;
return mConstants[*id];
}
template<typename T, typename>
T MaterialInstance::getConstant(const char* name, size_t nameLength) const {
return downcast(this)->getConstantImpl<T>({ name, nameLength });
}
// Explicit template instantiation of our supported types.
//
// Mutable spec constants will probably only ever allow bools, but it's nice to keep the API
// forwards-compatible.
template UTILS_PUBLIC bool MaterialInstance::getConstant<bool>(const char* name, size_t nameLength) const;
// ------------------------------------------------------------------------------------------------
Material const* MaterialInstance::getMaterial() const noexcept {
return downcast(this)->getMaterial();
}
+37
View File
@@ -229,6 +229,11 @@ bool MaterialParser::getConstants(FixedCapacityVector<MaterialConstant>* contain
return get<ChunkMaterialConstants>(container);
}
bool MaterialParser::getMutableConstants(
FixedCapacityVector<MaterialMutableConstant>* container) const noexcept {
return get<ChunkMaterialMutableConstants>(container);
}
bool MaterialParser::getPushConstants(CString* structVarName,
FixedCapacityVector<MaterialPushConstant>* value) const noexcept {
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialPushConstants);
@@ -751,6 +756,38 @@ bool ChunkMaterialConstants::unflatten(Unflattener& unflattener,
return true;
}
bool ChunkMaterialMutableConstants::unflatten(Unflattener& unflattener,
FixedCapacityVector<MaterialMutableConstant>* materialConstants) {
assert_invariant(materialConstants);
// Read number of constants.
uint64_t numConstants = 0;
if (!unflattener.read(&numConstants)) {
return false;
}
materialConstants->reserve(numConstants);
materialConstants->resize(numConstants);
for (uint64_t i = 0; i < numConstants; i++) {
CString constantName;
bool defaultValue;
if (!unflattener.read(&constantName)) {
return false;
}
if (!unflattener.read(&defaultValue)) {
return false;
}
(*materialConstants)[i].name = std::move(constantName);
(*materialConstants)[i].defaultValue = defaultValue;
}
return true;
}
bool ChunkMaterialPushConstants::unflatten(Unflattener& unflattener,
CString* structVarName,
FixedCapacityVector<MaterialPushConstant>* materialPushConstants) {
+10
View File
@@ -49,6 +49,7 @@ class BufferInterfaceBlock;
class SamplerInterfaceBlock;
struct SubpassInfo;
struct MaterialConstant;
struct MaterialMutableConstant;
struct MaterialPushConstant;
class MaterialParser {
@@ -79,6 +80,8 @@ public:
bool getShaderModels(uint32_t* value) const noexcept;
bool getMaterialProperties(uint64_t* value) const noexcept;
bool getConstants(utils::FixedCapacityVector<MaterialConstant>* value) const noexcept;
bool getMutableConstants(utils::FixedCapacityVector<MaterialMutableConstant>* value)
const noexcept;
bool getPushConstants(utils::CString* structVarName,
utils::FixedCapacityVector<MaterialPushConstant>* value) const noexcept;
@@ -237,6 +240,13 @@ struct ChunkMaterialConstants {
static filamat::ChunkType const tag = filamat::MaterialConstants;
};
struct ChunkMaterialMutableConstants {
static bool unflatten(filaflat::Unflattener& unflattener,
utils::FixedCapacityVector<MaterialMutableConstant>* materialConstants);
using Container = utils::FixedCapacityVector<MaterialMutableConstant>;
static filamat::ChunkType const tag = filamat::MaterialMutableConstants;
};
struct ChunkMaterialPushConstants {
static bool unflatten(filaflat::Unflattener& unflattener, utils::CString* structVarName,
utils::FixedCapacityVector<MaterialPushConstant>* materialPushConstants);
+10 -3
View File
@@ -187,7 +187,10 @@ FMaterial* PostProcessManager::PostProcessMaterial::getMaterial(FEngine& engine,
if (UTILS_UNLIKELY(mSize)) {
loadMaterial(engine);
}
mMaterial->prepareProgram(Variant{ Variant::type_t(variant) });
mMaterial->prepareProgram(Variant{ Variant::type_t(variant) },
// TODO(exv): allow post process materials to use mutable spec constants?
Program::MutableSpecConstantsInfo(0)
);
return mMaterial;
}
@@ -423,7 +426,10 @@ UTILS_NOINLINE
PipelineState PostProcessManager::getPipelineState(
FMaterial const* const ma, PostProcessVariant variant) const noexcept {
return {
.program = ma->getProgram(Variant{ Variant::type_t(variant) }),
.program = ma->getProgram(Variant{ Variant::type_t(variant) },
// TODO(exv): allow post process materials to use mutable spec constants?
Program::MutableSpecConstantsInfo(0)
),
.vertexBufferInfo = mFullScreenQuadVbih,
.pipelineLayout = {
.setLayout = {
@@ -2661,8 +2667,9 @@ void PostProcessManager::TaaJitterCamera(
current.jitter = jitterPosition(previous.frameId);
float2 jitter = current.jitter;
switch (mEngine.getBackend()) {
case Backend::VULKAN:
case Backend::METAL:
case Backend::VULKAN:
case Backend::WEBGPU:
jitter.y = -jitter.y;
UTILS_FALLTHROUGH;
case Backend::OPENGL:
+4 -2
View File
@@ -246,7 +246,8 @@ void RenderPass::appendCommands(FEngine const& engine,
for (Command const* first = curr, *last = curr + commandCount ; first != last ; ++first) {
if (UTILS_LIKELY((first->key & CUSTOM_MASK) == uint64_t(CustomCommand::PASS))) {
auto ma = first->info.mi->getMaterial();
ma->prepareProgram(first->info.materialVariant);
ma->prepareProgram(first->info.materialVariant,
first->info.mi->getMutableSpecConstants());
}
}
}
@@ -1098,7 +1099,8 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
}
assert_invariant(ma);
pipeline.program = ma->getProgram(info.materialVariant);
pipeline.program = ma->getProgram(info.materialVariant,
info.mi->getMutableSpecConstants());
if (UTILS_UNLIKELY(memcmp(&pipeline, &currentPipeline, sizeof(PipelineState)) != 0)) {
currentPipeline = pipeline;
+6 -3
View File
@@ -629,7 +629,8 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
.textureDimension = uint16_t(options.mapSize),
.shadowDimension = uint16_t(options.mapSize - 2u),
.textureSpaceFlipped = engine.getBackend() == Backend::METAL ||
engine.getBackend() == Backend::VULKAN,
engine.getBackend() == Backend::VULKAN ||
engine.getBackend() == Backend::WEBGPU,
.vsm = view.hasVSM()
};
@@ -806,7 +807,8 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, FEngine& engin
.textureDimension = uint16_t(options->mapSize),
.shadowDimension = uint16_t(options->mapSize - 2u),
.textureSpaceFlipped = engine.getBackend() == Backend::METAL ||
engine.getBackend() == Backend::VULKAN,
engine.getBackend() == Backend::VULKAN ||
engine.getBackend() == Backend::WEBGPU,
.vsm = view.hasVSM()
};
@@ -898,7 +900,8 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
.textureDimension = uint16_t(options->mapSize),
.shadowDimension = uint16_t(options->mapSize), // point-lights don't have a border
.textureSpaceFlipped = engine.getBackend() == Backend::METAL ||
engine.getBackend() == Backend::VULKAN,
engine.getBackend() == Backend::VULKAN ||
engine.getBackend() == Backend::WEBGPU,
.vsm = view.hasVSM()
};
+2 -1
View File
@@ -345,7 +345,8 @@ void FEngine::init() {
// Compute a clip-space [-1 to 1] to texture space [0 to 1] matrix, taking into account
// backend differences.
const bool textureSpaceYFlipped = mBackend == Backend::METAL || mBackend == Backend::VULKAN;
const bool textureSpaceYFlipped = mBackend == Backend::METAL || mBackend == Backend::VULKAN ||
mBackend == Backend::WEBGPU;
if (textureSpaceYFlipped) {
mUvFromClipMatrix = mat4f(mat4f::row_major_init{
0.5f, 0.0f, 0.0f, 0.5f,
+4
View File
@@ -718,6 +718,7 @@ public:
// 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 {
@@ -755,6 +756,9 @@ public:
{ "features.engine.debug.assert_destroy_material_before_material_instance",
"Assert when a Material is destroyed but its instances are still alive.",
&features.engine.debug.assert_destroy_material_before_material_instance, false },
{ "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 },
}};
utils::Slice<const FeatureFlag> getFeatureFlags() const noexcept {
+100 -32
View File
@@ -338,8 +338,24 @@ FMaterial::FMaterial(FEngine& engine, const Builder& builder,
processBlendingMode(parser);
processSpecializationConstants(engine, builder, parser);
processMutableSpecializationConstants(engine, builder, parser);
processPushConstants(engine, parser);
processDescriptorSets(engine, parser);
switch (mMaterialDomain) {
case filament::MaterialDomain::SURFACE:
mVariantBits = VARIANT_BITS;
break;
case filament::MaterialDomain::POST_PROCESS:
mVariantBits = POST_PROCESS_VARIANT_BITS;
break;
case filament::MaterialDomain::COMPUTE:
mVariantBits = 0;
break;
}
mCachedPrograms = FixedCapacityVector<backend::Handle<backend::HwProgram>>(
1 << (mVariantBits + mMaterialMutableConstants.size()));
precacheDepthVariants(engine);
#if FILAMENT_ENABLE_MATDBG
@@ -442,13 +458,17 @@ void FMaterial::compile(CompilerPriorityQueue const priority,
UserVariantFilterMask const variantFilter =
~variantSpec & UserVariantFilterMask(UserVariantFilterBit::ALL);
int mutableConstantVariations = 1 << mMaterialMutableConstants.size();
if (UTILS_LIKELY(mEngine.getDriverApi().isParallelShaderCompileSupported())) {
auto const& variants = isVariantLit() ?
VariantUtils::getLitVariants() : VariantUtils::getUnlitVariants();
for (auto const variant: variants) {
if (!variantFilter || variant == Variant::filterUserVariant(variant, variantFilter)) {
if (hasVariant(variant)) {
prepareProgram(variant, priority);
for (int spec = 0; spec < mutableConstantVariations; spec++) {
prepareProgram(variant, Program::MutableSpecConstantsInfo(spec), priority);
}
}
}
}
@@ -489,6 +509,11 @@ FMaterialInstance* FMaterial::getDefaultInstance() noexcept {
return mDefaultMaterialInstance;
}
bool FMaterial::isCached(Variant const variant,
const backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const noexcept {
return bool(mCachedPrograms[getCachedProgramIndex(variant, mutableSpecConstants)]);
}
bool FMaterial::hasParameter(const char* name) const noexcept {
return mUniformInterfaceBlock.hasField(name) ||
mSamplerInterfaceBlock.hasSampler(name) ||
@@ -529,14 +554,15 @@ bool FMaterial::hasVariant(Variant const variant) const noexcept {
}
void FMaterial::prepareProgramSlow(Variant const variant,
const backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
backend::CompilerPriorityQueue const priorityQueue) const noexcept {
assert_invariant(mEngine.hasFeatureLevel(mFeatureLevel));
switch (getMaterialDomain()) {
case MaterialDomain::SURFACE:
getSurfaceProgramSlow(variant, priorityQueue);
getSurfaceProgramSlow(variant, mutableSpecConstants, priorityQueue);
break;
case MaterialDomain::POST_PROCESS:
getPostProcessProgramSlow(variant, priorityQueue);
getPostProcessProgramSlow(variant, mutableSpecConstants, priorityQueue);
break;
case MaterialDomain::COMPUTE:
// TODO: implement MaterialDomain::COMPUTE
@@ -545,6 +571,7 @@ void FMaterial::prepareProgramSlow(Variant const variant,
}
void FMaterial::getSurfaceProgramSlow(Variant const variant,
const backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
CompilerPriorityQueue const priorityQueue) const noexcept {
// filterVariant() has already been applied in generateCommands(), shouldn't be needed here
// if we're unlit, we don't have any bits that correspond to lit materials
@@ -555,25 +582,25 @@ void FMaterial::getSurfaceProgramSlow(Variant const variant,
Variant const vertexVariant = Variant::filterVariantVertex(variant);
Variant const fragmentVariant = Variant::filterVariantFragment(variant);
Program pb{ getProgramWithVariants(variant, vertexVariant, fragmentVariant) };
Program pb{ getProgramWithVariants(variant, vertexVariant, fragmentVariant, mutableSpecConstants) };
pb.priorityQueue(priorityQueue);
pb.multiview(
mEngine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW &&
Variant::isStereoVariant(variant));
createAndCacheProgram(std::move(pb), variant);
createAndCacheProgram(std::move(pb), variant, mutableSpecConstants);
}
void FMaterial::getPostProcessProgramSlow(Variant const variant,
const backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
CompilerPriorityQueue const priorityQueue) const noexcept {
Program pb{ getProgramWithVariants(variant, variant, variant) };
Program pb{ getProgramWithVariants(variant, variant, variant, mutableSpecConstants) };
pb.priorityQueue(priorityQueue);
createAndCacheProgram(std::move(pb), variant);
createAndCacheProgram(std::move(pb), variant, mutableSpecConstants);
}
Program FMaterial::getProgramWithVariants(
Variant variant,
Variant vertexVariant,
Variant fragmentVariant) const {
Program FMaterial::getProgramWithVariants(Variant variant,
Variant vertexVariant, Variant fragmentVariant,
const backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const {
FEngine const& engine = mEngine;
const ShaderModel sm = engine.getShaderModel();
const bool isNoop = engine.getBackend() == Backend::NOOP;
@@ -634,29 +661,34 @@ Program FMaterial::getProgramWithVariants(
mProgramDescriptorBindings[+DescriptorSetBindingPoints::PER_RENDERABLE]);
program.descriptorBindings(+DescriptorSetBindingPoints::PER_MATERIAL,
mProgramDescriptorBindings[+DescriptorSetBindingPoints::PER_MATERIAL]);
program.specializationConstants(mSpecializationConstants);
program.specializationConstants(mSpecializationConstants,
CONFIG_MAX_RESERVED_SPEC_CONSTANTS + mMaterialConstants.size(), mutableSpecConstants);
program.pushConstants(ShaderStage::VERTEX, mPushConstants[uint8_t(ShaderStage::VERTEX)]);
program.pushConstants(ShaderStage::FRAGMENT, mPushConstants[uint8_t(ShaderStage::FRAGMENT)]);
program.cacheId(hash::combine(size_t(mCacheId), variant.key));
program.cacheId(
hash::combine(size_t(mCacheId),
hash::combine(variant.key, mutableSpecConstants.getValue())));
return program;
}
void FMaterial::createAndCacheProgram(Program&& p, Variant const variant) const noexcept {
void FMaterial::createAndCacheProgram(Program&& p, Variant const variant,
Program::MutableSpecConstantsInfo const mutableSpecConstants) const noexcept {
FEngine const& engine = mEngine;
DriverApi& driverApi = mEngine.getDriverApi();
bool const isShared = isSharedVariant(variant);
size_t const cacheIndex = getCachedProgramIndex(variant, mutableSpecConstants);
// Check if the default material has this program cached
if (isShared) {
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial();
if (pDefaultMaterial) {
auto const program = pDefaultMaterial->mCachedPrograms[variant.key];
auto const program = pDefaultMaterial->mCachedPrograms[cacheIndex];
if (program) {
mCachedPrograms[variant.key] = program;
mCachedPrograms[cacheIndex] = program;
return;
}
}
@@ -665,17 +697,17 @@ void FMaterial::createAndCacheProgram(Program&& p, Variant const variant) const
auto const program = driverApi.createProgram(std::move(p));
driverApi.setDebugTag(program.getId(), mName);
assert_invariant(program);
mCachedPrograms[variant.key] = program;
mCachedPrograms[cacheIndex] = program;
// If the default material doesn't already have this program cached, and all caching conditions
// are met (Surface Domain and no custom depth shader), cache it now.
// New Materials will inherit these program automatically.
if (isShared) {
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial();
if (pDefaultMaterial && !pDefaultMaterial->mCachedPrograms[variant.key]) {
if (pDefaultMaterial && !pDefaultMaterial->mCachedPrograms[cacheIndex]) {
// set the tag to the default material name
driverApi.setDebugTag(program.getId(), mName);
pDefaultMaterial->mCachedPrograms[variant.key] = program;
pDefaultMaterial->mCachedPrograms[cacheIndex] = program;
}
}
}
@@ -723,6 +755,14 @@ size_t FMaterial::getParameters(ParameterInfo* parameters, size_t count) const n
return count;
}
std::optional<uint32_t> FMaterial::getMutableConstantId(std::string_view name) const noexcept {
auto const pos = mMutableSpecializationConstantsNameToIndex.find(name);
if (pos != mMutableSpecializationConstantsNameToIndex.end()) {
return pos->second;
}
return std::nullopt;
}
#if FILAMENT_ENABLE_MATDBG
// Swaps in an edited version of the original package that was used to create the material. The
@@ -781,7 +821,9 @@ void FMaterial::onQueryCallback(void* userdata, VariantList* pActiveVariants) {
#endif // FILAMENT_ENABLE_MATDBG
[[nodiscard]] Handle<HwProgram> FMaterial::getProgramWithMATDBG(Variant const variant) const noexcept {
[[nodiscard]] Handle<HwProgram> FMaterial::getProgramWithMATDBG(Variant const variant,
Program::MutableSpecConstantsInfo const mutableSpecConstants) const noexcept {
size_t cacheIndex = getCachedProgramIndex(variant, mutableSpecConstants);
#if FILAMENT_ENABLE_MATDBG
assert_invariant((size_t)variant.key < VARIANT_COUNT);
std::unique_lock lock(mActiveProgramsLock);
@@ -796,13 +838,13 @@ void FMaterial::onQueryCallback(void* userdata, VariantList* pActiveVariants) {
lock.unlock();
if (isSharedVariant(variant)) {
FMaterial const* const pDefaultMaterial = mEngine.getDefaultMaterial();
if (pDefaultMaterial && pDefaultMaterial->mCachedPrograms[variant.key]) {
return pDefaultMaterial->getProgram(variant);
if (pDefaultMaterial && pDefaultMaterial->mCachedPrograms[cacheIndex]) {
return pDefaultMaterial->getProgram(variant, mutableSpecConstants);
}
}
#endif
assert_invariant(mCachedPrograms[variant.key]);
return mCachedPrograms[variant.key];
assert_invariant(mCachedPrograms[cacheIndex]);
return mCachedPrograms[cacheIndex];
}
void FMaterial::destroyPrograms(FEngine& engine,
@@ -815,7 +857,7 @@ void FMaterial::destroyPrograms(FEngine& engine,
case MaterialDomain::SURFACE: {
if (mIsDefaultMaterial || mHasCustomDepthShader) {
// default material, or we have custom depth shaders, we destroy all variants
for (size_t k = 0, n = VARIANT_COUNT; k < n; ++k) {
for (size_t k = 0, n = cachedPrograms.size(); k < n; ++k) {
if ((k & variantMask) == variantValue) {
// Only destroy if the handle is valid. Not strictly needed, but we have a lot
// of variants, and this generates traffic in the command queue.
@@ -833,7 +875,7 @@ void FMaterial::destroyPrograms(FEngine& engine,
UTILS_UNUSED_IN_RELEASE
auto const UTILS_NULLABLE pDefaultMaterial = engine.getDefaultMaterial();
for (size_t k = 0, n = VARIANT_COUNT; k < n; ++k) {
for (size_t k = 0, n = cachedPrograms.size(); k < n; ++k) {
if ((k & variantMask) == variantValue) {
// Only destroy if the handle is valid. Not strictly needed, but we have a lot
// of variant, and this generates traffic in the command queue.
@@ -845,7 +887,7 @@ void FMaterial::destroyPrograms(FEngine& engine,
// During Engine::shutdown, auto-cleanup destroys the
// default material first
assert_invariant(!pDefaultMaterial ||
pDefaultMaterial->mCachedPrograms[k]);
pDefaultMaterial->mCachedPrograms[k & (VARIANT_COUNT - 1)]);
// we don't own this variant, skip, but clear the entry.
cachedPrograms[k].clear();
continue;
@@ -859,7 +901,7 @@ void FMaterial::destroyPrograms(FEngine& engine,
break;
}
case MaterialDomain::POST_PROCESS: {
for (size_t k = 0, n = POST_PROCESS_VARIANT_COUNT; k < n; ++k) {
for (size_t k = 0, n = cachedPrograms.size(); k < n; ++k) {
if ((k & variantMask) == variantValue) {
// Only destroy if the handle is valid. Not strictly needed, but we have a lot
// of variant, and this generates traffic in the command queue.
@@ -871,8 +913,13 @@ void FMaterial::destroyPrograms(FEngine& engine,
break;
}
case MaterialDomain::COMPUTE: {
// Compute programs don't have variants
driverApi.destroyProgram(std::move(cachedPrograms[0]));
for (size_t k = 0, n = cachedPrograms.size(); k < n; ++k) {
// Only destroy if the handle is valid. Not strictly needed, but we have a lot
// of variant, and this generates traffic in the command queue.
if (cachedPrograms[k]) {
driverApi.destroyProgram(std::move(cachedPrograms[k]));
}
}
break;
}
}
@@ -1107,6 +1154,19 @@ void FMaterial::processSpecializationConstants(FEngine& engine, Builder const& b
}
}
void FMaterial::processMutableSpecializationConstants(FEngine& engine, Builder const& builder,
MaterialParser const* const parser) {
// Older materials won't have a mutable constants chunk, but that's okay.
parser->getMutableConstants(&mMaterialMutableConstants);
for (size_t i = 0, c = mMaterialMutableConstants.size(); i < c; i++) {
auto& item = mMaterialMutableConstants[i];
// the key can be a string_view because mMaterialMutableConstants owns the CString
std::string_view const key{ item.name.data(), item.name.size() };
mMutableSpecializationConstantsNameToIndex[key] = i;
mDefaultMutableSpecializationConstants.set(i, item.defaultValue);
}
}
void FMaterial::processPushConstants(FEngine&, MaterialParser const* parser) {
FixedCapacityVector<Program::PushConstant>& vertexConstants =
mPushConstants[uint8_t(ShaderStage::VERTEX)];
@@ -1162,7 +1222,8 @@ void FMaterial::precacheDepthVariants(FEngine& engine) {
}
assert_invariant(Variant::isValidDepthVariant(variant));
if (hasVariant(variant)) {
prepareProgram(variant);
// The default material shouldn't ever have mutable spec constants.
prepareProgram(variant, Program::MutableSpecConstantsInfo(0));
}
}
return;
@@ -1175,9 +1236,16 @@ void FMaterial::precacheDepthVariants(FEngine& engine) {
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial();
assert_invariant(pDefaultMaterial);
auto const allDepthVariants = VariantUtils::getDepthVariants();
int mutableConstantVariations = 1 << mMaterialMutableConstants.size();
for (auto const variant: allDepthVariants) {
assert_invariant(Variant::isValidDepthVariant(variant));
mCachedPrograms[variant.key] = pDefaultMaterial->mCachedPrograms[variant.key];
size_t defaultCacheIndex = pDefaultMaterial->getCachedProgramIndex(variant,
Program::MutableSpecConstantsInfo(0));
for (int spec = 0; spec < mutableConstantVariations; spec++) {
size_t cacheIndex = getCachedProgramIndex(variant,
Program::MutableSpecConstantsInfo(spec));
mCachedPrograms[cacheIndex] = pDefaultMaterial->mCachedPrograms[defaultCacheIndex];
}
}
}
}
+46 -14
View File
@@ -122,9 +122,8 @@ public:
FEngine& getEngine() const noexcept { return mEngine; }
bool isCached(Variant const variant) const noexcept {
return bool(mCachedPrograms[variant.key]);
}
bool isCached(Variant const variant,
const backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const noexcept;
void invalidate(Variant::type_t variantMask = 0, Variant::type_t variantValue = 0) noexcept;
@@ -132,26 +131,31 @@ public:
// Must be called outside of backend render pass.
// Must be called before getProgram() below.
void prepareProgram(Variant const variant,
backend::Program::MutableSpecConstantsInfo const mutableSpecConstants,
backend::CompilerPriorityQueue const priorityQueue = CompilerPriorityQueue::HIGH) const noexcept {
// prepareProgram() is called for each RenderPrimitive in the scene, so it must be efficient.
if (UTILS_UNLIKELY(!isCached(variant))) {
prepareProgramSlow(variant, priorityQueue);
if (UTILS_UNLIKELY(!isCached(variant, mutableSpecConstants))) {
prepareProgramSlow(variant, mutableSpecConstants, priorityQueue);
}
}
// getProgram returns the backend program for the material's given variant.
// getProgram returns the backend program for the material's given variant and set of mutable
// spec constants.
// Must be called after prepareProgram().
[[nodiscard]]
backend::Handle<backend::HwProgram> getProgram(Variant const variant) const noexcept {
backend::Handle<backend::HwProgram> getProgram(Variant const variant,
backend::Program::MutableSpecConstantsInfo const mutableSpecConstants) const noexcept {
#if FILAMENT_ENABLE_MATDBG
return getProgramWithMATDBG(variant);
return getProgramWithMATDBG(variant, mutableSpecConstants);
#endif
assert_invariant(mCachedPrograms[variant.key]);
return mCachedPrograms[variant.key];
size_t index = getCachedProgramIndex(variant, mutableSpecConstants);
assert_invariant(mCachedPrograms[index]);
return mCachedPrograms[index];
}
[[nodiscard]]
backend::Handle<backend::HwProgram> getProgramWithMATDBG(Variant variant) const noexcept;
backend::Handle<backend::HwProgram> getProgramWithMATDBG(Variant variant,
backend::Program::MutableSpecConstantsInfo const mutableSpecConstants) const noexcept;
bool isVariantLit() const noexcept { return mIsVariantLit; }
@@ -208,6 +212,12 @@ public:
}
size_t getParameters(ParameterInfo* parameters, size_t count) const noexcept;
uint32_t getMutableConstantCount() const noexcept { return mMaterialMutableConstants.size(); }
std::optional<uint32_t> getMutableConstantId(std::string_view name) const noexcept;
backend::Program::MutableSpecConstantsInfo getDefaultMutableConstants() const noexcept {
return mDefaultMutableSpecializationConstants;
}
uint32_t generateMaterialInstanceId() const noexcept { return mMaterialInstanceId++; }
void destroyPrograms(FEngine& engine,
@@ -261,34 +271,48 @@ public:
private:
bool hasVariant(Variant variant) const noexcept;
void prepareProgramSlow(Variant variant,
backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
CompilerPriorityQueue priorityQueue) const noexcept;
void getSurfaceProgramSlow(Variant variant,
backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
CompilerPriorityQueue priorityQueue) const noexcept;
void getPostProcessProgramSlow(Variant variant,
backend::Program::MutableSpecConstantsInfo mutableSpecConstants,
CompilerPriorityQueue priorityQueue) const noexcept;
backend::Program getProgramWithVariants(Variant variant,
Variant vertexVariant, Variant fragmentVariant) const;
Variant vertexVariant, Variant fragmentVariant,
backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const;
void processBlendingMode(MaterialParser const* parser);
void processSpecializationConstants(FEngine& engine, Builder const& builder,
MaterialParser const* parser);
void processMutableSpecializationConstants(FEngine& engine, Builder const& builder,
MaterialParser const* parser);
void processPushConstants(FEngine& engine, MaterialParser const* parser);
void precacheDepthVariants(FEngine& engine);
void processDescriptorSets(FEngine& engine, MaterialParser const* parser);
void createAndCacheProgram(backend::Program&& p, Variant variant) const noexcept;
void createAndCacheProgram(backend::Program&& p, Variant variant,
backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const noexcept;
inline bool isSharedVariant(Variant const variant) const {
return (mMaterialDomain == MaterialDomain::SURFACE) && !mIsDefaultMaterial &&
!mHasCustomDepthShader && Variant::isValidDepthVariant(variant);
}
inline size_t getCachedProgramIndex(const Variant variant,
const backend::Program::MutableSpecConstantsInfo mutableSpecConstants) const noexcept {
return static_cast<size_t>(variant.key) |
(static_cast<size_t>(mutableSpecConstants.getValue()) << mVariantBits);
}
// try to order by frequency of use
mutable std::array<backend::Handle<backend::HwProgram>, VARIANT_COUNT> mCachedPrograms;
mutable utils::FixedCapacityVector<backend::Handle<backend::HwProgram>> mCachedPrograms;
DescriptorSetLayout mPerViewDescriptorSetLayout;
DescriptorSetLayout mPerViewDescriptorSetLayoutVsm;
DescriptorSetLayout mDescriptorSetLayout;
@@ -324,6 +348,7 @@ private:
bool mHasCustomDepthShader = false;
bool mIsDefaultMaterial = false;
bool mSpecularAntiAliasing = false;
uint8_t mVariantBits;
// reserve some space to construct the default material instance
mutable FMaterialInstance* mDefaultMaterialInstance = nullptr;
@@ -348,6 +373,13 @@ private:
// current specialization constants for the HwProgram
utils::FixedCapacityVector<backend::Program::SpecializationConstant> mSpecializationConstants;
// Mutable constants defined by this Material.
utils::FixedCapacityVector<MaterialMutableConstant> mMaterialMutableConstants;
// A map from the Constant name to the mMutableMaterialConstant index
std::unordered_map<std::string_view, uint32_t> mMutableSpecializationConstantsNameToIndex;
// Default values for the mutable constants.
backend::Program::MutableSpecConstantsInfo mDefaultMutableSpecializationConstants;
// current push constants for the HwProgram
std::array<utils::FixedCapacityVector<backend::Program::PushConstant>,
backend::Program::SHADER_TYPE_COUNT>
+3 -1
View File
@@ -63,6 +63,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
const char* name) noexcept
: mMaterial(material),
mDescriptorSet("MaterialInstance", material->getDescriptorSetLayout()),
mConstants(material->getDefaultMutableConstants()),
mCulling(CullingMode::BACK),
mShadowCulling(CullingMode::BACK),
mDepthFunc(RasterState::DepthFunc::LE),
@@ -128,6 +129,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
mTextureParameters(other->mTextureParameters),
mDescriptorSet(other->mDescriptorSet.duplicate(
"MaterialInstance", mMaterial->getDescriptorSetLayout())),
mConstants(other->mConstants),
mPolygonOffset(other->mPolygonOffset),
mStencilState(other->mStencilState),
mMaskThreshold(other->mMaskThreshold),
@@ -429,7 +431,7 @@ void FMaterialInstance::fixMissingSamplers() const {
FEngine const& engine = mMaterial->getEngine();
filament::DescriptorSetLayout const& layout = mMaterial->getDescriptorSetLayout();
if (pos->format != SamplerFormat::FLOAT) {
if (pos->format == SamplerFormat::FLOAT) {
// TODO: we only handle missing samplers that are FLOAT
switch (pos->type) {
case SamplerType::SAMPLER_2D:
+13 -1
View File
@@ -31,6 +31,7 @@
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <backend/Program.h>
#include <utils/BitmaskEnum.h>
#include <utils/bitset.h>
@@ -66,7 +67,7 @@ public:
void terminate(FEngine& engine);
void commitStreamUniformAssociations(FEngine::DriverApi& driver);
void commit(FEngine& engine) const;
void commit(FEngine::DriverApi& driver) const;
@@ -242,6 +243,10 @@ public:
using MaterialInstance::setParameter;
const backend::Program::MutableSpecConstantsInfo getMutableSpecConstants() const noexcept {
return mConstants;
}
private:
friend class FMaterial;
friend class MaterialInstance;
@@ -264,6 +269,12 @@ private:
template<typename T>
T getParameterImpl(std::string_view name) const;
template<typename T>
void setConstantImpl(std::string_view name, T const& value);
template<typename T>
T getConstantImpl(std::string_view name) const;
// keep these grouped, they're accessed together in the render-loop
FMaterial const* mMaterial = nullptr;
@@ -277,6 +288,7 @@ private:
mutable DescriptorSet mDescriptorSet;
UniformBuffer mUniforms;
bool mHasStreamUniformAssociations = false;
backend::Program::MutableSpecConstantsInfo mConstants;
backend::PolygonOffset mPolygonOffset{};
backend::StencilState mStencilState{};
+25 -15
View File
@@ -428,22 +428,32 @@ void FTexture::setImage(FEngine& engine, size_t const level,
<< ") > texture depth (" << effectiveTextureDepthOrLayers << ") at level ("
<< unsigned(level) << ")";
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 bpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment);
size_t const bpl = bpr * height; // TODO: PBD should have a "layer stride"
// TODO: PBD should have a p.depth (# layers to skip)
FILAMENT_CHECK_PRECONDITION(bpp * p.left + bpr * p.top + bpl * (0 + depth) <= p.size)
<< "buffer overflow: (size=" << size_t(p.size) << ", stride=" << size_t(p.stride)
<< ", left=" << unsigned(p.left) << ", top=" << unsigned(p.top)
<< ") smaller than specified region "
"{{"
<< unsigned(xoffset) << "," << unsigned(yoffset) << "," << unsigned(zoffset) << "},{"
<< unsigned(width) << "," << unsigned(height) << "," << unsigned(depth) << ")}}";
if (p.type != PixelDataType::COMPRESSED) {
using PBD = PixelBufferDescriptor;
size_t const stride = p.stride ? p.stride : width;
size_t const bpp = PBD::computePixelSize(p.format, p.type);
size_t const bpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment);
size_t const pbdWidth = stride;
// TODO: PBD should have a "layer stride" (using "depth" as a substitute).
size_t const pbdDepth = depth;
size_t const pbdHeight = p.size / bpr / pbdDepth;
assert_invariant(p.size % bpr == 0 && (p.size / bpr) % pbdDepth == 0);
engine.getDriverApi().update3DImage(mHandle,
uint8_t(level), xoffset, yoffset, zoffset, width, height, depth, std::move(p));
// TODO: PBD should have a p.depth (# layers to skip)
FILAMENT_CHECK_PRECONDITION(
bpp * (pbdWidth - p.left) * (pbdHeight - p.top) * (pbdDepth - 0) >=
bpp * (width - xoffset) * (height - yoffset) * (depth - zoffset))
<< "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)
<< ")}}";
}
engine.getDriverApi().update3DImage(mHandle, uint8_t(level), xoffset, yoffset, zoffset, width,
height, depth, std::move(p));
// this method shouldn't have been const
const_cast<FTexture*>(this)->updateLodRange(level);
+17 -2
View File
@@ -47,6 +47,13 @@
namespace filament {
namespace {
// TODO: reconcile this value (defined in VertexBuffer.h) with DriverEnums' MAX_VERTEX_BUFFER_COUNT
constexpr size_t DOCUMENTED_MAX_VERTEX_BUFFER_COUNT = 8;
} // anonymous
using namespace backend;
using namespace filament::math;
@@ -148,8 +155,16 @@ VertexBuffer::Builder& VertexBuffer::Builder::name(utils::StaticString const& na
VertexBuffer* VertexBuffer::Builder::build(Engine& engine) {
FILAMENT_CHECK_PRECONDITION(mImpl->mVertexCount > 0) << "vertexCount cannot be 0";
FILAMENT_CHECK_PRECONDITION(mImpl->mBufferCount > 0) << "bufferCount cannot be 0";
FILAMENT_CHECK_PRECONDITION(mImpl->mBufferCount <= MAX_VERTEX_BUFFER_COUNT)
<< "bufferCount cannot be more than " << MAX_VERTEX_BUFFER_COUNT;
static_assert(DOCUMENTED_MAX_VERTEX_BUFFER_COUNT <= MAX_VERTEX_BUFFER_COUNT);
if (downcast(engine).features.engine.debug.assert_vertex_buffer_count_exceeds_8) {
FILAMENT_CHECK_PRECONDITION(mImpl->mBufferCount <= DOCUMENTED_MAX_VERTEX_BUFFER_COUNT)
<< "bufferCount cannot be more than " << DOCUMENTED_MAX_VERTEX_BUFFER_COUNT;
} else if (mImpl->mBufferCount > DOCUMENTED_MAX_VERTEX_BUFFER_COUNT) {
utils::slog.w << "bufferCount cannot be more than " << DOCUMENTED_MAX_VERTEX_BUFFER_COUNT
<< utils::io::endl;
}
// Next we check if any unused buffer slots have been allocated. This helps prevent errors
// because uploading to an unused slot can trigger undefined behavior in the backend.
+2 -2
View File
@@ -47,8 +47,8 @@ void FSR_ScalingSetup(FSRUniforms* outUniforms, FSRScalingConfig config) noexcep
// in which case it uses the left-bottom offset.
auto yoffset = config.input.bottom;
if (config.backend == backend::Backend::METAL ||
config.backend == backend::Backend::VULKAN) {
if (config.backend == backend::Backend::METAL || config.backend == backend::Backend::VULKAN ||
config.backend == backend::Backend::WEBGPU) {
yoffset = config.inputHeight - (config.input.bottom + config.input.height);
}
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.60.1"
spec.version = "1.61.0"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.60.1/filament-v1.60.1-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.61.0/filament-v1.61.0-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {
@@ -54,6 +54,7 @@ enum UTILS_PUBLIC ChunkType : uint64_t {
MaterialDescriptorSetLayoutInfo = charTo64bitNum("MAT_DSLI"),
MaterialProperties = charTo64bitNum("MAT_PROP"),
MaterialConstants = charTo64bitNum("MAT_CONS"),
MaterialMutableConstants = charTo64bitNum("MAT_MCNS"),
MaterialPushConstants = charTo64bitNum("MAT_PCON"),
MaterialName = charTo64bitNum("MAT_NAME"),
@@ -30,7 +30,19 @@ struct MaterialConstant {
ConstantType type;
MaterialConstant() = default;
MaterialConstant(const char* name, ConstantType type) : name(name), type(type) {}
MaterialConstant(utils::CString name, ConstantType type) : name(std::move(name)), type(type) {}
};
struct MaterialMutableConstant {
utils::CString name;
// The Filament engine stores mutable spec constants as a bitfield, meaning that must override
// all of the default values built into in the shader souces. Therefore, unlike the immutable
// constants above, we have to store the default values in the material metadata.
bool defaultValue;
MaterialMutableConstant() = default;
MaterialMutableConstant(utils::CString name, bool defaultValue)
: name(std::move(name)), defaultValue(defaultValue) {}
};
}
@@ -307,14 +307,15 @@ public:
MaterialBuilder& parameter(const char* name, size_t size, UniformType type,
ParameterPrecision precision = ParameterPrecision::DEFAULT);
//! Add a constant parameter to this material.
//! Add a (mutable) constant parameter to this material.
template<typename T>
using is_supported_constant_parameter_t = typename std::enable_if<
std::is_same<int32_t, T>::value ||
std::is_same<float, T>::value ||
std::is_same<bool, T>::value>::type;
template<typename T, typename = is_supported_constant_parameter_t<T>>
MaterialBuilder& constant(const char *name, ConstantType type, T defaultValue = 0);
MaterialBuilder& constant(const char* name, ConstantType type, bool isMutable,
T defaultValue = 0);
/**
* Add a sampler parameter to this material.
@@ -871,6 +872,7 @@ private:
PropertyList mProperties;
ParameterList mParameters;
ConstantList mConstants;
ConstantList mMutableConstants;
PushConstantList mPushConstants;
SubpassList mSubpasses;
VariableList mVariables;
+29 -12
View File
@@ -317,12 +317,19 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType sample
}
template<typename T, typename>
MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType const type, T defaultValue) {
auto result = std::find_if(mConstants.begin(), mConstants.end(), [name](const Constant& c) {
return c.name == CString(name);
});
FILAMENT_CHECK_POSTCONDITION(result == mConstants.end())
MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType const type,
bool isMutable, T defaultValue) {
ConstantList& constants = isMutable ? mMutableConstants : mConstants;
const ConstantList& otherConstants = isMutable ? mConstants : mMutableConstants;
auto result = std::find_if(constants.begin(), constants.end(),
[name](const Constant& c) { return c.name == CString(name); });
FILAMENT_CHECK_POSTCONDITION(result == constants.end())
<< "There is already a constant parameter present with the name " << name << ".";
auto otherResult = std::find_if(otherConstants.begin(), otherConstants.end(),
[name](const Constant& c) { return c.name == CString(name); });
FILAMENT_CHECK_POSTCONDITION(otherResult == otherConstants.end())
<< "There is already a constant parameter present with the name " << name << ".";
Constant constant {
.name = CString(name),
.type = type,
@@ -354,15 +361,20 @@ MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType const
assert_invariant(false);
}
mConstants.push_back(constant);
if (isMutable) {
FILAMENT_CHECK_POSTCONDITION(type == ConstantType::BOOL)
<< "Mutable spec constants must be bool type.";
}
constants.push_back(constant);
return *this;
}
template MaterialBuilder& MaterialBuilder::constant<int32_t>(
const char* name, ConstantType type, int32_t defaultValue);
const char* name, ConstantType type, bool isMutable, int32_t defaultValue);
template MaterialBuilder& MaterialBuilder::constant<float>(
const char* name, ConstantType type, float defaultValue);
const char* name, ConstantType type, bool isMutable, float defaultValue);
template MaterialBuilder& MaterialBuilder::constant<bool>(
const char* name, ConstantType type, bool defaultValue);
const char* name, ConstantType type, bool isMutable, bool defaultValue);
MaterialBuilder& MaterialBuilder::buffer(BufferInterfaceBlock bib) {
FILAMENT_CHECK_POSTCONDITION(mBuffers.size() < MAX_BUFFERS_COUNT) << "Too many buffers";
@@ -907,7 +919,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
BlobDictionary spirvDictionary;
// End: must be protected by lock
ShaderGenerator sg(mProperties, mVariables, mOutputs, mDefines, mConstants, mPushConstants,
ShaderGenerator sg(mProperties, mVariables, mOutputs, mDefines, mConstants, mMutableConstants, mPushConstants,
mMaterialFragmentCode.getResolved(), mMaterialFragmentCode.getLineOffset(),
mMaterialVertexCode.getResolved(), mMaterialVertexCode.getLineOffset(),
mMaterialDomain);
@@ -1492,7 +1504,7 @@ bool MaterialBuilder::needsStandardDepthProgram() const noexcept {
std::string MaterialBuilder::peek(backend::ShaderStage const stage,
const CodeGenParams& params, const PropertyList& properties) noexcept {
ShaderGenerator const sg(properties, mVariables, mOutputs, mDefines, mConstants, mPushConstants,
ShaderGenerator const sg(properties, mVariables, mOutputs, mDefines, mConstants, mMutableConstants, mPushConstants,
mMaterialFragmentCode.getResolved(), mMaterialFragmentCode.getLineOffset(),
mMaterialVertexCode.getResolved(), mMaterialVertexCode.getLineOffset(),
mMaterialDomain);
@@ -1618,9 +1630,14 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo&
// User constant parameters
FixedCapacityVector<MaterialConstant> constantsEntry(mConstants.size());
std::transform(mConstants.begin(), mConstants.end(), constantsEntry.begin(),
[](Constant const& c) { return MaterialConstant(c.name.c_str(), c.type); });
[](Constant const& c) { return MaterialConstant(c.name, c.type); });
container.push<MaterialConstantParametersChunk>(std::move(constantsEntry));
FixedCapacityVector<MaterialMutableConstant> mutableConstantsEntry(mMutableConstants.size());
std::transform(mMutableConstants.begin(), mMutableConstants.end(), mutableConstantsEntry.begin(),
[](Constant const& c) { return MaterialMutableConstant(c.name, c.defaultValue.b); });
container.push<MaterialMutableConstantParametersChunk>(std::move(mutableConstantsEntry));
FixedCapacityVector<MaterialPushConstant> pushConstantsEntry(mPushConstants.size());
std::transform(mPushConstants.begin(), mPushConstants.end(), pushConstantsEntry.begin(),
[](PushConstant const& c) {
@@ -117,6 +117,20 @@ void MaterialConstantParametersChunk::flatten(Flattener& f) {
// ------------------------------------------------------------------------------------------------
MaterialMutableConstantParametersChunk::MaterialMutableConstantParametersChunk(
FixedCapacityVector<MaterialMutableConstant> constants)
: Chunk(MaterialMutableConstants), mConstants(std::move(constants)) {}
void MaterialMutableConstantParametersChunk::flatten(Flattener& f) {
f.writeUint64(mConstants.size());
for (const auto& constant : mConstants) {
f.writeString(constant.name.c_str());
f.writeBool(constant.defaultValue);
}
}
// ------------------------------------------------------------------------------------------------
MaterialPushConstantParametersChunk::MaterialPushConstantParametersChunk(
CString const& structVarName, FixedCapacityVector<MaterialPushConstant> constants)
: Chunk(MaterialPushConstants),

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