Compare commits

...

93 Commits

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

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

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


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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Powei Feng <powei@google.com>
2025-06-12 17:55:35 +00:00
Juan Caldas
921dbddad9 webgpu: add condition to sample (#8860)
* Add option for enabling MSAA
2025-06-12 12:14:54 -04:00
Andy Hovingh
6406d4602c webgpu: texture changes to better support mipmap generation later 2025-06-11 22:35:42 -05:00
Sungun Park
880cd66166 Release Filament 1.61.1 2025-06-12 00:31:33 +00:00
Juan Caldas
f27ff203cf webgpu: Enable multisampling in the sample (#8854) 2025-06-11 23:09:59 +00:00
Ben Doherty
3ea8e529cc Switch filament and backend to Abseil logging (#8691) 2025-06-11 10:08:52 -07:00
Jeremy Nelson
572dd233d7 adding check for Stencil8 2025-06-10 20:06:00 -07:00
Jeremy Nelson
c19070e477 better default cases for loadOp and storeOp 2025-06-10 20:06:00 -07:00
Jeremy Nelson
64328b7388 pass through defaultDepthStencilFormat 2025-06-10 20:06:00 -07:00
Powei Feng
1414fdcd38 github: fix commit message action (#8846)
Checkout sha instead of the branch
2025-06-10 22:55:48 +00:00
Matthew Hoffman
e607a035ae Add TODOs for backend test features that shouldn't be made yet. (#8829)
Some edge case handling features are possibly needed, but should only be made
once they have use cases that exercise that behavior. So for now just
leave TODOs explaining where to add it in the future.

BUGS=[422804941,422803382]
2025-06-10 21:43:47 +00:00
Ben Doherty
0496e089c1 Remove old screenshot output for readPixels backend test (#8840) 2025-06-10 13:30:00 -07: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
rafadevai
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 Goyal
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
Juan Caldas
c73d11858e Webgpu: Move Filter mode check (#8804)
BUGS=[397432947]
2025-06-02 14:10:33 +00:00
Powei Feng
23b67be41a Fix always bind uniform logic in MaterialInstance (#8801)
The logic for duplicating UBO was omitted after #8739
2025-06-01 23:27:55 -07:00
bridgewaterrobbie
7d53baad5c Add float32 filtering feature, so that we can safely assume non-int non-depth types are filtering. 2025-05-30 17:53:04 -04:00
doriswu
1ae33a23fe Use highp for square distance 2025-05-30 13:52:44 -07:00
Rafael Dominguez
351d9287af Update the clang-format rule for comments
Make sure theres a space before the comment

So now instead of getting
 #endif// comment

it will be
 #endif // comment
2025-05-30 13:52:01 -07:00
Mathias Agopian
e7e5004946 attempt to fix external streams with protected context
There was several issues:

1) when we're switching contexts (e.g. between protect and regular) we
   needed up reattach all SurfaceView (i.e. streams), because they need
   to be attached on currently active context.

2) reattaching, because it's implemented as detach + attach, would 
   destroy the current gl texture id and create a new one. However,
   because of the way descriptor-sets were implemented, that GL
   texture id was kept inside the descriptor, later leading to using
   a destroyed texture id.
   The fix here is to store texture handles in descriptors, so that
   we can update the id independently. 

3) we also needed to invalidate all bound descriptor sets because it's
   now possible for descriptor sets to have outdated descriptors
2025-05-30 13:15:40 -07:00
242 changed files with 8538 additions and 3851 deletions

View File

@@ -57,7 +57,7 @@ SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 0
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false

View File

@@ -0,0 +1,45 @@
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: Find commit message (PR)
shell: bash
id: checkout_code
if: github.event_name == 'pull_request'
run: |
echo "+++++ head commit message +++++"
echo "$(git log -1 --no-merges)"
echo "+++++++++++++++++++++++++++++++"
echo "hash=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
git checkout ${{ github.event.pull_request.head.sha }}
echo "$(git log -1 --no-merges)" >> /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: Cleanup Find commit message (PR)
shell: bash
if: github.event_name == 'pull_request'
run: |
git checkout ${{ steps.checkout_code.outputs.hash }}

35
.github/workflows/postsubmit.yml vendored Normal file
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

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,9 +109,9 @@ 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
@@ -121,20 +120,21 @@ jobs:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- id: get_commit_msg
uses: ./.github/actions/get-commit-msg
- 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

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:

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

View File

@@ -49,6 +49,8 @@ option(FILAMENT_SUPPORTS_OSMESA "Enable OSMesa (headless GL context) for Filamen
option(FILAMENT_ENABLE_FGVIEWER "Enable the frame graph viewer" OFF)
option(FILAMENT_USE_ABSEIL_LOGGING "Use Abseil to log, may increase binary size" OFF)
# This is to disable GTAO for the short-term while we investigate a way to better manage size increases.
# On the regular filament build (where size is of less concern), we enable GTAO by default.
option(FILAMENT_DISABLE_GTAO "Disable GTAO" OFF)
@@ -591,6 +593,10 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT FILAMENT_BACKEND_DEBUG_FLAG STREQU
unset(FILAMENT_BACKEND_DEBUG_FLAG)
endif()
if (FILAMENT_USE_ABSEIL_LOGGING)
add_definitions(-DFILAMENT_USE_ABSEIL_LOGGING)
endif()
# ==================================================================================================
# Material compilation flags
# ==================================================================================================

View File

@@ -1,9 +1,9 @@
# 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
## Release notes for next branch cut

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.1'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.60.1'
pod 'Filament', '~> 1.61.1'
```
## Documentation

View File

@@ -7,6 +7,13 @@ 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.2
- samples: samples now have a CLI to select backend api
## v1.61.1
## v1.61.0
- materials: sampler now export their type in the material binary [⚠️ **New Material Version**]

View File

@@ -59,6 +59,10 @@ add_library(smol-v STATIC IMPORTED)
set_target_properties(smol-v PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libsmol-v.a)
add_library(abseil STATIC IMPORTED)
set_target_properties(abseil PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libabseil.a)
if (FILAMENT_ENABLE_FGVIEWER)
add_library(fgviewer STATIC IMPORTED)
set_target_properties(fgviewer PROPERTIES IMPORTED_LOCATION
@@ -128,6 +132,7 @@ target_link_libraries(filament-jni
PRIVATE jnigraphics
PRIVATE utils
PRIVATE perfetto
PRIVATE abseil
# libgeometry is PUBLIC because gltfio uses it.
PUBLIC geometry

View File

@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.60.1
VERSION_NAME=1.61.1
POM_DESCRIPTION=Real-time physically based rendering engine for Android.

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

View File

@@ -204,9 +204,10 @@
"license": "Apache-2.0"
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -409,9 +410,9 @@
"integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"

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% ^

View File

@@ -601,6 +601,10 @@ target_link_libraries(${TARGET} PUBLIC filaflat)
target_link_libraries(${TARGET} PUBLIC filabridge)
target_link_libraries(${TARGET} PUBLIC ibl-lite)
if (FILAMENT_USE_ABSEIL_LOGGING)
target_link_libraries(${TARGET} PUBLIC absl::log)
endif()
if (FILAMENT_ENABLE_FGVIEWER)
target_link_libraries(${TARGET} PUBLIC fgviewer)
add_definitions(-DFILAMENT_ENABLE_FGVIEWER=1)

View File

@@ -92,7 +92,7 @@ Copy your platform's Makefile below into a `Makefile` inside the same directory.
### Linux
```make
FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -lvkshaders -libl
FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -lvkshaders -libl -labseil
CC=clang++
main: main.o
@@ -110,12 +110,13 @@ clean:
### macOS
```make
FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -lvkshaders -libl
FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -lvkshaders -libl -labseil
FRAMEWORKS=-framework Cocoa -framework Metal -framework CoreVideo
CC=clang++
ARCH ?= $(shell uname -m)
main: main.o
$(CC) -Llib/x86_64/ main.o $(FILAMENT_LIBS) $(FRAMEWORKS) -o main
$(CC) -Llib/$(ARCH)/ main.o $(FILAMENT_LIBS) $(FRAMEWORKS) -o main
main.o: main.cpp
$(CC) -Iinclude/ -std=c++17 -c main.cpp
@@ -139,7 +140,7 @@ used to change the run-time library version.
```make
FILAMENT_LIBS=filament.lib backend.lib bluegl.lib bluevk.lib filabridge.lib filaflat.lib \
utils.lib geometry.lib smol-v.lib ibl.lib vkshaders.lib
utils.lib geometry.lib smol-v.lib ibl.lib vkshaders.lib abseil.lib
CC=cl.exe
main.exe: main.obj

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/SpdMipmapGenerator/SpdMipmapGenerator.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
)
if (WIN32)
list(APPEND SRCS src/webgpu/platform/WebGPUPlatformWindows.cpp)
@@ -382,6 +404,10 @@ endif()
target_link_libraries(${TARGET} PUBLIC math)
target_link_libraries(${TARGET} PUBLIC utils)
if (FILAMENT_USE_ABSEIL_LOGGING)
target_link_libraries(${TARGET} PRIVATE absl::log)
endif()
# Android, iOS, and WebGL do not use bluegl.
if(FILAMENT_SUPPORTS_OPENGL AND NOT IOS AND NOT ANDROID AND NOT WEBGL)
target_link_libraries(${TARGET} PRIVATE bluegl)

View File

@@ -495,18 +495,12 @@ struct DescriptorSetLayoutBinding {
DescriptorFlags flags = DescriptorFlags::NONE;
uint16_t count = 0;
// TODO: uncomment when needed. Note that this class is used as hash key. We need to ensure
// no uninitialized padding bytes.
// uint8_t externalSamplerDataIndex = EXTERNAL_SAMPLER_DATA_INDEX_UNUSED;
friend bool operator==(DescriptorSetLayoutBinding const& lhs,
DescriptorSetLayoutBinding const& rhs) noexcept {
return lhs.type == rhs.type &&
lhs.flags == rhs.flags &&
lhs.count == rhs.count &&
lhs.stageFlags == rhs.stageFlags;
// lhs.stageFlags == rhs.stageFlags &&
// lhs.externalSamplerDataIndex == rhs.externalSamplerDataIndex;
}
};
@@ -1254,26 +1248,6 @@ enum class SamplerCompareFunc : uint8_t {
N //!< Never. The depth / stencil test always fails.
};
//! this API is copied from (and only applies to) the Vulkan spec.
//! These specify YUV to RGB conversions.
enum class SamplerYcbcrModelConversion : uint8_t {
RGB_IDENTITY = 0,
YCBCR_IDENTITY = 1,
YCBCR_709 = 2,
YCBCR_601 = 3,
YCBCR_2020 = 4,
};
enum class SamplerYcbcrRange : uint8_t {
ITU_FULL = 0,
ITU_NARROW = 1,
};
enum class ChromaLocation : uint8_t {
COSITED_EVEN = 0,
MIDPOINT = 1,
};
//! Sampler parameters
struct SamplerParams { // NOLINT
SamplerMagFilter filterMag : 1; //!< magnification filter (NEAREST)
@@ -1342,94 +1316,9 @@ static_assert(sizeof(SamplerParams) == 4);
static_assert(sizeof(SamplerParams) <= sizeof(uint64_t),
"SamplerParams must be no more than 64 bits");
//! Sampler parameters
struct SamplerYcbcrConversion {// NOLINT
SamplerYcbcrModelConversion ycbcrModel : 4;
TextureSwizzle r : 4;
TextureSwizzle g : 4;
TextureSwizzle b : 4;
TextureSwizzle a : 4;
SamplerYcbcrRange ycbcrRange : 1;
ChromaLocation xChromaOffset : 1;
ChromaLocation yChromaOffset : 1;
SamplerMagFilter chromaFilter : 1;
uint8_t padding;
struct Hasher {
size_t operator()(const SamplerYcbcrConversion p) const noexcept {
// we don't use std::hash<> here, so we don't have to include <functional>
return *reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&p));
}
};
struct EqualTo {
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
assert_invariant(lhs.padding == 0);
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
return *pLhs == *pRhs;
}
};
struct LessThan {
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
assert_invariant(lhs.padding == 0);
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
return *pLhs < *pRhs;
}
};
private:
friend bool operator == (SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs)
noexcept {
return SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
}
friend bool operator != (SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs)
noexcept {
return !SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
}
friend bool operator < (SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs)
noexcept {
return SamplerYcbcrConversion::LessThan{}(lhs, rhs);
}
};
static_assert(sizeof(SamplerYcbcrConversion) == 4);
static_assert(sizeof(SamplerYcbcrConversion) <= sizeof(uint64_t),
"SamplerYcbcrConversion must be no more than 64 bits");
struct ExternalSamplerDatum {
ExternalSamplerDatum(SamplerYcbcrConversion ycbcr, SamplerParams spm, uint32_t extFmt)
: YcbcrConversion(ycbcr),
samplerParams(spm),
externalFormat(extFmt) {}
bool operator==(ExternalSamplerDatum const& rhs) const {
return (YcbcrConversion == rhs.YcbcrConversion && samplerParams == rhs.samplerParams &&
externalFormat == rhs.externalFormat);
}
struct EqualTo {
bool operator()(const ExternalSamplerDatum& lhs,
const ExternalSamplerDatum& rhs) const noexcept {
return (lhs.YcbcrConversion == rhs.YcbcrConversion &&
lhs.samplerParams == rhs.samplerParams &&
lhs.externalFormat == rhs.externalFormat);
}
};
SamplerYcbcrConversion YcbcrConversion;
SamplerParams samplerParams;
uint32_t externalFormat;
};
// No implicit padding allowed due to it being a hash key.
static_assert(sizeof(ExternalSamplerDatum) == 12);
struct DescriptorSetLayout {
std::variant<utils::StaticString, utils::CString, std::monostate> label;
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
// TODO: uncomment when needed
// utils::FixedCapacityVector<ExternalSamplerDatum> externalSamplerData;
};
//! blending equation function

View File

@@ -149,6 +149,13 @@ public:
* - PlatformEGLAndroid
*/
bool assertNativeWindowIsValid = false;
/**
* The action to take if a Drawable cannot be acquired. If true, the
* frame is aborted instead of panic. This is only supported for:
* - PlatformMetal
*/
bool metalDisablePanicOnDrawableFailure = false;
};
Platform() noexcept;

View File

@@ -128,7 +128,7 @@ protected:
bool setExternalImage(ExternalImageHandleRef externalImage, ExternalTexture* texture) noexcept override;
/**
* Logs glGetError() to slog.e
* Logs glGetError() to LOG(ERROR)
* @param name a string giving some context on the error. Typically __func__.
*/
static void logEglError(const char* name) noexcept;

View File

@@ -39,7 +39,7 @@
#define FILAMENT_DEBUG_COMMANDS_NONE 0x0
// Command debugging enabled. No logging by default.
#define FILAMENT_DEBUG_COMMANDS_ENABLE 0x1
// Command debugging enabled. Every command logged to slog.d
// Command debugging enabled. Every command logged to DLOG(INFO)
#define FILAMENT_DEBUG_COMMANDS_LOG 0x2
// Command debugging enabled. Every command logged to systrace
#define FILAMENT_DEBUG_COMMANDS_SYSTRACE 0x4

View File

@@ -16,7 +16,7 @@
#include "private/backend/CircularBuffer.h"
#include <utils/Log.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/architecture.h>
#include <utils/ashmem.h>
@@ -127,7 +127,7 @@ void* CircularBuffer::alloc(size_t size) {
"couldn't allocate " << (size * 2 / 1024) <<
" KiB of virtual address space for the command buffer";
slog.w << "Using 'soft' CircularBuffer (" << (size * 2 / 1024) << " KiB)" << io::endl;
LOG(WARNING) << "Using 'soft' CircularBuffer (" << (size * 2 / 1024) << " KiB)";
// guard page at the end
void* guard = (void*)(uintptr_t(data) + size * 2);

View File

@@ -20,12 +20,12 @@
#include <private/utils/Tracing.h>
#include <utils/compiler.h>
#include <utils/Log.h>
#include <utils/Logger.h>
#include <utils/Mutex.h>
#include <utils/ostream.h>
#include <utils/Panic.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/ostream.h>
#include <algorithm>
#include <mutex>
@@ -121,11 +121,10 @@ void CommandBufferQueue::flush() {
#ifndef NDEBUG
size_t const totalUsed = circularBuffer.size() - mFreeSpace;
slog.d << "CommandStream used too much space (will block): "
<< "needed space " << requiredSize << " out of " << mFreeSpace
<< ", totalUsed=" << totalUsed << ", current=" << used
<< ", queue size=" << mCommandBuffersToExecute.size() << " buffers"
<< io::endl;
DLOG(INFO) << "CommandStream used too much space (will block): "
<< "needed space " << requiredSize << " out of " << mFreeSpace
<< ", totalUsed=" << totalUsed << ", current=" << used
<< ", queue size=" << mCommandBuffersToExecute.size() << " buffers";
mHighWatermark = std::max(mHighWatermark, totalUsed);
#endif

View File

@@ -22,10 +22,13 @@
#include <utils/CallStack.h>
#endif
#include <utils/compiler.h>
#include <utils/Log.h>
#include <utils/ostream.h>
#include <private/utils/Tracing.h>
#include <utils/Logger.h>
#include <utils/Profiler.h>
#include <utils/compiler.h>
#include <utils/ostream.h>
#include <utils/sstream.h>
#include <cstddef>
#include <functional>
@@ -129,9 +132,10 @@ void CommandType<void (Driver::*)(ARGS...)>::Command<METHOD>::log(std::index_seq
#if DEBUG_COMMAND_STREAM
static_assert(UTILS_HAS_RTTI, "DEBUG_COMMAND_STREAM can only be used with RTTI");
std::string command = utils::CallStack::demangleTypeName(typeid(Command).name()).c_str();
slog.d << extractMethodName(command) << " : size=" << sizeof(Command) << "\n\t";
printParameterPack(slog.d, std::get<I>(mArgs)...);
slog.d << io::endl;
DLOG(INFO) << extractMethodName(command) << " : size=" << sizeof(Command);
utils::io::sstream parameterPack;
printParameterPack(parameterPack, std::get<I>(mArgs)...);
DLOG(INFO) << "\t" << parameterPack.c_str();
#endif
}

View File

@@ -25,9 +25,9 @@
#include <private/utils/Tracing.h>
#include <utils/Logger.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/Log.h>
#include <utils/ostream.h>
#include <math/half.h>
@@ -149,7 +149,7 @@ void DriverBase::scheduleRelease(AcquiredImage const& image) noexcept {
void DriverBase::debugCommandBegin(CommandStream* cmds, bool synchronous, const char* methodName) noexcept {
if constexpr (bool(FILAMENT_DEBUG_COMMANDS > FILAMENT_DEBUG_COMMANDS_NONE)) {
if constexpr (bool(FILAMENT_DEBUG_COMMANDS & FILAMENT_DEBUG_COMMANDS_LOG)) {
utils::slog.d << methodName << utils::io::endl;
DLOG(INFO) << methodName;
}
if constexpr (bool(FILAMENT_DEBUG_COMMANDS & FILAMENT_DEBUG_COMMANDS_SYSTRACE)) {
FILAMENT_TRACING_CONTEXT(FILAMENT_TRACING_CATEGORY_FILAMENT);

View File

@@ -20,7 +20,7 @@
#include <utils/Allocator.h>
#include <utils/CString.h>
#include <utils/Log.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/compiler.h>
#include <utils/debug.h>
@@ -57,8 +57,8 @@ HandleAllocator<P0, P1, P2>::Allocator::Allocator(AreaPolicy::HeapArea const& ar
size_t const maxHeapSize = std::min(area.size(), HANDLE_INDEX_MASK * getAlignment());
if (UTILS_UNLIKELY(maxHeapSize != area.size())) {
slog.w << "HandleAllocator heap size reduced to "
<< maxHeapSize << " from " << area.size() << io::endl;
LOG(WARNING) << "HandleAllocator heap size reduced to " << maxHeapSize << " from "
<< area.size();
}
// make sure we start with a clean arena. This is needed to ensure that all blocks start

View File

@@ -19,8 +19,8 @@
#include "MetalContext.h"
#include "MetalUtils.h"
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/Log.h>
namespace filament::backend {
@@ -323,7 +323,7 @@ id<MTLFunction> MetalBlitter::compileFragmentFunction(BlitFunctionKey key) const
if (!library || !function) {
if (error) {
auto description = [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
utils::slog.e << description << utils::io::endl;
LOG(ERROR) << description;
}
}
FILAMENT_CHECK_POSTCONDITION(library && function)
@@ -349,7 +349,7 @@ id<MTLFunction> MetalBlitter::getBlitVertexFunction() {
if (!library || !function) {
if (error) {
auto description = [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
utils::slog.e << description << utils::io::endl;
LOG(ERROR) << description;
}
}
FILAMENT_CHECK_POSTCONDITION(library && function)

View File

@@ -18,8 +18,8 @@
#include "MetalContext.h"
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/Log.h>
#include <utils/trap.h>
#include <thread>
@@ -77,8 +77,7 @@ void MetalBufferPool::releaseBuffer(MetalBufferPoolEntry const *stage) noexcept
auto iter = mUsedStages.find(stage);
if (iter == mUsedStages.end()) {
utils::slog.e << "Unknown Metal buffer: " << stage->capacity << " bytes"
<< utils::io::endl;
LOG(ERROR) << "Unknown Metal buffer: " << stage->capacity << " bytes";
return;
}
stage->lastAccessed = mCurrentFrame;

View File

@@ -18,8 +18,9 @@
#include "MetalHandles.h"
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Logger.h>
#include <utils/debug.h>
#include <utility>
@@ -112,8 +113,8 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
auto errorCode = (MTLCommandBufferError)buffer.error.code;
if (@available(macOS 11.0, *)) {
if (errorCode == MTLCommandBufferErrorMemoryless) {
utils::slog.w << "Metal: memoryless geometry limit reached. "
"Continuing with private storage mode." << utils::io::endl;
LOG(WARNING) << "Metal: memoryless geometry limit reached. Continuing with private "
"storage mode.";
context->memorylessLimitsReached = true;
}
}

View File

@@ -38,10 +38,10 @@
#include <Metal/Metal.h>
#include <QuartzCore/QuartzCore.h>
#include <utils/Log.h>
#include <utils/Invocable.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/sstream.h>
#include <utils/Invocable.h>
#include <algorithm>
@@ -78,20 +78,18 @@ Driver* MetalDriverFactory::create(PlatformMetal* const platform, const Platform
// MetalVertexBufferInfo : 552 moderate
// -- less than or equal to 552 bytes
utils::slog.d
<< "\nMetalSwapChain: " << sizeof(MetalSwapChain)
<< "\nMetalBufferObject: " << sizeof(MetalBufferObject)
<< "\nMetalVertexBuffer: " << sizeof(MetalVertexBuffer)
<< "\nMetalVertexBufferInfo: " << sizeof(MetalVertexBufferInfo)
<< "\nMetalIndexBuffer: " << sizeof(MetalIndexBuffer)
<< "\nMetalRenderPrimitive: " << sizeof(MetalRenderPrimitive)
<< "\nMetalTexture: " << sizeof(MetalTexture)
<< "\nMetalTimerQuery: " << sizeof(MetalTimerQuery)
<< "\nHwStream: " << sizeof(HwStream)
<< "\nMetalRenderTarget: " << sizeof(MetalRenderTarget)
<< "\nMetalFence: " << sizeof(MetalFence)
<< "\nMetalProgram: " << sizeof(MetalProgram)
<< utils::io::endl;
DLOG(INFO) << "MetalSwapChain: " << sizeof(MetalSwapChain);
DLOG(INFO) << "MetalBufferObject: " << sizeof(MetalBufferObject);
DLOG(INFO) << "MetalVertexBuffer: " << sizeof(MetalVertexBuffer);
DLOG(INFO) << "MetalVertexBufferInfo: " << sizeof(MetalVertexBufferInfo);
DLOG(INFO) << "MetalIndexBuffer: " << sizeof(MetalIndexBuffer);
DLOG(INFO) << "MetalRenderPrimitive: " << sizeof(MetalRenderPrimitive);
DLOG(INFO) << "MetalTexture: " << sizeof(MetalTexture);
DLOG(INFO) << "MetalTimerQuery: " << sizeof(MetalTimerQuery);
DLOG(INFO) << "HwStream: " << sizeof(HwStream);
DLOG(INFO) << "MetalRenderTarget: " << sizeof(MetalRenderTarget);
DLOG(INFO) << "MetalFence: " << sizeof(MetalFence);
DLOG(INFO) << "MetalProgram: " << sizeof(MetalProgram);
#endif
return MetalDriver::create(platform, driverConfig);
}
@@ -135,19 +133,18 @@ MetalDriver::MetalDriver(
initializeSupportedGpuFamilies(mContext);
utils::slog.v << "Supported GPU families: " << utils::io::endl;
LOG(INFO) << "Supported GPU families: ";
if (mContext->highestSupportedGpuFamily.common > 0) {
utils::slog.v << " MTLGPUFamilyCommon" << (int) mContext->highestSupportedGpuFamily.common << utils::io::endl;
LOG(INFO) << " MTLGPUFamilyCommon" << (int) mContext->highestSupportedGpuFamily.common;
}
if (mContext->highestSupportedGpuFamily.apple > 0) {
utils::slog.v << " MTLGPUFamilyApple" << (int) mContext->highestSupportedGpuFamily.apple << utils::io::endl;
LOG(INFO) << " MTLGPUFamilyApple" << (int) mContext->highestSupportedGpuFamily.apple;
}
if (mContext->highestSupportedGpuFamily.mac > 0) {
utils::slog.v << " MTLGPUFamilyMac" << (int) mContext->highestSupportedGpuFamily.mac << utils::io::endl;
LOG(INFO) << " MTLGPUFamilyMac" << (int) mContext->highestSupportedGpuFamily.mac;
}
utils::slog.v << "Features:" << utils::io::endl;
utils::slog.v << " readWriteTextureSupport: " <<
(bool) mContext->device.readWriteTextureSupport << utils::io::endl;
LOG(INFO) << "Features:";
LOG(INFO) << " readWriteTextureSupport: " << (bool) mContext->device.readWriteTextureSupport;
// In order to support texture swizzling, the GPU needs to support it and the system be running
// iOS 13+.
@@ -588,7 +585,7 @@ void MetalDriver::createProgramR(Handle<HwProgram> rph, Program&& program) {
#if FILAMENT_METAL_DEBUG_LOG
auto handleId = rph.getId();
DEBUG_LOG("createProgramR(rph = %d, program = ", handleId);
utils::slog.d << program << utils::io::endl;
DLOG(INFO) << program;
#endif
construct_handle<MetalProgram>(rph, *mContext, std::move(program));
}
@@ -2077,7 +2074,7 @@ void MetalDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGro
error:&error];
if (error) {
auto description = [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
utils::slog.e << description << utils::io::endl;
LOG(ERROR) << description;
}
assert_invariant(!error);

View File

@@ -20,14 +20,14 @@
#include "MetalEnums.h"
#include "MetalUtils.h"
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/Log.h>
#include <utils/trap.h>
#define NSERROR_CHECK(message) \
if (error) { \
auto description = [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding]; \
utils::slog.e << description << utils::io::endl; \
LOG(ERROR) << description; \
} \
FILAMENT_CHECK_POSTCONDITION(error == nil) << message;

View File

@@ -27,10 +27,11 @@
#include "private/backend/BackendUtils.h"
#include <utils/compiler.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/trap.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/trap.h>
#include <math/scalar.h>
@@ -115,14 +116,14 @@ MetalSwapChain::MetalSwapChain(
type(SwapChainType::CAMETALLAYER) {
if (!(flags & SwapChain::CONFIG_TRANSPARENT) && !nativeWindow.opaque) {
utils::slog.w << "Warning: Filament SwapChain has no CONFIG_TRANSPARENT flag, "
"but the CAMetaLayer(" << (__bridge void*) nativeWindow << ")"
" has .opaque set to NO." << utils::io::endl;
LOG(WARNING) << "Warning: Filament SwapChain has no CONFIG_TRANSPARENT flag, but the "
"CAMetaLayer("
<< (__bridge void*) nativeWindow << ") has .opaque set to NO.";
}
if ((flags & SwapChain::CONFIG_TRANSPARENT) && nativeWindow.opaque) {
utils::slog.w << "Warning: Filament SwapChain has the CONFIG_TRANSPARENT flag, "
"but the CAMetaLayer(" << (__bridge void*) nativeWindow << ")"
" has .opaque set to YES." << utils::io::endl;
LOG(WARNING) << "Warning: Filament SwapChain has the CONFIG_TRANSPARENT flag, but the "
"CAMetaLayer("
<< (__bridge void*) nativeWindow << ") has .opaque set to YES.";
}
// Needed so we can use the SwapChain as a blit source.

View File

@@ -21,6 +21,7 @@
#include <backend/Program.h>
#include <utils/JobSystem.h>
#include <utils/Logger.h>
#include <utils/Mutex.h>
#include <chrono>
@@ -147,7 +148,7 @@ bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept {
if (error) {
auto description =
[error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
utils::slog.w << description << utils::io::endl;
LOG(WARNING) << description;
errorMessage = error.localizedDescription;
}
PANIC_LOG("Failed to compile Metal program.");
@@ -178,7 +179,7 @@ bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept {
if (error) {
auto description =
[error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
utils::slog.w << description << utils::io::endl;
LOG(WARNING) << description;
errorMessage = error.localizedDescription;
}
PANIC_LOG("Failed to load main0 in Metal program.");

View File

@@ -18,7 +18,7 @@
#include "MetalEnums.h"
#include <utils/Log.h>
#include <utils/Logger.h>
namespace filament {
namespace backend {
@@ -95,7 +95,7 @@ id<MTLRenderPipelineState> PipelineStateCreator::operator()(id<MTLDevice> device
[NSString stringWithFormat:@"Could not create Metal pipeline state: %@",
error ? error.localizedDescription : @"unknown error"];
auto description = [errorMessage cStringUsingEncoding:NSUTF8StringEncoding];
utils::slog.e << description << utils::io::endl;
LOG(ERROR) << description;
[[NSException exceptionWithName:@"MetalRenderPipelineFailure"
reason:errorMessage
userInfo:nil] raise];
@@ -155,7 +155,7 @@ id<MTLSamplerState> SamplerStateCreator::operator()(id<MTLDevice> device,
// MTLSamplerDescriptor.
// In practice, this means shadows are not supported when running in the simulator.
if (![device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) {
utils::slog.w << "Warning: sample comparison not supported by this GPU" << utils::io::endl;
LOG(WARNING) << "Warning: sample comparison not supported by this GPU";
samplerDescriptor.compareFunction = MTLCompareFunctionNever;
}
#endif

View File

@@ -19,7 +19,7 @@
#include "MetalDriverFactory.h"
#include <utils/Log.h>
#include <utils/Logger.h>
#import <Foundation/Foundation.h>
@@ -53,6 +53,9 @@ PlatformMetal::~PlatformMetal() noexcept {
}
Driver* PlatformMetal::createDriver(void* /*sharedContext*/, const Platform::DriverConfig& driverConfig) noexcept {
pImpl->mDrawableFailureBehavior = driverConfig.metalDisablePanicOnDrawableFailure
? DrawableFailureBehavior::ABORT_FRAME
: DrawableFailureBehavior::PANIC;
return MetalDriverFactory::create(this, driverConfig);
}
@@ -129,9 +132,8 @@ void PlatformMetalImpl::createDeviceImpl(MetalDevice& outDevice) {
result = MTLCreateSystemDefaultDevice();
}
utils::slog.i << "Selected physical device '"
<< [result.name cStringUsingEncoding:NSUTF8StringEncoding] << "'"
<< utils::io::endl;
LOG(INFO) << "Selected physical device '"
<< [result.name cStringUsingEncoding:NSUTF8StringEncoding] << "'";
outDevice.device = result;
mDevice = result;

View File

@@ -162,8 +162,11 @@ void GLDescriptorSet::update(OpenGLContext&,
}, descriptors[binding].desc);
}
void GLDescriptorSet::update(OpenGLContext& gl,
descriptor_binding_t binding, GLTexture* t, SamplerParams params) noexcept {
void GLDescriptorSet::update(OpenGLContext& gl, HandleAllocatorGL& handleAllocator,
descriptor_binding_t binding, TextureHandle th, SamplerParams params) noexcept {
GLTexture* t = th ? handleAllocator.handle_cast<GLTexture*>(th) : nullptr;
assert_invariant(binding < descriptors.size());
std::visit([=, &gl](auto&& arg) mutable {
using T = std::decay_t<decltype(arg)>;
@@ -196,20 +199,12 @@ void GLDescriptorSet::update(OpenGLContext& gl,
}
}
arg.target = t ? t->gl.target : 0;
arg.id = t ? t->gl.id : 0;
arg.external = t ? t->gl.external : false;
arg.handle = th;
if constexpr (std::is_same_v<T, Sampler> ||
std::is_same_v<T, SamplerWithAnisotropyWorkaround>) {
if constexpr (std::is_same_v<T, SamplerWithAnisotropyWorkaround>) {
arg.anisotropy = float(1u << params.anisotropyLog2);
}
if (t) {
arg.ref = t->ref;
arg.baseLevel = t->gl.baseLevel;
arg.maxLevel = t->gl.maxLevel;
arg.swizzle = t->gl.swizzle;
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
arg.sampler = gl.getSampler(params);
#else
@@ -225,39 +220,39 @@ void GLDescriptorSet::update(OpenGLContext& gl,
}, descriptors[binding].desc);
}
template<typename T>
void GLDescriptorSet::updateTextureView(OpenGLContext& gl,
HandleAllocatorGL& handleAllocator, GLuint unit, T const& desc) noexcept {
HandleAllocatorGL& handleAllocator, GLuint unit, GLTexture const* t) noexcept {
// The common case is that we don't have a ref handle (we only have one if
// the texture ever had a View on it).
assert_invariant(desc.ref);
GLTextureRef* const ref = handleAllocator.handle_cast<GLTextureRef*>(desc.ref);
if (UTILS_UNLIKELY((desc.baseLevel != ref->baseLevel || desc.maxLevel != ref->maxLevel))) {
assert_invariant(t);
assert_invariant(t->ref);
GLTextureRef* const ref = handleAllocator.handle_cast<GLTextureRef*>(t->ref);
if (UTILS_UNLIKELY((t->gl.baseLevel != ref->baseLevel || t->gl.maxLevel != ref->maxLevel))) {
// If we have views, then it's still uncommon that we'll switch often
// handle the case where we reset to the original texture
GLint baseLevel = GLint(desc.baseLevel); // NOLINT(*-signed-char-misuse)
GLint maxLevel = GLint(desc.maxLevel); // NOLINT(*-signed-char-misuse)
GLint baseLevel = GLint(t->gl.baseLevel); // NOLINT(*-signed-char-misuse)
GLint maxLevel = GLint(t->gl.maxLevel); // NOLINT(*-signed-char-misuse)
if (baseLevel > maxLevel) {
baseLevel = 0;
maxLevel = 1000; // per OpenGL spec
}
// that is very unfortunate that we have to call activeTexture here
gl.activeTexture(unit);
glTexParameteri(desc.target, GL_TEXTURE_BASE_LEVEL, baseLevel);
glTexParameteri(desc.target, GL_TEXTURE_MAX_LEVEL, maxLevel);
ref->baseLevel = desc.baseLevel;
ref->maxLevel = desc.maxLevel;
glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, baseLevel);
glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, maxLevel);
ref->baseLevel = t->gl.baseLevel;
ref->maxLevel = t->gl.maxLevel;
}
if (UTILS_UNLIKELY(desc.swizzle != ref->swizzle)) {
if (UTILS_UNLIKELY(t->gl.swizzle != ref->swizzle)) {
using namespace GLUtils;
gl.activeTexture(unit);
#if !defined(__EMSCRIPTEN__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2)
glTexParameteri(desc.target, GL_TEXTURE_SWIZZLE_R, (GLint)getSwizzleChannel(desc.swizzle[0]));
glTexParameteri(desc.target, GL_TEXTURE_SWIZZLE_G, (GLint)getSwizzleChannel(desc.swizzle[1]));
glTexParameteri(desc.target, GL_TEXTURE_SWIZZLE_B, (GLint)getSwizzleChannel(desc.swizzle[2]));
glTexParameteri(desc.target, GL_TEXTURE_SWIZZLE_A, (GLint)getSwizzleChannel(desc.swizzle[3]));
glTexParameteri(t->gl.target, GL_TEXTURE_SWIZZLE_R, (GLint)getSwizzleChannel(t->gl.swizzle[0]));
glTexParameteri(t->gl.target, GL_TEXTURE_SWIZZLE_G, (GLint)getSwizzleChannel(t->gl.swizzle[1]));
glTexParameteri(t->gl.target, GL_TEXTURE_SWIZZLE_B, (GLint)getSwizzleChannel(t->gl.swizzle[2]));
glTexParameteri(t->gl.target, GL_TEXTURE_SWIZZLE_A, (GLint)getSwizzleChannel(t->gl.swizzle[3]));
#endif
ref->swizzle = desc.swizzle;
ref->swizzle = t->gl.swizzle;
}
}
@@ -310,27 +305,31 @@ void GLDescriptorSet::bind(
}
} else if constexpr (std::is_same_v<T, Sampler>) {
GLuint const unit = p.getTextureUnit(set, binding);
if (arg.target) {
gl.bindTexture(unit, arg.target, arg.id, arg.external);
if (arg.handle) {
GLTexture const* const t = handleAllocator.handle_cast<GLTexture*>(arg.handle);
gl.bindTexture(unit, t->gl.target, t->gl.id, t->gl.external);
gl.bindSampler(unit, arg.sampler);
if (UTILS_UNLIKELY(arg.ref)) {
updateTextureView(gl, handleAllocator, unit, arg);
if (UTILS_UNLIKELY(t->ref)) {
updateTextureView(gl, handleAllocator, unit, t);
}
} else {
gl.unbindTextureUnit(unit);
}
} else if constexpr (std::is_same_v<T, SamplerWithAnisotropyWorkaround>) {
GLuint const unit = p.getTextureUnit(set, binding);
if (arg.target) {
gl.bindTexture(unit, arg.target, arg.id, arg.external);
if (arg.handle) {
GLTexture const* const t = handleAllocator.handle_cast<GLTexture*>(arg.handle);
gl.bindTexture(unit, t->gl.target, t->gl.id, t->gl.external);
gl.bindSampler(unit, arg.sampler);
if (UTILS_UNLIKELY(arg.ref)) {
updateTextureView(gl, handleAllocator, unit, arg);
if (UTILS_UNLIKELY(t->ref)) {
updateTextureView(gl, handleAllocator, unit, t);
}
#if defined(GL_EXT_texture_filter_anisotropic)
// Driver claims to support anisotropic filtering, but it fails when set on
// the sampler, we have to set it on the texture instead.
glTexParameterf(arg.target, GL_TEXTURE_MAX_ANISOTROPY_EXT,
glTexParameterf(t->gl.target, GL_TEXTURE_MAX_ANISOTROPY_EXT,
std::min(gl.gets.max_anisotropy, float(arg.anisotropy)));
#endif
} else {
@@ -339,19 +338,20 @@ void GLDescriptorSet::bind(
} else if constexpr (std::is_same_v<T, SamplerGLES2>) {
// in ES2 the sampler parameters need to be set on the texture itself
GLuint const unit = p.getTextureUnit(set, binding);
if (arg.target) {
gl.bindTexture(unit, arg.target, arg.id, arg.external);
if (arg.handle) {
GLTexture const* const t = handleAllocator.handle_cast<GLTexture*>(arg.handle);
gl.bindTexture(unit, t->gl.target, t->gl.id, t->gl.external);
SamplerParams const params = arg.params;
glTexParameteri(arg.target, GL_TEXTURE_MIN_FILTER,
glTexParameteri(t->gl.target, GL_TEXTURE_MIN_FILTER,
(GLint)GLUtils::getTextureFilter(params.filterMin));
glTexParameteri(arg.target, GL_TEXTURE_MAG_FILTER,
glTexParameteri(t->gl.target, GL_TEXTURE_MAG_FILTER,
(GLint)GLUtils::getTextureFilter(params.filterMag));
glTexParameteri(arg.target, GL_TEXTURE_WRAP_S,
glTexParameteri(t->gl.target, GL_TEXTURE_WRAP_S,
(GLint)GLUtils::getWrapMode(params.wrapS));
glTexParameteri(arg.target, GL_TEXTURE_WRAP_T,
glTexParameteri(t->gl.target, GL_TEXTURE_WRAP_T,
(GLint)GLUtils::getWrapMode(params.wrapT));
#if defined(GL_EXT_texture_filter_anisotropic)
glTexParameterf(arg.target, GL_TEXTURE_MAX_ANISOTROPY_EXT,
glTexParameterf(t->gl.target, GL_TEXTURE_MAX_ANISOTROPY_EXT,
std::min(gl.gets.max_anisotropy, arg.anisotropy));
#endif
} else {
@@ -360,7 +360,7 @@ void GLDescriptorSet::bind(
}
}, entry.desc);
});
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void GLDescriptorSet::validate(HandleAllocatorGL& allocator,

View File

@@ -59,8 +59,8 @@ struct GLDescriptorSet : public HwDescriptorSet {
descriptor_binding_t binding, GLBufferObject* bo, size_t offset, size_t size) noexcept;
// update a sampler descriptor in the set
void update(OpenGLContext& gl,
descriptor_binding_t binding, GLTexture* t, SamplerParams params) noexcept;
void update(OpenGLContext& gl, HandleAllocatorGL& handleAllocator,
descriptor_binding_t binding, TextureHandle th, SamplerParams params) noexcept;
// conceptually bind the set to the command buffer
void bind(
@@ -111,46 +111,19 @@ private:
// A sampler descriptor
struct Sampler {
uint16_t target; // 2 (GLenum)
bool external = false; // 1
bool reserved = false; // 1
GLuint id = 0; // 4
TextureHandle handle; // 4
GLuint sampler = 0; // 4
Handle<GLTextureRef> ref; // 4
int8_t baseLevel = 0x7f; // 1
int8_t maxLevel = -1; // 1
std::array<TextureSwizzle, 4> swizzle{ // 4
TextureSwizzle::CHANNEL_0,
TextureSwizzle::CHANNEL_1,
TextureSwizzle::CHANNEL_2,
TextureSwizzle::CHANNEL_3
};
};
struct SamplerWithAnisotropyWorkaround {
uint16_t target; // 2 (GLenum)
bool external = false; // 1
bool reserved = false; // 1
GLuint id = 0; // 4
TextureHandle handle; // 4
GLuint sampler = 0; // 4
Handle<GLTextureRef> ref; // 4
math::half anisotropy = 1.0f; // 2
int8_t baseLevel = 0x7f; // 1
int8_t maxLevel = -1; // 1
std::array<TextureSwizzle, 4> swizzle{ // 4
TextureSwizzle::CHANNEL_0,
TextureSwizzle::CHANNEL_1,
TextureSwizzle::CHANNEL_2,
TextureSwizzle::CHANNEL_3
};
};
// A sampler descriptor for ES2
struct SamplerGLES2 {
uint16_t target; // 2 (GLenum)
bool external = false; // 1
bool reserved = false; // 1
GLuint id = 0; // 4
TextureHandle handle; // 4
SamplerParams params{}; // 4
float anisotropy = 1.0f; // 4
};
@@ -165,9 +138,8 @@ private:
};
static_assert(sizeof(Descriptor) <= 32);
template<typename T>
static void updateTextureView(OpenGLContext& gl,
HandleAllocatorGL& handleAllocator, GLuint unit, T const& desc) noexcept;
HandleAllocatorGL& handleAllocator, GLuint unit, GLTexture const* t) noexcept;
utils::FixedCapacityVector<Descriptor> descriptors; // 16
utils::bitset64 dynamicBuffers; // 8

View File

@@ -18,6 +18,7 @@
#include "private/backend/Driver.h"
#include <utils/Logger.h>
#include <utils/compiler.h>
#include <utils/ostream.h>
#include <utils/trap.h>
@@ -25,6 +26,7 @@
#include <string_view>
#include <stddef.h>
#include <cstdio>
namespace filament::backend {
@@ -55,19 +57,21 @@ std::string_view getGLErrorString(GLenum error) noexcept {
}
UTILS_NOINLINE
GLenum checkGLError(io::ostream& out, const char* function, size_t line) noexcept {
GLenum checkGLError(const char* function, size_t line) noexcept {
GLenum const error = glGetError();
if (UTILS_VERY_UNLIKELY(error != GL_NO_ERROR)) {
auto const string = getGLErrorString(error);
out << "OpenGL error " << io::hex << error << " (" << string << ") in \""
<< function << "\" at line " << io::dec << line << io::endl;
char hexError[16];
snprintf(hexError, sizeof(hexError), "%#x", error);
LOG(ERROR) << "OpenGL error " << hexError << " (" << string << ") in \"" << function
<< "\" at line " << line;
}
return error;
}
UTILS_NOINLINE
void assertGLError(io::ostream& out, const char* function, size_t line) noexcept {
GLenum const err = checkGLError(out, function, line);
void assertGLError(const char* function, size_t line) noexcept {
GLenum const err = checkGLError(function, line);
if (UTILS_VERY_UNLIKELY(err != GL_NO_ERROR)) {
debug_trap();
}
@@ -97,19 +101,21 @@ std::string_view getFramebufferStatusString(GLenum status) noexcept {
}
UTILS_NOINLINE
GLenum checkFramebufferStatus(io::ostream& out, GLenum target, const char* function, size_t line) noexcept {
GLenum checkFramebufferStatus(GLenum target, const char* function, size_t line) noexcept {
GLenum const status = glCheckFramebufferStatus(target);
if (UTILS_VERY_UNLIKELY(status != GL_FRAMEBUFFER_COMPLETE)) {
auto const string = getFramebufferStatusString(status);
out << "OpenGL framebuffer error " << io::hex << status << " (" << string << ") in \""
<< function << "\" at line " << io::dec << line << io::endl;
char hexStatus[16];
snprintf(hexStatus, sizeof(hexStatus), "%#x", status);
LOG(ERROR) << "OpenGL framebuffer error " << hexStatus << " (" << string << ") in \""
<< function << "\" at line " << line;
}
return status;
}
UTILS_NOINLINE
void assertFramebufferStatus(io::ostream& out, GLenum target, const char* function, size_t line) noexcept {
GLenum const status = checkFramebufferStatus(out, target, function, line);
void assertFramebufferStatus(GLenum target, const char* function, size_t line) noexcept {
GLenum const status = checkFramebufferStatus(target, function, line);
if (UTILS_VERY_UNLIKELY(status != GL_FRAMEBUFFER_COMPLETE)) {
debug_trap();
}

View File

@@ -33,21 +33,21 @@
namespace filament::backend::GLUtils {
std::string_view getGLErrorString(GLenum error) noexcept;
GLenum checkGLError(utils::io::ostream& out, const char* function, size_t line) noexcept;
void assertGLError(utils::io::ostream& out, const char* function, size_t line) noexcept;
GLenum checkGLError(const char* function, size_t line) noexcept;
void assertGLError(const char* function, size_t line) noexcept;
std::string_view getFramebufferStatusString(GLenum err) noexcept;
GLenum checkFramebufferStatus(utils::io::ostream& out, GLenum target, const char* function, size_t line) noexcept;
void assertFramebufferStatus(utils::io::ostream& out, GLenum target, const char* function, size_t line) noexcept;
GLenum checkFramebufferStatus(GLenum target, const char* function, size_t line) noexcept;
void assertFramebufferStatus(GLenum target, const char* function, size_t line) noexcept;
#ifdef NDEBUG
# define CHECK_GL_ERROR(out)
# define CHECK_GL_ERROR_NON_FATAL(out)
# define CHECK_GL_FRAMEBUFFER_STATUS(out, target)
# define CHECK_GL_ERROR()
# define CHECK_GL_ERROR_NON_FATAL()
# define CHECK_GL_FRAMEBUFFER_STATUS(target)
#else
# define CHECK_GL_ERROR(out) { GLUtils::assertGLError(out, __func__, __LINE__); }
# define CHECK_GL_ERROR_NON_FATAL(out) { GLUtils::checkGLError(out, __func__, __LINE__); }
# define CHECK_GL_FRAMEBUFFER_STATUS(out, target) { GLUtils::checkFramebufferStatus(out, target, __func__, __LINE__); }
# define CHECK_GL_ERROR() { GLUtils::assertGLError(__func__, __LINE__); }
# define CHECK_GL_ERROR_NON_FATAL() { GLUtils::checkGLError(__func__, __LINE__); }
# define CHECK_GL_FRAMEBUFFER_STATUS(target) { GLUtils::checkFramebufferStatus( target, __func__, __LINE__); }
#endif
constexpr GLuint getComponentCount(ElementType const type) noexcept {

View File

@@ -22,9 +22,9 @@
#include <backend/platforms/OpenGLPlatform.h>
#include <backend/DriverEnums.h>
#include <utils/Logger.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/Log.h>
#include <utils/ostream.h>
#include <functional>
@@ -77,8 +77,8 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform,
state.version = (char const*)glGetString(GL_VERSION);
state.shader = (char const*)glGetString(GL_SHADING_LANGUAGE_VERSION);
slog.v << "[" << state.vendor << "], [" << state.renderer << "], "
"[" << state.version << "], [" << state.shader << "]" << io::endl;
LOG(INFO) << "[" << state.vendor << "], [" << state.renderer << "], "
"[" << state.version << "], [" << state.shader << "]";
/*
* Figure out GL / GLES version, extensions and capabilities we need to
@@ -164,51 +164,33 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform,
}
#endif
slog.v << "Feature level: " << +mFeatureLevel << '\n';
slog.v << "Active workarounds: " << '\n';
LOG(INFO) << "Feature level: " << +mFeatureLevel;
LOG(INFO) << "Active workarounds: ";
UTILS_NOUNROLL
for (auto [enabled, name, _] : mBugDatabase) {
if (enabled) {
slog.v << name << '\n';
LOG(INFO) << name;
}
}
flush(slog.v);
#ifndef NDEBUG
// this is useful for development
slog.v
<< "GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT = "
<< gets.max_anisotropy << '\n'
<< "GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS = "
<< gets.max_combined_texture_image_units << '\n'
<< "GL_MAX_TEXTURE_SIZE = "
<< gets.max_texture_size << '\n'
<< "GL_MAX_CUBE_MAP_TEXTURE_SIZE = "
<< gets.max_cubemap_texture_size << '\n'
<< "GL_MAX_3D_TEXTURE_SIZE = "
<< gets.max_3d_texture_size << '\n'
<< "GL_MAX_ARRAY_TEXTURE_LAYERS = "
<< gets.max_array_texture_layers << '\n'
<< "GL_MAX_DRAW_BUFFERS = "
<< gets.max_draw_buffers << '\n'
<< "GL_MAX_RENDERBUFFER_SIZE = "
<< gets.max_renderbuffer_size << '\n'
<< "GL_MAX_SAMPLES = "
<< gets.max_samples << '\n'
<< "GL_MAX_TEXTURE_IMAGE_UNITS = "
<< gets.max_texture_image_units << '\n'
<< "GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS = "
<< gets.max_transform_feedback_separate_attribs << '\n'
<< "GL_MAX_UNIFORM_BLOCK_SIZE = "
<< gets.max_uniform_block_size << '\n'
<< "GL_MAX_UNIFORM_BUFFER_BINDINGS = "
<< gets.max_uniform_buffer_bindings << '\n'
<< "GL_NUM_PROGRAM_BINARY_FORMATS = "
<< gets.num_program_binary_formats << '\n'
<< "GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT = "
<< gets.uniform_buffer_offset_alignment << '\n'
;
flush(slog.v);
LOG(INFO) << "GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT = " << gets.max_anisotropy;
LOG(INFO) << "GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS = " << gets.max_combined_texture_image_units;
LOG(INFO) << "GL_MAX_TEXTURE_SIZE = " << gets.max_texture_size;
LOG(INFO) << "GL_MAX_CUBE_MAP_TEXTURE_SIZE = " << gets.max_cubemap_texture_size;
LOG(INFO) << "GL_MAX_3D_TEXTURE_SIZE = " << gets.max_3d_texture_size;
LOG(INFO) << "GL_MAX_ARRAY_TEXTURE_LAYERS = " << gets.max_array_texture_layers;
LOG(INFO) << "GL_MAX_DRAW_BUFFERS = " << gets.max_draw_buffers;
LOG(INFO) << "GL_MAX_RENDERBUFFER_SIZE = " << gets.max_renderbuffer_size;
LOG(INFO) << "GL_MAX_SAMPLES = " << gets.max_samples;
LOG(INFO) << "GL_MAX_TEXTURE_IMAGE_UNITS = " << gets.max_texture_image_units;
LOG(INFO) << "GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS = "
<< gets.max_transform_feedback_separate_attribs;
LOG(INFO) << "GL_MAX_UNIFORM_BLOCK_SIZE = " << gets.max_uniform_block_size;
LOG(INFO) << "GL_MAX_UNIFORM_BUFFER_BINDINGS = " << gets.max_uniform_buffer_bindings;
LOG(INFO) << "GL_NUM_PROGRAM_BINARY_FORMATS = " << gets.num_program_binary_formats;
LOG(INFO) << "GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT = " << gets.uniform_buffer_offset_alignment;
#endif
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
@@ -242,15 +224,14 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform,
if (ext.KHR_debug) {
auto cb = +[](GLenum, GLenum type, GLuint, GLenum severity, GLsizei length,
const GLchar* message, const void *) {
io::ostream* stream = &slog.i;
auto logSeverity = utils::LogSeverity::kInfo;
switch (severity) {
case GL_DEBUG_SEVERITY_HIGH: stream = &slog.e; break;
case GL_DEBUG_SEVERITY_MEDIUM: stream = &slog.w; break;
case GL_DEBUG_SEVERITY_LOW: stream = &slog.d; break;
case GL_DEBUG_SEVERITY_HIGH: logSeverity = utils::LogSeverity::kError; break;
case GL_DEBUG_SEVERITY_MEDIUM: logSeverity = utils::LogSeverity::kWarning; break;
case GL_DEBUG_SEVERITY_LOW: logSeverity = utils::LogSeverity::kInfo; break;
case GL_DEBUG_SEVERITY_NOTIFICATION:
default: break;
}
io::ostream& out = *stream;
const char* level = ": ";
switch (type) {
case GL_DEBUG_TYPE_ERROR: level = "ERROR: "; break;
@@ -262,7 +243,7 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform,
case GL_DEBUG_TYPE_MARKER: level = "MARKER: "; break;
default: break;
}
out << "KHR_debug " << level << std::string_view{ message, size_t(length) } << io::endl;
LOG(LEVEL(logSeverity)) << "KHR_debug " << level << std::string_view{ message, size_t(length) };
};
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
@@ -707,9 +688,8 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor
GLUtils::unordered_string_set const exts = GLUtils::split(extensions);
if constexpr (DEBUG_PRINT_EXTENSIONS) {
for (auto extension: exts) {
slog.d << "\"" << std::string_view(extension) << "\"\n";
DLOG(INFO) << "\"" << std::string_view(extension) << "\"";
}
flush(slog.d);
}
// figure out and initialize the extensions we need
@@ -783,9 +763,8 @@ void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor)
}
if constexpr (DEBUG_PRINT_EXTENSIONS) {
for (auto extension: exts) {
slog.d << "\"" << std::string_view(extension) << "\"\n";
DLOG(INFO) << "\"" << std::string_view(extension) << "\"";
}
flush(slog.d);
}
using namespace std::literals;
@@ -1045,7 +1024,7 @@ GLuint OpenGLContext::getSamplerSlow(SamplerParams params) const noexcept {
std::min(gets.max_anisotropy, anisotropy));
}
#endif
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
mSamplerMap[params] = s;
return s;
}

View File

@@ -48,12 +48,13 @@
#include <type_traits>
#include <utils/BitmaskEnum.h>
#include <utils/FixedCapacityVector.h>
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Invocable.h>
#include <utils/Log.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/Slice.h>
#include <utils/Systrace.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/ostream.h>
@@ -184,20 +185,18 @@ OpenGLDriver* OpenGLDriver::create(OpenGLPlatform* platform,
// GLVertexBufferInfo : 132 moderate
// -- less than or equal to 136 bytes
slog.d
<< "\nGLSwapChain: " << sizeof(GLSwapChain)
<< "\nGLBufferObject: " << sizeof(GLBufferObject)
<< "\nGLVertexBuffer: " << sizeof(GLVertexBuffer)
<< "\nGLVertexBufferInfo: " << sizeof(GLVertexBufferInfo)
<< "\nGLIndexBuffer: " << sizeof(GLIndexBuffer)
<< "\nGLRenderPrimitive: " << sizeof(GLRenderPrimitive)
<< "\nGLTexture: " << sizeof(GLTexture)
<< "\nGLTimerQuery: " << sizeof(GLTimerQuery)
<< "\nGLStream: " << sizeof(GLStream)
<< "\nGLRenderTarget: " << sizeof(GLRenderTarget)
<< "\nGLFence: " << sizeof(GLFence)
<< "\nOpenGLProgram: " << sizeof(OpenGLProgram)
<< io::endl;
DLOG(INFO) << "GLSwapChain: " << sizeof(GLSwapChain);
DLOG(INFO) << "GLBufferObject: " << sizeof(GLBufferObject);
DLOG(INFO) << "GLVertexBuffer: " << sizeof(GLVertexBuffer);
DLOG(INFO) << "GLVertexBufferInfo: " << sizeof(GLVertexBufferInfo);
DLOG(INFO) << "GLIndexBuffer: " << sizeof(GLIndexBuffer);
DLOG(INFO) << "GLRenderPrimitive: " << sizeof(GLRenderPrimitive);
DLOG(INFO) << "GLTexture: " << sizeof(GLTexture);
DLOG(INFO) << "GLTimerQuery: " << sizeof(GLTimerQuery);
DLOG(INFO) << "GLStream: " << sizeof(GLStream);
DLOG(INFO) << "GLRenderTarget: " << sizeof(GLRenderTarget);
DLOG(INFO) << "GLFence: " << sizeof(GLFence);
DLOG(INFO) << "OpenGLProgram: " << sizeof(OpenGLProgram);
#endif
// here we check we're on a supported version of GL before initializing the driver
@@ -289,7 +288,7 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
mStreamsWithPendingAcquiredImage.reserve(8);
#ifndef NDEBUG
slog.i << "OS version: " << mPlatform.getOSVersion() << io::endl;
LOG(INFO) << "OS version: " << mPlatform.getOSVersion();
#endif
// Timer queries are core in GL 3.3, otherwise we need EXT_disjoint_timer_query
@@ -668,7 +667,7 @@ void OpenGLDriver::createIndexBufferR(
gl.bindVertexArray(nullptr);
gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib->gl.buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, nullptr, getBufferUsage(usage));
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void OpenGLDriver::createBufferObjectR(Handle<HwBufferObject> boh,
@@ -693,7 +692,7 @@ void OpenGLDriver::createBufferObjectR(Handle<HwBufferObject> boh,
glBufferData(bo->gl.binding, byteCount, nullptr, getBufferUsage(usage));
}
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void OpenGLDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
@@ -730,7 +729,7 @@ void OpenGLDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
// this records the index buffer into the currently bound VAO
gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib->gl.buffer);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void OpenGLDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
@@ -760,7 +759,7 @@ void OpenGLDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
}
construct<OpenGLProgram>(ph, *this, std::move(program));
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
UTILS_NOINLINE
@@ -953,7 +952,7 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
renderBufferStorage(t->gl.id, internalFormat, w, h, samples);
}
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void OpenGLDriver::createTextureViewR(Handle<HwTexture> th,
@@ -999,7 +998,7 @@ void OpenGLDriver::createTextureViewR(Handle<HwTexture> th,
assert_invariant(ref);
ref->count++;
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void OpenGLDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwTexture> srch,
@@ -1062,7 +1061,7 @@ void OpenGLDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwText
assert_invariant(ref);
ref->count++;
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void OpenGLDriver::createTextureExternalImage2R(Handle<HwTexture> th, SamplerType target,
@@ -1225,7 +1224,7 @@ void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
#endif
}
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer const* vb) {
@@ -1485,7 +1484,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
// we shouldn't be here
break;
}
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
} else
#ifndef __EMSCRIPTEN__
#ifdef GL_EXT_multisampled_render_to_texture
@@ -1506,7 +1505,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment,
GL_RENDERBUFFER, t->gl.id);
}
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
} else
#endif // GL_EXT_multisampled_render_to_texture
#endif // __EMSCRIPTEN__
@@ -1588,13 +1587,13 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
break;
}
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
rt->gl.resolve |= resolveFlags;
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_FRAMEBUFFER)
CHECK_GL_ERROR()
CHECK_GL_FRAMEBUFFER_STATUS(GL_FRAMEBUFFER)
}
void OpenGLDriver::renderBufferStorage(GLuint rbo, GLenum internalformat, uint32_t width, // NOLINT(readability-convert-member-functions-to-static)
@@ -1623,7 +1622,7 @@ void OpenGLDriver::renderBufferStorage(GLuint rbo, GLenum internalformat, uint32
// unbind the renderbuffer, to avoid any later confusion
glBindRenderbuffer(GL_RENDERBUFFER, 0);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void OpenGLDriver::createDefaultRenderTargetR(
@@ -1720,7 +1719,7 @@ void OpenGLDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
if (UTILS_LIKELY(!getContext().isES2())) {
glDrawBuffers((GLsizei)maxDrawBuffers, bufs);
}
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
#endif
@@ -1760,7 +1759,7 @@ void OpenGLDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
assert_invariant(any(targets & TargetBufferFlags::ALL));
assert_invariant(tmin == tmax);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void OpenGLDriver::createFenceR(Handle<HwFence> fh, int) {
@@ -2111,7 +2110,7 @@ void OpenGLDriver::setAcquiredImage(Handle<HwStream> sh, void* hwbuffer, const m
if (UTILS_UNLIKELY(glstream->user_thread.pending.image)) {
scheduleRelease(glstream->user_thread.pending);
slog.w << "Acquired image is set more than once per frame." << io::endl;
LOG(WARNING) << "Acquired image is set more than once per frame.";
}
glstream->user_thread.pending = mPlatform.transformAcquiredImage({
@@ -2531,13 +2530,36 @@ void OpenGLDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain>
mPlatform.makeCurrent(scDraw->swapChain, scRead->swapChain,
[this]() {
for (auto t: mTexturesWithStreamsAttached) {
if (t->hwStream->streamType == StreamType::NATIVE) {
mPlatform.detach(t->hwStream->stream);
}
}
// OpenGL context is about to change, unbind everything
mContext.unbindEverything();
},
[this](size_t index) {
for (auto t: mTexturesWithStreamsAttached) {
if (t->hwStream->streamType == StreamType::NATIVE) {
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);
}
}
// force invalidation of all bound descriptor sets
decltype(mInvalidDescriptorSetBindings) changed;
changed.setValue((1 << MAX_DESCRIPTOR_SET_COUNT) - 1);
mInvalidDescriptorSetBindings |= changed;
// OpenGL context has changed, resynchronize the state with the cache
mContext.synchronizeStateAndCache(index);
slog.d << "*** OpenGL context change : " << (index ? "protected" : "default") << io::endl;
DLOG(INFO) << "*** OpenGL context change : " << (index ? "protected" : "default");
});
mCurrentDrawSwapChain = scDraw;
@@ -2578,7 +2600,7 @@ void OpenGLDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh,
vb->bufferObjectsVersion = (version + 1) % kMaxVersion;
}
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void OpenGLDriver::updateIndexBuffer(
@@ -2595,7 +2617,7 @@ void OpenGLDriver::updateIndexBuffer(
scheduleDestroy(std::move(p));
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void OpenGLDriver::registerBufferObjectStreams(Handle<HwBufferObject> boh, BufferObjectStreamDescriptor&& streams) {
@@ -2675,7 +2697,7 @@ void OpenGLDriver::updateBufferObject(
scheduleDestroy(std::move(bd));
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void OpenGLDriver::updateBufferObjectUnsynchronized(
@@ -2722,7 +2744,7 @@ retry:
scheduleDestroy(std::move(bd));
}
}
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
#endif
}
@@ -2772,7 +2794,7 @@ void OpenGLDriver::generateMipmaps(Handle<HwTexture> th) {
glGenerateMipmap(t->gl.target);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void OpenGLDriver::setTextureData(GLTexture const* t, uint32_t level,
@@ -2881,7 +2903,7 @@ void OpenGLDriver::setTextureData(GLTexture const* t, uint32_t level,
scheduleDestroy(std::move(p));
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void OpenGLDriver::setCompressedTextureData(GLTexture const* t, uint32_t level,
@@ -2967,7 +2989,7 @@ void OpenGLDriver::setCompressedTextureData(GLTexture const* t, uint32_t level,
scheduleDestroy(std::move(p));
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void OpenGLDriver::setupExternalImage2(Platform::ExternalImageHandleRef image) {
@@ -3009,6 +3031,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;
@@ -3037,7 +3060,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;
}
@@ -3061,8 +3089,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.
@@ -3118,7 +3152,7 @@ void OpenGLDriver::beginRenderPass(Handle<HwRenderTarget> rth,
TargetBufferFlags discardFlags = params.flags.discardStart & rt->targets;
GLuint const fbo = gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo);
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_FRAMEBUFFER)
CHECK_GL_FRAMEBUFFER_STATUS(GL_FRAMEBUFFER)
// each render-pass starts with a disabled scissor
gl.disable(GL_SCISSOR_TEST);
@@ -3130,7 +3164,7 @@ void OpenGLDriver::beginRenderPass(Handle<HwRenderTarget> rth,
if (attachmentCount) {
gl.procs.invalidateFramebuffer(GL_FRAMEBUFFER, attachmentCount, attachments.data());
}
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
} else {
// It's important to clear the framebuffer before drawing, as it resets
// the fb to a known state (resets fb compression and possibly other things).
@@ -3217,7 +3251,7 @@ void OpenGLDriver::endRenderPass(int) {
if (attachmentCount) {
gl.procs.invalidateFramebuffer(GL_FRAMEBUFFER, attachmentCount, attachments.data());
}
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
}
@@ -3263,13 +3297,13 @@ void OpenGLDriver::resolvePass(ResolveAction action, GLRenderTarget const* rt,
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, read);
gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, draw);
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
CHECK_GL_FRAMEBUFFER_STATUS(GL_READ_FRAMEBUFFER)
CHECK_GL_FRAMEBUFFER_STATUS(GL_DRAW_FRAMEBUFFER)
gl.disable(GL_SCISSOR_TEST);
glBlitFramebuffer(0, 0, (GLint)rt->width, (GLint)rt->height,
0, 0, (GLint)rt->width, (GLint)rt->height, mask, GL_NEAREST);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
#endif
}
@@ -3448,7 +3482,7 @@ void OpenGLDriver::readPixels(Handle<HwRenderTarget> src,
if (buffer) {
gl.bindFramebuffer(GL_FRAMEBUFFER, s->gl.fbo_read ? s->gl.fbo_read : s->gl.fbo);
glReadPixels(GLint(x), GLint(y), GLint(width), GLint(height), glFormat, glType, buffer);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
// now we need to flip the buffer vertically to match our API
size_t const stride = p.stride ? p.stride : width;
@@ -3480,7 +3514,7 @@ void OpenGLDriver::readPixels(Handle<HwRenderTarget> src,
glBufferData(GL_PIXEL_PACK_BUFFER, pboSize, nullptr, GL_STATIC_DRAW);
glReadPixels(GLint(x), GLint(y), GLint(width), GLint(height), glFormat, glType, nullptr);
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, 0);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
// we're forced to make a copy on the heap because otherwise it deletes std::function<> copy
// constructor.
@@ -3520,7 +3554,7 @@ void OpenGLDriver::readPixels(Handle<HwRenderTarget> src,
glDeleteBuffers(1, &pbo);
scheduleDestroy(std::move(p));
delete pUserBuffer;
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
});
#endif
}
@@ -3545,7 +3579,7 @@ void OpenGLDriver::readBufferSubData(BufferObjectHandle boh,
glCopyBufferSubData(bo->gl.binding, GL_PIXEL_PACK_BUFFER, offset, 0, size);
gl.bindBuffer(bo->gl.binding, 0);
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, 0);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
// then, we schedule a mapBuffer of the PBO later, once the fence has signaled
auto* pUserBuffer = new BufferDescriptor(std::move(p));
@@ -3562,7 +3596,7 @@ void OpenGLDriver::readBufferSubData(BufferObjectHandle boh,
glDeleteBuffers(1, &pbo);
scheduleDestroy(std::move(p));
delete pUserBuffer;
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
});
} else {
gl.bindBuffer(bo->gl.binding, bo->gl.id);
@@ -3575,7 +3609,7 @@ void OpenGLDriver::readBufferSubData(BufferObjectHandle boh,
}
gl.bindBuffer(bo->gl.binding, 0);
scheduleDestroy(std::move(p));
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
#endif
}
@@ -3605,7 +3639,7 @@ void OpenGLDriver::whenFrameComplete(const std::function<void()>& fn) noexcept {
void OpenGLDriver::whenGpuCommandsComplete(const std::function<void()>& fn) noexcept {
GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
mGpuCommandCompleteOps.emplace_back(sync, fn);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void OpenGLDriver::executeGpuCommandsCompleteOps() noexcept {
@@ -3725,8 +3759,7 @@ void OpenGLDriver::updateDescriptorSetTexture(
TextureHandle th,
SamplerParams params) {
GLDescriptorSet* ds = handle_cast<GLDescriptorSet*>(dsh);
GLTexture* t = th ? handle_cast<GLTexture*>(th) : nullptr;
ds->update(mContext, binding, t, params);
ds->update(mContext, mHandleAllocator, binding, th, params);
}
void OpenGLDriver::flush(int) {
@@ -3824,7 +3857,7 @@ void OpenGLDriver::clearWithRasterPipe(TargetBufferFlags clearFlags,
}
}
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void OpenGLDriver::resolve(
@@ -3935,7 +3968,7 @@ void OpenGLDriver::blit(
case SamplerType::SAMPLER_EXTERNAL:
break;
}
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
CHECK_GL_FRAMEBUFFER_STATUS(GL_DRAW_FRAMEBUFFER)
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, fbo[1]);
switch (s->target) {
@@ -3961,14 +3994,14 @@ void OpenGLDriver::blit(
case SamplerType::SAMPLER_EXTERNAL:
break;
}
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
CHECK_GL_FRAMEBUFFER_STATUS(GL_READ_FRAMEBUFFER)
gl.disable(GL_SCISSOR_TEST);
glBlitFramebuffer(
srcOrigin.x, srcOrigin.y, srcOrigin.x + size.x, srcOrigin.y + size.y,
dstOrigin.x, dstOrigin.y, dstOrigin.x + size.x, dstOrigin.y + size.y,
mask, GL_NEAREST);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
gl.unbindFramebuffer(GL_DRAW_FRAMEBUFFER);
gl.unbindFramebuffer(GL_READ_FRAMEBUFFER);
@@ -4031,15 +4064,15 @@ void OpenGLDriver::blitDEPRECATED(TargetBufferFlags buffers,
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, s->gl.fbo);
gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, d->gl.fbo);
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
CHECK_GL_FRAMEBUFFER_STATUS(GL_READ_FRAMEBUFFER)
CHECK_GL_FRAMEBUFFER_STATUS(GL_DRAW_FRAMEBUFFER)
gl.disable(GL_SCISSOR_TEST);
glBlitFramebuffer(
srcRect.left, srcRect.bottom, srcRect.right(), srcRect.top(),
dstRect.left, dstRect.bottom, dstRect.right(), dstRect.top(),
GL_COLOR_BUFFER_BIT, glFilterMode);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
#endif
}
@@ -4166,9 +4199,9 @@ void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
#endif
#if FILAMENT_ENABLE_MATDBG
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
CHECK_GL_ERROR_NON_FATAL()
#else
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
#endif
}
@@ -4198,9 +4231,9 @@ void OpenGLDriver::draw2GLES2(uint32_t indexOffset, uint32_t indexCount, uint32_
reinterpret_cast<const void*>(indexOffset << rp->gl.indicesShift));
#if FILAMENT_ENABLE_MATDBG
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
CHECK_GL_ERROR_NON_FATAL()
#else
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
#endif
}
@@ -4250,9 +4283,9 @@ void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, uint3 workGroupCou
#endif // BACKEND_OPENGL_LEVEL_GLES31
#if FILAMENT_ENABLE_MATDBG
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
CHECK_GL_ERROR_NON_FATAL()
#else
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
#endif
}

View File

@@ -214,7 +214,7 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
case DescriptorType::INPUT_ATTACHMENT:
break;
}
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
}

View File

@@ -114,14 +114,14 @@ void TimerQueryNativeFactory::createTimerQuery(GLTimerQuery* tq) {
tq->state = std::make_shared<GLTimerQuery::State>();
mContext.procs.genQueries(1u, &tq->state->gl.query);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void TimerQueryNativeFactory::destroyTimerQuery(GLTimerQuery* tq) {
assert_invariant(tq->state);
mContext.procs.deleteQueries(1u, &tq->state->gl.query);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
tq->state.reset();
}
@@ -131,14 +131,14 @@ void TimerQueryNativeFactory::beginTimeElapsedQuery(GLTimerQuery* tq) {
tq->state->elapsed.store(int64_t(TimerQueryResult::NOT_READY), std::memory_order_relaxed);
mContext.procs.beginQuery(GL_TIME_ELAPSED, tq->state->gl.query);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
void TimerQueryNativeFactory::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* tq) {
assert_invariant(tq->state);
mContext.procs.endQuery(GL_TIME_ELAPSED);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
std::weak_ptr<GLTimerQuery::State> const weak = tq->state;
@@ -153,7 +153,7 @@ void TimerQueryNativeFactory::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQ
GLuint available = 0;
context.procs.getQueryObjectuiv(state->gl.query, GL_QUERY_RESULT_AVAILABLE, &available);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
if (!available) {
// we need to try this one again later
return false;

View File

@@ -23,6 +23,7 @@
#include "OpenGLDriver.h"
#include <iterator>
#include <optional>
#include <private/backend/BackendUtils.h>
#include <backend/DriverEnums.h>
@@ -30,14 +31,14 @@
#include <private/utils/Tracing.h>
#include <utils/compiler.h>
#include <utils/CString.h>
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h>
#include <utils/JobSystem.h>
#include <utils/Log.h>
#include <utils/ostream.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/ostream.h>
#include <algorithm>
#include <array>
@@ -64,9 +65,9 @@ static std::string to_string(bool const b) { return b ? "true" : "false"; }
static std::string to_string(int const i) { return std::to_string(i); }
static std::string to_string(float const f) { return "float(" + std::to_string(f) + ")"; }
static void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name,
GLuint shaderId, CString const& sourceCode) noexcept;
static void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept;
static void logCompilationError(ShaderStage shaderType, const char* name, GLuint shaderId,
CString const& sourceCode) noexcept;
static void logProgramLinkError(char const* name, GLuint program) noexcept;
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext const& context, char* source,
size_t len) noexcept;
@@ -110,17 +111,33 @@ 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{};
// Only valid when the blob functions are provided by users. The validity of this variable
// doesn't guarantee that the program was created from the cache blob.
BlobCacheKey key;
// Used for the `THREAD_POOL` mode.
mutable Mutex lock;
mutable Condition cond;
bool signaled = false;
// Indicate this program was created from the cache blob.
bool retrievedFromBlobCache = false;
};
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() {
compiler.submitCallbackHandle(handle);
trySubmittingCallback();
}
/* static */ void ShaderCompilerService::setUserData(const program_token_t& token,
@@ -248,6 +265,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
// Try retrieving the cached program blob if available.
token->gl.program = mBlobCache.retrieve(&token->key, mDriver.mPlatform, program);
if (token->gl.program) {
token->retrievedFromBlobCache = true;
return token;
}
@@ -326,12 +344,15 @@ GLuint ShaderCompilerService::getProgram(program_token_t& token) {
assert_invariant(token);// This function should be called when the token is still alive.
if (token->compiler.mMode == Mode::THREAD_POOL) {
auto const job = token->compiler.mCompilerThreadPool.dequeue(token);
if (!job) {
// It's likely that the job was already completed. But it may be still being
// executed at this moment. Just try waiting for it to avoid a race.
token->wait();
// Finalize any pending shader compilation tasks only when the token was created without cache.
if (!token->retrievedFromBlobCache) {
if (token->compiler.mMode == Mode::THREAD_POOL) {
auto const job = token->compiler.mCompilerThreadPool.dequeue(token);
if (!job) {
// It's likely that the job was already completed. But it may be still being
// executed at this moment. Just try waiting for it to avoid a race.
token->wait();
}
}
}
@@ -339,7 +360,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 +413,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;
}
@@ -422,8 +443,9 @@ void ShaderCompilerService::ensureTokenIsReady(program_token_t const& token) {
// just log warnings here instead of repeatedly checking compile status. If this turns
// out to be a real issue later, we would need to consider doing the canonical way.
if (!isCompileCompleted(token)) {
slog.w << "Shader compilation for OpenGL program " << token->name.c_str_safe()
<< " is not completed yet. The following program link may not succeed.";
LOG(WARNING)
<< "Shader compilation for OpenGL program " << token->name.c_str_safe()
<< " is not completed yet. The following program link may not succeed.";
}
linkProgram(mDriver.getContext(), token);
@@ -633,8 +655,7 @@ void ShaderCompilerService::executeTickOps() noexcept {
}
// Something went wrong. Log the error message.
const ShaderStage type = static_cast<ShaderStage>(i);
logCompilationError(slog.e, type, token->name.c_str_safe(), shader,
token->shaderSourceCode[i]);
logCompilationError(type, token->name.c_str_safe(), shader, token->shaderSourceCode[i]);
}
}
@@ -659,6 +680,7 @@ void ShaderCompilerService::executeTickOps() noexcept {
}
glLinkProgram(program);
token->gl.program = program;
token->trySubmittingCallback();
}
/* static */ bool ShaderCompilerService::isLinkCompleted(program_token_t const& token) noexcept {
@@ -685,7 +707,7 @@ void ShaderCompilerService::executeTickOps() noexcept {
glGetProgramiv(token->gl.program, GL_LINK_STATUS, &status);
if (UTILS_UNLIKELY(status != GL_TRUE)) {
// Something went wrong. Log the error message.
logProgramLinkError(slog.e, token->name.c_str_safe(), token->gl.program);
logProgramLinkError(token->name.c_str_safe(), token->gl.program);
linked = false;
}
// No need to keep the shaders around regardless of the result of the program linking.
@@ -735,7 +757,7 @@ void ShaderCompilerService::executeTickOps() noexcept {
// ------------------------------------------------------------------------------------------------
UTILS_NOINLINE
/* static */ void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name,
/* static */ void logCompilationError(ShaderStage shaderType, const char* name,
GLuint const shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
{ // scope for the temporary string storage
@@ -757,8 +779,9 @@ UTILS_NOINLINE
CString infoLog(length);
glGetShaderInfoLog(shaderId, length, nullptr, infoLog.data());
out << "Compilation error in " << to_string(shaderType) << " shader \"" << name << "\":\n"
<< "\"" << infoLog.c_str() << "\"" << io::endl;
LOG(ERROR) << "Compilation error in " << to_string(shaderType) << " shader \"" << name
<< "\":";
LOG(ERROR) << "\"" << infoLog.c_str() << "\"";
}
#ifndef NDEBUG
@@ -773,26 +796,25 @@ UTILS_NOINLINE
} else {
line = shader.substr(start, end - start);
}
out << lc++ << ": " << line.c_str() << '\n';
LOG(ERROR) << lc++ << ": " << line.c_str();
if (end == std::string::npos) {
break;
}
start = end + 1;
}
out << io::endl;
LOG(ERROR) << "";
#endif
}
UTILS_NOINLINE
/* static */ void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept {
/* static */ void logProgramLinkError(char const* name, GLuint program) noexcept {
GLint length = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
CString infoLog(length);
glGetProgramInfoLog(program, length, nullptr, infoLog.data());
out << "Link error in \"" << name << "\":\n"
<< "\"" << infoLog.c_str() << "\"" << io::endl;
LOG(ERROR) << "Link error in \"" << name << "\":\n" << "\"" << infoLog.c_str() << "\"";
}
// If usages of the Google-style line directive are present, remove them, as some

View File

@@ -96,7 +96,7 @@ CocoaExternalImage::SharedGl::~SharedGl() noexcept {
CocoaExternalImage::CocoaExternalImage(const CVOpenGLTextureCacheRef textureCache,
const SharedGl &sharedGl) noexcept : mSharedGl(sharedGl), mTextureCache(textureCache) {
glGenFramebuffers(1, &mFBO);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
CocoaExternalImage::~CocoaExternalImage() noexcept {
@@ -125,7 +125,7 @@ bool CocoaExternalImage::set(CVPixelBufferRef image) noexcept {
mTexture = createTextureFromImage(image);
mRgbaTexture = encodeCopyRectangleToTexture2D(CVOpenGLTextureGetName(mTexture),
CVPixelBufferGetWidth(image), CVPixelBufferGetHeight(image));
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
return true;
}
@@ -182,33 +182,33 @@ GLuint CocoaExternalImage::encodeCopyRectangleToTexture2D(GLuint rectangle,
// Create a texture to hold the result of the blit image.
glBindTexture(GL_TEXTURE_2D, texture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
// source textures
glBindSampler(0, mSharedGl.sampler);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_RECTANGLE, rectangle);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
// destination texture
glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_FRAMEBUFFER)
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_FRAMEBUFFER_STATUS(GL_FRAMEBUFFER)
CHECK_GL_ERROR()
// draw
glViewport(0, 0, width, height);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
glUseProgram(mSharedGl.program);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
glDisableVertexAttribArray(0);
glDrawArrays(GL_TRIANGLES, 0, 3);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
mState.restore();
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
return texture;
}

View File

@@ -119,7 +119,7 @@ CocoaTouchExternalImage::CocoaTouchExternalImage(const CVOpenGLESTextureCacheRef
glGenFramebuffers(1, &mFBO);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
}
CocoaTouchExternalImage::~CocoaTouchExternalImage() noexcept {
@@ -247,7 +247,7 @@ GLuint CocoaTouchExternalImage::encodeColorConversionPass(GLuint yPlaneTexture,
glBindTexture(GL_TEXTURE_2D, texture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
// source textures
glBindSampler(0, mSharedGl.sampler);
@@ -261,8 +261,8 @@ GLuint CocoaTouchExternalImage::encodeColorConversionPass(GLuint yPlaneTexture,
glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_FRAMEBUFFER)
CHECK_GL_ERROR()
CHECK_GL_FRAMEBUFFER_STATUS(GL_FRAMEBUFFER)
// geometry
glBindVertexArray(0);
@@ -275,7 +275,7 @@ GLuint CocoaTouchExternalImage::encodeColorConversionPass(GLuint yPlaneTexture,
glUseProgram(mSharedGl.program);
glDrawArrays(GL_TRIANGLES, 0, 3);
CHECK_GL_ERROR(utils::slog.e)
CHECK_GL_ERROR()
mState.restore();

View File

@@ -20,9 +20,9 @@
#include <backend/Platform.h>
#include <utils/Logger.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/Log.h>
#include <utils/ostream.h>
#if __has_include(<android/surface_texture.h>)
@@ -59,7 +59,7 @@ void ExternalStreamManagerAndroid::destroy(ExternalStreamManagerAndroid* pExtern
ExternalStreamManagerAndroid::ExternalStreamManagerAndroid() noexcept
: mVm(VirtualMachineEnv::get()) {
if (__builtin_available(android 28, *)) {
slog.d << "Using ASurfaceTexture" << io::endl;
DLOG(INFO) << "Using ASurfaceTexture";
}
}

View File

@@ -32,9 +32,9 @@
#endif
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/Invocable.h>
#include <utils/Log.h>
#include <utils/Logger.h>
#include <utils/debug.h>
#include <utils/ostream.h>
#include <algorithm>
@@ -74,7 +74,7 @@ void PlatformEGL::logEglError(const char* name) noexcept {
}
void PlatformEGL::logEglError(const char* name, EGLint error) noexcept {
slog.e << name << " failed with " << getEglErrorName(error) << io::endl;
LOG(ERROR) << name << " failed with " << getEglErrorName(error);
}
const char* PlatformEGL::getEglErrorName(EGLint error) noexcept {
@@ -101,7 +101,7 @@ void PlatformEGL::clearGlError() noexcept {
// clear GL error that may have been set by previous calls
GLenum const error = glGetError();
if (error != GL_NO_ERROR) {
slog.w << "Ignoring pending GL error " << io::hex << error << io::endl;
LOG(WARNING) << "Ignoring pending GL error " << io::hex << error;
}
}
@@ -142,7 +142,7 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& drive
}
if (UTILS_UNLIKELY(!initialized)) {
slog.e << "eglInitialize failed" << io::endl;
LOG(ERROR) << "eglInitialize failed";
return nullptr;
}

View File

@@ -29,13 +29,12 @@
#include <android/native_window.h>
#include <android/hardware_buffer.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/android/PerformanceHintManager.h>
#include <utils/compiler.h>
#include <utils/compiler.h>
#include <utils/ostream.h>
#include <utils/Panic.h>
#include <utils/Log.h>
#include <utils/compiler.h>
#include <utils/ostream.h>
#include <EGL/egl.h>
@@ -327,7 +326,7 @@ bool PlatformEGLAndroid::setImage(ExternalImageEGLAndroid const* eglExternalImag
EGL_NATIVE_BUFFER_ANDROID, clientBuffer, imageAttrs);
if (eglImage == EGL_NO_IMAGE_KHR) {
// Handle error
slog.e << "Failed to create EGL image" << io::endl;
LOG(ERROR) << "Failed to create EGL image";
glDeleteTextures(1, &texture->id);
return false;
}
@@ -340,7 +339,7 @@ bool PlatformEGLAndroid::setImage(ExternalImageEGLAndroid const* eglExternalImag
glBindTexture(texture->target, texture->id);
GLenum error = glGetError();
if (UTILS_UNLIKELY(error != GL_NO_ERROR)) {
slog.e << "Error after glBindTexture: " << error << io::endl;
LOG(ERROR) << "Error after glBindTexture: " << error;
glDeleteTextures(1, &texture->id);
eglDestroyImageKHR(eglGetCurrentDisplay(), eglImage);
glActiveTexture(prevActiveTexture);
@@ -350,7 +349,7 @@ bool PlatformEGLAndroid::setImage(ExternalImageEGLAndroid const* eglExternalImag
glEGLImageTargetTexture2DOES(texture->target, static_cast<GLeglImageOES>(eglImage));
error = glGetError();
if (UTILS_UNLIKELY(error != GL_NO_ERROR)) {
slog.e << "Error after glEGLImageTargetTexture2DOES: " << error << io::endl;
LOG(ERROR) << "Error after glEGLImageTargetTexture2DOES: " << error;
glDeleteTextures(1, &texture->id);
eglDestroyImageKHR(eglGetCurrentDisplay(), eglImage);
glActiveTexture(prevActiveTexture);
@@ -408,7 +407,7 @@ AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage source) n
EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(pHardwareBuffer);
if (!clientBuffer) {
slog.e << "Unable to get EGLClientBuffer from AHardwareBuffer." << io::endl;
LOG(ERROR) << "Unable to get EGLClientBuffer from AHardwareBuffer.";
return {};
}
@@ -427,7 +426,7 @@ AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage source) n
EGLImageKHR eglImage = eglCreateImageKHR(mEGLDisplay,
EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attributes.data());
if (eglImage == EGL_NO_IMAGE_KHR) {
slog.e << "eglCreateImageKHR returned no image." << io::endl;
LOG(ERROR) << "eglCreateImageKHR returned no image.";
return {};
}
@@ -442,7 +441,7 @@ AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage source) n
auto patchedCallback = [](void* image, void* userdata) {
Closure* closure = (Closure*)userdata;
if (eglDestroyImageKHR(closure->display, (EGLImageKHR) image) == EGL_FALSE) {
slog.e << "eglDestroyImageKHR failed." << io::endl;
LOG(ERROR) << "eglDestroyImageKHR failed.";
}
closure->acquiredImage.callback(closure->acquiredImage.image, closure->acquiredImage.userData);
delete closure;

View File

@@ -21,9 +21,9 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <utils/compiler.h>
#include <utils/Log.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/compiler.h>
using namespace utils;
@@ -42,12 +42,12 @@ backend::Driver* PlatformEGLHeadless::createDriver(void* sharedContext,
const Platform::DriverConfig& driverConfig) noexcept {
EGLBoolean bindAPI = eglBindAPI(EGL_OPENGL_API);
if (UTILS_UNLIKELY(!bindAPI)) {
slog.e << "eglBindAPI EGL_OPENGL_API failed" << io::endl;
LOG(ERROR) << "eglBindAPI EGL_OPENGL_API failed";
return nullptr;
}
int bindBlueGL = bluegl::bind();
if (UTILS_UNLIKELY(bindBlueGL != 0)) {
slog.e << "bluegl bind failed" << io::endl;
LOG(ERROR) << "bluegl bind failed";
return nullptr;
}

View File

@@ -16,7 +16,7 @@
#include <backend/platforms/PlatformGLX.h>
#include <utils/Log.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <X11/Xlib.h>
@@ -84,7 +84,7 @@ static PFNGLXGETPROCADDRESSPROC getProcAddress;
static bool loadLibraries() {
g_glx.library = dlopen(LIBRARY_GLX, RTLD_LOCAL | RTLD_NOW);
if (!g_glx.library) {
utils::slog.e << "Could not find library " << LIBRARY_GLX << utils::io::endl;
LOG(ERROR) << "Could not find library " << LIBRARY_GLX;
return false;
}
@@ -115,7 +115,7 @@ static bool loadLibraries() {
g_x11.library = dlopen(LIBRARY_X11, RTLD_LOCAL | RTLD_NOW);
if (!g_x11.library) {
utils::slog.e << "Could not find library " << LIBRARY_X11 << utils::io::endl;
LOG(ERROR) << "Could not find library " << LIBRARY_X11;
return false;
}
@@ -134,7 +134,7 @@ Driver* PlatformGLX::createDriver(void* sharedGLContext,
// Get the display device
mGLXDisplay = g_x11.openDisplay(NULL);
if (mGLXDisplay == nullptr) {
utils::slog.e << "Failed to open X display. (exiting)." << utils::io::endl;
LOG(ERROR) << "Failed to open X display. (exiting).";
exit(1);
}
@@ -145,8 +145,7 @@ Driver* PlatformGLX::createDriver(void* sharedGLContext,
r = g_glx.queryContext(mGLXDisplay, sharedCtx, GLX_FBCONFIG_ID, &usedFbId);
if (r != 0) {
utils::slog.e << "Failed to get GLX_FBCONFIG_ID from shared GL context."
<< utils::io::endl;
LOG(ERROR) << "Failed to get GLX_FBCONFIG_ID from shared GL context.";
return nullptr;
}
@@ -154,7 +153,7 @@ Driver* PlatformGLX::createDriver(void* sharedGLContext,
GLXFBConfig* fbConfigs = g_glx.getFbConfigs(mGLXDisplay, 0, &numConfigs);
if (fbConfigs == nullptr) {
utils::slog.e << "Failed to get the available GLXFBConfigs." << utils::io::endl;
LOG(ERROR) << "Failed to get the available GLXFBConfigs.";
return nullptr;
}
@@ -164,8 +163,7 @@ Driver* PlatformGLX::createDriver(void* sharedGLContext,
for (int i = 0; i < numConfigs; ++i) {
r = g_glx.getFbConfigAttrib(mGLXDisplay, fbConfigs[i], GLX_FBCONFIG_ID, &fbId);
if (r != 0) {
utils::slog.e << "Failed to get GLX_FBCONFIG_ID for entry " << i << "."
<< utils::io::endl;
LOG(ERROR) << "Failed to get GLX_FBCONFIG_ID for entry " << i << ".";
continue;
}
@@ -176,8 +174,7 @@ Driver* PlatformGLX::createDriver(void* sharedGLContext,
}
if (fbIndex < 0) {
utils::slog.e << "Failed to find an `GLXFBConfig` with the requested ID."
<< utils::io::endl;
LOG(ERROR) << "Failed to find an `GLXFBConfig` with the requested ID.";
return nullptr;
}
@@ -202,8 +199,7 @@ Driver* PlatformGLX::createDriver(void* sharedGLContext,
getProcAddress((GLubyte*)"glXCreateContextAttribsARB");
if (glXCreateContextAttribs == nullptr) {
utils::slog.i << "Unable to retrieve function pointer for `glXCreateContextAttribs()`."
<< utils::io::endl;
LOG(INFO) << "Unable to retrieve function pointer for `glXCreateContextAttribs()`.";
return nullptr;
}

View File

@@ -32,7 +32,7 @@
#include "GL/glext.h"
#include "GL/wglext.h"
#include <utils/Log.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
namespace {
@@ -55,8 +55,7 @@ void reportWindowsError(DWORD dwError) {
0, nullptr
);
utils::slog.e << "Windows error code: " << dwError << ". " << lpMessageBuffer
<< utils::io::endl;
LOG(ERROR) << "Windows error code: " << dwError << ". " << lpMessageBuffer;
LocalFree(lpMessageBuffer);
}
@@ -106,7 +105,7 @@ Driver* PlatformWGL::createDriver(void* sharedGLContext,
HDC whdc = mWhdc = GetDC(mHWnd);
if (whdc == NULL) {
dwError = GetLastError();
utils::slog.e << "CreateWindowA() failed" << utils::io::endl;
LOG(ERROR) << "CreateWindowA() failed";
goto error;
}
@@ -117,8 +116,7 @@ Driver* PlatformWGL::createDriver(void* sharedGLContext,
tempContext = wglCreateContext(whdc);
if (!wglMakeCurrent(whdc, tempContext)) {
dwError = GetLastError();
utils::slog.e << "wglMakeCurrent() failed, whdc=" << whdc << ", tempContext=" <<
tempContext << utils::io::endl;
LOG(ERROR) << "wglMakeCurrent() failed, whdc=" << whdc << ", tempContext=" << tempContext;
goto error;
}
@@ -142,7 +140,7 @@ Driver* PlatformWGL::createDriver(void* sharedGLContext,
}
if (!mContext) {
utils::slog.e << "wglCreateContextAttribs() failed, whdc=" << whdc << utils::io::endl;
LOG(ERROR) << "wglCreateContextAttribs() failed, whdc=" << whdc;
goto error;
}
@@ -152,8 +150,7 @@ Driver* PlatformWGL::createDriver(void* sharedGLContext,
if (!wglMakeCurrent(whdc, mContext)) {
dwError = GetLastError();
utils::slog.e << "wglMakeCurrent() failed, whdc=" << whdc << ", mContext=" <<
mContext << utils::io::endl;
LOG(ERROR) << "wglMakeCurrent() failed, whdc=" << whdc << ", mContext=" << mContext;
goto error;
}

View File

@@ -40,7 +40,7 @@ inline void blitFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect, V
FVK_LOGD << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
<< " layout=" << src.getLayout()
<< " to=" << dst.texture->getVkImage() << ",level=" << (int) dst.level
<< " layout=" << dst.getLayout() << utils::io::endl;
<< " layout=" << dst.getLayout();
}
VkImageSubresourceRange const srcRange = src.getSubresourceRange();
@@ -80,7 +80,7 @@ inline void resolveFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect
FVK_LOGD << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
<< " layout=" << src.getLayout()
<< " to=" << dst.texture->getVkImage() << ",level=" << (int) dst.level
<< " layout=" << dst.getLayout() << utils::io::endl;
<< " layout=" << dst.getLayout();
}
VkImageSubresourceRange const srcRange = src.getSubresourceRange();

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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -215,7 +215,7 @@ VkSemaphore VulkanCommandBuffer::submit() {
}
FVK_LOGI << ") "
<< " signal=" << mSubmission
<< " fence=" << mFence << utils::io::endl;
<< " fence=" << mFence;
#endif
mFenceStatus->setStatus(VK_NOT_READY);
@@ -224,7 +224,7 @@ VkSemaphore VulkanCommandBuffer::submit() {
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
if (result != VK_SUCCESS) {
FVK_LOGD << "Failed command buffer submission result: " << result << utils::io::endl;
FVK_LOGD << "Failed command buffer submission result: " << result;
}
#endif
assert_invariant(result == VK_SUCCESS);
@@ -490,7 +490,7 @@ void VulkanCommands::pushGroupMarker(char const* str, VulkanGroupMarkers::Timest
mProtectedPool->pushMarker(str, timestamp);
}
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
FVK_LOGD << "----> " << str << utils::io::endl;
FVK_LOGD << "----> " << str;
#endif
}
@@ -502,8 +502,7 @@ void VulkanCommands::popGroupMarker() {
auto const& startTime = ret.second;
auto const endTime = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = endTime - startTime;
FVK_LOGD << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms"
<< utils::io::endl;
FVK_LOGD << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms";
#else
mPool->popMarker();
#endif // FVK_DEBUG_PRINT_GROUP_MARKERS

View File

@@ -17,7 +17,7 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANCONSTANTS_H
#define TNT_FILAMENT_BACKEND_VULKANCONSTANTS_H
#include <utils/Log.h>
#include <utils/Logger.h>
#include <stdint.h>
@@ -73,14 +73,16 @@
#define FVK_DEBUG_RESOURCE_LEAK 0x00010000
// Set this to enable logging "only" to one output stream. This is useful in the case where we want
// to debug with print statements and want ordered logging (e.g slog.i and slog.e will not appear in
// order of calls).
// to debug with print statements and want ordered logging (e.g LOG(INFO) and LOG(ERROR) will not
// appear in order of calls).
#define FVK_DEBUG_FORCE_LOG_TO_I 0x00020000
// Enable a minimal set of traces to assess the performance of the backend.
// 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 \
@@ -172,15 +174,15 @@ static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION));
#endif
#if FVK_ENABLED(FVK_DEBUG_FORCE_LOG_TO_I)
#define FVK_LOGI (utils::slog.i)
#define FVK_LOGI LOG(INFO)
#define FVK_LOGD FVK_LOGI
#define FVK_LOGE FVK_LOGI
#define FVK_LOGW FVK_LOGI
#else
#define FVK_LOGE (utils::slog.e)
#define FVK_LOGW (utils::slog.w)
#define FVK_LOGD (utils::slog.d)
#define FVK_LOGI (utils::slog.i)
#define FVK_LOGE LOG(ERROR)
#define FVK_LOGW LOG(WARNING)
#define FVK_LOGD DLOG(INFO)
#define FVK_LOGI LOG(INFO)
#endif
// All vkCreate* functions take an optional allocator. For now we select the default allocator by

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 {

View File

@@ -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,
};

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,
@@ -99,15 +104,15 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugReportCallback(VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location,
int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData) {
if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
FVK_LOGE << "VULKAN ERROR: (" << pLayerPrefix << ") " << pMessage << utils::io::endl;
FVK_LOGE << "VULKAN ERROR: (" << pLayerPrefix << ") " << pMessage;
} else {
// TODO: emit best practices warnings about aggressive pipeline barriers.
if (strstr(pMessage, "ALL_GRAPHICS_BIT") || strstr(pMessage, "ALL_COMMANDS_BIT")) {
return VK_FALSE;
}
FVK_LOGW << "VULKAN WARNING: (" << pLayerPrefix << ") " << pMessage << utils::io::endl;
FVK_LOGW << "VULKAN WARNING: (" << pLayerPrefix << ") " << pMessage;
}
FVK_LOGE << utils::io::endl;
FVK_LOGE;
return VK_FALSE;
}
#endif // FVK_ENABLED(FVK_DEBUG_VALIDATION)
@@ -117,18 +122,16 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsCallback(VkDebugUtilsMessageSeverityFla
VkDebugUtilsMessageTypeFlagsEXT types, const VkDebugUtilsMessengerCallbackDataEXT* cbdata,
void* pUserData) {
if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
FVK_LOGE << "VULKAN ERROR: (" << cbdata->pMessageIdName << ") " << cbdata->pMessage
<< utils::io::endl;
FVK_LOGE << "VULKAN ERROR: (" << cbdata->pMessageIdName << ") " << cbdata->pMessage;
} else {
// TODO: emit best practices warnings about aggressive pipeline barriers.
if (strstr(cbdata->pMessage, "ALL_GRAPHICS_BIT")
|| strstr(cbdata->pMessage, "ALL_COMMANDS_BIT")) {
return VK_FALSE;
}
FVK_LOGW << "VULKAN WARNING: (" << cbdata->pMessageIdName << ") " << cbdata->pMessage
<< utils::io::endl;
FVK_LOGW << "VULKAN WARNING: (" << cbdata->pMessageIdName << ") " << cbdata->pMessage;
}
FVK_LOGE << utils::io::endl;
FVK_LOGE << "";
return VK_FALSE;
}
#endif // FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
@@ -209,7 +212,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()),
@@ -270,20 +274,18 @@ Driver* VulkanDriver::create(VulkanPlatform* platform, VulkanContext const& cont
// VulkanRenderTarget : 312 few
// -- less than or equal to 312 bytes
FVK_LOGD
<< "\nVulkanSwapChain: " << sizeof(VulkanSwapChain)
<< "\nVulkanBufferObject: " << sizeof(VulkanBufferObject)
<< "\nVulkanVertexBuffer: " << sizeof(VulkanVertexBuffer)
<< "\nVulkanVertexBufferInfo: " << sizeof(VulkanVertexBufferInfo)
<< "\nVulkanIndexBuffer: " << sizeof(VulkanIndexBuffer)
<< "\nVulkanRenderPrimitive: " << sizeof(VulkanRenderPrimitive)
<< "\nVulkanTexture: " << sizeof(VulkanTexture)
<< "\nVulkanTimerQuery: " << sizeof(VulkanTimerQuery)
<< "\nHwStream: " << sizeof(HwStream)
<< "\nVulkanRenderTarget: " << sizeof(VulkanRenderTarget)
<< "\nVulkanFence: " << sizeof(VulkanFence)
<< "\nVulkanProgram: " << sizeof(VulkanProgram)
<< utils::io::endl;
FVK_LOGD << "VulkanSwapChain: " << sizeof(VulkanSwapChain);
FVK_LOGD << "VulkanBufferObject: " << sizeof(VulkanBufferObject);
FVK_LOGD << "VulkanVertexBuffer: " << sizeof(VulkanVertexBuffer);
FVK_LOGD << "VulkanVertexBufferInfo: " << sizeof(VulkanVertexBufferInfo);
FVK_LOGD << "VulkanIndexBuffer: " << sizeof(VulkanIndexBuffer);
FVK_LOGD << "VulkanRenderPrimitive: " << sizeof(VulkanRenderPrimitive);
FVK_LOGD << "VulkanTexture: " << sizeof(VulkanTexture);
FVK_LOGD << "VulkanTimerQuery: " << sizeof(VulkanTimerQuery);
FVK_LOGD << "HwStream: " << sizeof(HwStream);
FVK_LOGD << "VulkanRenderTarget: " << sizeof(VulkanRenderTarget);
FVK_LOGD << "VulkanFence: " << sizeof(VulkanFence);
FVK_LOGD << "VulkanProgram: " << sizeof(VulkanProgram);
#endif
assert_invariant(platform);
@@ -328,7 +330,6 @@ void VulkanDriver::terminate() {
// descriptorSetLayoutCache
mExternalImageManager.terminate();
mStagePool.terminate();
mPipelineCache.terminate();
mFramebufferCache.terminate();
mSamplerCache.terminate();
@@ -339,6 +340,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 +381,7 @@ void VulkanDriver::collectGarbage() {
mCommands.gc();
mDescriptorSetCache.gc();
mStagePool.gc();
mBufferCache.gc();
mFramebufferCache.gc();
mPipelineCache.gc();
@@ -514,7 +525,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 +542,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();
}
@@ -784,14 +795,12 @@ void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
mResourceManager.gc();
if ((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0 && !isSRGBSwapChainSupported()) {
FVK_LOGW << "sRGB swapchain requested, but Platform does not support it"
<< utils::io::endl;
FVK_LOGW << "sRGB swapchain requested, but Platform does not support it";
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
}
if (flags & backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
if (!isProtectedContentSupported()) {
FVK_LOGW << "protected swapchain requested, but Platform does not support it"
<< utils::io::endl;
FVK_LOGW << "protected swapchain requested, but Platform does not support it";
}
}
auto swapChain = resource_ptr<VulkanSwapChain>::make(&mResourceManager, sch, mPlatform,
@@ -802,8 +811,7 @@ void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
void VulkanDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width,
uint32_t height, uint64_t flags) {
if ((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0 && !isSRGBSwapChainSupported()) {
FVK_LOGW << "sRGB swapchain requested, but Platform does not support it"
<< utils::io::endl;
FVK_LOGW << "sRGB swapchain requested, but Platform does not support it";
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
}
assert_invariant(width > 0 && height > 0 && "Vulkan requires non-zero swap chain dimensions.");
@@ -1223,7 +1231,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 +1246,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 +1257,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));
}
@@ -1291,7 +1299,7 @@ TimerQueryResult VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint
uint64_t const end = results.endTime;
if (begin >= end) {
// TODO: queries might have ran on different command buffers.
FVK_LOGW << "Timestamps are not monotonically increasing. " << utils::io::endl;
FVK_LOGW << "Timestamps are not monotonically increasing. ";
*elapsedTime = 0;
return TimerQueryResult::ERROR;
}
@@ -1622,8 +1630,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 +1906,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);
}
@@ -2029,7 +2037,7 @@ void VulkanDriver::debugCommandBegin(CommandStream* cmds, bool synchronous, cons
assert_invariant(inRenderPass);
inRenderPass = false;
} else if (inRenderPass && OUTSIDE_COMMANDS.find(command) != OUTSIDE_COMMANDS.end()) {
FVK_LOGE << command.data() << " issued inside a render pass." << utils::io::endl;
FVK_LOGE << command.data() << " issued inside a render pass.";
}
#endif
}

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;

View File

@@ -100,8 +100,7 @@ VkFramebuffer VulkanFboCache::getFramebuffer(FboKey const& config) noexcept {
<< "for render pass " << config.renderPass << ", "
<< "samples = " << int(config.samples) << ", "
<< "depth = " << (config.depth ? 1 : 0) << ", "
<< "attachmentCount = " << attachmentCount
<< utils::io::endl;
<< "attachmentCount = " << attachmentCount;
#endif
VkFramebufferCreateInfo info {
@@ -341,8 +340,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept
<< "needsResolveMask = " << int(config.needsResolveMask) << ", "
<< "usesLazilyAllocatedMemory = " << int(config.usesLazilyAllocatedMemory) << ", "
<< "viewCount = " << int(config.viewCount) << ", "
<< "colorAttachmentCount[0] = " << subpasses[0].colorAttachmentCount
<< utils::io::endl;
<< "colorAttachmentCount[0] = " << subpasses[0].colorAttachmentCount;
#endif
return renderPass;

View File

@@ -287,7 +287,7 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
FVK_LOGD << "Created VulkanProgram " << builder << ", shaders = (" << modules[0]
<< ", " << modules[1] << ")" << utils::io::endl;
<< ", " << modules[1] << ")";
#endif
}
@@ -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) {}

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

View File

@@ -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

View File

@@ -218,8 +218,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = ("
<< shaderStages[0].module << ", " << shaderStages[1].module << ")"
<< utils::io::endl;
<< shaderStages[0].module << ", " << shaderStages[1].module << ")";
#endif
PipelineCacheEntry cacheEntry = {
.lastUsed = mCurrentTime,
@@ -228,7 +227,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
VKALLOC, &cacheEntry.handle);
assert_invariant(error == VK_SUCCESS);
if (error != VK_SUCCESS) {
FVK_LOGE << "vkCreateGraphicsPipelines error " << error << utils::io::endl;
FVK_LOGE << "vkCreateGraphicsPipelines error " << error;
return nullptr;
}
return &mPipelines.emplace(mPipelineRequirements, cacheEntry).first.value();
@@ -242,7 +241,7 @@ void VulkanPipelineCache::bindProgram(fvkmemory::resource_ptr<VulkanProgram> pro
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
if (mPipelineRequirements.shaders[0] == VK_NULL_HANDLE ||
mPipelineRequirements.shaders[1] == VK_NULL_HANDLE) {
FVK_LOGE << "Binding missing shader: " << program->name.c_str() << utils::io::endl;
FVK_LOGE << "Binding missing shader: " << program->name.c_str();
}
#endif
}

View File

@@ -42,7 +42,7 @@ fvkmemory::resource_ptr<VulkanTimerQuery> VulkanQueryManager::getNextQuery(
fvkmemory::ResourceManager* resourceManager) {
auto unused = ~mUsed;
if (unused.empty()) {
FVK_LOGE << "More than " << mUsed.size() << " timers are not supported." << utils::io::endl;
FVK_LOGE << "More than " << mUsed.size() << " timers are not supported.";
return {};
}

View File

@@ -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;
@@ -171,7 +171,7 @@ void VulkanReadPixels::run(fvkmemory::resource_ptr<VulkanRenderTarget> srcTarget
#if FVK_ENABLED(FVK_DEBUG_READ_PIXELS)
FVK_LOGD << "readPixels created image=" << stagingImage
<< " to copy from image=" << srcTexture->getVkImage()
<< " src-layout=" << srcTexture->getLayout(0, 0) << utils::io::endl;
<< " src-layout=" << srcTexture->getLayout(0, 0);
#endif
VkMemoryRequirements memReqs;
@@ -188,28 +188,27 @@ void VulkanReadPixels::run(fvkmemory::resource_ptr<VulkanRenderTarget> srcTarget
memoryTypeIndex = selectMemoryFunc(memReqs.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
FVK_LOGW
<< "readPixels is slow because VK_MEMORY_PROPERTY_HOST_CACHED_BIT is not available"
<< utils::io::endl;
<< "readPixels is slow because VK_MEMORY_PROPERTY_HOST_CACHED_BIT is not available";
}
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES)
<< "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,9 +303,8 @@ 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;
FVK_LOGE << "Failed to wait for readPixels fence";
return;
}
@@ -324,7 +322,7 @@ void VulkanReadPixels::run(fvkmemory::resource_ptr<VulkanRenderTarget> srcTarget
fvkutils::getComponentCount(srcFormat), srcPixels,
static_cast<int>(subResourceLayout.rowPitch), static_cast<int>(width),
static_cast<int>(height), swizzle)) {
FVK_LOGE << "Unsupported PixelDataFormat or PixelDataType" << utils::io::endl;
FVK_LOGE << "Unsupported PixelDataFormat or PixelDataType";
}
vkUnmapMemory(device, stagingMemory);

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;
FVK_LOGE << "Allocation error: " << result;
} else {
FVK_LOGD << "Allocated stage with hndl " << buffer;
}
#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);

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;

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;
@@ -187,8 +188,8 @@ VkImageUsageFlags getUsage(VulkanContext const& context, uint8_t samples,
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(physicalDevice, vkFormat, &props);
if (!(props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) {
FVK_LOGW << "Texture usage is SAMPLEABLE but format " << vkFormat << " is not "
"sampleable with optimal tiling." << utils::io::endl;
FVK_LOGW << "Texture usage is SAMPLEABLE but format " << vkFormat
<< " is not sampleable with optimal tiling.";
}
}
#endif
@@ -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,
@@ -326,7 +361,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
imageInfo.flags = VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
} else {
FVK_LOGW << "Note: creating 2D views on 3D image is not available on this platform. "
<< "i.e. we cannot render to slices of a 3D image" << utils::io::endl;
<< "i.e. we cannot render to slices of a 3D image";
}
} else if (target == SamplerType::SAMPLER_CUBEMAP) {
imageInfo.arrayLayers = 6;
@@ -385,7 +420,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
<< "type = " << imageInfo.imageType << ", "
<< "flags = " << imageInfo.flags << ", "
<< "target = " << static_cast<int>(target) <<", "
<< "format = " << vkFormat << utils::io::endl;
<< "format = " << vkFormat;
}
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to create image."
<< " error=" << static_cast<int32_t>(result);
@@ -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();
@@ -681,14 +731,14 @@ bool VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceR
<< range.levelCount << ")" << " from=" << oldLayout << " to=" << newLayout
<< " format=" << mState->mVkFormat << " depth="
<< fvkutils::isVkDepthFormat(mState->mVkFormat)
<< " slice-by-slice=" << transitionSliceBySlice << utils::io::endl;
<< " slice-by-slice=" << transitionSliceBySlice;
#endif
} else {
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION)
FVK_LOGD << "transition texture=" << mState->mTextureImage << " (" << range.baseArrayLayer
<< "," << range.baseMipLevel << ")" << " count=(" << range.layerCount << ","
<< range.levelCount << ")" << " to=" << newLayout
<< " is skipped because of no change in layout" << utils::io::endl;
<< " is skipped because of no change in layout";
#endif
}
@@ -799,8 +849,7 @@ void VulkanTexture::print() const {
level < (mPrimaryViewRange.baseMipLevel + mPrimaryViewRange.levelCount);
FVK_LOGD << "[" << mState->mTextureImage << "]: (" << layer << "," << level
<< ")=" << getLayout(layer, level)
<< " primary=" << primary
<< utils::io::endl;
<< " primary=" << primary;
}
}
@@ -809,8 +858,7 @@ void VulkanTexture::print() const {
FVK_LOGD << "[" << mState->mTextureImage << ", imageView=" << view.second << "]=>"
<< " (" << range.baseArrayLayer << "," << range.baseMipLevel << ")"
<< " count=(" << range.layerCount << "," << range.levelCount << ")"
<< " aspect=" << range.aspectMask << " viewType=" << view.first.type
<< utils::io::endl;
<< " aspect=" << range.aspectMask << " viewType=" << view.first.type;
}
}
#endif

View File

@@ -29,6 +29,8 @@ using namespace bluevk;
namespace filament::backend {
using namespace fvkutils;
VulkanYcbcrConversionCache::VulkanYcbcrConversionCache(VkDevice device)
: mDevice(device) {}

View File

@@ -17,6 +17,8 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
#define TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
#include "utils/Definitions.h"
#include <backend/DriverEnums.h>
#include <utils/Hash.h>
@@ -30,7 +32,7 @@ namespace filament::backend {
class VulkanYcbcrConversionCache {
public:
struct Params {
SamplerYcbcrConversion conversion = {}; // 4
fvkutils::SamplerYcbcrConversion conversion = {}; // 4
VkFormat format; // 4
uint64_t externalFormat = 0; // 8
};
@@ -45,16 +47,15 @@ private:
struct ConversionEqualTo {
bool operator()(Params lhs, Params rhs) const noexcept {
SamplerYcbcrConversion::EqualTo equal;
fvkutils::SamplerYcbcrConversion::EqualTo equal;
return equal(lhs.conversion, rhs.conversion) &&
lhs.externalFormat == rhs.externalFormat &&
lhs.format == rhs.format;
lhs.externalFormat == rhs.externalFormat && lhs.format == rhs.format;
}
};
using ConversionHashFn = utils::hash::MurmurHashFn<Params>;
tsl::robin_map<Params, VkSamplerYcbcrConversion, ConversionHashFn, ConversionEqualTo> mCache;
};
}// namespace filament::backend
} // namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H

View File

@@ -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 "";
}

View File

@@ -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>

View File

@@ -17,6 +17,7 @@
#include "vulkan/memory/ResourceManager.h"
#include "vulkan/VulkanHandles.h"
#include <utils/Logger.h>
#include <utils/Panic.h>
namespace filament::backend::fvkmemory {
@@ -77,6 +78,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 +108,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;
}
@@ -121,11 +128,11 @@ void ResourceManager::traceConstruction(ResourceType type, HandleId id) {
void ResourceManager::print() const noexcept {
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
utils::slog.e << "-------------------" << utils::io::endl;
LOG(ERROR) << "-------------------";
for (size_t i = 0; i < (size_t) ResourceType::UNDEFINED_TYPE; ++i) {
utils::slog.e <<" " << getTypeStr((ResourceType) i) << "=" << COUNTER[i] << utils::io::endl;
LOG(ERROR) << " " << getTypeStr((ResourceType) i) << "=" << COUNTER[i];
}
utils::slog.e << "+++++++++++++++++++" << utils::io::endl;
LOG(ERROR) << "+++++++++++++++++++";
#endif
}

View File

@@ -25,8 +25,9 @@
#include "vulkan/utils/Helper.h"
#include <bluevk/BlueVK.h>
#include <utils/PrivateImplementation-impl.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/PrivateImplementation-impl.h>
#define SWAPCHAIN_RET_FUNC(func, handle, ...) \
if (mImpl->mSurfaceSwapChains.find(handle) != mImpl->mSurfaceSwapChains.end()) { \
@@ -92,6 +93,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) {
@@ -104,7 +115,7 @@ void printDeviceInfo(VkInstance instance, VkPhysicalDevice device) {
chainStruct(&physicalDeviceProperties2, &driverProperties);
vkGetPhysicalDeviceProperties2(device, &physicalDeviceProperties2);
FVK_LOGI << "Vulkan device driver: " << driverProperties.driverName << " "
<< driverProperties.driverInfo << utils::io::endl;
<< driverProperties.driverInfo;
}
VkPhysicalDeviceProperties deviceProperties;
@@ -141,7 +152,7 @@ void printDeviceInfo(VkInstance instance, VkPhysicalDevice device) {
<< "(vendor " << utils::io::hex << vendorID << ", "
<< "device " << deviceID << ", "
<< "driver " << driverVersion << ", " << utils::io::dec << "api " << major << "."
<< minor << ")" << utils::io::endl;
<< minor << ")";
}
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
@@ -151,14 +162,20 @@ 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 << " ";
}
}
FVK_LOGI << utils::io::endl;
FVK_LOGI << "";
}
#endif
@@ -278,10 +295,10 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
} else {
#if defined(__ANDROID__)
FVK_LOGD << "Validation layers are not available; did you set jniLibs in your "
<< "gradle file?" << utils::io::endl;
<< "gradle file?";
#else
FVK_LOGD << "Validation layer not available; did you install the Vulkan SDK?\n"
<< "Please ensure that VK_LAYER_PATH is set correctly." << utils::io::endl;
<< "Please ensure that VK_LAYER_PATH is set correctly.";
#endif // __ANDROID__
}
@@ -617,7 +634,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);
@@ -962,8 +985,7 @@ SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags,
if (flags & backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
if (!mImpl->mContext.mProtectedMemorySupported) {
utils::slog.w << "protected swapchain requested, but VulkanPlatform does not support it"
<< utils::io::endl;
LOG(WARNING) << "protected swapchain requested, but VulkanPlatform does not support it";
}
}

View File

@@ -163,8 +163,7 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
// the number of images, though there may be limits related to the total amount of memory used
// by presentable images."
if (maxImageCount != 0 && desiredImageCount > maxImageCount) {
FVK_LOGE << "Swap chain does not support " << desiredImageCount << " images."
<< utils::io::endl;
FVK_LOGE << "Swap chain does not support " << desiredImageCount << " images.";
desiredImageCount = caps.minImageCount;
}
@@ -266,8 +265,7 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
<< "swapchain-size=" << mSwapChainBundle.colors.size() << ", "
<< "identity-transform=" << (caps.currentTransform == 1) << ", "
<< "depth=" << mSwapChainBundle.depthFormat << ", "
<< "protected=" << mSwapChainBundle.isProtected
<< io::endl;
<< "protected=" << mSwapChainBundle.isProtected;
VkSemaphoreCreateInfo const semaphoreCreateInfo = {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
@@ -293,7 +291,7 @@ VkResult VulkanPlatformSurfaceSwapChain::acquire(VulkanPlatform::ImageSyncData*
// Users should be notified of a suboptimal surface, but it should not cause a cascade of
// log messages or a loop of re-creations.
if (result == VK_SUBOPTIMAL_KHR && !mSuboptimal) {
FVK_LOGW << "Vulkan Driver: Suboptimal swap chain." << io::endl;
FVK_LOGW << "Vulkan Driver: Suboptimal swap chain.";
mSuboptimal = true;
}
return result;
@@ -315,7 +313,7 @@ VkResult VulkanPlatformSurfaceSwapChain::present(uint32_t index, VkSemaphore fin
// On Android Q and above, a suboptimal surface is always reported after screen rotation:
// https://android-developers.googleblog.com/2020/02/handling-device-orientation-efficiently.html
if (result == VK_SUBOPTIMAL_KHR && !mSuboptimal) {
FVK_LOGW << "Vulkan Driver: Suboptimal swap chain." << utils::io::endl;
FVK_LOGW << "Vulkan Driver: Suboptimal swap chain.";
mSuboptimal = true;
}
return result;

View File

@@ -17,6 +17,8 @@
#ifndef TNT_FILAMENT_BACKEND_VULKAN_UTILS_CONVERSION_H
#define TNT_FILAMENT_BACKEND_VULKAN_UTILS_CONVERSION_H
#include "Definitions.h"
#include <backend/DriverEnums.h>
#include <private/backend/BackendUtils.h> // for getFormatSize()

View File

@@ -17,6 +17,8 @@
#ifndef TNT_FILAMENT_BACKEND_VULKAN_UTILS_DEFINITIONS_H
#define TNT_FILAMENT_BACKEND_VULKAN_UTILS_DEFINITIONS_H
#include <backend/DriverEnums.h>
#include <utils/bitset.h>
#include <utils/FixedCapacityVector.h>
@@ -32,313 +34,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;
@@ -367,6 +381,81 @@ static constexpr uint8_t getFragmentStageShift() noexcept {
// We have at most 4 descriptor sets. This is to indicate which ones are active.
using DescriptorSetMask = utils::bitset8;
//! this API is copied from (and only applies to) the Vulkan spec.
//! These specify YUV to RGB conversions.
enum class SamplerYcbcrModelConversion : uint8_t {
RGB_IDENTITY = 0,
YCBCR_IDENTITY = 1,
YCBCR_709 = 2,
YCBCR_601 = 3,
YCBCR_2020 = 4,
};
enum class SamplerYcbcrRange : uint8_t {
ITU_FULL = 0,
ITU_NARROW = 1,
};
enum class ChromaLocation : uint8_t {
COSITED_EVEN = 0,
MIDPOINT = 1,
};
//! Sampler parameters
struct SamplerYcbcrConversion { // NOLINT
SamplerYcbcrModelConversion ycbcrModel : 4;
TextureSwizzle r : 4;
TextureSwizzle g : 4;
TextureSwizzle b : 4;
TextureSwizzle a : 4;
SamplerYcbcrRange ycbcrRange : 1;
ChromaLocation xChromaOffset : 1;
ChromaLocation yChromaOffset : 1;
SamplerMagFilter chromaFilter : 1;
uint8_t padding;
struct Hasher {
size_t operator()(const SamplerYcbcrConversion p) const noexcept {
// we don't use std::hash<> here, so we don't have to include <functional>
return *reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&p));
}
};
struct EqualTo {
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
assert_invariant(lhs.padding == 0);
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
return *pLhs == *pRhs;
}
};
struct LessThan {
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
assert_invariant(lhs.padding == 0);
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
return *pLhs < *pRhs;
}
};
private:
friend bool operator==(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) noexcept {
return SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
}
friend bool operator!=(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) noexcept {
return !SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
}
friend bool operator<(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) noexcept {
return SamplerYcbcrConversion::LessThan{}(lhs, rhs);
}
};
static_assert(sizeof(SamplerYcbcrConversion) == 4);
static_assert(sizeof(SamplerYcbcrConversion) <= sizeof(uint64_t),
"SamplerYcbcrConversion must be no more than 64 bits");
} // namespace filament::backend::fvkutils
#endif // TNT_FILAMENT_BACKEND_VULKAN_UTILS_DEFINITIONS_H

View File

@@ -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>

View File

@@ -0,0 +1,5 @@
WebGPU does not include a trivial way to create mipmaps, and thus we must provide a render pass or compute pass to generate them.
This directory is the result of porting https://github.com/JolifantoBambla/webgpu-spd to C++, which itself is a WebGPU port of https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/blob/main/docs/samples/single-pass-downsampler.md.
The first version of our port is primarily from a Gemini "Convert this to C++" request, which worked as an MVP with very few changes. Future commits will likely alter this version significantly, but it is being merged in this early state to facilitate rapid iteration.

View File

@@ -0,0 +1,960 @@
#include "SpdMipmapGenerator.h"
#include <sstream>
#include <stdexcept>
// C++ port of https://github.com/JolifantoBambla/webgpu-spd for early experiments
namespace spd {
// Helper to convert enums to strings for map keys or shader code
const char* to_string(wgpu::TextureFormat format) {
switch (format) {
case wgpu::TextureFormat::RGBA8Unorm:
return "rgba8unorm";
case wgpu::TextureFormat::BGRA8Unorm:
return "bgra8unorm";
case wgpu::TextureFormat::R32Float:
return "r32float";
case wgpu::TextureFormat::RG32Float:
return "rg32float";
case wgpu::TextureFormat::RGBA32Float:
return "rgba32float";
case wgpu::TextureFormat::R16Float:
return "r16float";
case wgpu::TextureFormat::RG16Float:
return "rg16float";
case wgpu::TextureFormat::RGBA16Float:
return "rgba16float";
// Add other formats as needed
default:
return "rgba8unorm";
}
}
std::string MipmapGenerator::GetFilterCode(SPDFilter filter) {
switch (filter) {
case SPDFilter::Min:
return R"(
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
return min(min(v0, v1), min(v2, v3));
}
)";
case SPDFilter::Max:
return R"(
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
return max(max(v0, v1), max(v2, v3));
}
)";
case SPDFilter::MinMax:
return R"(
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
let max4 = max(max(v0.xy, v1.xy), max(v2.xy, v3.xy));
return vec4<SPDScalar>(min(min(v0.x, v1.x), min(v2.x, v3.x)), max(max4.x, max4.y), 0.0, 0.0);
}
)";
case SPDFilter::Average:
default:
return R"(
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
return (v0 + v1 + v2 + v3) * 0.25;
}
)";
}
}
MipmapGenerator::MipmapGenerator(const wgpu::Device& device)
: m_device(device) {
wgpu::BindGroupLayoutEntry bglEntry{};
bglEntry.binding = 0;
bglEntry.visibility = wgpu::ShaderStage::Compute;
bglEntry.buffer.type = wgpu::BufferBindingType::Uniform;
bglEntry.buffer.minBindingSize = 16;
wgpu::BindGroupLayoutDescriptor bglDesc{};
bglDesc.entryCount = 1;
bglDesc.entries = &bglEntry;
m_internalResourcesBindGroupLayout =
m_device.CreateBindGroupLayout(&bglDesc); // Basic BGL for <= 6 mips
wgpu::Limits deviceLimits;
m_device.GetLimits(&deviceLimits);
// Max number of *output* mips we can generate in a single pass.
// This is device.maxStorageTexturesPerShaderStage - 1 (for the source texture binding),
// further capped at 11 output mips (which means 12 total texture bindings: 1 source + 11
// outputs). WebGPU minspec for maxStorageTexturesPerShaderStage is 4, so
// deviceLimits.maxStorageTexturesPerShaderStage >= 4. Thus, m_maxMipsPerPass will be at least
// std::min(4u - 1, 11u) = 3.
m_maxMipsPerPass =
std::min(static_cast<uint32_t>(deviceLimits.maxStorageTexturesPerShaderStage) - 1, 11u);
m_maxArrayLayers = deviceLimits.maxStorageTexturesPerShaderStage;
// If we can generate more than 6 mips in a pass, we need an advanced BGL
// that includes atomic counter and mid-mip buffer for levels > 5.
if (m_maxMipsPerPass > 6) {
std::vector<wgpu::BindGroupLayoutEntry> entriesAdvanced(3);
// Entry 0: Uniform buffer (DownsamplePassMeta)
entriesAdvanced[0].binding = 0;
entriesAdvanced[0].visibility = wgpu::ShaderStage::Compute;
entriesAdvanced[0].buffer.type = wgpu::BufferBindingType::Uniform;
entriesAdvanced[0].buffer.minBindingSize = 16; // sizeof(DownsamplePassMeta) effectively
// Entry 1: Storage buffer (spd_global_counter)
entriesAdvanced[1].binding = 1;
entriesAdvanced[1].visibility = wgpu::ShaderStage::Compute;
entriesAdvanced[1].buffer.type = wgpu::BufferBindingType::Storage;
entriesAdvanced[1].buffer.minBindingSize = 4; // sizeof(atomic<u32>)
// Entry 2: Storage buffer (mip_dst_6_buffer)
entriesAdvanced[2].binding = 2;
entriesAdvanced[2].visibility = wgpu::ShaderStage::Compute;
entriesAdvanced[2].buffer.type = wgpu::BufferBindingType::Storage;
// Size for array<array<array<vec4<f32>, 64>, 64>> which is 16 (bytes for vec4f32) * 64 * 64
// * arrayLayers. The shader defines it as [slice][uv.y][uv.x], so 64x64 per slice.
// minBindingSize here is for one slice as a baseline. Actual buffer will be larger for
// multiple array layers.
entriesAdvanced[2].buffer.minBindingSize = 16 * 64 * 64;
wgpu::BindGroupLayoutDescriptor bglDescAdvanced{};
bglDescAdvanced.label = "SPD Internal BGL (Advanced)";
bglDescAdvanced.entryCount = static_cast<uint32_t>(entriesAdvanced.size());
bglDescAdvanced.entries = entriesAdvanced.data();
m_internalResourcesBindGroupLayout_Advanced =
m_device.CreateBindGroupLayout(&bglDescAdvanced);
}
}
void MipmapGenerator::PreparePipelines(wgpu::TextureFormat format, SPDFilter filter,
bool halfPrecision) {
SPDScalarType scalarType = SanitizeScalarType(format, halfPrecision);
// Prepare for a reasonable number of mips, up to m_maxMipsPerPass
// The loop should go from 1 up to and including m_maxMipsPerPass.
// If m_maxMipsPerPass is 0 (e.g. maxStorageTexturesPerShaderStage <=1 ), this loop won't run.
for (uint32_t i = 1; i <= m_maxMipsPerPass; ++i) {
GetOrCreatePipeline({ format, scalarType, filter, i });
}
}
SPDScalarType MipmapGenerator::SanitizeScalarType(wgpu::TextureFormat format, bool halfPrecision) {
std::string formatStr = to_string(format);
std::transform(formatStr.begin(), formatStr.end(), formatStr.begin(), ::tolower);
SPDScalarType texelType = SPDScalarType::F32; // Default to F32
if (formatStr.find("sint") != std::string::npos) {
texelType = SPDScalarType::I32;
} else if (formatStr.find("uint") != std::string::npos) {
texelType = SPDScalarType::U32;
}
if (halfPrecision && texelType == SPDScalarType::F32) {
if (m_device.HasFeature(wgpu::FeatureName::ShaderF16)) {
return SPDScalarType::F16;
} else {
// Optional: Log a warning here that halfPrecision was requested for a float format
// but the device does not support ShaderF16, so F32 will be used.
}
} else if (halfPrecision && texelType != SPDScalarType::F32) {
// Optional: Log a warning here that halfPrecision was requested for a non-float format.
}
return texelType;
}
SPDPipeline& MipmapGenerator::GetOrCreatePipeline(const PipelineCacheKey& key) {
uint32_t numMipsForPipeline = std::min(key.numMips, m_maxMipsPerPass);
if (m_pipelines.find(key) == m_pipelines.end()) {
// Create the pipeline
SPDPipeline spdPipeline;
std::vector<wgpu::BindGroupLayoutEntry> mipsBglEntries;
// Bindings are 0 for source, 1 to numMipsForPipeline for destinations
for (uint32_t i = 0; i <= numMipsForPipeline; ++i) {
wgpu::BindGroupLayoutEntry entry{};
entry.binding = i;
entry.visibility = wgpu::ShaderStage::Compute;
if (i == 0) {
if (key.scalarType == SPDScalarType::I32) {
entry.texture.sampleType = wgpu::TextureSampleType::Sint;
} else if (key.scalarType == SPDScalarType::U32) {
entry.texture.sampleType = wgpu::TextureSampleType::Uint;
} else if (key.scalarType == SPDScalarType::F32 || key.scalarType == SPDScalarType::F16) {
entry.texture.sampleType = wgpu::TextureSampleType::Float;
} else {
entry.texture.sampleType = wgpu::TextureSampleType::UnfilterableFloat;
}
entry.texture.viewDimension = wgpu::TextureViewDimension::e2DArray;
} else {
entry.storageTexture.access = wgpu::StorageTextureAccess::WriteOnly;
entry.storageTexture.format = key.format;
entry.storageTexture.viewDimension = wgpu::TextureViewDimension::e2DArray;
}
mipsBglEntries.push_back(entry);
}
wgpu::BindGroupLayoutDescriptor mipsBglDesc{};
mipsBglDesc.entryCount = mipsBglEntries.size();
mipsBglDesc.entries = mipsBglEntries.data();
spdPipeline.mipsBindGroupLayout = m_device.CreateBindGroupLayout(&mipsBglDesc);
std::string shaderCode = MakeShaderCode(key.format, GetFilterCode(key.filter),
numMipsForPipeline, key.scalarType);
wgpu::ShaderModuleWGSLDescriptor wgslDesc{};
wgslDesc.code = shaderCode.c_str();
wgpu::ShaderModuleDescriptor shaderModuleDesc{};
shaderModuleDesc.nextInChain = &wgslDesc;
wgpu::ShaderModule shaderModule = m_device.CreateShaderModule(&shaderModuleDesc);
wgpu::BindGroupLayout chosenInternalBGL =
(numMipsForPipeline > 6 && m_internalResourcesBindGroupLayout_Advanced)
? m_internalResourcesBindGroupLayout_Advanced
: m_internalResourcesBindGroupLayout;
wgpu::BindGroupLayout bgls[] = { spdPipeline.mipsBindGroupLayout, chosenInternalBGL };
wgpu::PipelineLayoutDescriptor layoutDesc{};
layoutDesc.bindGroupLayoutCount = 2;
layoutDesc.bindGroupLayouts = bgls;
wgpu::ComputePipelineDescriptor pipelineDesc{};
pipelineDesc.layout = m_device.CreatePipelineLayout(&layoutDesc);
pipelineDesc.compute.module = shaderModule;
pipelineDesc.compute.entryPoint = "downsample";
spdPipeline.pipeline = m_device.CreateComputePipeline(&pipelineDesc);
m_pipelines[key] = std::move(spdPipeline);
}
return m_pipelines[key];
}
void MipmapGenerator::Generate(wgpu::CommandEncoder& commandEncoder, wgpu::Texture srcTexture,
const SPDPassConfig& config) {
if (config.numMips <= 1 || config.numMips <= config.sourceMipLevel + 1) {
return;
}
if (m_maxMipsPerPass == 0) {
return;
}
PreparePipelines(srcTexture.GetFormat(), config.filter, config.halfPrecision);
uint32_t totalSrcArrayLayers = srcTexture.GetDepthOrArrayLayers();
// Loop over array layer chunks
for (uint32_t currentBaseArrayLayer = 0; currentBaseArrayLayer < totalSrcArrayLayers;
currentBaseArrayLayer += m_maxArrayLayers) {
uint32_t arrayLayersThisPass =
std::min(m_maxArrayLayers, totalSrcArrayLayers - currentBaseArrayLayer);
uint32_t mipsToGenerateCount = config.numMips - (config.sourceMipLevel + 1);
uint32_t currentSourceMipLevel = config.sourceMipLevel;
while (mipsToGenerateCount > 0) {
uint32_t mipsThisPass = std::min(mipsToGenerateCount, m_maxMipsPerPass);
SPDPassConfig passSpecificConfig = {};
passSpecificConfig.filter = config.filter;
passSpecificConfig.halfPrecision = config.halfPrecision;
passSpecificConfig.targetTexture =
config.targetTexture ? config.targetTexture : srcTexture;
passSpecificConfig.numMips = mipsThisPass;
passSpecificConfig.sourceMipLevel = currentSourceMipLevel;
// Pass baseArrayLayer and numArrayLayers directly to generatePass
generatePass(commandEncoder, srcTexture, passSpecificConfig, currentBaseArrayLayer,
arrayLayersThisPass);
mipsToGenerateCount -= mipsThisPass;
currentSourceMipLevel += mipsThisPass;
}
}
}
void MipmapGenerator::generatePass(wgpu::CommandEncoder& commandEncoder, wgpu::Texture srcTexture,
const SPDPassConfig& passConfig, uint32_t baseArrayLayer, uint32_t numArrayLayers) {
uint32_t width = srcTexture.GetWidth() >> passConfig.sourceMipLevel;
uint32_t height = srcTexture.GetHeight() >> passConfig.sourceMipLevel;
uint32_t arrayLayerCountForThisPass = numArrayLayers;
wgpu::Texture target = passConfig.targetTexture ? passConfig.targetTexture : srcTexture;
uint32_t numMipsThisPass = passConfig.numMips;
if (numMipsThisPass == 0) return;
SPDScalarType scalarType = SanitizeScalarType(srcTexture.GetFormat(), passConfig.halfPrecision);
SPDPipeline& spdPipeline = GetOrCreatePipeline(
{ target.GetFormat(), scalarType, passConfig.filter, numMipsThisPass });
// --- Create Bind Group 0 (Mips) ---
std::vector<wgpu::BindGroupEntry> mipEntries;
wgpu::TextureViewDescriptor srcViewDesc{};
srcViewDesc.dimension = wgpu::TextureViewDimension::e2DArray;
srcViewDesc.baseMipLevel = passConfig.sourceMipLevel;
srcViewDesc.mipLevelCount = 1;
srcViewDesc.baseArrayLayer = baseArrayLayer;
srcViewDesc.arrayLayerCount = arrayLayerCountForThisPass;
wgpu::BindGroupEntry srcEntry{};
srcEntry.binding = 0;
srcEntry.textureView = srcTexture.CreateView(&srcViewDesc);
mipEntries.push_back(srcEntry);
for (uint32_t i = 0; i < numMipsThisPass; ++i) {
wgpu::TextureViewDescriptor dstViewDesc{};
dstViewDesc.dimension = wgpu::TextureViewDimension::e2DArray;
dstViewDesc.baseMipLevel = passConfig.sourceMipLevel + i + 1;
dstViewDesc.mipLevelCount = 1;
dstViewDesc.baseArrayLayer = baseArrayLayer;
dstViewDesc.arrayLayerCount = arrayLayerCountForThisPass;
wgpu::BindGroupEntry dstEntry{};
dstEntry.binding = i + 1;
dstEntry.textureView = target.CreateView(&dstViewDesc);
mipEntries.push_back(dstEntry);
}
wgpu::BindGroupDescriptor mipBindGroupDesc{};
mipBindGroupDesc.layout = spdPipeline.mipsBindGroupLayout;
mipBindGroupDesc.entryCount = mipEntries.size();
mipBindGroupDesc.entries = mipEntries.data();
wgpu::BindGroup mipsBindGroup = m_device.CreateBindGroup(&mipBindGroupDesc);
// --- Create Bind Group 1 (Internal Resources) ---
uint32_t widthDiv64 = (width + 63) / 64;
uint32_t heightDiv64 = (height + 63) / 64;
uint32_t numWorkGroups = widthDiv64 * heightDiv64;
struct DownsamplePassMeta {
uint32_t work_group_offset[2] = { 0, 0 };
uint32_t num_work_groups;
uint32_t mips;
uint32_t padding[12];
} meta;
meta.num_work_groups = numWorkGroups;
meta.mips = numMipsThisPass;
wgpu::BufferDescriptor metaBufferDesc{};
metaBufferDesc.size = sizeof(DownsamplePassMeta);
metaBufferDesc.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst;
wgpu::Buffer metaBuffer = m_device.CreateBuffer(&metaBufferDesc);
m_device.GetQueue().WriteBuffer(metaBuffer, 0, &meta, sizeof(meta));
wgpu::BindGroup internalBindGroup;
wgpu::BindGroupLayout chosenInternalBGL =
(numMipsThisPass > 6 && m_internalResourcesBindGroupLayout_Advanced)
? m_internalResourcesBindGroupLayout_Advanced
: m_internalResourcesBindGroupLayout;
if (numMipsThisPass > 6 && m_internalResourcesBindGroupLayout_Advanced) {
wgpu::BufferDescriptor atomicCounterBufferDesc{};
atomicCounterBufferDesc.size = sizeof(uint32_t) * arrayLayerCountForThisPass;
atomicCounterBufferDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst;
wgpu::Buffer atomicCounterBuffer = m_device.CreateBuffer(&atomicCounterBufferDesc);
commandEncoder.ClearBuffer(atomicCounterBuffer, 0, WGPU_WHOLE_SIZE);
wgpu::BufferDescriptor midMipBufferDesc{};
midMipBufferDesc.size = 64 * 64 * 16 * arrayLayerCountForThisPass;
midMipBufferDesc.usage = wgpu::BufferUsage::Storage;
wgpu::Buffer midMipBuffer = m_device.CreateBuffer(&midMipBufferDesc);
std::vector<wgpu::BindGroupEntry> internalEntries(3);
internalEntries[0].binding = 0;
internalEntries[0].buffer = metaBuffer;
internalEntries[0].size = sizeof(DownsamplePassMeta);
internalEntries[1].binding = 1;
internalEntries[1].buffer = atomicCounterBuffer;
internalEntries[1].size = atomicCounterBuffer.GetSize();
internalEntries[2].binding = 2;
internalEntries[2].buffer = midMipBuffer;
internalEntries[2].size = midMipBuffer.GetSize();
wgpu::BindGroupDescriptor internalBindGroupDesc{};
internalBindGroupDesc.layout = chosenInternalBGL;
internalBindGroupDesc.entryCount = static_cast<uint32_t>(internalEntries.size());
internalBindGroupDesc.entries = internalEntries.data();
internalBindGroup = m_device.CreateBindGroup(&internalBindGroupDesc);
} else {
wgpu::BindGroupEntry metaEntry{};
metaEntry.binding = 0;
metaEntry.buffer = metaBuffer;
metaEntry.size = sizeof(DownsamplePassMeta);
wgpu::BindGroupDescriptor internalBindGroupDesc{};
internalBindGroupDesc.layout = chosenInternalBGL;
internalBindGroupDesc.entryCount = 1;
internalBindGroupDesc.entries = &metaEntry;
internalBindGroup = m_device.CreateBindGroup(&internalBindGroupDesc);
}
// --- Dispatch ---
wgpu::ComputePassEncoder pass = commandEncoder.BeginComputePass();
pass.SetPipeline(spdPipeline.pipeline);
pass.SetBindGroup(0, mipsBindGroup);
pass.SetBindGroup(1, internalBindGroup);
pass.DispatchWorkgroups(widthDiv64, heightDiv64, arrayLayerCountForThisPass);
pass.End();
}
// Main shader generation logic
// Helper function to check if a string is in a vector of strings
bool includes(const std::vector<std::string>& vec, const std::string& str) {
for (const auto& s: vec) {
if (s == str) {
return true;
}
}
return false;
}
std::string MakeShaderCode(wgpu::TextureFormat outputFormat, const std::string& filterOp,
unsigned int numMips, // Assuming a default value for numMips
SPDScalarType scalarType) { // Default scalarType
std::stringstream ss;
std::string texelType;
if (scalarType == SPDScalarType::I32) {
texelType = "i32";
} else if (scalarType == SPDScalarType::U32) {
texelType = "u32";
} else {
texelType = "f32";
}
bool useF16 = (scalarType == SPDScalarType::F16);
std::string filterCode = filterOp;
if (filterOp == SPD_FILTER_AVERAGE && !includes({ "f32", "f16" }, texelType)) {
// Replace "* 0.25" with "/ 4"
size_t pos = filterCode.find("* 0.25");
if (pos != std::string::npos) {
filterCode.replace(pos, std::string("* 0.25").length(), "/ 4");
}
}
// Generate mipsBindings
std::string mipsBindings;
for (unsigned int i = 0; i < numMips; ++i) {
mipsBindings += "@group(0) @binding(" + std::to_string(i + 1) + ") var dst_mip_" +
std::to_string(i + 1) + ": texture_storage_2d_array<" +
to_string(outputFormat) + ", write>;\n";
}
// Generate mipsAccessorBody
std::string mipsAccessorBody;
for (unsigned int i = 0; i < numMips; ++i) {
if (i == 5 && numMips > 6) {
mipsAccessorBody += " else if mip == 6 {\n";
mipsAccessorBody += " textureStore(dst_mip_6, uv, slice, " +
(useF16 ? "vec4<" + texelType + ">(value)" : "value") + ");\n";
mipsAccessorBody += " mip_dst_6_buffer[slice][uv.y][uv.x] = value;\n";
mipsAccessorBody += " }";
} else {
if (i != 0) {
mipsAccessorBody += " else ";
}
mipsAccessorBody += "if mip == " + std::to_string(i + 1) + " {\n";
mipsAccessorBody += " textureStore(dst_mip_" + std::to_string(i + 1) +
", uv, slice, " +
(useF16 ? "vec4<" + texelType + ">(value)" : "value") + ");\n";
mipsAccessorBody += " }";
}
}
std::string mipsAccessor =
"fn store_dst_mip(value: vec4<SPDScalar>, uv: vec2<u32>, slice: u32, mip: u32) {\n" +
mipsAccessorBody + "\n}";
std::string midMipAccessor = "return mip_dst_6_buffer[slice][uv.y][uv.x];";
// Start building the final shader code string
ss << R"(
// This file is part of the FidelityFX SDK.
//
// Copyright (C) 2023 Advanced Micro Devices, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the “Software”), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Definitions --------------------------------------------------------------------------------------------------------
)";
if (useF16) {
ss << "enable f16;\n";
}
ss << "alias SPDScalar = " << texelType
<< ";\n\n"; // Using texelType here, assuming SPDScalar maps to it.
ss << R"(
// Helpers ------------------------------------------------------------------------------------------------------------
/**
* A helper function performing a remap 64x1 to 8x8 remapping which is necessary for 2D wave reductions.
* * The 64-wide lane indices to 8x8 remapping is performed as follows:
* 00 01 08 09 10 11 18 19
* 02 03 0a 0b 12 13 1a 1b
* 04 05 0c 0d 14 15 1c 1d
* 06 07 0e 0f 16 17 1e 1f
* 20 21 28 29 30 31 38 39
* 22 23 2a 2b 32 33 3a 3b
* 24 25 2c 2d 34 35 3c 3d
* 26 27 2e 2f 36 37 3e 3f
* * @param a: The input 1D coordinate to remap.
*
* @returns The remapped 2D coordinates.
*/
fn remap_for_wave_reduction(a: u32) -> vec2<u32> {
return vec2<u32>(
insertBits(extractBits(a, 2u, 3u), a, 0u, 1u),
insertBits(extractBits(a, 3u, 3u), extractBits(a, 1u, 2u), 0u, 2u)
);
}
fn map_to_xy(local_invocation_index: u32) -> vec2<u32> {
let sub_xy: vec2<u32> = remap_for_wave_reduction(local_invocation_index % 64);
return vec2<u32>(
sub_xy.x + 8 * ((local_invocation_index >> 6) % 2),
sub_xy.y + 8 * ((local_invocation_index >> 7))
);
}
/*
* Compute a linear value from a SRGB value.
* * @param value: The value to convert to linear from SRGB.
* * @returns A value in SRGB space.
*/
/*
fn srgb_to_linear(value: SPDScalar) -> SPDScalar {
let j = vec3<SPDScalar>(0.0031308 * 12.92, 12.92, 1.0 / 2.4);
let k = vec2<SPDScalar>(1.055, -0.055);
return clamp(j.x, value * j.y, pow(value, j.z) * k.x + k.y);
}
*/
// Resources & Accessors -----------------------------------------------------------------------------------------------
struct DownsamplePassMeta {
work_group_offset: vec2<u32>,
num_work_groups: u32,
mips: u32,
}
// In the original version dst_mip_i is an image2Darray [SPD_MAX_MIP_LEVELS+1], i.e., 12+1, but WGSL doesn't support arrays of textures yet
// Also these are read_write because for mips 7-13, the workgroup reads from mip level 6 - since most formats don't support read_write access in WGSL yet, we use a single read_write buffer in such cases instead
@group(0) @binding(0) var src_mip_0: texture_2d_array<)"
<< texelType << R"(>;
)" << mipsBindings
<< R"(
@group(1) @binding(0) var<uniform> downsample_pass_meta : DownsamplePassMeta;
@group(1) @binding(1) var<storage, read_write> spd_global_counter: array<atomic<u32>>;
@group(1) @binding(2) var<storage, read_write> mip_dst_6_buffer: array<array<array<vec4<f32>, 64>, 64>>;
fn get_mips() -> u32 {
return downsample_pass_meta.mips;
}
fn get_num_work_groups() -> u32 {
return downsample_pass_meta.num_work_groups;
}
fn get_work_group_offset() -> vec2<u32> {
return downsample_pass_meta.work_group_offset;
}
fn load_src_image(uv: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
return vec4<SPDScalar>(textureLoad(src_mip_0, uv, slice, 0));
}
fn load_mid_mip_image(uv: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
)";
if (numMips > 6) {
ss << midMipAccessor;
} else {
ss << "return vec4<SPDScalar>();";
}
ss << R"(
}
)" << mipsAccessor
<< R"(
// Workgroup -----------------------------------------------------------------------------------------------------------
var<workgroup> spd_intermediate: array<array<vec4<SPDScalar>, 16>, 16>;
var<workgroup> spd_counter: atomic<u32>;
fn spd_increase_atomic_counter(slice: u32) {
atomicStore(&spd_counter, atomicAdd(&spd_global_counter[slice], 1));
}
fn spd_get_atomic_counter() -> u32 {
return atomicLoad(&spd_counter);
}
fn spd_reset_atomic_counter(slice: u32) {
atomicStore(&spd_global_counter[slice], 0);
}
// Cotnrol flow --------------------------------------------------------------------------------------------------------
fn spd_barrier() {
// in glsl this does: groupMemoryBarrier(); barrier();
workgroupBarrier();
}
// Only last active workgroup should proceed
fn spd_exit_workgroup(num_work_groups: u32, local_invocation_index: u32, slice: u32) -> bool {
// global atomic counter
if (local_invocation_index == 0) {
spd_increase_atomic_counter(slice);
}
spd_barrier();
return spd_get_atomic_counter() != (num_work_groups - 1);
}
// Pixel access --------------------------------------------------------------------------------------------------------
)" << filterCode
<< R"(
fn spd_store(pix: vec2<u32>, out_value: vec4<SPDScalar>, mip: u32, slice: u32) {
store_dst_mip(out_value, pix, slice, mip + 1);
}
fn spd_load_intermediate(x: u32, y: u32) -> vec4<SPDScalar> {
return spd_intermediate[x][y];
}
fn spd_store_intermediate(x: u32, y: u32, value: vec4<SPDScalar>) {
spd_intermediate[x][y] = value;
}
fn spd_reduce_intermediate(i0: vec2<u32>, i1: vec2<u32>, i2: vec2<u32>, i3: vec2<u32>) -> vec4<SPDScalar> {
let v0 = spd_load_intermediate(i0.x, i0.y);
let v1 = spd_load_intermediate(i1.x, i1.y);
let v2 = spd_load_intermediate(i2.x, i2.y);
let v3 = spd_load_intermediate(i3.x, i3.y);
return spd_reduce_4(v0, v1, v2, v3);
}
fn spd_reduce_load_4(base: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
let v0 = load_src_image(base + vec2<u32>(0, 0), slice);
let v1 = load_src_image(base + vec2<u32>(0, 1), slice);
let v2 = load_src_image(base + vec2<u32>(1, 0), slice);
let v3 = load_src_image(base + vec2<u32>(1, 1), slice);
return spd_reduce_4(v0, v1, v2, v3);
}
fn spd_reduce_load_mid_mip_4(base: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
let v0 = load_mid_mip_image(base + vec2<u32>(0, 0), slice);
let v1 = load_mid_mip_image(base + vec2<u32>(0, 1), slice);
let v2 = load_mid_mip_image(base + vec2<u32>(1, 0), slice);
let v3 = load_mid_mip_image(base + vec2<u32>(1, 1), slice);
return spd_reduce_4(v0, v1, v2, v3);
}
// Main logic ---------------------------------------------------------------------------------------------------------
fn spd_downsample_mips_0_1(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
var v: array<vec4<SPDScalar>, 4>;
let workgroup64 = workgroup_id.xy * 64;
let workgroup32 = workgroup_id.xy * 32;
let workgroup16 = workgroup_id.xy * 16;
var tex = workgroup64 + vec2<u32>(x * 2, y * 2);
var pix = workgroup32 + vec2<u32>(x, y);
v[0] = spd_reduce_load_4(tex, slice);
spd_store(pix, v[0], 0, slice);
tex = workgroup64 + vec2<u32>(x * 2 + 32, y * 2);
pix = workgroup32 + vec2<u32>(x + 16, y);
v[1] = spd_reduce_load_4(tex, slice);
spd_store(pix, v[1], 0, slice);
tex = workgroup64 + vec2<u32>(x * 2, y * 2 + 32);
pix = workgroup32 + vec2<u32>(x, y + 16);
v[2] = spd_reduce_load_4(tex, slice);
spd_store(pix, v[2], 0, slice);
tex = workgroup64 + vec2<u32>(x * 2 + 32, y * 2 + 32);
pix = workgroup32 + vec2<u32>(x + 16, y + 16);
v[3] = spd_reduce_load_4(tex, slice);
spd_store(pix, v[3], 0, slice);
if mip <= 1 {
return;
}
for (var i = 0u; i < 4u; i++) {
spd_store_intermediate(x, y, v[i]);
spd_barrier();
if local_invocation_index < 64 {
v[i] = spd_reduce_intermediate(
vec2<u32>(x * 2 + 0, y * 2 + 0),
vec2<u32>(x * 2 + 1, y * 2 + 0),
vec2<u32>(x * 2 + 0, y * 2 + 1),
vec2<u32>(x * 2 + 1, y * 2 + 1)
);
spd_store(workgroup16 + vec2<u32>(x + (i % 2) * 8, y + (i / 2) * 8), v[i], 1, slice);
}
spd_barrier();
}
if local_invocation_index < 64 {
spd_store_intermediate(x + 0, y + 0, v[0]);
spd_store_intermediate(x + 8, y + 0, v[1]);
spd_store_intermediate(x + 0, y + 8, v[2]);
spd_store_intermediate(x + 8, y + 8, v[3]);
}
}
fn spd_downsample_mip_2(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
if local_invocation_index < 64u {
let v = spd_reduce_intermediate(
vec2<u32>(x * 2 + 0, y * 2 + 0),
vec2<u32>(x * 2 + 1, y * 2 + 0),
vec2<u32>(x * 2 + 0, y * 2 + 1),
vec2<u32>(x * 2 + 1, y * 2 + 1)
);
spd_store(workgroup_id.xy * 8 + vec2<u32>(x, y), v, mip, slice);
// store to LDS, try to reduce bank conflicts
// x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 x
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
// ...
// x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
spd_store_intermediate(x * 2 + y % 2, y * 2, v);
}
}
fn spd_downsample_mip_3(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
if local_invocation_index < 16u {
// x 0 x 0
// 0 0 0 0
// 0 x 0 x
// 0 0 0 0
let v = spd_reduce_intermediate(
vec2<u32>(x * 4 + 0 + 0, y * 4 + 0),
vec2<u32>(x * 4 + 2 + 0, y * 4 + 0),
vec2<u32>(x * 4 + 0 + 1, y * 4 + 2),
vec2<u32>(x * 4 + 2 + 1, y * 4 + 2)
);
spd_store(workgroup_id.xy * 4 + vec2<u32>(x, y), v, mip, slice);
// store to LDS
// x 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0 0
// ...
// 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0
// ...
// 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x
// ...
spd_store_intermediate(x * 4 + y, y * 4, v);
}
}
fn spd_downsample_mip_4(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
if local_invocation_index < 4u {
// x 0 0 0 x 0 0 0
// ...
// 0 x 0 0 0 x 0 0
let v = spd_reduce_intermediate(
vec2<u32>(x * 8 + 0 + 0 + y * 2, y * 8 + 0),
vec2<u32>(x * 8 + 4 + 0 + y * 2, y * 8 + 0),
vec2<u32>(x * 8 + 0 + 1 + y * 2, y * 8 + 4),
vec2<u32>(x * 8 + 4 + 1 + y * 2, y * 8 + 4)
);
spd_store(workgroup_id.xy * 2 + vec2<u32>(x, y), v, mip, slice);
// store to LDS
// x x x x 0 ...
// 0 ...
spd_store_intermediate(x + y * 2, 0, v);
}
}
fn spd_downsample_mip_5(workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
if local_invocation_index < 1u {
// x x x x 0 ...
// 0 ...
let v = spd_reduce_intermediate(vec2<u32>(0, 0), vec2<u32>(1, 0), vec2<u32>(2, 0), vec2<u32>(3, 0));
spd_store(workgroup_id.xy, v, mip, slice);
}
}
fn spd_downsample_next_four(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, base_mip: u32, mips: u32, slice: u32) {
if mips <= base_mip {
return;
}
spd_barrier();
spd_downsample_mip_2(x, y, workgroup_id, local_invocation_index, base_mip, slice);
if mips <= base_mip + 1 {
return;
}
spd_barrier();
spd_downsample_mip_3(x, y, workgroup_id, local_invocation_index, base_mip + 1, slice);
if mips <= base_mip + 2 {
return;
}
spd_barrier();
spd_downsample_mip_4(x, y, workgroup_id, local_invocation_index, base_mip + 2, slice);
if mips <= base_mip + 3 {
return;
}
spd_barrier();
spd_downsample_mip_5(workgroup_id, local_invocation_index, base_mip + 3, slice);
}
fn spd_downsample_last_four(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, base_mip: u32, mips: u32, slice: u32, exit: bool) {
if mips <= base_mip {
return;
}
spd_barrier();
if !exit {
spd_downsample_mip_2(x, y, workgroup_id, local_invocation_index, base_mip, slice);
}
if mips <= base_mip + 1 {
return;
}
spd_barrier();
if !exit {
spd_downsample_mip_3(x, y, workgroup_id, local_invocation_index, base_mip + 1, slice);
}
if mips <= base_mip + 2 {
return;
}
spd_barrier();
if !exit {
spd_downsample_mip_4(x, y, workgroup_id, local_invocation_index, base_mip + 2, slice);
}
if mips <= base_mip + 3 {
return;
}
spd_barrier();
if !exit {
spd_downsample_mip_5(workgroup_id, local_invocation_index, base_mip + 3, slice);
}
}
fn spd_downsample_mips_6_7(x: u32, y: u32, mips: u32, slice: u32) {
var tex = vec2<u32>(x * 4 + 0, y * 4 + 0);
var pix = vec2<u32>(x * 2 + 0, y * 2 + 0);
let v0 = spd_reduce_load_mid_mip_4(tex, slice);
spd_store(pix, v0, 6, slice);
tex = vec2<u32>(x * 4 + 2, y * 4 + 0);
pix = vec2<u32>(x * 2 + 1, y * 2 + 0);
let v1 = spd_reduce_load_mid_mip_4(tex, slice);
spd_store(pix, v1, 6, slice);
tex = vec2<u32>(x * 4 + 0, y * 4 + 2);
pix = vec2<u32>(x * 2 + 0, y * 2 + 1);
let v2 = spd_reduce_load_mid_mip_4(tex, slice);
spd_store(pix, v2, 6, slice);
tex = vec2<u32>(x * 4 + 2, y * 4 + 2);
pix = vec2<u32>(x * 2 + 1, y * 2 + 1);
let v3 = spd_reduce_load_mid_mip_4(tex, slice);
spd_store(pix, v3, 6, slice);
if mips <= 7 {
return;
}
// no barrier needed, working on values only from the same thread
let v = spd_reduce_4(v0, v1, v2, v3);
spd_store(vec2<u32>(x, y), v, 7, slice);
spd_store_intermediate(x, y, v);
}
fn spd_downsample_last_6(x: u32, y: u32, local_invocation_index: u32, mips: u32, num_work_groups: u32, slice: u32) {
if mips <= 6 {
return;
}
// increase the global atomic counter for the given slice and check if it's the last remaining thread group:
// terminate if not, continue if yes.
let exit = spd_exit_workgroup(num_work_groups, local_invocation_index, slice);
// can't exit directly because subsequent barrier calls break uniform control flow...
if !exit {
// reset the global atomic counter back to 0 for the next spd dispatch
spd_reset_atomic_counter(slice);
// After mip 5 there is only a single workgroup left that downsamples the remaining up to 64x64 texels.
// compute MIP level 6 and 7
spd_downsample_mips_6_7(x, y, mips, slice);
}
// compute MIP level 8, 9, 10, 11
spd_downsample_last_four(x, y, vec2<u32>(0, 0), local_invocation_index, 8, mips, slice, exit);
}
/// Downsamples a 64x64 tile based on the work group id.
/// If after downsampling it's the last active thread group, computes the remaining MIP levels.
///
/// @param [in] workGroupID index of the work group / thread group
/// @param [in] localInvocationIndex index of the thread within the thread group in 1D
/// @param [in] mips the number of total MIP levels to compute for the input texture
/// @param [in] numWorkGroups the total number of dispatched work groups / thread groups for this slice
/// @param [in] slice the slice of the input texture
fn spd_downsample(workgroup_id: vec2<u32>, local_invocation_index: u32, mips: u32, num_work_groups: u32, slice: u32) {
let xy = map_to_xy(local_invocation_index);
spd_downsample_mips_0_1(xy.x, xy.y, workgroup_id, local_invocation_index, mips, slice);
spd_downsample_next_four(xy.x, xy.y, workgroup_id, local_invocation_index, 2, mips, slice);
)";
if (numMips > 6) {
ss << " spd_downsample_last_6(xy.x, xy.y, local_invocation_index, mips, "
"num_work_groups, slice);\n";
}
ss << R"(}
// Entry points -------------------------------------------------------------------------------------------------------
@compute
@workgroup_size(256, 1, 1)
fn downsample(@builtin(local_invocation_index) local_invocation_index: u32, @builtin(workgroup_id) workgroup_id: vec3<u32>) {
spd_downsample(
workgroup_id.xy + get_work_group_offset(),
local_invocation_index,
get_mips(),
get_num_work_groups(),
workgroup_id.z
);
}
)";
return ss.str();
}
} // namespace spd

View File

@@ -0,0 +1,99 @@
#pragma once
#include <optional>
#include <string>
#include <tsl/robin_map.h>
#include <vector>
#include <webgpu/webgpu_cpp.h>
// C++ port of https://github.com/JolifantoBambla/webgpu-spd for early experiments
namespace spd {
// Enum for selecting the downsampling filter.
enum class SPDFilter { Average, Min, Max, MinMax };
// Enum for shader scalar types.
enum class SPDScalarType { F32, F16, I32, U32 };
// Configuration for a single mipmap generation pass.
struct SPDPassConfig {
SPDFilter filter = SPDFilter::Average;
wgpu::Texture targetTexture = nullptr;
uint32_t numMips = 0; // For the public API, this is total desired mips. Internally, mips for
// the current pass.
bool halfPrecision = false;
uint32_t sourceMipLevel = 0;
};
// Holds a pipeline and its corresponding bind group layout.
struct SPDPipeline {
wgpu::BindGroupLayout mipsBindGroupLayout = nullptr;
wgpu::ComputePipeline pipeline = nullptr;
};
// Manages pipeline creation, caching, and execution for mipmap generation.
class MipmapGenerator {
public:
MipmapGenerator(const wgpu::Device& device);
// Pre-creates pipelines for specified formats and filters.
void PreparePipelines(wgpu::TextureFormat format, SPDFilter filter, bool halfPrecision = false);
// Generates a compute pass for creating mipmaps.
void Generate(wgpu::CommandEncoder& commandEncoder, wgpu::Texture srcTexture,
const SPDPassConfig& config);
private:
void generatePass(wgpu::CommandEncoder& commandEncoder, wgpu::Texture srcTexture,
const SPDPassConfig& passConfig, uint32_t baseArrayLayer,
uint32_t numArrayLayers); // Internal pass config might differ slightly if needed
wgpu::Device m_device;
wgpu::BindGroupLayout m_internalResourcesBindGroupLayout;
wgpu::BindGroupLayout m_internalResourcesBindGroupLayout_Advanced = nullptr;
// Maximum number of mips that can be generated in a single pass.
uint32_t m_maxMipsPerPass;
uint32_t m_maxArrayLayers;
// Key for the pipeline cache.
struct PipelineCacheKey {
wgpu::TextureFormat format;
SPDScalarType scalarType;
SPDFilter filter;
uint32_t numMips;
bool operator==(const PipelineCacheKey& other) const {
return format == other.format && scalarType == other.scalarType &&
filter == other.filter && numMips == other.numMips;
}
};
// Hash function for PipelineCacheKey.
struct PipelineCacheKeyHash {
std::size_t operator()(const PipelineCacheKey& key) const {
std::size_t h1 = std::hash<wgpu::TextureFormat>()(key.format);
std::size_t h2 = std::hash<SPDScalarType>()(key.scalarType);
std::size_t h3 = std::hash<SPDFilter>()(key.filter);
std::size_t h4 = std::hash<uint32_t>()(key.numMips);
// A simple way to combine hashes.
return h1 ^ (h2 << 1) ^ (h3 << 2) ^ (h4 << 3);
}
};
// Cached pipelines.
tsl::robin_map<PipelineCacheKey, SPDPipeline, PipelineCacheKeyHash> m_pipelines;
// Helper methods
SPDPipeline& GetOrCreatePipeline(const PipelineCacheKey& key);
SPDScalarType SanitizeScalarType(wgpu::TextureFormat format, bool halfPrecision);
std::string GetFilterCode(SPDFilter filter);
};
// Assuming SPD_FILTER_AVERAGE is a string constant
const std::string SPD_FILTER_AVERAGE = "value * 0.25"; // Original filter operation
// Generates the WGSL shader code dynamically.
std::string MakeShaderCode(wgpu::TextureFormat outputFormat,
const std::string& filterOp = SPD_FILTER_AVERAGE, uint32_t numMips = 0,
SPDScalarType scalarType = SPDScalarType::F32);
} // namespace spd

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Lukas Herzberger
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -17,10 +17,12 @@
#ifndef TNT_FILAMENT_BACKEND_WEBGPUCONSTANTS_H
#define TNT_FILAMENT_BACKEND_WEBGPUCONSTANTS_H
#include <utils/Log.h>
#include <utils/Logger.h>
#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)
@@ -30,8 +32,8 @@
#define FWGPU_PRINT_SYSTEM 0x00000002
// Set this to enable logging "only" to one output stream. This is useful in the case where we want
// to debug with print statements and want ordered logging (e.g slog.i and slog.e will not appear in
// order of calls).
// to debug with print statements and want ordered logging (e.g LOG(INFO) and LOG(ERROR) will not
// appear in order of calls).
#define FWGPU_DEBUG_FORCE_LOG_TO_I 0x00000004
// Useful default combinations
@@ -52,15 +54,15 @@
#define FWGPU_ENABLED(flags) (((FWGPU_DEBUG_FLAGS) & (flags)) == (flags))
#if FWGPU_ENABLED(FWGPU_DEBUG_FORCE_LOG_TO_I)
#define FWGPU_LOGI (utils::slog.i)
#define FWGPU_LOGI LOG(INFO)
#define FWGPU_LOGD FWGPU_LOGI
#define FWGPU_LOGE FWGPU_LOGI
#define FWGPU_LOGW FWGPU_LOGI
#else
#define FWGPU_LOGE (utils::slog.e)
#define FWGPU_LOGW (utils::slog.w)
#define FWGPU_LOGD (utils::slog.d)
#define FWGPU_LOGI (utils::slog.i)
#define FWGPU_LOGE LOG(ERROR)
#define FWGPU_LOGW LOG(WARNING)
#define FWGPU_LOGD DLOG(INFO)
#define FWGPU_LOGI LOG(INFO)
#endif
constexpr uint64_t REQUEST_ADAPTER_TIMEOUT_NANOSECONDS =

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

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>
@@ -29,6 +29,8 @@
#include <utils/compiler.h>
#include "SpdMipmapGenerator/SpdMipmapGenerator.h"
#include <tsl/robin_map.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
@@ -64,7 +66,7 @@ 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;
@@ -73,8 +75,9 @@ private:
wgpu::TextureView mTextureView = nullptr;
wgpu::RenderPassEncoder mRenderPassEncoder = nullptr;
wgpu::CommandBuffer mCommandBuffer = nullptr;
WGPURenderTarget* mDefaultRenderTarget = nullptr;
WGPURenderTarget* mCurrentRenderTarget = nullptr;
WebGPURenderTarget* mDefaultRenderTarget = nullptr;
WebGPURenderTarget* mCurrentRenderTarget = nullptr;
spd::MipmapGenerator mMipMapGenerator;
tsl::robin_map<uint32_t, wgpu::RenderPipeline> mPipelineMap;

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