Compare commits

..

38 Commits

Author SHA1 Message Date
bridgewaterrobbie
9a74936103 Update Gitignore to ignore generated files. 2025-04-22 14:23:31 -04:00
Powei Feng
7967157fbb renderdiff: fix breakage (#8648)
- Re-enable renderdiff test.
- Cache the mesa directory so that we're not spending time pulling
  and compiling it.
- Move python prereqs into the script and use venv.
2025-04-22 18:23:05 +00:00
Andy Hovingh
597ced13e1 webgpu: initial shader compilation 2025-04-22 12:56:03 -05:00
Sungun Park
c3542b135e Fix crash for ShaderCompilerService (#8626)
This change fixes a crash that occurs in ShaderCompilerService under a
certain condition described below.

Functions are called in the order described below.
1. `ShaderCompilerService::initialize(...)` is called when a new gl
   program is created. There are cases whre the corresponding TickOp may
   be still alive at the end of this method.
2. `OpenGLProgram::~OpenGLProgram()` is called when the program is
   immediately destroyed without being used. This deletes gl.program.
3. `ShaderCompilerService::tick()` is called later, and it references
   the dangling gl object `gl.program` in the lambda function, and it
   crashes.

This change also includes refactoring the class `ShaderCompilerService`
to make code simpler and less error-prone.

TEST = Tested on Desktop, Android, and Web by running sample apps

BUGS = [394319326, 407090622]
2025-04-22 17:31:40 +00:00
Mathias Agopian
ca3ff7e08e Use the Perfetto SDK instead of ATRACE
Perfetto has significantly less overhead. The User facing API is
mostly unchanged:

Here are the differences:

- SYSTRACE_ENABLE() does nothing on ANDROID, initializes systraces on darwin.
- SYSTRACE_DISABLE() is removed.
- A new "gltfio" tag is added.
- SYSTRACE_TAG *must* be defined before including `utils/Systrace.h`
- `utils/Systrace.h` should not be used from a public header
- the new SYSTRACE_TAG_DISABLE disables systrace at compile time


For android a data source MUST be created in the perfetto config:

```
data_sources {
  config {
    name: "track_event"
    track_event_config {
      enabled_categories: ["filament", "jobsystem", "gltfio"]
      disabled_categories: "*"
    }
  }
}
```

This can for example be added to AGI's custom/advanced config.

FIXES=[407572663]
2025-04-22 09:31:29 -07:00
Mathias Agopian
42c760a92f add the Perfetto SDK to libutils
for android NDK projects we also need to add it to the dependent
libraries.
2025-04-22 09:31:29 -07:00
Mathias Agopian
327a537bcc Add the Perfetto SDK to the build 2025-04-22 09:31:29 -07:00
Matthew Hoffman
cfc4f34c18 Use EXPECT_IMAGE in all backend tests. (#8628) 2025-04-21 19:57:40 -05:00
Sungun Park
6dac384bd9 Release Filament 1.59.3 2025-04-21 22:05:28 +00:00
rafadevai
d666f8a0ba VK: Create and use a VkPipelineCache (#8631)
To minimize the cost to recreated old pipelines that
has been evicted from VulkanPipelineCache, make the
driver cache the pipeline objects.
2025-04-21 18:14:58 +00:00
Matthew Hoffman
d6ae3a57b5 Replace backend test expected hash with loading the image. (#8622)
BUGS=[402474473]
2025-04-18 15:59:01 -05:00
Powei Feng
c2155f3f98 github: disable renderdiff (#8640)
The test is broken. Disable while investigating.
2025-04-18 11:10:10 -07:00
bridgewaterrobbie
e2a3637413 webgpu: Avoid using handle 'using' redefines for consistency 2025-04-18 11:42:10 -04:00
bridgewaterrobbie
390df4e42c Tweak constructor of WebGPUDescriptorSetLayout to take a ref rather than a pointer 2025-04-18 11:04:24 -04:00
Powei Feng
21dd1319df Release Filament 1.59.2 2025-04-16 15:47:19 -07:00
bridgewaterrobbie
444ac8d6a6 Handle WebGPUDescriptorSetLayout creation 2025-04-15 18:51:26 -04:00
Syed Idris Shah
aac1e7dc4c Move adapter and device creation to the begining 2025-04-15 18:51:26 -04:00
Matthew Hoffman
5b8a1e5e58 Add a macro for skipping tests based on backend/OS. (#8602)
BUGS=[398198557]
2025-04-15 17:03:48 -05:00
Andy Hovingh
ad1b36d2b3 fix: DEBUG_COMMAND_STREAM could not be compiled as true due to lack of BufferObjectStreamDescriptor insertion operator 2025-04-14 16:50:53 -05:00
Sungun Park
315c2c273f Cleanup shader compiler (#8613)
There's no functional difference.
2025-04-14 18:27:52 +00:00
Juan Caldas
005d835dbe wgpu: Add Vertex and Index Buffer (#8616)
* implement vertex and index buffers
2025-04-14 10:36:14 -04:00
Jeremy Nelson
cade94ab2c webgpu: surface window size for swapchain/surface 2025-04-11 17:30:08 -05:00
Max Rebuschatis
5cd2f5626c Fix bug in FMaterialInstance::setParameterImpl() where mTextureParameters
was not updated when a texture was reassigned if !texture->textureHandleCanMutate().

This could cause a crash in FMaterialInstance::commit() since the old texture
binding was still in mTextureParameters but may have been destroyed.
2025-04-11 12:51:46 -07:00
Jeremy Nelson
94bbcbf1c3 wgpu: combine swapchain classes 2025-04-10 09:20:12 -05:00
Andy Hovingh
52c998d2b6 webgpu: use handle allocator with swapchain 2025-04-10 07:37:53 -05:00
rafadevai
527d831c15 VK: Reuse renderpasses after swapchain resize (#8601)
When a swapchain is resized the FBO cache gets resets
which includes the framebuffer and renderpass objects.
This causes pipelines to be recreated instead of being
reused from the previous frames, causing stalls.

In the case of renderpasses there's no need to clear these
objects, since the state of the renderpass is the same, only
their refcount should be adjusted, fixing the issue mentioned
above.
2025-04-10 07:08:20 +00:00
Syed Idris Shah
2790f2e64c Cleanup the dead code from VulkanPlatformApple.mm
__APPLE__ is defined for both macos and ios platform.
#elif code will never be excuted.
2025-04-09 14:16:11 -04:00
Benjamin Doherty
757640e850 Release Filament 1.59.1 2025-04-09 10:20:51 -07:00
Ben Doherty
9d9abca33a Validate textures have non-zero dimensions (#8607) 2025-04-09 10:18:17 -07:00
Syed Idris Shah
bea7a7c73f Implement basic webgpu driver backend to render a background color
Add priliminay implementations of CommandBuffer, RenderColor Attachment,
RenderPass encoders and web gpu queue. This is sufficient to render
background color on a surface using webgpu api's.
2025-04-08 16:28:26 -04:00
Syed Idris Shah
dd62f98784 Treat native Window as CAMetalLayer
Attach MetalLayer to Native View same as vulkan and metal backend.
This change assumes native window is already a CAMetalLayer. This is no different
for IOS and Mac.
2025-04-08 16:28:26 -04:00
Syed Idris Shah
395a3eda9b Introduce basic HandleAllocator for webgpu
Implement stub implementations for different handles. This is a
place holder patch. These handles will be enhanced and used by webgpu
driver for backend implementations.
2025-04-08 16:28:26 -04:00
Syed Idris Shah
9d18cc35e0 Fix webgpu android build after dawn update 2025-04-08 14:58:59 -04:00
Andy Hovingh
2e37ebe70c webgpu: compile WGSL shaders into Android materials when needed 2025-04-08 13:13:21 -05:00
Matthew Hoffman
63ef412be1 Shared shader helpers for backend tests. (#8562) 2025-04-08 12:45:59 -05:00
rafadevai
f0996296c1 VK: Remove explicitImageReadyWait callback (#8603)
This function was created to make sure the swapchain
image was available before submitting the work to the GPU.

So the order of the process looked like

| Acquire | Begin | Record | Wait | Present | End |

After this change the expectation is that when the
swapchain acquires a new image it will also wait until
is actually available from the platform.

| Acquire | Begin | Record | Present | End |

In the case the image is not available, the expectation
is that the acquire will wait until it is and then start
processing the frame.
2025-04-08 06:09:49 +00:00
Powei Feng
a60d570b3b vk: preparing for Ycbcr conversion (#8596)
- Move utils functions to the right files
 - Add conversion from vk formats to filament
 - Refactor how to enable multiview and external sampler in
   VulkanPlatform
 - Make VuklanDescriptorSet and VuklanDescriptorSetLayout more
   general to allow for mutability
 - Reorder vulkan files in CMakeLists.txt to be sorted in
   alphabetical order.
2025-04-08 05:50:37 +00:00
Powei Feng
3fdb9f311a github: add code-correctness presubmit (#8595) 2025-04-04 15:17:07 -07:00
142 changed files with 245589 additions and 2378 deletions

View File

@@ -19,6 +19,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- name: Run build script - name: Run build script
run: | run: |
WORKFLOW_OS=`echo \`uname\` | sed "s/Darwin/mac/" | tr [:upper:] [:lower:]` WORKFLOW_OS=`echo \`uname\` | sed "s/Darwin/mac/" | tr [:upper:] [:lower:]`
@@ -44,6 +46,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: actions/setup-java@v3 - uses: actions/setup-java@v3
with: with:
distribution: 'temurin' distribution: 'temurin'
@@ -60,6 +64,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- name: Run build script - name: Run build script
run: | run: |
cd build/ios && printf "y" | ./build.sh presubmit cd build/ios && printf "y" | ./build.sh presubmit
@@ -102,9 +108,21 @@ jobs:
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: '3.x' python-version: '3.x'
- name: Install python prereqs - name: Cache Mesa and deps
run: pip install mako setuptools pyyaml id: mesa-cache
- name: Run script uses: actions/cache@v4 # Use a specific version
with:
path: |
$HOME/Library/Caches/Homebrew
mesa
key: ${{ runner.os }}-mesa-deps-${{ vars.MESA_VERSION }}
- name: Get Mesa
id: mesa-prereq
env:
MESA_VERSION: ${{ vars.MESA_VERSION }}
run: |
bash test/utils/get_mesa.sh
- name: Run Test
run: | run: |
bash test/renderdiff/test.sh bash test/renderdiff/test.sh
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
@@ -122,3 +140,31 @@ jobs:
run: source ./build/linux/ci-common.sh && ./build.sh -W debug test_filamat filament run: source ./build/linux/ci-common.sh && ./build.sh -W debug test_filamat filament
- name: Run test - name: Run test
run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl* run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
code-correcteness:
name: code-correctness
runs-on: 'macos-14-xlarge'
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install prerequisites
run: |
pip install pyyaml
brew install llvm
sudo ln -s "$(brew --prefix llvm)/bin/clang-tidy" "/usr/local/bin/clang-tidy"
- name: Run build script
# We need to build before clang-tidy can run analysis
run: |
# This will build for all three desktop backends on mac
./build.sh -p desktop debug gltf_viewer
- name: Run test
run: |
bash test/code-correctness/test.sh

15
.gitignore vendored
View File

@@ -18,3 +18,18 @@ test*.json
results results
/compile_commands.json /compile_commands.json
/.cache /.cache
build/.cmake/
build/CMakeCache.txt
build/CMakeFiles/
build/Makefile
build/SPIRV-Tools*
build/cmake_install.cmake
build/compile_commands.json
build/filament/
build/include/
build/libs/
build/mac/ninja
build/samples/
build/shaders/
build/third_party/
build/tools/

View File

@@ -801,6 +801,7 @@ add_subdirectory(${EXTERNAL}/draco/tnt)
add_subdirectory(${EXTERNAL}/jsmn/tnt) add_subdirectory(${EXTERNAL}/jsmn/tnt)
add_subdirectory(${EXTERNAL}/stb/tnt) add_subdirectory(${EXTERNAL}/stb/tnt)
add_subdirectory(${EXTERNAL}/getopt) add_subdirectory(${EXTERNAL}/getopt)
add_subdirectory(${EXTERNAL}/perfetto/tnt)
# Note that this has to be placed after mikktspace in order for combine_static_libs to work. # Note that this has to be placed after mikktspace in order for combine_static_libs to work.
add_subdirectory(${LIBRARIES}/geometry) add_subdirectory(${LIBRARIES}/geometry)

View File

@@ -7,5 +7,3 @@ for next branch cut* header.
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut ## Release notes for next branch cut
- Fix build/compile errors when upgrading to MacOS 15.4

View File

@@ -31,7 +31,7 @@ repositories {
} }
dependencies { dependencies {
implementation 'com.google.android.filament:filament-android:1.59.0' implementation 'com.google.android.filament:filament-android:1.59.3'
} }
``` ```
@@ -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: iOS projects can use CocoaPods to install the latest release:
```shell ```shell
pod 'Filament', '~> 1.59.0' pod 'Filament', '~> 1.59.3'
``` ```
## Documentation ## Documentation

View File

@@ -7,6 +7,16 @@ 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 Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md). [NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.59.4
## v1.59.3
## v1.59.2
- Fix build/compile errors when upgrading to MacOS 15.4
## v1.59.1 ## v1.59.1

View File

@@ -142,16 +142,13 @@ abstract class MaterialCompiler extends TaskWithBinary {
if (!exclude_vulkan) { if (!exclude_vulkan) {
matcArgs += ['-a', 'vulkan'] matcArgs += ['-a', 'vulkan']
} }
def include_webgpu = providers def include_webgpu = providers
.gradleProperty("com.google.android.filament.include-webgpu") .gradleProperty("com.google.android.filament.include-webgpu")
.forUseAtConfigurationTime().present .forUseAtConfigurationTime().present
if (include_webgpu) {
if (!include_webgpu) { matcArgs += ['-a', 'webgpu', '--variant-filter=skinning,stereo']
matcArgs += ['-a', 'webgpu']
} }
def mat_no_opt = providers def mat_no_opt = providers
.gradleProperty("com.google.android.filament.matnopt") .gradleProperty("com.google.android.filament.matnopt")
.forUseAtConfigurationTime().present .forUseAtConfigurationTime().present

View File

@@ -26,6 +26,10 @@ add_library(utils STATIC IMPORTED)
set_target_properties(utils PROPERTIES IMPORTED_LOCATION set_target_properties(utils PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a) ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a)
add_library(perfetto STATIC IMPORTED)
set_target_properties(perfetto PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libperfetto.a)
add_library(filabridge STATIC IMPORTED) add_library(filabridge STATIC IMPORTED)
set_target_properties(filabridge PROPERTIES IMPORTED_LOCATION set_target_properties(filabridge PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilabridge.a) ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilabridge.a)
@@ -40,6 +44,7 @@ set_target_properties(shaders PROPERTIES IMPORTED_LOCATION
set(FILAMAT_INCLUDE_DIRS set(FILAMAT_INCLUDE_DIRS
../../libs/utils/include ../../libs/utils/include
../../third_party/perfetto
) )
include_directories(${FILAMENT_DIR}/include) include_directories(${FILAMENT_DIR}/include)
@@ -55,6 +60,7 @@ target_link_libraries(filamat-jni
filabridge filabridge
shaders shaders
utils utils
perfetto
log log
smol-v smol-v
$<$<STREQUAL:${FILAMENT_SUPPORTS_WEBGPU},ON>:tint> $<$<STREQUAL:${FILAMENT_SUPPORTS_WEBGPU},ON>:tint>

View File

@@ -21,6 +21,10 @@ add_library(utils STATIC IMPORTED)
set_target_properties(utils PROPERTIES IMPORTED_LOCATION set_target_properties(utils PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a) ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a)
add_library(perfetto STATIC IMPORTED)
set_target_properties(perfetto PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libperfetto.a)
add_library(ibl-lite STATIC IMPORTED) add_library(ibl-lite STATIC IMPORTED)
set_target_properties(ibl-lite PROPERTIES IMPORTED_LOCATION set_target_properties(ibl-lite PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libibl-lite.a) ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libibl-lite.a)
@@ -123,6 +127,7 @@ target_link_libraries(filament-jni
PRIVATE android PRIVATE android
PRIVATE jnigraphics PRIVATE jnigraphics
PRIVATE utils PRIVATE utils
PRIVATE perfetto
# libgeometry is PUBLIC because gltfio uses it. # libgeometry is PUBLIC because gltfio uses it.
PUBLIC geometry PUBLIC geometry
@@ -141,6 +146,7 @@ target_include_directories(filament-jni PRIVATE
${FILAMENT_DIR}/include ${FILAMENT_DIR}/include
../../filament/backend/include ../../filament/backend/include
../../third_party/robin-map ../../third_party/robin-map
../../third_party/perfetto
../../libs/utils/include) ../../libs/utils/include)
# Force a relink when the version script is changed: # Force a relink when the version script is changed:

View File

@@ -35,6 +35,10 @@ add_library(utils STATIC IMPORTED)
set_target_properties(utils PROPERTIES IMPORTED_LOCATION set_target_properties(utils PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a) ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a)
add_library(perfetto STATIC IMPORTED)
set_target_properties(perfetto PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libperfetto.a)
add_library(uberzlib STATIC IMPORTED) add_library(uberzlib STATIC IMPORTED)
set_target_properties(uberzlib PROPERTIES IMPORTED_LOCATION set_target_properties(uberzlib PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libuberzlib.a) ${FILAMENT_DIR}/lib/${ANDROID_ABI}/libuberzlib.a)
@@ -121,6 +125,7 @@ set(GLTFIO_INCLUDE_DIRS
../../third_party/meshoptimizer/src ../../third_party/meshoptimizer/src
../../third_party/robin-map ../../third_party/robin-map
../../third_party/stb ../../third_party/stb
../../third_party/perfetto
../../libs/utils/include ../../libs/utils/include
../../libs/ktxreader/include ../../libs/ktxreader/include
) )
@@ -129,7 +134,7 @@ add_library(gltfio-jni SHARED ${GLTFIO_SRCS})
target_include_directories(gltfio-jni PRIVATE ${GLTFIO_INCLUDE_DIRS}) target_include_directories(gltfio-jni PRIVATE ${GLTFIO_INCLUDE_DIRS})
set_target_properties(gltfio-jni PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/libgltfio-jni.symbols) set_target_properties(gltfio-jni PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/libgltfio-jni.symbols)
set_target_properties(gltfio-jni PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/libgltfio-jni.map) set_target_properties(gltfio-jni PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/libgltfio-jni.map)
target_link_libraries(gltfio-jni filament-jni utils uberzlib log stb ktxreader basis_transcoder zstd uberarchive) target_link_libraries(gltfio-jni filament-jni utils perfetto uberzlib log stb ktxreader basis_transcoder zstd uberarchive)
target_link_libraries(gltfio-jni dracodec meshoptimizer) target_link_libraries(gltfio-jni dracodec meshoptimizer)
target_compile_definitions(gltfio-jni PUBLIC GLTFIO_DRACO_SUPPORTED=1) target_compile_definitions(gltfio-jni PUBLIC GLTFIO_DRACO_SUPPORTED=1)
target_include_directories(gltfio-jni PRIVATE ${DRACO_DIR}/src) target_include_directories(gltfio-jni PRIVATE ${DRACO_DIR}/src)

View File

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

View File

@@ -177,29 +177,6 @@ endif()
if (FILAMENT_SUPPORTS_VULKAN) if (FILAMENT_SUPPORTS_VULKAN)
list(APPEND SRCS list(APPEND SRCS
include/backend/platforms/VulkanPlatform.h include/backend/platforms/VulkanPlatform.h
src/vulkan/VulkanDescriptorSetCache.cpp
src/vulkan/VulkanDescriptorSetCache.h
src/vulkan/VulkanDescriptorSetLayoutCache.cpp
src/vulkan/VulkanDescriptorSetLayoutCache.h
src/vulkan/VulkanPipelineLayoutCache.cpp
src/vulkan/VulkanPipelineLayoutCache.h
src/vulkan/memory/ResourceManager.cpp
src/vulkan/memory/ResourceManager.h
src/vulkan/memory/ResourcePointer.h
src/vulkan/memory/Resource.cpp
src/vulkan/memory/Resource.h
src/vulkan/platform/VulkanPlatform.cpp
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
src/vulkan/utils/Conversion.cpp
src/vulkan/utils/Conversion.h
src/vulkan/utils/Definitions.h
src/vulkan/utils/Helper.h
src/vulkan/utils/Image.h
src/vulkan/utils/Image.cpp
src/vulkan/utils/Spirv.h
src/vulkan/utils/Spirv.cpp
src/vulkan/utils/StaticVector.h
src/vulkan/VulkanAsyncHandles.h src/vulkan/VulkanAsyncHandles.h
src/vulkan/VulkanBlitter.cpp src/vulkan/VulkanBlitter.cpp
src/vulkan/VulkanBlitter.h src/vulkan/VulkanBlitter.h
@@ -210,6 +187,10 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanConstants.h src/vulkan/VulkanConstants.h
src/vulkan/VulkanContext.cpp src/vulkan/VulkanContext.cpp
src/vulkan/VulkanContext.h src/vulkan/VulkanContext.h
src/vulkan/VulkanDescriptorSetCache.cpp
src/vulkan/VulkanDescriptorSetCache.h
src/vulkan/VulkanDescriptorSetLayoutCache.cpp
src/vulkan/VulkanDescriptorSetLayoutCache.h
src/vulkan/VulkanDriver.cpp src/vulkan/VulkanDriver.cpp
src/vulkan/VulkanDriver.h src/vulkan/VulkanDriver.h
src/vulkan/VulkanDriverFactory.h src/vulkan/VulkanDriverFactory.h
@@ -217,24 +198,43 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanFboCache.h src/vulkan/VulkanFboCache.h
src/vulkan/VulkanHandles.cpp src/vulkan/VulkanHandles.cpp
src/vulkan/VulkanHandles.h src/vulkan/VulkanHandles.h
src/vulkan/VulkanMemory.h
src/vulkan/VulkanMemory.cpp src/vulkan/VulkanMemory.cpp
src/vulkan/VulkanMemory.h
src/vulkan/VulkanPipelineCache.cpp src/vulkan/VulkanPipelineCache.cpp
src/vulkan/VulkanPipelineCache.h src/vulkan/VulkanPipelineCache.h
src/vulkan/VulkanPipelineLayoutCache.cpp
src/vulkan/VulkanPipelineLayoutCache.h
src/vulkan/VulkanQueryManager.cpp src/vulkan/VulkanQueryManager.cpp
src/vulkan/VulkanQueryManager.h src/vulkan/VulkanQueryManager.h
src/vulkan/VulkanReadPixels.cpp
src/vulkan/VulkanReadPixels.h
src/vulkan/VulkanSamplerCache.cpp src/vulkan/VulkanSamplerCache.cpp
src/vulkan/VulkanSamplerCache.h src/vulkan/VulkanSamplerCache.h
src/vulkan/VulkanStagePool.cpp src/vulkan/VulkanStagePool.cpp
src/vulkan/VulkanStagePool.h src/vulkan/VulkanStagePool.h
src/vulkan/VulkanSwapChain.cpp src/vulkan/VulkanSwapChain.cpp
src/vulkan/VulkanSwapChain.h src/vulkan/VulkanSwapChain.h
src/vulkan/VulkanReadPixels.cpp
src/vulkan/VulkanReadPixels.h
src/vulkan/VulkanTexture.cpp src/vulkan/VulkanTexture.cpp
src/vulkan/VulkanTexture.h src/vulkan/VulkanTexture.h
src/vulkan/VulkanYcbcrConversionCache.cpp src/vulkan/VulkanYcbcrConversionCache.cpp
src/vulkan/VulkanYcbcrConversionCache.h src/vulkan/VulkanYcbcrConversionCache.h
src/vulkan/memory/Resource.cpp
src/vulkan/memory/Resource.h
src/vulkan/memory/ResourceManager.cpp
src/vulkan/memory/ResourceManager.h
src/vulkan/memory/ResourcePointer.h
src/vulkan/platform/VulkanPlatform.cpp
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
src/vulkan/utils/Conversion.cpp
src/vulkan/utils/Conversion.h
src/vulkan/utils/Definitions.h
src/vulkan/utils/Helper.h
src/vulkan/utils/Image.cpp
src/vulkan/utils/Image.h
src/vulkan/utils/Spirv.cpp
src/vulkan/utils/Spirv.h
src/vulkan/utils/StaticVector.h
) )
if (LINUX OR WIN32) if (LINUX OR WIN32)
list(APPEND SRCS src/vulkan/platform/VulkanPlatformLinuxWindows.cpp) list(APPEND SRCS src/vulkan/platform/VulkanPlatformLinuxWindows.cpp)
@@ -255,10 +255,11 @@ if (FILAMENT_SUPPORTS_WEBGPU)
src/webgpu/WebGPUConstants.h src/webgpu/WebGPUConstants.h
src/webgpu/WebGPUDriver.cpp src/webgpu/WebGPUDriver.cpp
src/webgpu/WebGPUDriver.h src/webgpu/WebGPUDriver.h
src/webgpu/WebGPUSwapChain.cpp
src/webgpu/WebGPUSwapChain.h
src/webgpu/WebGPUHandles.cpp src/webgpu/WebGPUHandles.cpp
src/webgpu/WebGPUHandles.h src/webgpu/WebGPUHandles.h
src/webgpu/WebGPUSwapChain.cpp
src/webgpu/WebGPUSwapChain.h
src/webgpu/WGPUProgram.cpp
) )
if (WIN32) if (WIN32)
list(APPEND SRCS src/webgpu/platform/WebGPUPlatformWindows.cpp) list(APPEND SRCS src/webgpu/platform/WebGPUPlatformWindows.cpp)
@@ -507,7 +508,10 @@ if (APPLE OR LINUX)
test/Arguments.cpp test/Arguments.cpp
test/ImageExpectations.cpp test/ImageExpectations.cpp
test/Lifetimes.cpp test/Lifetimes.cpp
test/PlatformRunner.cpp
test/Shader.cpp test/Shader.cpp
test/SharedShaders.cpp
test/Skip.cpp
test/test_FeedbackLoops.cpp test/test_FeedbackLoops.cpp
test/test_Blit.cpp test/test_Blit.cpp
test/test_MissingRequiredAttributes.cpp test/test_MissingRequiredAttributes.cpp
@@ -531,6 +535,9 @@ if (APPLE OR LINUX)
filamat filamat
SPIRV SPIRV
spirv-cross-glsl) spirv-cross-glsl)
# Create input/output directories for test result images.
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/actual_images)
file(COPY test/expected_images DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/images)
endif() endif()
# TODO: Disabling IOS test due to breakage wrt glslang update # TODO: Disabling IOS test due to breakage wrt glslang update

View File

@@ -55,4 +55,9 @@ public:
} // namespace filament::backend } // namespace filament::backend
#if !defined(NDEBUG)
utils::io::ostream& operator<<(utils::io::ostream& out,
const filament::backend::BufferObjectStreamDescriptor& b);
#endif
#endif // TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H #endif // TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H

View File

@@ -104,10 +104,6 @@ public:
// Semaphore to be signaled once the image is available. // Semaphore to be signaled once the image is available.
VkSemaphore imageReadySemaphore = VK_NULL_HANDLE; VkSemaphore imageReadySemaphore = VK_NULL_HANDLE;
// A function called right before vkQueueSubmit. After this call, the image must be
// available. This pointer can be null if imageReadySemaphore is not VK_NULL_HANDLE.
std::function<void(SwapChainPtr handle)> explicitImageReadyWait = nullptr;
}; };
VulkanPlatform(); VulkanPlatform();

View File

@@ -38,6 +38,12 @@ public:
[[nodiscard]] wgpu::Instance& getInstance() noexcept { return mInstance; } [[nodiscard]] wgpu::Instance& getInstance() noexcept { return mInstance; }
// TODO consider that this functionality is not WebGPU-specific, and thus could be
// placed in a generic place and even reused across backends. Alternatively,
// a 3rd party library could be considered. However, this was a simple and
// quick change and works for now.
// gets the size (height and width) of the surface/window
[[nodiscard]] wgpu::Extent2D getSurfaceExtent(void* nativeWindow) const;
// either returns a valid surface or panics // either returns a valid surface or panics
[[nodiscard]] wgpu::Surface createSurface(void* nativeWindow, uint64_t flags); [[nodiscard]] wgpu::Surface createSurface(void* nativeWindow, uint64_t flags);
// either returns a valid adapter or panics // either returns a valid adapter or panics

View File

@@ -84,7 +84,7 @@ void CommandStream::execute(void* buffer) {
Profiler profiler; Profiler profiler;
if (SYSTRACE_TAG) { if constexpr (SYSTRACE_TAG) {
if (UTILS_UNLIKELY(mUsePerformanceCounter)) { if (UTILS_UNLIKELY(mUsePerformanceCounter)) {
// we want to remove all this when tracing is completely disabled // we want to remove all this when tracing is completely disabled
profiler.resetEvents(Profiler::EV_CPU_CYCLES | Profiler::EV_BPU_MISSES); profiler.resetEvents(Profiler::EV_CPU_CYCLES | Profiler::EV_BPU_MISSES);
@@ -100,7 +100,7 @@ void CommandStream::execute(void* buffer) {
} }
}); });
if (SYSTRACE_TAG) { if constexpr (SYSTRACE_TAG) {
if (UTILS_UNLIKELY(mUsePerformanceCounter)) { if (UTILS_UNLIKELY(mUsePerformanceCounter)) {
// we want to remove all this when tracing is completely disabled // we want to remove all this when tracing is completely disabled
profiler.stop(); profiler.stop();

View File

@@ -85,6 +85,7 @@ OpenGLProgram::~OpenGLProgram() noexcept {
delete lazyInitializationData; delete lazyInitializationData;
ShaderCompilerService::terminate(mToken); ShaderCompilerService::terminate(mToken);
assert_invariant(!mToken);
} }
delete [] mUniformsRecords; delete [] mUniformsRecords;

File diff suppressed because it is too large Load Diff

View File

@@ -24,23 +24,23 @@
#include "OpenGLBlobCache.h" #include "OpenGLBlobCache.h"
#include <backend/CallbackHandler.h> #include <backend/CallbackHandler.h>
#include <backend/DriverEnums.h>
#include <backend/Program.h> #include <backend/Program.h>
#include <utils/CString.h> #include <utils/CString.h>
#include <utils/FixedCapacityVector.h> #include <utils/FixedCapacityVector.h>
#include <utils/Invocable.h>
#include <utils/JobSystem.h> #include <utils/JobSystem.h>
#include <atomic> #include <array>
#include <condition_variable>
#include <deque>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <thread> #include <tuple>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <stdint.h>
namespace filament::backend { namespace filament::backend {
class OpenGLDriver; class OpenGLDriver;
@@ -57,6 +57,8 @@ class ShaderCompilerService {
public: public:
using program_token_t = std::shared_ptr<OpenGLProgramToken>; using program_token_t = std::shared_ptr<OpenGLProgramToken>;
using shaders_t = std::array<GLuint, Program::SHADER_TYPE_COUNT>;
using shaders_source_t = std::array<utils::CString, Program::SHADER_TYPE_COUNT>;
explicit ShaderCompilerService(OpenGLDriver& driver); explicit ShaderCompilerService(OpenGLDriver& driver);
@@ -82,6 +84,7 @@ public:
void tick(); void tick();
// Destroys a valid token and all associated resources. Used to "cancel" a program compilation. // Destroys a valid token and all associated resources. Used to "cancel" a program compilation.
// This function is not called if `initialize(token)` is already invoked.
static void terminate(program_token_t& token); static void terminate(program_token_t& token);
// stores a user data pointer in the token // stores a user data pointer in the token
@@ -90,6 +93,12 @@ public:
// retrieves the user data pointer stored in the token // retrieves the user data pointer stored in the token
static void* getUserData(const program_token_t& token) noexcept; static void* getUserData(const program_token_t& token) noexcept;
// Issue one callback handle.
CallbackManager::Handle issueCallbackHandle() const noexcept;
// Return a callback handle to the callback manager.
void submitCallbackHandle(CallbackManager::Handle handle) noexcept;
// call the callback when all active programs are ready // call the callback when all active programs are ready
void notifyWhenAllProgramsAreReady( void notifyWhenAllProgramsAreReady(
CallbackHandler* handler, CallbackHandler::Callback callback, void* user); CallbackHandler* handler, CallbackHandler::Callback callback, void* user);
@@ -97,7 +106,7 @@ public:
private: private:
struct Job { struct Job {
template<typename FUNC> template<typename FUNC>
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {} Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {} // NOLINT(*-explicit-constructor)
Job(std::function<bool(Job const& job)> fn, Job(std::function<bool(Job const& job)> fn,
CallbackHandler* handler, void* user, CallbackHandler::Callback callback) CallbackHandler* handler, void* user, CallbackHandler::Callback callback)
: fn(std::move(fn)), handler(handler), user(user), callback(callback) { : fn(std::move(fn)), handler(handler), user(user), callback(callback) {
@@ -126,39 +135,49 @@ private:
using ContainerType = std::tuple<CompilerPriorityQueue, program_token_t, Job>; using ContainerType = std::tuple<CompilerPriorityQueue, program_token_t, Job>;
std::vector<ContainerType> mRunAtNextTickOps; std::vector<ContainerType> mRunAtNextTickOps;
GLuint initialize(ShaderCompilerService::program_token_t& token) noexcept; GLuint initialize(program_token_t& token);
void ensureTokenIsReady(program_token_t const& token);
static void getProgramFromCompilerPool(program_token_t& token) noexcept; void runAtNextTick(CompilerPriorityQueue priority, program_token_t const& token,
Job job) noexcept;
static void compileShaders(
OpenGLContext& context,
Program::ShaderSource shadersSource,
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
bool multiview,
std::array<GLuint, Program::SHADER_TYPE_COUNT>& outShaders,
std::array<utils::CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept;
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
char* source, size_t len) noexcept;
static void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount,
char* source, size_t len) noexcept;
static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) noexcept;
static std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept;
static GLuint linkProgram(OpenGLContext& context,
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders,
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& attributes) noexcept;
static bool checkProgramStatus(program_token_t const& token) noexcept;
void runAtNextTick(CompilerPriorityQueue priority,
const program_token_t& token, Job job) noexcept;
void executeTickOps() noexcept; void executeTickOps() noexcept;
bool cancelTickOp(program_token_t token) noexcept; bool cancelTickOp(program_token_t const& token) noexcept;
// order of insertion is important
// Compile shaders with the given `shaderSource`. `gl.shaders` is always populated with valid
// shader IDs after this method. But this doesn't necessarily mean the shaders are successfully
// compiled. Errors can be checked by calling `checkCompileStatus` later.
static void compileShaders(OpenGLContext& context, Program::ShaderSource shadersSource,
utils::FixedCapacityVector<Program::SpecializationConstant> const&
specializationConstants,
bool multiview, program_token_t const& token) noexcept;
// Check if the shader compilation is completed. You may want to call this when the extension
// `KHR_parallel_shader_compile` is enabled.
static bool isCompileCompleted(program_token_t const& token) noexcept;
// Check compilation status of the shaders and log errors on failure.
static void checkCompileStatus(program_token_t const& token) noexcept;
// Create a program by linking the compiled shaders. `gl.program` is always populated with a
// valid program ID after this method. But this doesn't necessarily mean the program is
// successfully linked. Errors can be checked by calling `checkLinkStatusAndCleanupShaders`
// later.
static void linkProgram(OpenGLContext const& context, program_token_t const& token) noexcept;
// Check if the program link is completed. You may want to call this when the extension
// `KHR_parallel_shader_compile` is enabled.
static bool isLinkCompleted(program_token_t const& token) noexcept;
// Check link status of the program and log errors on failure. Return the result of the link.
// Also cleanup shaders regardless of the result.
static bool checkLinkStatusAndCleanupShaders(program_token_t const& token) noexcept;
// Try caching the program if we haven't done it yet. Cache it only when the program is valid.
static void tryCachingProgram(OpenGLBlobCache& cache, OpenGLPlatform& platform,
program_token_t const& token) noexcept;
// Cleanup GL resources.
static void cleanupProgramAndShaders(program_token_t const& token) noexcept;
}; };
} // namespace filament::backend } // namespace filament::backend

View File

@@ -15,6 +15,7 @@
*/ */
#include <backend/BufferDescriptor.h> #include <backend/BufferDescriptor.h>
#include <backend/BufferObjectStreamDescriptor.h>
#include <backend/DescriptorSetOffsetArray.h> #include <backend/DescriptorSetOffsetArray.h>
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include <backend/PipelineState.h> #include <backend/PipelineState.h>
@@ -437,6 +438,10 @@ io::ostream& operator<<(io::ostream& out, BufferDescriptor const& b) {
<< ", user=" << b.getUser() << " }"; << ", user=" << b.getUser() << " }";
} }
io::ostream& operator<<(io::ostream& out, const BufferObjectStreamDescriptor& b) {
return out << "BufferObjectStreamDescriptor{ streams(" << b.mStreams.size() << ")=... }";
}
io::ostream& operator<<(io::ostream& out, PixelBufferDescriptor const& b) { io::ostream& operator<<(io::ostream& out, PixelBufferDescriptor const& b) {
BufferDescriptor const& base = static_cast<BufferDescriptor const&>(b); BufferDescriptor const& base = static_cast<BufferDescriptor const&>(b);
return out << "PixelBufferDescriptor{ " << base return out << "PixelBufferDescriptor{ " << base

View File

@@ -152,12 +152,12 @@ static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION));
#elif FVK_ENABLED(FVK_DEBUG_SYSTRACE) #elif FVK_ENABLED(FVK_DEBUG_SYSTRACE)
#include <utils/Systrace.h> #include <utils/Systrace.h>
#define FVK_SYSTRACE_CONTEXT() SYSTRACE_CONTEXT() #define FVK_SYSTRACE_CONTEXT() SYSTRACE_CONTEXT()
#define FVK_SYSTRACE_START(marker) SYSTRACE_NAME_BEGIN(marker) #define FVK_SYSTRACE_START(marker) SYSTRACE_NAME_BEGIN(marker)
#define FVK_SYSTRACE_END() SYSTRACE_NAME_END() #define FVK_SYSTRACE_END() SYSTRACE_NAME_END()
#define FVK_SYSTRACE_SCOPE() SYSTRACE_NAME(__func__) #define FVK_SYSTRACE_SCOPE() SYSTRACE_CALL()
#define FVK_PROFILE_MARKER(marker) FVK_SYSTRACE_SCOPE() #define FVK_PROFILE_MARKER(marker) SYSTRACE_CALL()
#else #else
#define FVK_SYSTRACE_CONTEXT() #define FVK_SYSTRACE_CONTEXT()

View File

@@ -70,13 +70,11 @@ struct VulkanRenderPass {
// context are stored in VulkanPlatform. // context are stored in VulkanPlatform.
struct VulkanContext { struct VulkanContext {
public: public:
inline uint32_t selectMemoryType(uint32_t flags, VkFlags reqs) const { static uint32_t selectMemoryType(VkPhysicalDeviceMemoryProperties const& memoryProperties,
if ((reqs & VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0) { uint32_t flags, VkFlags reqs) {
assert_invariant(isProtectedMemorySupported() == true);
}
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) { for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) {
if (flags & 1) { if (flags & 1) {
if ((mMemoryProperties.memoryTypes[i].propertyFlags & reqs) == reqs) { if ((memoryProperties.memoryTypes[i].propertyFlags & reqs) == reqs) {
return i; return i;
} }
} }
@@ -85,6 +83,13 @@ public:
return (uint32_t) VK_MAX_MEMORY_TYPES; return (uint32_t) VK_MAX_MEMORY_TYPES;
} }
inline uint32_t selectMemoryType(uint32_t flags, VkFlags reqs) const {
if ((reqs & VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0) {
assert_invariant(isProtectedMemorySupported());
}
return selectMemoryType(mMemoryProperties, flags, reqs);
}
inline fvkutils::VkFormatList const& getAttachmentDepthStencilFormats() const { inline fvkutils::VkFormatList const& getAttachmentDepthStencilFormats() const {
return mDepthStencilFormats; return mDepthStencilFormats;
} }
@@ -118,7 +123,7 @@ public:
} }
inline bool isMultiviewEnabled() const noexcept { inline bool isMultiviewEnabled() const noexcept {
return mMultiviewEnabled; return mPhysicalDeviceVk11Features.multiview == VK_TRUE;
} }
inline bool isClipDistanceSupported() const noexcept { inline bool isClipDistanceSupported() const noexcept {
@@ -142,6 +147,9 @@ private:
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = { VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
}; };
VkPhysicalDeviceVulkan11Features mPhysicalDeviceVk11Features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
};
VkPhysicalDeviceFeatures2 mPhysicalDeviceFeatures = { VkPhysicalDeviceFeatures2 mPhysicalDeviceFeatures = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
}; };
@@ -154,7 +162,6 @@ private:
}; };
bool mDebugMarkersSupported = false; bool mDebugMarkersSupported = false;
bool mDebugUtilsSupported = false; bool mDebugUtilsSupported = false;
bool mMultiviewEnabled = false;
bool mLazilyAllocatedMemorySupported = false; bool mLazilyAllocatedMemorySupported = false;
bool mProtectedMemorySupported = false; bool mProtectedMemorySupported = false;

View File

@@ -282,18 +282,25 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
fvkutils::DescriptorSetMask curMask = setMask; fvkutils::DescriptorSetMask curMask = setMask;
auto& updateSets = mStashedSets; auto& updateSets = mStashedSets;
auto& lastBoundSets = mLastBoundInfo.boundSets; bool const pipelineLayoutIsSame = mLastBoundInfo.pipelineLayout == pipelineLayout;
setMask.forEachSetBit([&](size_t index) { if (pipelineLayoutIsSame) {
if (!updateSets[index] || updateSets[index] == lastBoundSets[index]) { auto& lastBoundSets = mLastBoundInfo.boundSets;
curMask.unset(index); setMask.forEachSetBit([&](size_t index) {
if (!updateSets[index] || updateSets[index] == lastBoundSets[index]) {
curMask.unset(index);
}
});
if (curMask.none() &&
mLastBoundInfo.setMask == setMask && mLastBoundInfo.boundSets == updateSets) {
return;
} }
}); } else {
setMask.forEachSetBit([&](size_t index) {
if (curMask.none() && if (!updateSets[index]) {
(mLastBoundInfo.pipelineLayout == pipelineLayout && mLastBoundInfo.setMask == setMask && curMask.unset(index);
mLastBoundInfo.boundSets == updateSets)) { }
return; });
} }
curMask.forEachSetBit([&updateSets, commands, pipelineLayout](size_t index) { curMask.forEachSetBit([&updateSets, commands, pipelineLayout](size_t index) {
@@ -301,7 +308,7 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
auto set = updateSets[index]; auto set = updateSets[index];
VkCommandBuffer const cmdbuffer = commands->buffer(); VkCommandBuffer const cmdbuffer = commands->buffer();
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index, vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index,
1, &set->vkSet, set->uniqueDynamicUboCount, set->getOffsets()->data()); 1, &set->getVkSet(), set->uniqueDynamicUboCount, set->getOffsets()->data());
commands->acquire(set); commands->acquire(set);
}); });
@@ -329,7 +336,7 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
} }
VkWriteDescriptorSet const descriptorWrite = { VkWriteDescriptorSet const descriptorWrite = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = set->vkSet, .dstSet = set->getVkSet(),
.dstBinding = binding, .dstBinding = binding,
.descriptorCount = 1, .descriptorCount = 1,
.descriptorType = type, .descriptorType = type,
@@ -342,10 +349,6 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set, void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture, uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
VkSampler sampler) noexcept { VkSampler sampler) noexcept {
VkDescriptorImageInfo info{
.sampler = sampler,
.imageLayout = fvkutils::getVkLayout(texture->getDefaultLayout()),
};
VkImageSubresourceRange range = texture->getPrimaryViewRange(); VkImageSubresourceRange range = texture->getPrimaryViewRange();
VkImageViewType const expectedType = texture->getViewType(); VkImageViewType const expectedType = texture->getViewType();
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) && if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
@@ -355,12 +358,16 @@ void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescr
range.levelCount = 1; range.levelCount = 1;
range.layerCount = 1; range.layerCount = 1;
} }
info.imageView = texture->getView(range); VkDescriptorImageInfo info{
.sampler = sampler,
.imageView = texture->getView(range),
.imageLayout = fvkutils::getVkLayout(texture->getDefaultLayout()),
};
VkWriteDescriptorSet const descriptorWrite = { VkWriteDescriptorSet const descriptorWrite = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr, .pNext = nullptr,
.dstSet = set->vkSet, .dstSet = set->getVkSet(),
.dstBinding = binding, .dstBinding = binding,
.descriptorCount = 1, .descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
@@ -378,10 +385,10 @@ void VulkanDescriptorSetCache::updateInputAttachment(
fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet( fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet(
Handle<HwDescriptorSet> handle, fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) { Handle<HwDescriptorSet> handle, fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
auto const vkSet = mDescriptorPool->obtainSet(layout); auto const vkSet = getVkSet(layout);
auto const& count = layout->count; auto const& count = layout->count;
auto const vklayout = layout->getVkLayout(); auto const vklayout = layout->getVkLayout();
return fvkmemory::resource_ptr<VulkanDescriptorSet>::make(mResourceManager, handle, vkSet, auto set = fvkmemory::resource_ptr<VulkanDescriptorSet>::make(mResourceManager, handle,
layout->bitmask.dynamicUbo, layout->count.dynamicUbo, layout->bitmask.dynamicUbo, layout->count.dynamicUbo,
[vkSet, count, vklayout, this](VulkanDescriptorSet*) { [vkSet, count, vklayout, this](VulkanDescriptorSet*) {
// Note that mDescriptorPool could be gone due to terminate (when the backend shuts // Note that mDescriptorPool could be gone due to terminate (when the backend shuts
@@ -390,10 +397,20 @@ fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet
mDescriptorPool->recycle(count, vklayout, vkSet); mDescriptorPool->recycle(count, vklayout, vkSet);
} }
}); });
set->setVkSet(vkSet);
return set;
} }
void VulkanDescriptorSetCache::gc() { VkDescriptorSet VulkanDescriptorSetCache::getVkSet(
mStashedSets = {}; fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
return mDescriptorPool->obtainSet(layout);
} }
void VulkanDescriptorSetCache::manualRecyle(VulkanDescriptorSetLayout::Count const& count,
VkDescriptorSetLayout vklayout, VkDescriptorSet vkSet) {
mDescriptorPool->recycle(count, vklayout, vkSet);
}
void VulkanDescriptorSetCache::gc() { mStashedSets = {}; }
} // namespace filament::backend } // namespace filament::backend

View File

@@ -41,6 +41,8 @@ public:
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT; VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT;
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray; using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
using DescriptorSetArray =
std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>, UNIQUE_DESCRIPTOR_SET_COUNT>;
VulkanDescriptorSetCache(VkDevice device, fvkmemory::ResourceManager* resourceManager); VulkanDescriptorSetCache(VkDevice device, fvkmemory::ResourceManager* resourceManager);
~VulkanDescriptorSetCache(); ~VulkanDescriptorSetCache();
@@ -68,14 +70,21 @@ public:
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle, fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout); fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
// This method is only meant to be used with external samplers (or internally within this
// class).
VkDescriptorSet getVkSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
// This method is only meant to be used with external samplers.
void manualRecyle(VulkanDescriptorSetLayout::Count const& count, VkDescriptorSetLayout vklayout,
VkDescriptorSet vkSet);
DescriptorSetArray const& getBoundSets() const { return mStashedSets; }
void gc(); void gc();
private: private:
class DescriptorInfinitePool; class DescriptorInfinitePool;
using DescriptorSetArray =
std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>, UNIQUE_DESCRIPTOR_SET_COUNT>;
VkDevice mDevice; VkDevice mDevice;
fvkmemory::ResourceManager* mResourceManager; fvkmemory::ResourceManager* mResourceManager;
std::unique_ptr<DescriptorInfinitePool> mDescriptorPool; std::unique_ptr<DescriptorInfinitePool> mDescriptorPool;

View File

@@ -18,6 +18,8 @@
#include "VulkanHandles.h" #include "VulkanHandles.h"
#include <utils/Hash.h>
namespace filament::backend { namespace filament::backend {
namespace { namespace {
@@ -58,6 +60,56 @@ uint32_t appendBindings(VkDescriptorSetLayoutBinding* toBind, VkDescriptorType t
return count; return count;
} }
uint32_t appendSamplerBindings(VkDescriptorSetLayoutBinding* toBind,
fvkutils::SamplerBitmask const& mask, fvkutils::SamplerBitmask const& external,
utils::FixedCapacityVector<VkSampler> const& immutableSamplers) {
using Bitmask = fvkutils::SamplerBitmask;
uint32_t count = 0;
Bitmask alreadySeen;
uint8_t immutableIndex = 0;
size_t const immutableSamplerCount = immutableSamplers.size();
mask.forEachSetBit([&](size_t index) {
VkShaderStageFlags stages = 0;
uint32_t binding = 0;
if (index < fvkutils::getFragmentStageShift<Bitmask>()) {
binding = (uint32_t) index;
stages |= VK_SHADER_STAGE_VERTEX_BIT;
auto fragIndex = index + fvkutils::getFragmentStageShift<Bitmask>();
if (mask.test(fragIndex)) {
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
alreadySeen.set(fragIndex);
}
} else if (!alreadySeen.test(index)) {
// We are in fragment stage bits
binding = (uint32_t) (index - fvkutils::getFragmentStageShift<Bitmask>());
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
}
if (stages) {
toBind[count++] = {
.binding = binding,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = 1,
.stageFlags = stages,
.pImmutableSamplers = external[index] && immutableSamplerCount > immutableIndex
? &immutableSamplers[immutableIndex++]
: nullptr,
};
}
});
return count;
}
uint64_t computeImmutableSamplerHash(utils::FixedCapacityVector<VkSampler> const& samplers) {
size_t const size = samplers.size();
if (size == 0) {
return 0;
} else if (size == 1) {
return (uint64_t) samplers[0];
}
return utils::hash::murmur3((uint32_t*) samplers.data(), samplers.size() * 2, 0);
}
} // anonymous namespace } // anonymous namespace
VulkanDescriptorSetLayoutCache::VulkanDescriptorSetLayoutCache(VkDevice device, VulkanDescriptorSetLayoutCache::VulkanDescriptorSetLayoutCache(VkDevice device,
@@ -73,37 +125,44 @@ void VulkanDescriptorSetLayoutCache::terminate() noexcept {
} }
} }
VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
VulkanDescriptorSetLayout::Bitmask const& bitmasks,
utils::FixedCapacityVector<VkSampler> immutableSamplers) {
LayoutKey key = {
.bitmask = bitmasks,
.immutableSamplerHash = computeImmutableSamplerHash(immutableSamplers),
};
if (auto itr = mVkLayouts.find(key); itr != mVkLayouts.end()) {
return itr->second;
}
VkDescriptorSetLayoutBinding toBind[VulkanDescriptorSetLayout::MAX_BINDINGS];
uint32_t count = 0;
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
bitmasks.dynamicUbo);
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bitmasks.ubo);
count += appendSamplerBindings(&toBind[count], bitmasks.sampler, bitmasks.externalSampler,
immutableSamplers);
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
bitmasks.inputAttachment);
assert_invariant(count != 0 && "Need at least one binding for descriptor set layout.");
VkDescriptorSetLayoutCreateInfo dlinfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = count,
.pBindings = toBind,
};
VkDescriptorSetLayout vklayout;
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &vklayout);
mVkLayouts[key] = vklayout;
return vklayout;
}
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> VulkanDescriptorSetLayoutCache::createLayout( fvkmemory::resource_ptr<VulkanDescriptorSetLayout> VulkanDescriptorSetLayoutCache::createLayout(
Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info) { Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info) {
auto layout = fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::make(mResourceManager, handle, auto layout = fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::make(mResourceManager, handle,
info); info);
VkDescriptorSetLayout vklayout = VK_NULL_HANDLE; layout->setVkLayout(getVkLayout(layout->bitmask));
auto const& bitmasks = layout->bitmask;
if (auto itr = mVkLayouts.find(bitmasks); itr != mVkLayouts.end()) {
vklayout = itr->second;
} else {
VkDescriptorSetLayoutBinding toBind[VulkanDescriptorSetLayout::MAX_BINDINGS];
uint32_t count = 0;
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
bitmasks.dynamicUbo);
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bitmasks.ubo);
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
bitmasks.sampler);
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
bitmasks.inputAttachment);
assert_invariant(count != 0 && "Need at least one binding for descriptor set layout.");
VkDescriptorSetLayoutCreateInfo dlinfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.pNext = nullptr,
.bindingCount = count,
.pBindings = toBind,
};
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &vklayout);
mVkLayouts[bitmasks] = vklayout;
}
layout->setVkLayout(vklayout);
return layout; return layout;
} }

View File

@@ -26,12 +26,11 @@
#include <backend/TargetBufferInfo.h> #include <backend/TargetBufferInfo.h>
#include <utils/bitset.h> #include <utils/bitset.h>
#include <utils/FixedCapacityVector.h>
#include <bluevk/BlueVK.h> #include <bluevk/BlueVK.h>
#include <tsl/robin_map.h> #include <tsl/robin_map.h>
#include <memory>
namespace filament::backend { namespace filament::backend {
class VulkanDescriptorSetLayoutCache { class VulkanDescriptorSetLayoutCache {
@@ -41,21 +40,34 @@ public:
void terminate() noexcept; void terminate() noexcept;
// Just a wrapper around getVkLayout()
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> createLayout( fvkmemory::resource_ptr<VulkanDescriptorSetLayout> createLayout(
Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info); Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info);
// This method is meant to be used with external samplers
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout::Bitmask const& bitmasks,
utils::FixedCapacityVector<VkSampler> immutableSamplers = {});
private: private:
VkDevice mDevice; VkDevice mDevice;
fvkmemory::ResourceManager* mResourceManager; fvkmemory::ResourceManager* mResourceManager;
using BitmaskGroup = VulkanDescriptorSetLayout::Bitmask; struct LayoutKey {
using BitmaskGroupHashFn = utils::hash::MurmurHashFn<BitmaskGroup>; // this describes the layout using bitset.
struct BitmaskGroupEqual { VulkanDescriptorSetLayout::Bitmask bitmask = {};
bool operator()(BitmaskGroup const& k1, BitmaskGroup const& k2) const { return k1 == k2; } // number of immutable samplers can be arbitrary; so we hash them into 64-bit.
uint64_t immutableSamplerHash = 0;
};
static_assert(sizeof(LayoutKey) == 48);
using LayoutKeyHashFn = utils::hash::MurmurHashFn<LayoutKey>;
struct LayoutKeyEqual {
bool operator()(LayoutKey const& k1, LayoutKey const& k2) const {
return k1.bitmask == k2.bitmask && k1.immutableSamplerHash == k2.immutableSamplerHash;
}
}; };
tsl::robin_map<BitmaskGroup, VkDescriptorSetLayout, BitmaskGroupHashFn, BitmaskGroupEqual> tsl::robin_map<LayoutKey, VkDescriptorSetLayout, LayoutKeyHashFn, LayoutKeyEqual> mVkLayouts;
mVkLayouts;
}; };
} // namespace filament::backend } // namespace filament::backend

View File

@@ -327,7 +327,7 @@ void VulkanDriver::terminate() {
mStagePool.terminate(); mStagePool.terminate();
mPipelineCache.terminate(); mPipelineCache.terminate();
mFramebufferCache.reset(); mFramebufferCache.terminate();
mSamplerCache.terminate(); mSamplerCache.terminate();
mDescriptorSetLayoutCache.terminate(); mDescriptorSetLayoutCache.terminate();
mDescriptorSetCache.terminate(); mDescriptorSetCache.terminate();
@@ -1523,7 +1523,7 @@ void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
swapChain->acquire(resized); swapChain->acquire(resized);
if (resized) { if (resized) {
mFramebufferCache.reset(); mFramebufferCache.resetFramebuffers();
} }
if (UTILS_LIKELY(mDefaultRenderTarget)) { if (UTILS_LIKELY(mDefaultRenderTarget)) {

View File

@@ -340,15 +340,21 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept
return renderPass; return renderPass;
} }
void VulkanFboCache::reset() noexcept { void VulkanFboCache::resetFramebuffers() noexcept {
for (auto pair : mFramebufferCache) { for (const auto& pair: mFramebufferCache) {
mRenderPassRefCount[pair.first.renderPass]--; mRenderPassRefCount[pair.first.renderPass]--;
vkDestroyFramebuffer(mDevice, pair.second.handle, VKALLOC); vkDestroyFramebuffer(mDevice, pair.second.handle, VKALLOC);
} }
mFramebufferCache.clear(); mFramebufferCache.clear();
for (auto pair : mRenderPassCache) { }
void VulkanFboCache::terminate() noexcept {
resetFramebuffers();
for (const auto& pair: mRenderPassCache) {
vkDestroyRenderPass(mDevice, pair.second.handle, VKALLOC); vkDestroyRenderPass(mDevice, pair.second.handle, VKALLOC);
} }
mRenderPassRefCount.clear();
mRenderPassCache.clear(); mRenderPassCache.clear();
} }

View File

@@ -106,8 +106,11 @@ public:
// Evicts old unused Vulkan objects. Call this once per frame. // Evicts old unused Vulkan objects. Call this once per frame.
void gc() noexcept; void gc() noexcept;
// Frees all Framebuffer objects. Call this every time a the swapchain is resized
void resetFramebuffers() noexcept;
// Frees all Vulkan objects. Call this during shutdown before the device is destroyed. // Frees all Vulkan objects. Call this during shutdown before the device is destroyed.
void reset() noexcept; void terminate() noexcept;
private: private:
VkDevice mDevice; VkDevice mDevice;

View File

@@ -30,6 +30,7 @@
#include <backend/platforms/VulkanPlatform.h> #include <backend/platforms/VulkanPlatform.h>
#include <utils/compiler.h> // UTILS_FALLTHROUGH
#include <utils/Panic.h> // ASSERT_POSTCONDITION #include <utils/Panic.h> // ASSERT_POSTCONDITION
using namespace bluevk; using namespace bluevk;
@@ -89,8 +90,9 @@ BitmaskGroup fromBackendLayout(DescriptorSetLayout const& layout) {
} }
break; break;
} }
// TODO: properly handle external sampler
case DescriptorType::SAMPLER_EXTERNAL: case DescriptorType::SAMPLER_EXTERNAL:
fromStageFlags(binding.stageFlags, binding.binding, mask.externalSampler);
UTILS_FALLTHROUGH;
case DescriptorType::SAMPLER: { case DescriptorType::SAMPLER: {
fromStageFlags(binding.stageFlags, binding.binding, mask.sampler); fromStageFlags(binding.stageFlags, binding.binding, mask.sampler);
break; break;

View File

@@ -66,18 +66,21 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
// The bitmask representation of a set layout. // The bitmask representation of a set layout.
struct Bitmask { struct Bitmask {
// TODO: better utiltize the space below and use bitset instead.
fvkutils::UniformBufferBitmask ubo; // 8 bytes fvkutils::UniformBufferBitmask ubo; // 8 bytes
fvkutils::UniformBufferBitmask dynamicUbo; // 8 bytes fvkutils::UniformBufferBitmask dynamicUbo; // 8 bytes
fvkutils::SamplerBitmask sampler; // 8 bytes fvkutils::SamplerBitmask sampler; // 8 bytes
fvkutils::InputAttachmentBitmask inputAttachment; // 8 bytes fvkutils::InputAttachmentBitmask inputAttachment; // 8 bytes
// This is a subset of the bitmask.sampler field.
fvkutils::SamplerBitmask externalSampler; // 8 bytes
bool operator==(Bitmask const& right) const { bool operator==(Bitmask const& right) const {
return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler && return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler &&
inputAttachment == right.inputAttachment; inputAttachment == right.inputAttachment &&
externalSampler == right.externalSampler;
} }
}; };
static_assert(sizeof(Bitmask) == 32); static_assert(sizeof(Bitmask) == 40);
// This is a convenience struct to quickly check layout compatibility in terms of descriptor set // This is a convenience struct to quickly check layout compatibility in terms of descriptor set
// pools. // pools.
@@ -119,10 +122,16 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
VulkanDescriptorSetLayout(DescriptorSetLayout const& layout); VulkanDescriptorSetLayout(DescriptorSetLayout const& layout);
// Note that we don't destroy the vklayout. This is done by the layout cache.
~VulkanDescriptorSetLayout() = default; ~VulkanDescriptorSetLayout() = default;
VkDescriptorSetLayout getVkLayout() const { return mVkLayout; } VkDescriptorSetLayout const& getVkLayout() const noexcept { return mVkLayout; }
void setVkLayout(VkDescriptorSetLayout vklayout) { mVkLayout = vklayout; }
// It is possible to have the layout switch out due to AHardwarebuffer (external image) format
// changes.
void setVkLayout(VkDescriptorSetLayout vklayout) noexcept { mVkLayout = vklayout; }
bool hasExternalSamplers() const noexcept { return bitmask.externalSampler.count() > 0; }
Bitmask const bitmask; Bitmask const bitmask;
Count const count; Count const count;
@@ -137,12 +146,11 @@ public:
// can use to repackage the vk handle. // can use to repackage the vk handle.
using OnRecycle = std::function<void(VulkanDescriptorSet*)>; using OnRecycle = std::function<void(VulkanDescriptorSet*)>;
VulkanDescriptorSet(VkDescriptorSet rawSet, VulkanDescriptorSet(
fvkutils::UniformBufferBitmask const& dynamicUboMask, fvkutils::UniformBufferBitmask const& dynamicUboMask,
uint8_t uniqueDynamicUboCount, uint8_t uniqueDynamicUboCount,
OnRecycle&& onRecycleFn) OnRecycle&& onRecycleFn)
: vkSet(rawSet), : dynamicUboMask(dynamicUboMask),
dynamicUboMask(dynamicUboMask),
uniqueDynamicUboCount(uniqueDynamicUboCount), uniqueDynamicUboCount(uniqueDynamicUboCount),
mOnRecycleFn(std::move(onRecycleFn)) {} mOnRecycleFn(std::move(onRecycleFn)) {}
@@ -152,6 +160,13 @@ public:
} }
} }
VkDescriptorSet const& getVkSet() const noexcept {
return mVkSet;
}
// Note that the only case where you'd set it more than once is with external images/samplers.
void setVkSet(VkDescriptorSet vkset) noexcept { mVkSet = vkset; }
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept { void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
mOffsets = std::move(offsets); mOffsets = std::move(offsets);
} }
@@ -163,11 +178,11 @@ public:
void acquire(fvkmemory::resource_ptr<VulkanTexture> texture); void acquire(fvkmemory::resource_ptr<VulkanTexture> texture);
void acquire(fvkmemory::resource_ptr<VulkanBufferObject> buffer); void acquire(fvkmemory::resource_ptr<VulkanBufferObject> buffer);
VkDescriptorSet const vkSet;
fvkutils::UniformBufferBitmask const dynamicUboMask; fvkutils::UniformBufferBitmask const dynamicUboMask;
uint8_t const uniqueDynamicUboCount; uint8_t const uniqueDynamicUboCount;
private: private:
VkDescriptorSet mVkSet = VK_NULL_HANDLE;
backend::DescriptorSetOffsetArray mOffsets; backend::DescriptorSetOffsetArray mOffsets;
std::vector<fvkmemory::resource_ptr<fvkmemory::Resource>> mResources; std::vector<fvkmemory::resource_ptr<fvkmemory::Resource>> mResources;
OnRecycle mOnRecycleFn; OnRecycle mOnRecycleFn;

View File

@@ -35,7 +35,12 @@ using namespace bluevk;
namespace filament::backend { namespace filament::backend {
VulkanPipelineCache::VulkanPipelineCache(VkDevice device) VulkanPipelineCache::VulkanPipelineCache(VkDevice device)
: mDevice(device) {} : mDevice(device) {
VkPipelineCacheCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
};
bluevk::vkCreatePipelineCache(mDevice, &createInfo, VKALLOC, &mPipelineCache);
}
void VulkanPipelineCache::bindLayout(VkPipelineLayout layout) noexcept { void VulkanPipelineCache::bindLayout(VkPipelineLayout layout) noexcept {
mPipelineRequirements.layout = layout; mPipelineRequirements.layout = layout;
@@ -215,7 +220,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
PipelineCacheEntry cacheEntry = { PipelineCacheEntry cacheEntry = {
.lastUsed = mCurrentTime, .lastUsed = mCurrentTime,
}; };
VkResult error = vkCreateGraphicsPipelines(mDevice, VK_NULL_HANDLE, 1, &pipelineCreateInfo, VkResult error = vkCreateGraphicsPipelines(mDevice, mPipelineCache, 1, &pipelineCreateInfo,
VKALLOC, &cacheEntry.handle); VKALLOC, &cacheEntry.handle);
assert_invariant(error == VK_SUCCESS); assert_invariant(error == VK_SUCCESS);
if (error != VK_SUCCESS) { if (error != VK_SUCCESS) {
@@ -271,6 +276,8 @@ void VulkanPipelineCache::terminate() noexcept {
} }
mPipelines.clear(); mPipelines.clear();
mBoundPipeline = {}; mBoundPipeline = {};
vkDestroyPipelineCache(mDevice, mPipelineCache, VKALLOC);
} }
void VulkanPipelineCache::gc() noexcept { void VulkanPipelineCache::gc() noexcept {

View File

@@ -198,6 +198,10 @@ private:
// Immutable state. // Immutable state.
VkDevice mDevice = VK_NULL_HANDLE; VkDevice mDevice = VK_NULL_HANDLE;
// Vuklan Driver pipeline cache handle. In the cases a pipeline has been evicted by the `gc`,
// recreating the same pipeline is cheaper, helping with frame stalling.
VkPipelineCache mPipelineCache = VK_NULL_HANDLE;
// Current requirements for the pipeline layout, pipeline, and descriptor sets. // Current requirements for the pipeline layout, pipeline, and descriptor sets.
PipelineKey mPipelineRequirements = {}; PipelineKey mPipelineRequirements = {};

View File

@@ -33,8 +33,15 @@ VkSampler VulkanSamplerCache::getSampler(Params params) noexcept {
if (UTILS_LIKELY(iter != mCache.end())) { if (UTILS_LIKELY(iter != mCache.end())) {
return iter->second; return iter->second;
} }
VkSamplerYcbcrConversionInfo ycbcrConversion = {
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
.conversion = params.conversion,
};
auto const& samplerParams = params.sampler; auto const& samplerParams = params.sampler;
VkSamplerCreateInfo samplerInfo{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, VkSamplerCreateInfo samplerInfo{
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.pNext = params.conversion != VK_NULL_HANDLE ? &ycbcrConversion : VK_NULL_HANDLE,
.magFilter = fvkutils::getFilter(samplerParams.filterMag), .magFilter = fvkutils::getFilter(samplerParams.filterMag),
.minFilter = fvkutils::getFilter(samplerParams.filterMin), .minFilter = fvkutils::getFilter(samplerParams.filterMin),
.mipmapMode = fvkutils::getMipmapMode(samplerParams.filterMin), .mipmapMode = fvkutils::getMipmapMode(samplerParams.filterMin),
@@ -48,7 +55,8 @@ VkSampler VulkanSamplerCache::getSampler(Params params) noexcept {
.minLod = 0.0f, .minLod = 0.0f,
.maxLod = fvkutils::getMaxLod(samplerParams.filterMin), .maxLod = fvkutils::getMaxLod(samplerParams.filterMin),
.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK, .borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
.unnormalizedCoordinates = VK_FALSE }; .unnormalizedCoordinates = VK_FALSE,
};
VkSampler sampler; VkSampler sampler;
VkResult result = vkCreateSampler(mDevice, &samplerInfo, VKALLOC, &sampler); VkResult result = vkCreateSampler(mDevice, &samplerInfo, VKALLOC, &sampler);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to create sampler." FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to create sampler."

View File

@@ -108,11 +108,6 @@ void VulkanSwapChain::present() {
mCommands->flush(); mCommands->flush();
// call the image ready wait function
if (mExplicitImageReadyWait != nullptr) {
mExplicitImageReadyWait(swapChain);
}
// We only present if it is not headless. No-op for headless. // We only present if it is not headless. No-op for headless.
if (!mHeadless) { if (!mHeadless) {
VkSemaphore const finishedDrawing = mCommands->acquireFinishedSignal(); VkSemaphore const finishedDrawing = mCommands->acquireFinishedSignal();
@@ -146,7 +141,6 @@ void VulkanSwapChain::acquire(bool& resized) {
VulkanPlatform::ImageSyncData imageSyncData; VulkanPlatform::ImageSyncData imageSyncData;
VkResult const result = mPlatform->acquire(swapChain, &imageSyncData); VkResult const result = mPlatform->acquire(swapChain, &imageSyncData);
mCurrentSwapIndex = imageSyncData.imageIndex; mCurrentSwapIndex = imageSyncData.imageIndex;
mExplicitImageReadyWait = imageSyncData.explicitImageReadyWait;
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR) FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
<< "Cannot acquire in swapchain. error=" << static_cast<int32_t>(result); << "Cannot acquire in swapchain. error=" << static_cast<int32_t>(result);
if (imageSyncData.imageReadySemaphore != VK_NULL_HANDLE) { if (imageSyncData.imageReadySemaphore != VK_NULL_HANDLE) {

View File

@@ -49,7 +49,9 @@ struct VulkanSwapChain : public HwSwapChain, fvkmemory::Resource {
void present(); void present();
void acquire(bool& reized); // Acquire a new image from the swapchain. If the image is not available it would wait until it
// is.
void acquire(bool& resized);
fvkmemory::resource_ptr<VulkanTexture> getCurrentColor() const noexcept { fvkmemory::resource_ptr<VulkanTexture> getCurrentColor() const noexcept {
uint32_t const imageIndex = mCurrentSwapIndex; uint32_t const imageIndex = mCurrentSwapIndex;
@@ -99,7 +101,6 @@ private:
VkExtent2D mExtent; VkExtent2D mExtent;
uint32_t mLayerCount; uint32_t mLayerCount;
uint32_t mCurrentSwapIndex; uint32_t mCurrentSwapIndex;
std::function<void(Platform::SwapChain* handle)> mExplicitImageReadyWait = nullptr;
bool mAcquired; bool mAcquired;
bool mIsFirstRenderPass; bool mIsFirstRenderPass;
}; };

View File

@@ -43,7 +43,7 @@ VkSamplerYcbcrConversion VulkanYcbcrConversionCache::getConversion(
TextureSwizzle const swizzleArray[] = { chroma.r, chroma.g, chroma.b, chroma.a }; TextureSwizzle const swizzleArray[] = { chroma.r, chroma.g, chroma.b, chroma.a };
VkSamplerYcbcrConversionCreateInfo conversionInfo = { VkSamplerYcbcrConversionCreateInfo conversionInfo = {
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO,
.format = VK_FORMAT_UNDEFINED, .format = fvkutils::getVkFormat(params.format),
.ycbcrModel = fvkutils::getYcbcrModelConversion(chroma.ycbcrModel), .ycbcrModel = fvkutils::getYcbcrModelConversion(chroma.ycbcrModel),
.ycbcrRange = fvkutils::getYcbcrRange(chroma.ycbcrRange), .ycbcrRange = fvkutils::getYcbcrRange(chroma.ycbcrRange),
.components = fvkutils::getSwizzleMap(swizzleArray), .components = fvkutils::getSwizzleMap(swizzleArray),
@@ -52,6 +52,7 @@ VkSamplerYcbcrConversion VulkanYcbcrConversionCache::getConversion(
.chromaFilter = fvkutils::getFilter(chroma.chromaFilter), .chromaFilter = fvkutils::getFilter(chroma.chromaFilter),
}; };
// We could put this in the platform class, but that seems like a bit of an overkill
#if defined(__ANDROID__) #if defined(__ANDROID__)
VkExternalFormatANDROID externalFormat = { VkExternalFormatANDROID externalFormat = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID, .sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
@@ -59,6 +60,7 @@ VkSamplerYcbcrConversion VulkanYcbcrConversionCache::getConversion(
}; };
if (params.externalFormat) { if (params.externalFormat) {
conversionInfo.pNext = &externalFormat; conversionInfo.pNext = &externalFormat;
conversionInfo.format = VK_FORMAT_UNDEFINED;
} }
#endif #endif

View File

@@ -31,7 +31,8 @@ class VulkanYcbcrConversionCache {
public: public:
struct Params { struct Params {
SamplerYcbcrConversion conversion = {}; SamplerYcbcrConversion conversion = {};
uint32_t padding = 0; TextureFormat format = {};
uint16_t padding = 0;
uint64_t externalFormat = 0; uint64_t externalFormat = 0;
}; };
static_assert(sizeof(Params) == 16); static_assert(sizeof(Params) == 16);

View File

@@ -211,6 +211,7 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME, VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME, VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
#endif #endif
// MoltenVk is the only non-conformant implementation we're interested in. // MoltenVk is the only non-conformant implementation we're interested in.
#if defined(__APPLE__) #if defined(__APPLE__)
@@ -326,7 +327,9 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
} }
VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice, VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
VkPhysicalDeviceFeatures2 const& features, uint32_t graphicsQueueFamilyIndex, VkPhysicalDeviceFeatures2 const& features,
VkPhysicalDeviceVulkan11Features const& vk11Features,
uint32_t graphicsQueueFamilyIndex,
uint32_t protectedGraphicsQueueFamilyIndex, ExtensionSet const& deviceExtensions, uint32_t protectedGraphicsQueueFamilyIndex, ExtensionSet const& deviceExtensions,
bool requestImageView2DOn3DImage) { bool requestImageView2DOn3DImage) {
VkDevice device; VkDevice device;
@@ -359,14 +362,28 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
// We could simply enable all supported features, but since that may have performance // We could simply enable all supported features, but since that may have performance
// consequences let's just enable the features we need. // consequences let's just enable the features we need.
VkPhysicalDeviceFeatures enabledFeatures{ VkPhysicalDeviceFeatures enabledFeatures = {
.depthClamp = features.features.depthClamp, .depthClamp = features.features.depthClamp,
.samplerAnisotropy = features.features.samplerAnisotropy, .samplerAnisotropy = features.features.samplerAnisotropy,
.textureCompressionETC2 = features.features.textureCompressionETC2, .textureCompressionETC2 = features.features.textureCompressionETC2,
.textureCompressionBC = features.features.textureCompressionBC, .textureCompressionBC = features.features.textureCompressionBC,
.shaderClipDistance = features.features.shaderClipDistance, .shaderClipDistance = features.features.shaderClipDistance,
}; };
deviceCreateInfo.pEnabledFeatures = &enabledFeatures;
VkPhysicalDeviceFeatures2 enabledFeatures2 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.features = enabledFeatures,
};
chainStruct(&deviceCreateInfo, &enabledFeatures2);
VkPhysicalDeviceVulkan11Features enabledVk11Features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
.multiview = vk11Features.multiview,
#if defined(__ANDROID__)
.samplerYcbcrConversion = vk11Features.samplerYcbcrConversion,
#endif
};
chainStruct(&deviceCreateInfo, &enabledVk11Features);
deviceCreateInfo.enabledExtensionCount = (uint32_t) requestExtensions.size(); deviceCreateInfo.enabledExtensionCount = (uint32_t) requestExtensions.size();
deviceCreateInfo.ppEnabledExtensionNames = requestExtensions.data(); deviceCreateInfo.ppEnabledExtensionNames = requestExtensions.data();
@@ -383,7 +400,7 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
VkPhysicalDeviceMultiviewFeaturesKHR multiview = { VkPhysicalDeviceMultiviewFeaturesKHR multiview = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES_KHR, .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES_KHR,
.multiview = VK_TRUE, .multiview = vk11Features.multiview,
.multiviewGeometryShader = VK_FALSE, .multiviewGeometryShader = VK_FALSE,
.multiviewTessellationShader = VK_FALSE, .multiviewTessellationShader = VK_FALSE,
}; };
@@ -734,6 +751,7 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES, .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES,
}; };
chainStruct(&context.mPhysicalDeviceFeatures, &queryProtectedMemoryFeatures); chainStruct(&context.mPhysicalDeviceFeatures, &queryProtectedMemoryFeatures);
chainStruct(&context.mPhysicalDeviceFeatures, &context.mPhysicalDeviceVk11Features);
chainStruct(&context.mPhysicalDeviceProperties, &protectedMemoryProperties); chainStruct(&context.mPhysicalDeviceProperties, &protectedMemoryProperties);
// Initialize the following fields: physicalDeviceProperties, memoryProperties, // Initialize the following fields: physicalDeviceProperties, memoryProperties,
@@ -795,10 +813,10 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
} }
if (mImpl->mDevice == VK_NULL_HANDLE) { if (mImpl->mDevice == VK_NULL_HANDLE) {
mImpl->mDevice = mImpl->mDevice = createLogicalDevice(mImpl->mPhysicalDevice,
createLogicalDevice(mImpl->mPhysicalDevice, context.mPhysicalDeviceFeatures, context.mPhysicalDeviceFeatures, context.mPhysicalDeviceVk11Features,
mImpl->mGraphicsQueueFamilyIndex, mImpl->mProtectedGraphicsQueueFamilyIndex, mImpl->mGraphicsQueueFamilyIndex, mImpl->mProtectedGraphicsQueueFamilyIndex,
deviceExts, requestPortabilitySubsetImageView2DOn3DImage); deviceExts, requestPortabilitySubsetImageView2DOn3DImage);
} }
assert_invariant(mImpl->mDevice != VK_NULL_HANDLE); assert_invariant(mImpl->mDevice != VK_NULL_HANDLE);
@@ -826,12 +844,10 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
if (!mImpl->mSharedContext) { if (!mImpl->mSharedContext) {
context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME); context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME); context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
context.mMultiviewEnabled = setContains(deviceExts, VK_KHR_MULTIVIEW_EXTENSION_NAME);
} else { } else {
VulkanSharedContext const* scontext = (VulkanSharedContext const*) sharedContext; VulkanSharedContext const* scontext = (VulkanSharedContext const*) sharedContext;
context.mDebugUtilsSupported = scontext->debugUtilsSupported; context.mDebugUtilsSupported = scontext->debugUtilsSupported;
context.mDebugMarkersSupported = scontext->debugMarkersSupported; context.mDebugMarkersSupported = scontext->debugMarkersSupported;
context.mMultiviewEnabled = scontext->multiviewSupported;
} }
// Check the availability of lazily allocated memory // Check the availability of lazily allocated memory

View File

@@ -24,28 +24,12 @@
#include <bluevk/BlueVK.h> #include <bluevk/BlueVK.h>
// Platform specific includes and defines // Platform specific includes and defines
#if defined(__APPLE__) #include <Cocoa/Cocoa.h>
#include <Cocoa/Cocoa.h> #import <Metal/Metal.h>
#import <Metal/Metal.h> #import <QuartzCore/CAMetalLayer.h>
#import <QuartzCore/CAMetalLayer.h>
#ifndef VK_MVK_macos_surface #ifndef VK_MVK_macos_surface
#error VK_MVK_macos_surface is not defined #error VK_MVK_macos_surface is not defined
#endif
#elif defined(FILAMENT_IOS)
// Metal is not available when building for the iOS simulator on Desktop.
#define METAL_AVAILABLE __has_include(<QuartzCore/CAMetalLayer.h>)
#if METAL_AVAILABLE
#import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h>
#endif
#ifndef VK_MVK_ios_surface
#error VK_MVK_ios_surface is not defined
#endif
#define METALVIEW_TAG 255
#else
#error Not a supported Apple + Vulkan platform
#endif #endif
using namespace bluevk; using namespace bluevk;
@@ -54,11 +38,7 @@ namespace filament::backend {
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() { VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
ExtensionSet const ret = { ExtensionSet const ret = {
#if defined(__APPLE__)
VK_MVK_MACOS_SURFACE_EXTENSION_NAME, // TODO: replace with VK_EXT_metal_surface VK_MVK_MACOS_SURFACE_EXTENSION_NAME, // TODO: replace with VK_EXT_metal_surface
#elif defined(FILAMENT_IOS) && defined(METAL_AVAILABLE)
VK_MVK_IOS_SURFACE_EXTENSION_NAME,
#endif
}; };
return ret; return ret;
} }
@@ -90,36 +70,20 @@ VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device,
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow, VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
VkInstance instance, uint64_t flags) noexcept { VkInstance instance, uint64_t flags) noexcept {
VkSurfaceKHR surface; VkSurfaceKHR surface;
#if defined(__APPLE__) NSView* nsview = (__bridge NSView*) nativeWindow;
NSView* nsview = (__bridge NSView*) nativeWindow; FILAMENT_CHECK_POSTCONDITION(nsview) << "Unable to obtain Metal-backed NSView.";
FILAMENT_CHECK_POSTCONDITION(nsview) << "Unable to obtain Metal-backed NSView.";
// Create the VkSurface.
FILAMENT_CHECK_POSTCONDITION(vkCreateMacOSSurfaceMVK)
<< "Unable to load vkCreateMacOSSurfaceMVK.";
VkMacOSSurfaceCreateInfoMVK createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
createInfo.pView = (__bridge void*) nsview;
VkResult result = vkCreateMacOSSurfaceMVK((VkInstance) instance, &createInfo, VKALLOC,
(VkSurfaceKHR*) &surface);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkCreateMacOSSurfaceMVK. error=" << static_cast<int32_t>(result);
#elif defined(FILAMENT_IOS) && defined(METAL_AVAILABLE)
CAMetalLayer* metalLayer = (CAMetalLayer*) nativeWindow;
// Create the VkSurface. // Create the VkSurface.
FILAMENT_CHECK_POSTCONDITION(vkCreateIOSSurfaceMVK) FILAMENT_CHECK_POSTCONDITION(vkCreateMacOSSurfaceMVK)
<< "Unable to load vkCreateIOSSurfaceMVK function."; << "Unable to load vkCreateMacOSSurfaceMVK.";
VkIOSSurfaceCreateInfoMVK createInfo = {}; VkMacOSSurfaceCreateInfoMVK createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK; createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
createInfo.pNext = NULL; createInfo.pView = (__bridge void*) nsview;
createInfo.flags = 0; VkResult result = vkCreateMacOSSurfaceMVK((VkInstance) instance, &createInfo, VKALLOC,
createInfo.pView = metalLayer;
VkResult result = vkCreateIOSSurfaceMVK((VkInstance) instance, &createInfo, VKALLOC,
(VkSurfaceKHR*) &surface); (VkSurfaceKHR*) &surface);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkCreateIOSSurfaceMVK failed. error=" << static_cast<int32_t>(result); << "vkCreateMacOSSurfaceMVK. error=" << static_cast<int32_t>(result);
#endif return std::make_tuple(surface, VkExtent2D{});
return std::make_tuple(surface, VkExtent2D{});
} }
} // namespace filament::backend } // namespace filament::backend

View File

@@ -668,4 +668,111 @@ VkShaderStageFlags getShaderStageFlags(ShaderStageFlags stageFlags) {
return flags; return flags;
} }
VkSamplerYcbcrModelConversion getYcbcrModelConversion(
SamplerYcbcrModelConversion model) {
switch (model) {
case SamplerYcbcrModelConversion::RGB_IDENTITY:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY;
case SamplerYcbcrModelConversion::YCBCR_IDENTITY:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY;
case SamplerYcbcrModelConversion::YCBCR_709:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709;
case SamplerYcbcrModelConversion::YCBCR_601:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601;
case SamplerYcbcrModelConversion::YCBCR_2020:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020;
default:
assert_invariant(false &&
"Unknown data type, conversion is not supported.");
}
}
VkSamplerYcbcrRange getYcbcrRange(SamplerYcbcrRange range) {
switch (range) {
case SamplerYcbcrRange::ITU_FULL:
return VK_SAMPLER_YCBCR_RANGE_ITU_FULL;
case SamplerYcbcrRange::ITU_NARROW:
return VK_SAMPLER_YCBCR_RANGE_ITU_NARROW;
default:
assert_invariant(false &&
"Unknown data type, conversion is not supported.");
}
}
VkChromaLocation getChromaLocation(ChromaLocation loc) {
switch (loc) {
case ChromaLocation::COSITED_EVEN:
return VK_CHROMA_LOCATION_COSITED_EVEN;
case ChromaLocation::MIDPOINT:
return VK_CHROMA_LOCATION_MIDPOINT;
default:
assert_invariant(false &&
"Unknown data type, conversion is not supported.");
}
}
SamplerYcbcrModelConversion getYcbcrModelConversionFilament(VkSamplerYcbcrModelConversion model) {
switch (model) {
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY:
return SamplerYcbcrModelConversion::RGB_IDENTITY;
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY:
return SamplerYcbcrModelConversion::YCBCR_IDENTITY;
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709:
return SamplerYcbcrModelConversion::YCBCR_709;
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601:
return SamplerYcbcrModelConversion::YCBCR_601;
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020:
return SamplerYcbcrModelConversion::YCBCR_2020;
default:
assert_invariant(false && "Unknown data type, conversion is not supported.");
return {};
}
}
SamplerYcbcrRange getYcbcrRangeFilament(VkSamplerYcbcrRange range) {
switch (range) {
case VK_SAMPLER_YCBCR_RANGE_ITU_FULL:
return SamplerYcbcrRange::ITU_FULL;
case VK_SAMPLER_YCBCR_RANGE_ITU_NARROW:
return SamplerYcbcrRange::ITU_NARROW;
default:
assert_invariant(false && "Unknown data type, conversion is not supported.");
return {};
}
}
ChromaLocation getChromaLocationFilament(VkChromaLocation loc) {
switch (loc) {
case VK_CHROMA_LOCATION_COSITED_EVEN:
return ChromaLocation::COSITED_EVEN;
case VK_CHROMA_LOCATION_MIDPOINT:
return ChromaLocation::MIDPOINT;
default:
assert_invariant(false && "Unknown data type, conversion is not supported.");
return {};
}
}
TextureSwizzle getSwizzleFilament(VkComponentSwizzle c, uint8_t rgbaIndex) {
switch (c) {
case VK_COMPONENT_SWIZZLE_ZERO:
return TextureSwizzle::SUBSTITUTE_ZERO;
case VK_COMPONENT_SWIZZLE_ONE:
return TextureSwizzle::SUBSTITUTE_ONE;
case VK_COMPONENT_SWIZZLE_IDENTITY:
return (TextureSwizzle) (((uint8_t) TextureSwizzle::CHANNEL_0) + rgbaIndex);
case VK_COMPONENT_SWIZZLE_R:
return TextureSwizzle::CHANNEL_0;
case VK_COMPONENT_SWIZZLE_G:
return TextureSwizzle::CHANNEL_1;
case VK_COMPONENT_SWIZZLE_B:
return TextureSwizzle::CHANNEL_2;
case VK_COMPONENT_SWIZZLE_A:
return TextureSwizzle::CHANNEL_3;
default:
assert_invariant(false && "Unknown data type, conversion is not supported.");
return {};
}
}
} // namespace filament::backend::fvkutils } // namespace filament::backend::fvkutils

View File

@@ -69,6 +69,12 @@ VkSamplerYcbcrModelConversion getYcbcrModelConversion(SamplerYcbcrModelConversio
VkSamplerYcbcrRange getYcbcrRange(SamplerYcbcrRange range); VkSamplerYcbcrRange getYcbcrRange(SamplerYcbcrRange range);
VkChromaLocation getChromaLocation(ChromaLocation loc); VkChromaLocation getChromaLocation(ChromaLocation loc);
// Ycbcr related functions
SamplerYcbcrModelConversion getYcbcrModelConversionFilament(VkSamplerYcbcrModelConversion model);
SamplerYcbcrRange getYcbcrRangeFilament(VkSamplerYcbcrRange range);
ChromaLocation getChromaLocationFilament(VkChromaLocation loc);
TextureSwizzle getSwizzleFilament(VkComponentSwizzle c, uint8_t rgbaIndex);
inline VkImageViewType getViewType(SamplerType target) { inline VkImageViewType getViewType(SamplerType target) {
switch (target) { switch (target) {
case SamplerType::SAMPLER_CUBEMAP: case SamplerType::SAMPLER_CUBEMAP:

View File

@@ -348,6 +348,10 @@ using SamplerBitmask = utils::bitset64;
// general. // general.
using InputAttachmentBitmask = utils::bitset64; using InputAttachmentBitmask = utils::bitset64;
constexpr uint8_t MAX_DESCRIPTOR_SET_BITMASK_BITS =
std::max(std::max(sizeof(UniformBufferBitmask), sizeof(SamplerBitmask)),
sizeof(InputAttachmentBitmask)) * 8;
template<typename Bitmask> template<typename Bitmask>
static constexpr uint8_t getVertexStageShift() noexcept { static constexpr uint8_t getVertexStageShift() noexcept {
// We assume the bottom half of bits are for vertex stages. // We assume the bottom half of bits are for vertex stages.

View File

@@ -193,49 +193,6 @@ uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask) {
return mostSignificantBit((sampleCount - 1) & mask); return mostSignificantBit((sampleCount - 1) & mask);
} }
VkSamplerYcbcrModelConversion getYcbcrModelConversion(
SamplerYcbcrModelConversion model) {
switch (model) {
case SamplerYcbcrModelConversion::RGB_IDENTITY:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY;
case SamplerYcbcrModelConversion::YCBCR_IDENTITY:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY;
case SamplerYcbcrModelConversion::YCBCR_709:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709;
case SamplerYcbcrModelConversion::YCBCR_601:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601;
case SamplerYcbcrModelConversion::YCBCR_2020:
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020;
default:
assert_invariant(false &&
"Unknown data type, conversion is not supported.");
}
}
VkSamplerYcbcrRange getYcbcrRange(SamplerYcbcrRange range) {
switch (range) {
case SamplerYcbcrRange::ITU_FULL:
return VK_SAMPLER_YCBCR_RANGE_ITU_FULL;
case SamplerYcbcrRange::ITU_NARROW:
return VK_SAMPLER_YCBCR_RANGE_ITU_NARROW;
default:
assert_invariant(false &&
"Unknown data type, conversion is not supported.");
}
}
VkChromaLocation getChromaLocation(ChromaLocation loc) {
switch (loc) {
case ChromaLocation::COSITED_EVEN:
return VK_CHROMA_LOCATION_COSITED_EVEN;
case ChromaLocation::MIDPOINT:
return VK_CHROMA_LOCATION_MIDPOINT;
default:
assert_invariant(false &&
"Unknown data type, conversion is not supported.");
}
}
} // namespace filament::backend::fvkutils } // namespace filament::backend::fvkutils
bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange& b) { bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange& b) {

View File

@@ -0,0 +1,150 @@
/*
* 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 "WebGPUHandles.h"
#include "WebGPUConstants.h"
#include "DriverBase.h"
#include <backend/DriverEnums.h>
#include <backend/Program.h>
#include <utils/Panic.h>
#include <utils/ostream.h>
#include <webgpu/webgpu_cpp.h>
#include <sstream>
#include <string_view>
#include <vector>
namespace filament::backend {
namespace {
[[nodiscard]] constexpr std::string_view toString(ShaderStage stage) {
switch (stage) {
case ShaderStage::VERTEX:
return "vertex";
case ShaderStage::FRAGMENT:
return "fragment";
case ShaderStage::COMPUTE:
return "compute";
}
}
[[nodiscard]] wgpu::ShaderModule createShaderModule(wgpu::Device& device, const char* programName,
std::array<utils::FixedCapacityVector<uint8_t>, Program::SHADER_TYPE_COUNT> const&
shaderSource,
ShaderStage stage) {
utils::FixedCapacityVector<uint8_t> const& sourceBytes =
shaderSource[static_cast<size_t>(stage)];
if (sourceBytes.empty()) {
return nullptr;// nothing to compile, the shader was not provided
}
wgpu::ShaderModuleWGSLDescriptor wgslDescriptor{};
wgslDescriptor.code = wgpu::StringView(reinterpret_cast<const char*>(sourceBytes.data()));
std::stringstream labelStream;
labelStream << programName << " " << toString(stage) << " shader";
auto label = labelStream.str();
wgpu::ShaderModuleDescriptor descriptor{
.nextInChain = &wgslDescriptor,
.label = label.data()
};
wgpu::ShaderModule module = device.CreateShaderModule(&descriptor);
FILAMENT_CHECK_POSTCONDITION(module != nullptr) << "Failed to create " << descriptor.label;
module.GetCompilationInfo(wgpu::CallbackMode::AllowSpontaneous,
[&descriptor](auto const& status, wgpu::CompilationInfo const* info) {
switch (status) {
case wgpu::CompilationInfoRequestStatus::CallbackCancelled:
FWGPU_LOGW << "Shader compilation info callback cancelled for "
<< descriptor.label << "?" << utils::io::endl;
return;
case wgpu::CompilationInfoRequestStatus::Success:
break;
}
if (info != nullptr) {
std::stringstream errorStream;
int errorCount = 0;
for (size_t msgIndex = 0; msgIndex < info->messageCount; msgIndex++) {
wgpu::CompilationMessage const& message = info->messages[msgIndex];
switch (message.type) {
case wgpu::CompilationMessageType::Info:
FWGPU_LOGI << descriptor.label << ": " << message.message
<< " line#:" << message.lineNum
<< " linePos:" << message.linePos
<< " offset:" << message.offset
<< " length:" << message.length << utils::io::endl;
break;
case wgpu::CompilationMessageType::Warning:
FWGPU_LOGW << "Warning compiling " << descriptor.label << ": "
<< message.message << " line#:" << message.lineNum
<< " linePos:" << message.linePos
<< " offset:" << message.offset
<< " length:" << message.length << utils::io::endl;
break;
case wgpu::CompilationMessageType::Error:
errorCount++;
errorStream << "Error " << errorCount << " : "
<< std::string_view(message.message)
<< " line#:" << message.lineNum
<< " linePos:" << message.linePos
<< " offset:" << message.offset
<< " length:" << message.length << "\n";
break;
}
}
FILAMENT_CHECK_POSTCONDITION(errorCount < 1)
<< errorCount << " error(s) compiling " << descriptor.label << ":\n"
<< errorStream.str();
}
FWGPU_LOGD << descriptor.label << " compiled successfully" << utils::io::endl;
});
return module;
}
std::vector<wgpu::ConstantEntry> convertConstants(
utils::FixedCapacityVector<filament::backend::Program::SpecializationConstant> const&
constantsInfo) {
std::vector<wgpu::ConstantEntry> constants(constantsInfo.size());
for (size_t i = 0; i < constantsInfo.size(); i++) {
filament::backend::Program::SpecializationConstant const& specConstant = constantsInfo[i];
wgpu::ConstantEntry& constantEntry = constants[i];
constantEntry.key = wgpu::StringView(std::to_string(specConstant.id));
if (auto* v = std::get_if<int32_t>(&specConstant.value)) {
constantEntry.value = static_cast<double>(*v);
} else if (auto* f = std::get_if<float>(&specConstant.value)) {
constantEntry.value = static_cast<double>(*f);
} else if (auto* b = std::get_if<bool>(&specConstant.value)) {
constantEntry.value = *b ? 0.0 : 1.0;
}
}
return constants;
}
}// namespace
WGPUProgram::WGPUProgram(wgpu::Device& device, Program& program)
: HwProgram(program.getName()),
vertexShaderModule(createShaderModule(device, name.c_str_safe(), program.getShadersSource(),
ShaderStage::VERTEX)),
fragmentShaderModule(createShaderModule(device, name.c_str_safe(), program.getShadersSource(),
ShaderStage::FRAGMENT)),
computeShaderModule(createShaderModule(device, name.c_str_safe(), program.getShadersSource(),
ShaderStage::COMPUTE)),
constants(convertConstants(program.getSpecializationConstants())) {}
}// namespace filament::backend

View File

@@ -16,13 +16,13 @@
#include "webgpu/WebGPUDriver.h" #include "webgpu/WebGPUDriver.h"
#include "WebGPUSwapChain.h"
#include "webgpu/WebGPUConstants.h" #include "webgpu/WebGPUConstants.h"
#include <backend/platforms/WebGPUPlatform.h> #include <backend/platforms/WebGPUPlatform.h>
#include "CommandStreamDispatcher.h" #include "CommandStreamDispatcher.h"
#include "DriverBase.h" #include "DriverBase.h"
#include "private/backend/Dispatcher.h" #include "private/backend/Dispatcher.h"
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include <backend/Handle.h> #include <backend/Handle.h>
@@ -228,6 +228,14 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfi
driverConfig.disableHeapHandleTags) { driverConfig.disableHeapHandleTags) {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) #if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printInstanceDetails(mPlatform.getInstance()); printInstanceDetails(mPlatform.getInstance());
#endif
mAdapter = mPlatform.requestAdapter(nullptr);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printAdapterDetails(mAdapter);
#endif
mDevice = mPlatform.requestDevice(mAdapter);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printDeviceDetails(mDevice);
#endif #endif
} }
@@ -262,9 +270,6 @@ void WebGPUDriver::tick(int) {
void WebGPUDriver::beginFrame(int64_t monotonic_clock_ns, void WebGPUDriver::beginFrame(int64_t monotonic_clock_ns,
int64_t refreshIntervalNs, uint32_t frameId) { int64_t refreshIntervalNs, uint32_t frameId) {
wgpu::CommandEncoderDescriptor commandEncoderDescriptor{};
commandEncoderDescriptor.nextInChain = nullptr;
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
} }
void WebGPUDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, void WebGPUDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
@@ -281,12 +286,6 @@ void WebGPUDriver::setPresentationTime(int64_t monotonic_clock_ns) {
} }
void WebGPUDriver::endFrame(uint32_t frameId) { void WebGPUDriver::endFrame(uint32_t frameId) {
// FWGPU_LOGW << __FUNCTION__<< "\n";
mQueue.Submit(1, &mCommandBuffer);
mCommandEncoder = nullptr;
mCommandBuffer = nullptr;
mTextureView = nullptr;
mSwapChain->Present();
} }
void WebGPUDriver::flush(int) { void WebGPUDriver::flush(int) {
@@ -296,15 +295,27 @@ void WebGPUDriver::finish(int) {
} }
void WebGPUDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) { void WebGPUDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
if (rph) {
destructHandle<WGPURenderPrimitive>(rph);
}
} }
void WebGPUDriver::destroyVertexBufferInfo(Handle<HwVertexBufferInfo> vbih) { void WebGPUDriver::destroyVertexBufferInfo(Handle<HwVertexBufferInfo> vbih) {
if (vbih) {
destructHandle<WGPUVertexBufferInfo>(vbih);
}
} }
void WebGPUDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vbh) { void WebGPUDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vbh) {
if (vbh) {
destructHandle<WGPUVertexBuffer>(vbh);
}
} }
void WebGPUDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) { void WebGPUDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
if (ibh) {
destructHandle<WGPUIndexBuffer>(ibh);
}
} }
void WebGPUDriver::destroyBufferObject(Handle<HwBufferObject> boh) { void WebGPUDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
@@ -314,19 +325,19 @@ void WebGPUDriver::destroyTexture(Handle<HwTexture> th) {
} }
void WebGPUDriver::destroyProgram(Handle<HwProgram> ph) { void WebGPUDriver::destroyProgram(Handle<HwProgram> ph) {
if (ph) {
destructHandle<WGPUProgram>(ph);
}
} }
void WebGPUDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) { void WebGPUDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
} }
void WebGPUDriver::destroySwapChain(Handle<HwSwapChain> sch) { void WebGPUDriver::destroySwapChain(Handle<HwSwapChain> sch) {
if (sch) {
destructHandle<WebGPUSwapChain>(sch);
}
mSwapChain = nullptr; mSwapChain = nullptr;
// TODO: use webgpu handle allocator from
// https://github.com/google/filament/pull/8566
// if (sch) {
// HwSwapChain* hwSwapChain = handleCast<HwSwapChain*>(sch);
// destruct(sch, hwSwapChain);
// }
} }
void WebGPUDriver::destroyStream(Handle<HwStream> sh) { void WebGPUDriver::destroyStream(Handle<HwStream> sh) {
@@ -336,16 +347,16 @@ void WebGPUDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
} }
void WebGPUDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> tqh) { void WebGPUDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> tqh) {
if (tqh) {
destructHandle<WebGPUDescriptorSetLayout>(tqh);
}
} }
void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> tqh) { void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> tqh) {
} }
Handle<HwSwapChain> WebGPUDriver::createSwapChainS() noexcept { Handle<HwSwapChain> WebGPUDriver::createSwapChainS() noexcept {
// TODO: use webgpu handle allocator from. return allocHandle<WebGPUSwapChain>();
// https://github.com/google/filament/pull/8566
// return allocAndConstructHandle<HwSwapChain>();
return Handle<HwSwapChain>((Handle<HwSwapChain>::HandleId) mNextFakeHandle++);
} }
Handle<HwSwapChain> WebGPUDriver::createSwapChainHeadlessS() noexcept { Handle<HwSwapChain> WebGPUDriver::createSwapChainHeadlessS() noexcept {
@@ -361,7 +372,7 @@ Handle<HwTexture> WebGPUDriver::importTextureS() noexcept {
} }
Handle<HwProgram> WebGPUDriver::createProgramS() noexcept { Handle<HwProgram> WebGPUDriver::createProgramS() noexcept {
return Handle<HwProgram>((Handle<HwProgram>::HandleId) mNextFakeHandle++); return allocHandle<WGPUProgram>();
} }
Handle<HwFence> WebGPUDriver::createFenceS() noexcept { Handle<HwFence> WebGPUDriver::createFenceS() noexcept {
@@ -377,7 +388,7 @@ Handle<HwIndexBuffer> WebGPUDriver::createIndexBufferS() noexcept {
} }
Handle<HwTexture> WebGPUDriver::createTextureViewS() noexcept { Handle<HwTexture> WebGPUDriver::createTextureViewS() noexcept {
return allocHandle<WGPUTexture>(); return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++);
} }
Handle<HwBufferObject> WebGPUDriver::createBufferObjectS() noexcept { Handle<HwBufferObject> WebGPUDriver::createBufferObjectS() noexcept {
@@ -397,7 +408,7 @@ Handle<HwDescriptorSet> WebGPUDriver::createDescriptorSetS() noexcept {
} }
Handle<HwRenderPrimitive> WebGPUDriver::createRenderPrimitiveS() noexcept { Handle<HwRenderPrimitive> WebGPUDriver::createRenderPrimitiveS() noexcept {
return Handle<HwRenderPrimitive>((Handle<HwRenderPrimitive>::HandleId) mNextFakeHandle++); return allocHandle<WGPURenderPrimitive>();
} }
Handle<HwVertexBufferInfo> WebGPUDriver::createVertexBufferInfoS() noexcept { Handle<HwVertexBufferInfo> WebGPUDriver::createVertexBufferInfoS() noexcept {
@@ -405,7 +416,7 @@ Handle<HwVertexBufferInfo> WebGPUDriver::createVertexBufferInfoS() noexcept {
} }
Handle<HwTexture> WebGPUDriver::createTextureViewSwizzleS() noexcept { Handle<HwTexture> WebGPUDriver::createTextureViewSwizzleS() noexcept {
return allocHandle<WGPUTexture>(); return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++);
} }
Handle<HwRenderTarget> WebGPUDriver::createDefaultRenderTargetS() noexcept { Handle<HwRenderTarget> WebGPUDriver::createDefaultRenderTargetS() noexcept {
@@ -413,8 +424,7 @@ Handle<HwRenderTarget> WebGPUDriver::createDefaultRenderTargetS() noexcept {
} }
Handle<HwDescriptorSetLayout> WebGPUDriver::createDescriptorSetLayoutS() noexcept { Handle<HwDescriptorSetLayout> WebGPUDriver::createDescriptorSetLayoutS() noexcept {
return Handle<HwDescriptorSetLayout>( return allocHandle<WebGPUDescriptorSetLayout>();
(Handle<HwDescriptorSetLayout>::HandleId) mNextFakeHandle++);
} }
Handle<HwTexture> WebGPUDriver::createTextureExternalImageS() noexcept { Handle<HwTexture> WebGPUDriver::createTextureExternalImageS() noexcept {
@@ -430,43 +440,15 @@ Handle<HwTexture> WebGPUDriver::createTextureExternalImagePlaneS() noexcept {
} }
void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) { void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
// TODO: use webgpu handle allocator from. mNativeWindow = nativeWindow;
// https://github.com/google/filament/pull/8566
// HwSwapChain* hwSwapChain = handleCast<HwSwapChain*>(sch);
assert_invariant(!mSwapChain); assert_invariant(!mSwapChain);
wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags); wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags);
mAdapter = mPlatform.requestAdapter(surface);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printAdapterDetails(mAdapter);
#endif
mDevice = mPlatform.requestDevice(mAdapter);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printDeviceDetails(mDevice);
#endif
mQueue = mDevice.GetQueue(); mQueue = mDevice.GetQueue();
mSwapChain = std::make_unique<WebGPUSwapChain>(std::move(surface), mAdapter, mDevice, flags); wgpu::Extent2D surfaceSize = mPlatform.getSurfaceExtent(mNativeWindow);
// TODO configure the surface (maybe before or after creating the swapchain? mSwapChain = constructHandle<WebGPUSwapChain>(sch, std::move(surface), surfaceSize, mAdapter,
// how do we get the surface extent?) mDevice, flags);
// TODO actually create the swapchain assert_invariant(mSwapChain);
// auto onQueueWorkDone = [](wgpu::QueueWorkDoneStatus status, void* /* pUserData */) {
// FWGPU_LOGW << "Queued work finished with status: " << status << std::endl;
// };
// mQueue.OnSubmittedWorkDone(wgpu::CallbackMode::WaitAnyOnly, [](wgpu::QueueWorkDoneStatus status, void* pUserdata) {
// FWGPU_LOGW << "Queued work finished with status:\n";
// }, nullptr /* pUserData */);
void* userDataPtr = nullptr;
mQueue.OnSubmittedWorkDone(
wgpu::CallbackMode::AllowProcessEvents,
[](wgpu::QueueWorkDoneStatus status, void* pUserData) {
FWGPU_LOGW << "Queued work finished with status: " << static_cast<int>(status) << "\n";
if (pUserData == nullptr) {
// Expected case
} else {
FWGPU_LOGW << "Unexpected non-null pUserData received.\n";
}
},
userDataPtr /* pUserData */
);
FWGPU_LOGW << "WebGPU support is still essentially a no-op at this point in development (only " FWGPU_LOGW << "WebGPU support is still essentially a no-op at this point in development (only "
"background components have been instantiated/selected, such as surface/screen, " "background components have been instantiated/selected, such as surface/screen, "
"graphics device/GPU, etc.), thus nothing is being drawn to the screen." "graphics device/GPU, etc.), thus nothing is being drawn to the screen."
@@ -479,9 +461,6 @@ void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
"rebuilding Filament with that flag, e.g. ./build.sh -x " "rebuilding Filament with that flag, e.g. ./build.sh -x "
<< FWGPU_PRINT_SYSTEM << " ..." << utils::io::endl; << FWGPU_PRINT_SYSTEM << " ..." << utils::io::endl;
#endif #endif
// TODO: use webgpu handle allocator from.
// https://github.com/google/filament/pull/8566
// hwSwapChain->swapChain = mSwapChain.get();
} }
void WebGPUDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width, void WebGPUDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width,
@@ -504,13 +483,7 @@ void WebGPUDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
TextureUsage usage) {} TextureUsage usage) {}
void WebGPUDriver::createTextureViewR(Handle<HwTexture> th, Handle<HwTexture> srch, void WebGPUDriver::createTextureViewR(Handle<HwTexture> th, Handle<HwTexture> srch,
uint8_t baseLevel, uint8_t levelCount) { uint8_t baseLevel, uint8_t levelCount) {}
// FWGPU_LOGW << __FUNCTION__<< "\n";
WGPUTexture const* src = handleCast<WGPUTexture>(srch);
(void) src;
// textures.insert(
// constructHandle<WGPUTexture>(th, src, baseLevel, levelCount));
}
void WebGPUDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwTexture> srch, void WebGPUDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwTexture> srch,
backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b, backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b,
@@ -535,7 +508,9 @@ void WebGPUDriver::importTextureR(Handle<HwTexture> th, intptr_t id, SamplerType
void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph, Handle<HwVertexBuffer> vbh, void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph, Handle<HwVertexBuffer> vbh,
Handle<HwIndexBuffer> ibh, PrimitiveType pt) {} Handle<HwIndexBuffer> ibh, PrimitiveType pt) {}
void WebGPUDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {} void WebGPUDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
constructHandle<WGPUProgram>(ph, mDevice, program);
}
void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) { void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
assert_invariant(!mDefaultRenderTarget); assert_invariant(!mDefaultRenderTarget);
@@ -552,7 +527,9 @@ void WebGPUDriver::createFenceR(Handle<HwFence> fh, int) {}
void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {} void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {}
void WebGPUDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh, void WebGPUDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
backend::DescriptorSetLayout&& info) {} backend::DescriptorSetLayout&& info) {
constructHandle<WebGPUDescriptorSetLayout>(dslh, std::move(info), mDevice);
}
void WebGPUDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh, void WebGPUDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
Handle<HwDescriptorSetLayout> dslh) {} Handle<HwDescriptorSetLayout> dslh) {}
@@ -704,9 +681,8 @@ void WebGPUDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t in
Handle<HwBufferObject> boh) { Handle<HwBufferObject> boh) {
auto* vertexBuffer = handleCast<WGPUVertexBuffer>(vbh); auto* vertexBuffer = handleCast<WGPUVertexBuffer>(vbh);
auto* bufferObject = handleCast<WGPUBufferObject>(boh); auto* bufferObject = handleCast<WGPUBufferObject>(boh);
assert_invariant(index < vertexBuffer->mBuffers.size()); assert_invariant(index < vertexBuffer->buffers.size());
vertexBuffer->setBuffer(bufferObject, index); vertexBuffer->setBuffer(bufferObject, index);
} }
void WebGPUDriver::update3DImage(Handle<HwTexture> th, void WebGPUDriver::update3DImage(Handle<HwTexture> th,
@@ -739,48 +715,71 @@ void WebGPUDriver::compilePrograms(CompilerPriorityQueue priority,
} }
void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassParams& params) { void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassParams& params) {
// FWGPU_LOGW << __FUNCTION__<< "\n"; wgpu::CommandEncoderDescriptor commandEncoderDescriptor = {
mTextureView = mSwapChain->GetNextSurfaceTextureView(params.viewport.width, params.viewport.height); .label = "command_encoder"
wgpu::RenderPassColorAttachment renderPassColorAttachment = {}; };
renderPassColorAttachment.view = mTextureView; mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
renderPassColorAttachment.resolveTarget = nullptr; assert_invariant(mCommandEncoder);
renderPassColorAttachment.loadOp = wgpu::LoadOp::Clear; // TODO: Remove this code once WebGPU pipeline is implemented
renderPassColorAttachment.storeOp = wgpu::StoreOp::Store; static float red = 1.0f;
renderPassColorAttachment.clearValue = wgpu::Color{1, 0 , 0 , 1}; if (red - 0.01 > 0) {
renderPassColorAttachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; red -= 0.01;
} else {
red = 1.0f;
}
assert_invariant(mTextureView);
wgpu::RenderPassColorAttachment renderPassColorAttachment = {
.view = mTextureView,
// TODO: remove this code once WebGPU Pipeline is implemented with render targets, pipeline and buffers.
.depthSlice = wgpu::kDepthSliceUndefined,
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store,
.clearValue = wgpu::Color{red, 0 , 0 , 1},
};
wgpu::RenderPassDescriptor renderPassDescriptor = {}; wgpu::RenderPassDescriptor renderPassDescriptor = {
renderPassDescriptor.nextInChain = nullptr; .colorAttachmentCount = 1,
renderPassDescriptor.colorAttachmentCount = 1; .colorAttachments = &renderPassColorAttachment,
renderPassDescriptor.colorAttachments = &renderPassColorAttachment; .depthStencilAttachment = nullptr,
renderPassDescriptor.depthStencilAttachment = nullptr; .timestampWrites = nullptr,
renderPassDescriptor.timestampWrites = nullptr; };
mRenderPassEncoder = mCommandEncoder.BeginRenderPass(&renderPassDescriptor); mRenderPassEncoder = mCommandEncoder.BeginRenderPass(&renderPassDescriptor);
mRenderPassEncoder.SetViewport((float)params.viewport.left, (float)params.viewport.bottom, mRenderPassEncoder.SetViewport(params.viewport.left, params.viewport.bottom,
(float) params.viewport.width, (float) params.viewport.height, params.depthRange.near, params.depthRange.far); params.viewport.width, params.viewport.height, params.depthRange.near, params.depthRange.far);
// (float) 1024/*params.viewport.width*/, (float) 640/*params.viewport.height*/, params.depthRange.near, params.depthRange.far);
} }
void WebGPUDriver::endRenderPass(int) { void WebGPUDriver::endRenderPass(int) {
// FWGPU_LOGW << __FUNCTION__<< "\n";
mRenderPassEncoder.End(); mRenderPassEncoder.End();
mRenderPassEncoder = nullptr; mRenderPassEncoder = nullptr;
wgpu::CommandBufferDescriptor commandBufferDescriptor {
.label = "command_buffer",
};
mCommandBuffer = mCommandEncoder.Finish(&commandBufferDescriptor);
assert_invariant(mCommandBuffer);
} }
void WebGPUDriver::nextSubpass(int) { void WebGPUDriver::nextSubpass(int) {
} }
void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) { void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {
ASSERT_PRECONDITION_NON_FATAL(drawSch == readSch,
"WebGPU driver does not support distinct draw/read swap chains.");
auto* swapChain = handleCast<WebGPUSwapChain>(drawSch);
mSwapChain = swapChain;
assert_invariant(mSwapChain);
wgpu::Extent2D surfaceSize = mPlatform.getSurfaceExtent(mNativeWindow);
mTextureView = mSwapChain->getCurrentSurfaceTextureView(surfaceSize);
assert_invariant(mTextureView);
} }
void WebGPUDriver::commit(Handle<HwSwapChain> sch) { void WebGPUDriver::commit(Handle<HwSwapChain> sch) {
wgpu::CommandBufferDescriptor commandBufferDescriptor = {}; mCommandEncoder = nullptr;
commandBufferDescriptor.nextInChain = nullptr; mQueue.Submit(1, &mCommandBuffer);
mCommandBuffer = mCommandEncoder.Finish(&commandBufferDescriptor); mCommandBuffer = nullptr;
mTextureView = nullptr;
// mQueue.Submit(1, &mCommandBuffer); assert_invariant(mSwapChain);
// mCommandBuffer = nullptr; mSwapChain->present();
} }
void WebGPUDriver::setPushConstant(backend::ShaderStage stage, uint8_t index, void WebGPUDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
@@ -808,7 +807,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> src,
scheduleDestroy(std::move(p)); scheduleDestroy(std::move(p));
} }
void WebGPUDriver::readBufferSubData(backend::BufferObjectHandle boh, void WebGPUDriver::readBufferSubData(Handle<HwBufferObject> boh,
uint32_t offset, uint32_t size, backend::BufferDescriptor&& p) { uint32_t offset, uint32_t size, backend::BufferDescriptor&& p) {
scheduleDestroy(std::move(p)); scheduleDestroy(std::move(p));
} }
@@ -837,8 +836,6 @@ void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
} }
void WebGPUDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) { void WebGPUDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
// mRenderPassEncoder.DrawIndexed(indexCount, instanceCount, indexOffset, 0, 0);
} }
void WebGPUDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph, void WebGPUDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph,
@@ -862,22 +859,22 @@ void WebGPUDriver::resetState(int) {
} }
void WebGPUDriver::updateDescriptorSetBuffer( void WebGPUDriver::updateDescriptorSetBuffer(
backend::DescriptorSetHandle dsh, Handle<HwDescriptorSet> dsh,
backend::descriptor_binding_t binding, backend::descriptor_binding_t binding,
backend::BufferObjectHandle boh, Handle<HwBufferObject> boh,
uint32_t offset, uint32_t offset,
uint32_t size) { uint32_t size) {
} }
void WebGPUDriver::updateDescriptorSetTexture( void WebGPUDriver::updateDescriptorSetTexture(
backend::DescriptorSetHandle dsh, Handle<HwDescriptorSet> dsh,
backend::descriptor_binding_t binding, backend::descriptor_binding_t binding,
backend::TextureHandle th, Handle<HwTexture> th,
SamplerParams params) { SamplerParams params) {
} }
void WebGPUDriver::bindDescriptorSet( void WebGPUDriver::bindDescriptorSet(
backend::DescriptorSetHandle dsh, Handle<HwDescriptorSet> dsh,
backend::descriptor_set_t set, backend::descriptor_set_t set,
backend::DescriptorSetOffsetArray&& offsets) { backend::DescriptorSetOffsetArray&& offsets) {
} }

View File

@@ -17,7 +17,7 @@
#ifndef TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H #ifndef TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
#define TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H #define TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
#include "webgpu/WebGPUSwapChain.h" #include "WebGPUHandles.h"
#include <backend/platforms/WebGPUPlatform.h> #include <backend/platforms/WebGPUPlatform.h>
#include "DriverBase.h" #include "DriverBase.h"
@@ -30,8 +30,6 @@
#include <webgpu/webgpu_cpp.h> #include <webgpu/webgpu_cpp.h>
#include "WebGPUHandles.h"
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
@@ -41,6 +39,8 @@
namespace filament::backend { namespace filament::backend {
class WebGPUSwapChain;
/** /**
* WebGPU backend (driver) implementation * WebGPU backend (driver) implementation
*/ */
@@ -62,8 +62,8 @@ private:
wgpu::Adapter mAdapter = nullptr; wgpu::Adapter mAdapter = nullptr;
wgpu::Device mDevice = nullptr; wgpu::Device mDevice = nullptr;
wgpu::Queue mQueue = nullptr; wgpu::Queue mQueue = nullptr;
// TODO consider moving to handle allocator when ready void* mNativeWindow = nullptr;
std::unique_ptr<WebGPUSwapChain> mSwapChain = nullptr; WebGPUSwapChain* mSwapChain = nullptr;
uint64_t mNextFakeHandle = 1; uint64_t mNextFakeHandle = 1;
wgpu::CommandEncoder mCommandEncoder = nullptr; wgpu::CommandEncoder mCommandEncoder = nullptr;
wgpu::TextureView mTextureView = nullptr; wgpu::TextureView mTextureView = nullptr;
@@ -103,11 +103,17 @@ private:
D* constructHandle(Handle<B>& handle, ARGS&& ... args) noexcept { D* constructHandle(Handle<B>& handle, ARGS&& ... args) noexcept {
return mHandleAllocator.construct<D>(handle, std::forward<ARGS>(args)...); return mHandleAllocator.construct<D>(handle, std::forward<ARGS>(args)...);
} }
template<typename D, typename B> template<typename D, typename B>
D* handleCast(Handle<B> handle) noexcept { D* handleCast(Handle<B> handle) noexcept {
return mHandleAllocator.handle_cast<D*>(handle); return mHandleAllocator.handle_cast<D*>(handle);
} }
template<typename D, typename B>
void destructHandle(Handle<B>& handle) noexcept {
auto* p = mHandleAllocator.handle_cast<D*>(handle);
return mHandleAllocator.deallocate(handle, p);
}
}; };
}// namespace filament::backend }// namespace filament::backend

View File

@@ -1,19 +1,152 @@
// /*
// Created by Idris Idris Shah on 3/21/25. * 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 "WebGPUHandles.h" #include "WebGPUHandles.h"
#include <utility>
namespace {
wgpu::Buffer createIndexBuffer(wgpu::Device const& device, uint8_t elementSize, uint32_t indexCount) {
wgpu::BufferDescriptor descriptor{ .label = "index_buffer",
.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Index,
.size = elementSize * indexCount,
.mappedAtCreation = false };
return device.CreateBuffer(&descriptor);
}
} // namespace
namespace filament::backend { namespace filament::backend {
WGPUVertexBuffer::WGPUVertexBuffer(uint32_t vextexCount, uint32_t bufferCount,
Handle<WGPUVertexBufferInfo> vbih) WGPUIndexBuffer::WGPUIndexBuffer(wgpu::Device const& device, uint8_t elementSize,
: HwVertexBuffer(vextexCount), uint32_t indexCount)
vbih(vbih), : buffer(createIndexBuffer(device, elementSize, indexCount)) {}
mBuffers(MAX_VERTEX_BUFFER_COUNT) {}
WGPUVertexBuffer::WGPUVertexBuffer(wgpu::Device const &device, uint32_t vextexCount, uint32_t bufferCount,
Handle<WGPUVertexBufferInfo> vbih)
: HwVertexBuffer(vextexCount),
vbih(vbih),
buffers(bufferCount) {
wgpu::BufferDescriptor descriptor {
.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Vertex,
.size = vextexCount * bufferCount,
.mappedAtCreation = false };
for (uint32_t i = 0; i < bufferCount; ++i) {
descriptor.label = ("vertex_buffer_" + std::to_string(i)).c_str();
buffers[i] = device.CreateBuffer(&descriptor);
}
}
// TODO: Empty function is a place holder for verxtex buffer updates and should be
// updated for that purpose.
void WGPUVertexBuffer::setBuffer(WGPUBufferObject* bufferObject, uint32_t index) {} void WGPUVertexBuffer::setBuffer(WGPUBufferObject* bufferObject, uint32_t index) {}
WGPUBufferObject::WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount) WGPUBufferObject::WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount)
: HwBufferObject(byteCount), : HwBufferObject(byteCount),
mBindingType(bindingType) {} bufferObjectBinding(bindingType) {}
wgpu::ShaderStage WebGPUDescriptorSetLayout::filamentStageToWGPUStage(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;
}
WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout,
wgpu::Device const& device) {
assert_invariant(device);
// 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;
uint samplerCount =
std::count_if(layout.bindings.begin(), layout.bindings.end(), [](auto& fEntry) {
return fEntry.type == DescriptorType::SAMPLER ||
fEntry.type == DescriptorType::SAMPLER_EXTERNAL;
});
std::vector<wgpu::BindGroupLayoutEntry> wEntries;
wEntries.reserve(layout.bindings.size() + samplerCount);
for (auto fEntry: layout.bindings) {
auto& wEntry = wEntries.emplace_back();
wEntry.visibility = filamentStageToWGPUStage(fEntry.stageFlags);
wEntry.binding = fEntry.binding * 2;
switch (fEntry.type) {
// TODO Metal treats these the same. Is this fine?
case DescriptorType::SAMPLER_EXTERNAL:
case DescriptorType::SAMPLER: {
// Sampler binding is 2n+1 due to split.
auto& samplerEntry = wEntries.emplace_back();
samplerEntry.binding = fEntry.binding * 2 + 1;
samplerEntry.visibility = wEntry.visibility;
// We are simply hoping that undefined and defaults suffices here.
samplerEntry.sampler.type = wgpu::SamplerBindingType::Undefined;
wEntry.texture.sampleType = wgpu::TextureSampleType::Undefined;
break;
}
case DescriptorType::UNIFORM_BUFFER: {
wEntry.buffer.hasDynamicOffset =
any(fEntry.flags & DescriptorFlags::DYNAMIC_OFFSET);
wEntry.buffer.type = wgpu::BufferBindingType::Uniform;
// TODO: Ideally we fill minBindingSize
break;
}
case DescriptorType::INPUT_ATTACHMENT: {
// TODO: support INPUT_ATTACHMENT. Metal does not currently.
PANIC_POSTCONDITION("Input Attachment is not supported");
break;
}
case DescriptorType::SHADER_STORAGE_BUFFER: {
// TODO: Vulkan does not support this, can we?
PANIC_POSTCONDITION("Shader storage is not supported");
break;
}
}
// Currently flags are only used to specify dynamic offset.
// UNUSED
// fEntry.count
}
wgpu::BindGroupLayoutDescriptor layoutDescriptor{
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
// debugging. For now, hack an incrementing value.
.label{ "layout_" + std::to_string(++layoutNum) },
.entryCount = wEntries.size(),
.entries = wEntries.data()
};
// TODO Do we need to defer this until we have more info on textures and samplers??
mLayout = device.CreateBindGroupLayout(&layoutDescriptor);
}
WebGPUDescriptorSetLayout::~WebGPUDescriptorSetLayout() {}
}// namespace filament::backend }// namespace filament::backend

View File

@@ -1,6 +1,19 @@
// /*
// Created by Idris Idris Shah on 3/21/25. * Copyright (C) 2025 The Android Open Source Project
// *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H #ifndef TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
#define TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H #define TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
@@ -9,13 +22,29 @@
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include <backend/Handle.h> #include <backend/Handle.h>
#include <utils/FixedCapacityVector.h> #include <utils/FixedCapacityVector.h>
#include <webgpu/webgpu_cpp.h> #include <webgpu/webgpu_cpp.h>
#include <cstdint>
#include <vector>
namespace filament::backend { namespace filament::backend {
class WGPUProgram final : public HwProgram {
public:
WGPUProgram(wgpu::Device&, Program&);
wgpu::ShaderModule vertexShaderModule = nullptr;
wgpu::ShaderModule fragmentShaderModule = nullptr;
wgpu::ShaderModule computeShaderModule = nullptr;
std::vector<wgpu::ConstantEntry> constants;
};
struct WGPUBufferObject; struct WGPUBufferObject;
// TODO: Currently WGPUVertexBufferInfo is not used by WebGPU for useful task.
// Update the struct when used by WebGPU driver.
struct WGPUVertexBufferInfo : public HwVertexBufferInfo { struct WGPUVertexBufferInfo : public HwVertexBufferInfo {
WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount, WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
AttributeArray const& attributes) AttributeArray const& attributes)
@@ -25,28 +54,46 @@ struct WGPUVertexBufferInfo : public HwVertexBufferInfo {
}; };
struct WGPUVertexBuffer : public HwVertexBuffer { struct WGPUVertexBuffer : public HwVertexBuffer {
WGPUVertexBuffer(uint32_t vextexCount, uint32_t bufferCount, Handle<WGPUVertexBufferInfo> vbih); WGPUVertexBuffer(wgpu::Device const &device, uint32_t vextexCount, uint32_t bufferCount,
void setBuffer(WGPUBufferObject* bufferObject, uint32_t index); Handle<WGPUVertexBufferInfo> vbih);
void setBuffer(WGPUBufferObject *bufferObject, uint32_t index);
Handle<WGPUVertexBufferInfo> vbih; Handle<WGPUVertexBufferInfo> vbih;
utils::FixedCapacityVector<wgpu::Buffer> mBuffers; utils::FixedCapacityVector<wgpu::Buffer> buffers;
}; };
struct WGPUIndexBuffer : public HwIndexBuffer { struct WGPUIndexBuffer : public HwIndexBuffer {
WGPUIndexBuffer(BufferUsage usage, uint8_t elementSize, uint32_t indexCount); WGPUIndexBuffer(wgpu::Device const &device, uint8_t elementSize,
uint32_t indexCount);
wgpu::Buffer buffer; wgpu::Buffer buffer;
}; };
// TODO: Currently WGPUVertexBufferInfo is not used by WebGPU for useful task.
// Update the struct when used by WebGPU driver.
struct WGPUBufferObject : HwBufferObject { struct WGPUBufferObject : HwBufferObject {
WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount); WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount);
wgpu::Buffer mBuffer; wgpu::Buffer buffer;
const BufferObjectBinding mBindingType; const BufferObjectBinding bufferObjectBinding;
};
class WebGPUDescriptorSetLayout : public HwDescriptorSetLayout {
public:
WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout, wgpu::Device const& device);
~WebGPUDescriptorSetLayout();
private:
// TODO: If this is useful elsewhere, remove it from this class
// Convert Filament Shader Stage Flags bitmask to webgpu equivilant
static wgpu::ShaderStage filamentStageToWGPUStage(ShaderStageFlags fFlags);
wgpu::BindGroupLayout mLayout;
}; };
class WGPUTexture : public HwTexture { // TODO: Currently WGPUTexture is not used by WebGPU for useful task.
public: // Update the struct when used by WebGPU driver.
struct WGPUTexture : public HwTexture {
WGPUTexture(SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, WGPUTexture(SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples,
uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage) noexcept; uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage) noexcept;
@@ -56,11 +103,22 @@ public:
wgpu::Texture texture = nullptr; wgpu::Texture texture = nullptr;
}; };
class WGPURenderTarget : public HwRenderTarget { struct WGPURenderPrimitive : public HwRenderPrimitive {
public: WGPURenderPrimitive();
void setBuffers(WGPUVertexBufferInfo const* const vbi,
WGPUVertexBuffer* vertexBuffer, WGPUIndexBuffer* indexBuffer);
WGPUVertexBuffer* vertexBuffer = nullptr;
WGPUIndexBuffer* indexBuffer = nullptr;
};
// TODO: Currently WGPURenderTarget is not used by WebGPU for useful task.
// Update the struct when used by WebGPU driver.
struct WGPURenderTarget : public HwRenderTarget {
class Attachment { class Attachment {
public: public:
friend class WGPURenderTarget; friend struct WGPURenderTarget;
Attachment() = default; Attachment() = default;
Attachment(WGPUTexture* gpuTexture, uint8_t level = 0, uint16_t layer = 0) Attachment(WGPUTexture* gpuTexture, uint8_t level = 0, uint16_t layer = 0)

View File

@@ -190,10 +190,13 @@ wgpu::CompositeAlphaMode selectAlphaMode(size_t availableAlphaModesCount,
} }
} }
void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device& device, void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
wgpu::SurfaceCapabilities const& capabilities, bool useSRGBColorSpace) { wgpu::SurfaceCapabilities const& capabilities, wgpu::Extent2D const& surfaceSize,
bool useSRGBColorSpace) {
config.device = device; config.device = device;
config.usage = wgpu::TextureUsage::RenderAttachment; config.usage = wgpu::TextureUsage::RenderAttachment;
config.width = surfaceSize.width;
config.height = surfaceSize.height;
config.format = config.format =
selectColorFormat(capabilities.formatCount, capabilities.formats, useSRGBColorSpace); selectColorFormat(capabilities.formatCount, capabilities.formats, useSRGBColorSpace);
config.presentMode = config.presentMode =
@@ -205,8 +208,8 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device& device,
namespace filament::backend { namespace filament::backend {
WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Adapter& adapter, WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize,
wgpu::Device& device, uint64_t flags) wgpu::Adapter& adapter, wgpu::Device& device, uint64_t flags)
: mSurface(surface) { : mSurface(surface) {
wgpu::SurfaceCapabilities capabilities = {}; wgpu::SurfaceCapabilities capabilities = {};
if (!mSurface.GetCapabilities(adapter, &capabilities)) { if (!mSurface.GetCapabilities(adapter, &capabilities)) {
@@ -217,59 +220,55 @@ WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Adapter& adapter
#endif #endif
} }
const bool useSRGBColorSpace = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0; const bool useSRGBColorSpace = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0;
initConfig(mConfig, device, capabilities, useSRGBColorSpace); initConfig(mConfig, device, capabilities, surfaceSize, useSRGBColorSpace);
mSurface.Configure(&mConfig);
} }
WebGPUSwapChain::~WebGPUSwapChain() { WebGPUSwapChain::~WebGPUSwapChain() {
if (mConfigured) { mSurface.Unconfigure();
mSurface.Unconfigure();
mConfigured = false;
}
} }
void WebGPUSwapChain::GetCurrentTexture(uint32_t width, uint32_t height, wgpu::SurfaceTexture* texture) { void WebGPUSwapChain::setExtent(wgpu::Extent2D const& currentSurfaceSize) {
if (width < 1 || height < 1) { FILAMENT_CHECK_POSTCONDITION(currentSurfaceSize.width > 0 || currentSurfaceSize.height > 0)
PANIC_LOG("WebGPUSwapChain::GetCurrentTexture: Invalid width and/or height requested."); << "WebGPUSwapChain::setExtent: Invalid width " << currentSurfaceSize.width
return; << " and/or height " << currentSurfaceSize.height << " requested.";
} if (mConfig.width != currentSurfaceSize.width || mConfig.height != currentSurfaceSize.height) {
if (mConfig.width != width || mConfig.height != height || !mConfigured) { mConfig.width = currentSurfaceSize.width;
mConfig.width = width; mConfig.height = currentSurfaceSize.height;
mConfig.height = height;
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) #if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printSurfaceConfiguration(mConfig); printSurfaceConfiguration(mConfig);
#endif #endif
FWGPU_LOGD << "Resizing to width " << mConfig.width << " height " << mConfig.height
<< utils::io::endl;
// TODO we may need to ensure no surface texture is flight when we do this. some
// synchronization may be necessary
mSurface.Configure(&mConfig); mSurface.Configure(&mConfig);
mConfigured = true;
return;
} }
mSurface.GetCurrentTexture(texture);
} }
wgpu::TextureView WebGPUSwapChain::GetNextSurfaceTextureView(uint32_t width, uint32_t height) { wgpu::TextureView WebGPUSwapChain::getCurrentSurfaceTextureView(
wgpu::Extent2D const& currentSurfaceSize) {
setExtent(currentSurfaceSize);
wgpu::SurfaceTexture surfaceTexture; wgpu::SurfaceTexture surfaceTexture;
GetCurrentTexture(width, height, &surfaceTexture); mSurface.GetCurrentTexture(&surfaceTexture);
if (surfaceTexture.status != wgpu::SurfaceGetCurrentTextureStatus::Success) { if (surfaceTexture.status != wgpu::SurfaceGetCurrentTextureStatus::SuccessOptimal) {
return nullptr; return nullptr;
} }
// Create a view for this surface texture // Create a view for this surface texture
wgpu::TextureViewDescriptor textureViewDescriptor; // TODO: review these initiliazations as webgpu pipeline gets mature
textureViewDescriptor.nextInChain = nullptr; wgpu::TextureViewDescriptor textureViewDescriptor = {
textureViewDescriptor.label = "Surface texture view"; .label = "texture_view",
textureViewDescriptor.format = surfaceTexture.texture.GetFormat(); .format = surfaceTexture.texture.GetFormat(),
textureViewDescriptor.dimension = wgpu::TextureViewDimension::e2D; .dimension = wgpu::TextureViewDimension::e2D,
textureViewDescriptor.baseMipLevel = 0; .baseMipLevel = 0,
textureViewDescriptor.mipLevelCount = 1; .mipLevelCount = 1,
textureViewDescriptor.baseArrayLayer = 0; .baseArrayLayer = 0,
textureViewDescriptor.arrayLayerCount = 1; .arrayLayerCount = 1
textureViewDescriptor.aspect = wgpu::TextureAspect::All; };
wgpu::TextureView textureView = surfaceTexture.texture.CreateView(&textureViewDescriptor); return surfaceTexture.texture.CreateView(&textureViewDescriptor);
return textureView;
} }
void WebGPUSwapChain::Present() { void WebGPUSwapChain::present() {
assert_invariant(mSurface); assert_invariant(mSurface);
mSurface.Present(); mSurface.Present();
} }

View File

@@ -19,26 +19,28 @@
#include <webgpu/webgpu_cpp.h> #include <webgpu/webgpu_cpp.h>
#include "DriverBase.h"
#include <backend/Platform.h> #include <backend/Platform.h>
#include <cstdint> #include <cstdint>
namespace filament::backend { namespace filament::backend {
class WebGPUSwapChain : public Platform::SwapChain { class WebGPUSwapChain final : public Platform::SwapChain, HwSwapChain {
public: public:
WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Adapter& adapter, wgpu::Device& device, WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize,
uint64_t flags); wgpu::Adapter& adapter, wgpu::Device& device, uint64_t flags);
~WebGPUSwapChain(); ~WebGPUSwapChain();
void GetCurrentTexture(uint32_t width, uint32_t height, wgpu::SurfaceTexture*); wgpu::TextureView getCurrentSurfaceTextureView(wgpu::Extent2D const&);
wgpu::TextureView GetNextSurfaceTextureView(uint32_t width, uint32_t height);
void Present(); void present();
private: private:
void setExtent(wgpu::Extent2D const&);
wgpu::Surface mSurface = {}; wgpu::Surface mSurface = {};
wgpu::SurfaceConfiguration mConfig = {}; wgpu::SurfaceConfiguration mConfig = {};
bool mConfigured = false;
}; };
} // namespace filament::backend } // namespace filament::backend

View File

@@ -18,6 +18,7 @@
#include <utils/Panic.h> #include <utils/Panic.h>
#include <android/native_window.h>
#include <webgpu/webgpu_cpp.h> #include <webgpu/webgpu_cpp.h>
#include <cstdint> #include <cstdint>
@@ -28,6 +29,14 @@
namespace filament::backend { namespace filament::backend {
wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
ANativeWindow* window = static_cast<ANativeWindow*>(nativeWindow);
return wgpu::Extent2D{
.width = static_cast<uint32_t>(ANativeWindow_getWidth(window)),
.height = static_cast<uint32_t>(ANativeWindow_getHeight(window))
};
}
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) { wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) {
wgpu::SurfaceSourceAndroidNativeWindow surfaceSourceAndroidWindow{}; wgpu::SurfaceSourceAndroidNativeWindow surfaceSourceAndroidWindow{};
surfaceSourceAndroidWindow.window = nativeWindow; surfaceSourceAndroidWindow.window = nativeWindow;

View File

@@ -24,21 +24,8 @@
#include <cstdint> #include <cstdint>
// Platform specific includes and defines // Platform specific includes and defines
#if defined(__APPLE__) #include <Cocoa/Cocoa.h>
#include <Cocoa/Cocoa.h> #import <QuartzCore/CAMetalLayer.h>
#import <QuartzCore/CAMetalLayer.h>
#elif defined(FILAMENT_IOS)
// Metal is not available when building for the iOS simulator on Desktop.
#define METAL_AVAILABLE __has_include(<QuartzCore/CAMetalLayer.h>)
#if METAL_AVAILABLE
#import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h>
#endif
// is this needed?
#define METALVIEW_TAG 255
#else
#error Not a supported Apple + WebGPU platform
#endif
/** /**
* Apple (Mac OS and IOS) specific implementation aspects of the WebGPU backend * Apple (Mac OS and IOS) specific implementation aspects of the WebGPU backend
@@ -46,14 +33,19 @@
namespace filament::backend { namespace filament::backend {
wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
// Both IOS and MacOS expects CAMetalLayer.
CAMetalLayer* metalLayer = (__bridge CAMetalLayer*) nativeWindow;
return wgpu::Extent2D{
.width = static_cast<uint32_t>(metalLayer.drawableSize.width),
.height = static_cast<uint32_t>(metalLayer.drawableSize.height)
};
}
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) { wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) {
wgpu::Surface surface = nullptr; wgpu::Surface surface = nullptr;
#if defined(__APPLE__) // Both IOS and MacOS expects CAMetalLayer.
auto nsView = (__bridge NSView*) nativeWindow; CAMetalLayer* metalLayer = (__bridge CAMetalLayer*) nativeWindow;
FILAMENT_CHECK_POSTCONDITION(nsView) << "Unable to obtain Metal-backed NSView.";
[nsView setWantsLayer:YES];
id metalLayer = [CAMetalLayer layer];
[nsView setLayer:metalLayer];
wgpu::SurfaceSourceMetalLayer surfaceSourceMetalLayer{}; wgpu::SurfaceSourceMetalLayer surfaceSourceMetalLayer{};
surfaceSourceMetalLayer.layer = (__bridge void*) metalLayer; surfaceSourceMetalLayer.layer = (__bridge void*) metalLayer;
wgpu::SurfaceDescriptor surfaceDescriptor = { wgpu::SurfaceDescriptor surfaceDescriptor = {
@@ -62,19 +54,6 @@ wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags
}; };
surface = mInstance.CreateSurface(&surfaceDescriptor); surface = mInstance.CreateSurface(&surfaceDescriptor);
FILAMENT_CHECK_POSTCONDITION(surface != nullptr) << "Unable to create Metal-backed surface."; FILAMENT_CHECK_POSTCONDITION(surface != nullptr) << "Unable to create Metal-backed surface.";
#elif defined(FILAMENT_IOS)
CAMetalLayer* metalLayer = (CAMetalLayer*) nativeWindow;
wgpu::SurfaceSourceMetalLayer surfaceSourceMetalLayer{};
surfaceSourceMetalLayer.layer = (__bridge void*) metalLayer;
wgpu::SurfaceDescriptor surfaceDescriptor = {
.nextInChain = &surfaceSourceMetalLayer,
.label = "metal_surface",
};
surface = mInstance.CreateSurface(&surfaceDescriptor);
FILAMENT_CHECK_POSTCONDITION(surface != nullptr) << "Unable to create Metal-backed surface.";
#else
#error Not a supported Apple + WebGPU platform
#endif
return surface; return surface;
} }

View File

@@ -79,6 +79,74 @@
namespace filament::backend { namespace filament::backend {
wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
auto surfaceExtent = wgpu::Extent2D{};
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
wl* ptrval = reinterpret_cast<wl*>(nativeWindow);
surfaceExtent.width = ptrval->width;
surfaceExtent.height = ptrval->height;
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Unable to get window size for Linux Wayland-backed surface.";
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11)
if (g_x11.library == nullptr) {
g_x11.library = dlopen(LIBRARY_X11, RTLD_LOCAL | RTLD_NOW);
FILAMENT_CHECK_PRECONDITION(g_x11.library) << "Unable to open X11 library.";
#if defined(FILAMENT_SUPPORTS_XCB)
g_x11.xcbConnect = (XCB_CONNECT) dlsym(g_x11.library, "xcb_connect");
int screen = 0;
g_x11.connection = g_x11.xcbConnect(nullptr, &screen);
#endif
#if defined(FILAMENT_SUPPORTS_XLIB)
g_x11.openDisplay = (X11_OPEN_DISPLAY) dlsym(g_x11.library, "XOpenDisplay");
g_x11.display = g_x11.openDisplay(NULL);
FILAMENT_CHECK_PRECONDITION(g_x11.display) << "Unable to open X11 display.";
#endif
}
#if defined(FILAMENT_SUPPORTS_XCB) || defined(FILAMENT_SUPPORTS_XLIB)
bool useXcb = false;
#endif
#if defined(FILAMENT_SUPPORTS_XCB)
#if defined(FILAMENT_SUPPORTS_XLIB)
useXcb = (SWAP_CHAIN_CONFIG_ENABLE_XCB) != 0;
#else
useXcb = true;
#endif
if (useXcb) {
const xcb_setup_t* setup = xcb_get_setup(g_x11.connection);
xcb_screen_iterator_t screen_iter = xcb_setup_roots_iterator(setup);
xcb_screen_t* screen = screen_iter.data;
surfaceExtent.width = static_cast<uint32_t>(screen->width_in_pixels);
surfaceExtent.height = static_cast<uint32_t>(screen->height_in_pixels);
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Unable to get window surface size for Linux (or FreeBSD) "
"XCB-backed surface.";
}
#endif
#if defined(FILAMENT_SUPPORTS_XLIB)
if (!useXcb) {
int screenNumber = DefaultScreen(g_x11.display);
Screen* screen = ScreenOfDisplay(g_x11.display, screenNumber);
surfaceExtent.width = static_cast<uint32_t>(WidthOfScreen(screen));
surfaceExtent.height = static_cast<uint32_t>(HeightOfScreen(screen));
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Unable to get window surface size for Linux (or FreeBSD) "
"XLib-backed surface.";
}
#endif
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Cannot get window surface size for X11 surface for Linux (or FreeBSD) OS "
"(not built with support for XCB or XLIB?)";
#elif defined(__linux__)
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Cannot get window surface size for Linux (or FreeBSD) OS "
"(not built with support for Wayland or X11?)";
#else
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
<< "Not a supported (Linux) OS + WebGPU platform";
#endif
return surfaceExtent;
}
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t flags) { wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t flags) {
wgpu::Surface surface = nullptr; wgpu::Surface surface = nullptr;
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND) #if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)

View File

@@ -30,6 +30,16 @@
namespace filament::backend { namespace filament::backend {
wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
HWND window = static_cast<HWND>(nativeWindow);
RECT windowRect;
GetWindowRect(window, &windowRect);
return wgpu::Extent2D{
.width = static_cast<uint32_t>(windowRect.right - windowRect.left),
.height = static_cast<uint32_t>(windowRect.bottom - windowRect.top)
};
}
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) { wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) {
// TODO verify this is necessary for Dawn implementation as well: // TODO verify this is necessary for Dawn implementation as well:
// On (at least) NVIDIA drivers, the Vulkan implementation (specifically the call to // On (at least) NVIDIA drivers, the Vulkan implementation (specifically the call to

View File

@@ -43,24 +43,29 @@ using namespace image;
namespace test { namespace test {
Backend BackendTest::sBackend = Backend::NOOP; Backend BackendTest::sBackend = Backend::NOOP;
OperatingSystem BackendTest::sOperatingSystem = OperatingSystem::OTHER;
bool BackendTest::sIsMobilePlatform = false; bool BackendTest::sIsMobilePlatform = false;
void BackendTest::init(Backend backend, bool isMobilePlatform) { void BackendTest::init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform) {
sBackend = backend; sBackend = backend;
sOperatingSystem = operatingSystem;
sIsMobilePlatform = isMobilePlatform; sIsMobilePlatform = isMobilePlatform;
} }
BackendTest::BackendTest() : commandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE, BackendTest::BackendTest() : commandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE,
CONFIG_COMMAND_BUFFERS_SIZE, /*mPaused=*/false) { CONFIG_COMMAND_BUFFERS_SIZE, /*mPaused=*/false) {
initializeDriver(); initializeDriver();
mImageExpectations.emplace(getDriverApi());
} }
BackendTest::~BackendTest() { BackendTest::~BackendTest() {
// Ensure all graphics commands and callbacks are finished.
flushAndWait();
mImageExpectations->evaluate();
// Note: Don't terminate the driver for OpenGL, as it wipes away the context and removes the buffer from the screen. // Note: Don't terminate the driver for OpenGL, as it wipes away the context and removes the buffer from the screen.
if (sBackend == Backend::OPENGL) { if (sBackend == Backend::OPENGL) {
return; return;
} }
flushAndWait();
driver->terminate(); driver->terminate();
delete driver; delete driver;
} }
@@ -154,49 +159,16 @@ void BackendTest::renderTriangle(
api.endRenderPass(); api.endRenderPass();
} }
void BackendTest::readPixelsAndAssertHash(const char* testName, size_t width, size_t height, bool BackendTest::matchesEnvironment(Backend backend) {
Handle<HwRenderTarget> rt, uint32_t expectedHash, bool exportScreenshot) { return sBackend == backend;
void* buffer = calloc(1, width * height * 4); }
struct Capture { bool BackendTest::matchesEnvironment(OperatingSystem operatingSystem) {
uint32_t expectedHash; return sOperatingSystem == operatingSystem;
char* name; }
bool exportScreenshot;
size_t width, height;
};
auto* c = new Capture();
c->expectedHash = expectedHash;
c->name = strdup(testName);
c->exportScreenshot = exportScreenshot;
c->width = width;
c->height = height;
PixelBufferDescriptor pbd(buffer, width * height * 4, PixelDataFormat::RGBA, PixelDataType::UBYTE, bool BackendTest::matchesEnvironment(OperatingSystem operatingSystem, Backend backend) {
1, 0, 0, width, [](void* buffer, size_t size, void* user) { return matchesEnvironment(operatingSystem) && matchesEnvironment(backend);
auto* c = (Capture*)user;
// Export a screenshot, if requested.
if (c->exportScreenshot) {
#ifndef FILAMENT_IOS
LinearImage image(c->width, c->height, 4);
image = toLinearWithAlpha<uint8_t>(c->width, c->height, c->width * 4,
(uint8_t*) buffer);
const std::string png = std::string(c->name) + ".png";
std::ofstream outputStream(png.c_str(), std::ios::binary | std::ios::trunc);
ImageEncoder::encode(outputStream, ImageEncoder::Format::PNG, image, "",
png);
#endif
}
// Hash the contents of the buffer and check that they match.
uint32_t hash = utils::hash::murmur3((const uint32_t*) buffer, size / 4, 0);
ASSERT_EQ(hash, c->expectedHash) << c->name << " failed: hashes do not match." << std::endl;
free(buffer);
free(c->name);
free(c);
}, (void*)c);
getDriverApi().readPixels(rt, 0, 0, width, height, std::move(pbd));
} }
class Environment : public ::testing::Environment { class Environment : public ::testing::Environment {
@@ -210,8 +182,8 @@ public:
} }
}; };
void initTests(Backend backend, bool isMobile, int& argc, char* argv[]) { void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile, int& argc, char* argv[]) {
BackendTest::init(backend, isMobile); BackendTest::init(backend, operatingSystem, isMobile);
::testing::InitGoogleTest(&argc, argv); ::testing::InitGoogleTest(&argc, argv);
::testing::AddGlobalTestEnvironment(new Environment); ::testing::AddGlobalTestEnvironment(new Environment);
} }

View File

@@ -25,15 +25,17 @@
#include "private/backend/DriverApi.h" #include "private/backend/DriverApi.h"
#include "PlatformRunner.h" #include "PlatformRunner.h"
#include "ImageExpectations.h"
namespace test { namespace test {
class BackendTest : public ::testing::Test { class BackendTest : public ::testing::Test {
public: public:
static void init(Backend backend, bool isMobilePlatform); static void init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform);
static Backend sBackend; static Backend sBackend;
static OperatingSystem sOperatingSystem;
static bool sIsMobilePlatform; static bool sIsMobilePlatform;
protected: protected:
@@ -64,13 +66,14 @@ protected:
filament::backend::Handle<filament::backend::HwProgram> program, filament::backend::Handle<filament::backend::HwProgram> program,
const filament::backend::RenderPassParams& params); const filament::backend::RenderPassParams& params);
void readPixelsAndAssertHash(const char* testName, size_t width, size_t height,
filament::backend::Handle<filament::backend::HwRenderTarget> rt, uint32_t expectedHash,
bool exportScreenshot = false);
filament::backend::DriverApi& getDriverApi() { return *commandStream; } filament::backend::DriverApi& getDriverApi() { return *commandStream; }
filament::backend::Driver& getDriver() { return *driver; } filament::backend::Driver& getDriver() { return *driver; }
ImageExpectations& getExpectations() { return *mImageExpectations; }
static bool matchesEnvironment(Backend backend);
static bool matchesEnvironment(OperatingSystem operatingSystem);
static bool matchesEnvironment(OperatingSystem operatingSystem, Backend backend);
private: private:
filament::backend::Driver* driver = nullptr; filament::backend::Driver* driver = nullptr;
@@ -78,6 +81,10 @@ private:
std::unique_ptr<filament::backend::DriverApi> commandStream; std::unique_ptr<filament::backend::DriverApi> commandStream;
filament::backend::Handle<filament::backend::HwBufferObject> uniform; filament::backend::Handle<filament::backend::HwBufferObject> uniform;
// This isn't truly optional, it just needs to delay construction until after the driver has
// been initialized
std::optional<ImageExpectations> mImageExpectations;
}; };
} // namespace test } // namespace test

View File

@@ -16,7 +16,6 @@
#include "ImageExpectations.h" #include "ImageExpectations.h"
#include "gtest/gtest.h"
#include "gmock/gmock.h" #include "gmock/gmock.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "utils/Hash.h" #include "utils/Hash.h"
@@ -28,14 +27,17 @@
#ifndef FILAMENT_IOS #ifndef FILAMENT_IOS
#include <imageio/ImageEncoder.h> #include <imageio/ImageEncoder.h>
#include <imageio/ImageDecoder.h>
#include <image/ColorTransform.h> #include <image/ColorTransform.h>
#endif #endif
ScreenshotParams::ScreenshotParams(int width, int height, std::string fileName, ScreenshotParams::ScreenshotParams(int width, int height, std::string fileName,
uint32_t expectedPixelHash) uint32_t expectedHash)
: mWidth(width), mHeight(height), mFileName(std::move(fileName)), : mWidth(width),
mExpectedPixelHash(expectedPixelHash) {} mHeight(height),
mExpectedPixelHash(expectedHash),
mFileName(std::move(fileName)) {}
int ScreenshotParams::width() const { int ScreenshotParams::width() const {
return mWidth; return mWidth;
@@ -49,24 +51,28 @@ uint32_t ScreenshotParams::expectedHash() const {
return mExpectedPixelHash; return mExpectedPixelHash;
} }
std::string ScreenshotParams::outputDirectoryPath() const { std::string ScreenshotParams::actualDirectoryPath() {
return "."; return "images/actual_images";
} }
std::string ScreenshotParams::generatedActualFileName() const { std::string ScreenshotParams::actualFileName() const {
return absl::StrFormat("%s_actual.png", mFileName); return absl::StrFormat("%s_actual.png", mFileName);
} }
std::string ScreenshotParams::generatedActualFilePath() const { std::string ScreenshotParams::actualFilePath() const {
return absl::StrFormat("%s/%s", outputDirectoryPath(), generatedActualFileName()); return absl::StrFormat("%s/%s", actualDirectoryPath(), actualFileName());
} }
std::string ScreenshotParams::goldenFileName() const { std::string ScreenshotParams::expectedDirectoryPath() {
return absl::StrFormat("%s_golden.png", mFileName); return "images/expected_images";
} }
std::string ScreenshotParams::goldenFilePath() const { std::string ScreenshotParams::expectedFileName() const {
return absl::StrFormat("%s/%s", outputDirectoryPath(), goldenFileName()); return absl::StrFormat("%s.png", mFileName);
}
std::string ScreenshotParams::expectedFilePath() const {
return absl::StrFormat("%s/%s", expectedDirectoryPath(), expectedFileName());
} }
ImageExpectation::ImageExpectation(const char* fileName, int lineNumber, ImageExpectation::ImageExpectation(const char* fileName, int lineNumber,
@@ -91,11 +97,22 @@ void ImageExpectation::evaluate() {
void ImageExpectation::compareImage() const { void ImageExpectation::compareImage() const {
bool bytesFilled = mResult.bytesFilled(); bool bytesFilled = mResult.bytesFilled();
// If this fails, it likely means that BackendTest::flushAndWait needs to be called before
// ImageExpectations is evaluated or destroyed.
EXPECT_THAT(bytesFilled, testing::IsTrue()) EXPECT_THAT(bytesFilled, testing::IsTrue())
<< "Render target wasn't copied to the buffer for " << mFileName; << "Render target wasn't copied to the buffer for " << mFileName;
if (bytesFilled) { if (bytesFilled) {
// Rather than directly compare the two images compare their hashes because comparing very
// large arrays generates way too much debug output to be useful.
uint32_t actualHash = mResult.hash(); uint32_t actualHash = mResult.hash();
EXPECT_THAT(actualHash, testing::Eq(mParams.expectedHash())); #ifndef FILAMENT_IOS
LoadedPng loadedImage(mParams.expectedFilePath());
uint32_t loadedImageHash = loadedImage.hash();
EXPECT_THAT(actualHash, testing::Eq(loadedImageHash)) << mParams.expectedFileName();
#endif
// For builds that can't load PNGs (currently iOS only) use the expected hash.
EXPECT_THAT(actualHash, testing::Eq(mParams.expectedHash())) << mParams.expectedFileName();
// TODO: Add better debug output, such as generating a diff image.
} }
} }
@@ -109,12 +126,13 @@ ImageExpectations::~ImageExpectations() {
void ImageExpectations::addExpectation(const char* fileName, int lineNumber, void ImageExpectations::addExpectation(const char* fileName, int lineNumber,
filament::backend::RenderTargetHandle renderTarget, ScreenshotParams params) { filament::backend::RenderTargetHandle renderTarget, ScreenshotParams params) {
mExpectations.emplace_back(fileName, lineNumber, mApi, std::move(params), renderTarget); mExpectations.emplace_back(std::make_unique<ImageExpectation>(fileName, lineNumber, mApi,
std::move(params), renderTarget));
} }
void ImageExpectations::evaluate() { void ImageExpectations::evaluate() {
for (auto& expectation: mExpectations) { for (auto& expectation: mExpectations) {
expectation.evaluate(); expectation->evaluate();
} }
mExpectations.clear(); mExpectations.clear();
} }
@@ -122,32 +140,28 @@ void ImageExpectations::evaluate() {
RenderTargetDump::RenderTargetDump(filament::backend::DriverApi& api, RenderTargetDump::RenderTargetDump(filament::backend::DriverApi& api,
filament::backend::RenderTargetHandle renderTarget, const ScreenshotParams& params) filament::backend::RenderTargetHandle renderTarget, const ScreenshotParams& params)
: mInternal(std::make_unique<RenderTargetDump::Internal>(params)) { : mInternal(std::make_unique<RenderTargetDump::Internal>(params)) {
#ifdef FILAMENT_IOS
bytesFilled_ = true;
bytes_.resize(size);
std::fill(bytes_.begin(), bytes_.end(), 0);
#else
const size_t size = mInternal->params.width() * mInternal->params.height() * 4; const size_t size = mInternal->params.width() * mInternal->params.height() * 4;
mInternal->bytes.resize(size); mInternal->bytes.resize(size);
auto cb = [](void* buffer, size_t size, void* user) { auto cb = [](void* buffer, size_t size, void* user) {
auto* internal = static_cast<RenderTargetDump::Internal*>(user); auto* internal = static_cast<RenderTargetDump::Internal*>(user);
internal->bytesFilled = true;
#ifndef FILAMENT_IOS
image::LinearImage image(internal->params.width(), internal->params.width(), 4); image::LinearImage image(internal->params.width(), internal->params.width(), 4);
image = image::toLinearWithAlpha<uint8_t>(internal->params.width(), image = image::toLinearWithAlpha<uint8_t>(internal->params.width(),
internal->params.height(), internal->params.height(),
internal->params.width() * 4, (uint8_t*)buffer); internal->params.width() * 4, (uint8_t*)buffer);
std::string filePath = internal->params.generatedActualFilePath(); std::string filePath = internal->params.actualFilePath();
std::ofstream pngStream(filePath, std::ios::binary | std::ios::trunc); std::ofstream pngStream(filePath, std::ios::binary | std::ios::trunc);
image::ImageEncoder::encode(pngStream, image::ImageEncoder::Format::PNG, image, "", image::ImageEncoder::encode(pngStream, image::ImageEncoder::Format::PNG, image, "",
filePath); filePath);
internal->bytesFilled = true; #endif
}; };
filament::backend::PixelBufferDescriptor pb(mInternal->bytes.data(), size, filament::backend::PixelBufferDescriptor pb(mInternal->bytes.data(), size,
filament::backend::PixelDataFormat::RGBA, filament::backend::PixelDataType::UBYTE, cb, filament::backend::PixelDataFormat::RGBA, filament::backend::PixelDataType::UBYTE, cb,
(void*)mInternal.get()); (void*)mInternal.get());
api.readPixels(renderTarget, 0, 0, mInternal->params.width(), mInternal->params.height(), api.readPixels(renderTarget, 0, 0, mInternal->params.width(), mInternal->params.height(),
std::move(pb)); std::move(pb));
#endif
} }
RenderTargetDump::~RenderTargetDump() { RenderTargetDump::~RenderTargetDump() {
@@ -169,4 +183,30 @@ bool RenderTargetDump::bytesFilled() const {
return mInternal->bytesFilled; return mInternal->bytesFilled;
} }
RenderTargetDump::Internal::Internal(const ScreenshotParams& params) : params(params) {} RenderTargetDump::Internal::Internal(const ScreenshotParams& params) : params(params) {}
LoadedPng::LoadedPng(std::string filePath) : mFilePath(std::move(filePath)) {
#ifndef FILAMENT_IOS
std::ifstream pngStream(mFilePath, std::ios::binary);
image::LinearImage loadedImage = image::ImageDecoder::decode(pngStream, filePath,
image::ImageDecoder::ColorSpace::LINEAR);
size_t valuesInImage = loadedImage.getWidth() * loadedImage.getHeight() *
loadedImage.getChannels();
// The linear image is loaded with each component as [0.0, 1.0] but should be [0, 255], so
// convert them.
mBytes = std::vector<unsigned char>(valuesInImage);
for (int i = 0; i < valuesInImage; ++i) {
mBytes[i] = static_cast<uint8_t>(loadedImage.get<float>()[i] * 255.0f);
}
#endif
// For platforms that don't support the image loading library, leave the loaded data blank.
}
uint32_t LoadedPng::hash() const {
EXPECT_THAT(mBytes, testing::Not(testing::IsEmpty()))
<< "Failed to load expected test result: " << mFilePath;
if (mBytes.empty()) {
return 0;
}
return utils::hash::murmur3((uint32_t*)mBytes.data(), mBytes.size() / 4, 0);
}

View File

@@ -46,11 +46,12 @@ public:
int height() const; int height() const;
uint32_t expectedHash() const; uint32_t expectedHash() const;
std::string outputDirectoryPath() const; static std::string actualDirectoryPath();
std::string generatedActualFileName() const; std::string actualFileName() const;
std::string generatedActualFilePath() const; std::string actualFilePath() const;
std::string goldenFileName() const; static std::string expectedDirectoryPath();
std::string goldenFilePath() const; std::string expectedFileName() const;
std::string expectedFilePath() const;
private: private:
int mWidth; int mWidth;
@@ -98,6 +99,17 @@ private:
std::unique_ptr<Internal> mInternal; std::unique_ptr<Internal> mInternal;
}; };
class LoadedPng {
public:
explicit LoadedPng(std::string filePath);
uint32_t hash() const;
private:
std::string mFilePath;
std::vector<unsigned char> mBytes;
};
class ImageExpectation { class ImageExpectation {
public: public:
ImageExpectation(const char* fileName, int lineNumber, filament::backend::DriverApi& api, ImageExpectation(const char* fileName, int lineNumber, filament::backend::DriverApi& api,
@@ -130,7 +142,8 @@ public:
private: private:
filament::backend::DriverApi& mApi; filament::backend::DriverApi& mApi;
std::vector<ImageExpectation> mExpectations; // Store expectations in unique pointers because they are self referential.
std::vector<std::unique_ptr<ImageExpectation>> mExpectations;
}; };
#endif //TNT_IMAGE_EXPECTATIONS_H #endif //TNT_IMAGE_EXPECTATIONS_H

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2019 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 "PlatformRunner.h"
namespace utils {
template<>
CString to_string<test::Backend>(test::Backend backend) noexcept {
switch (backend) {
case test::Backend::OPENGL: {
return "OpenGL";
}
case test::Backend::VULKAN: {
return "Vulkan";
}
case test::Backend::METAL: {
return "Metal";
}
case test::Backend::WEBGPU: {
return "WebGPU";
}
case test::Backend::NOOP:
default: {
return "No-op";
}
}
}
template<>
CString to_string(test::OperatingSystem os) noexcept {
switch (os) {
case test::OperatingSystem::LINUX: {
return "Linux";
}
case test::OperatingSystem::APPLE: {
return "Apple";
}
case test::OperatingSystem::OTHER:
default: {
return "Other";
}
}
}
} // namespace utils

View File

@@ -19,6 +19,7 @@
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include "utils/CString.h"
namespace test { namespace test {
@@ -34,6 +35,15 @@ enum class Backend : uint8_t {
NOOP = 5, NOOP = 5,
}; };
enum class OperatingSystem: uint8_t {
OTHER = 1,
// Also represents android phones.
LINUX = 2,
// Also represents iOS phones.
APPLE = 3,
// TODO: When tests support windows add it here.
};
struct NativeView { struct NativeView {
void* ptr = nullptr; void* ptr = nullptr;
size_t width = 0, height = 0; size_t width = 0, height = 0;
@@ -51,9 +61,10 @@ NativeView getNativeView();
* No tests will be run yet. * No tests will be run yet.
* *
* @param backend The backend to run the tests on. * @param backend The backend to run the tests on.
* @param operatingSystem The operating system the tests are being run on.
* @param isMobile True if the platform is a mobile platform (iOS or Android). * @param isMobile True if the platform is a mobile platform (iOS or Android).
*/ */
void initTests(Backend backend, bool isMobile, int& argc, char* argv[]); void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile, int& argc, char* argv[]);
/** /**
* Test runners should call runTests when they are ready for tests to be run. * Test runners should call runTests when they are ready for tests to be run.
@@ -68,6 +79,6 @@ int runTests();
*/ */
Backend parseArgumentsForBackend(int argc, char* argv[]); Backend parseArgumentsForBackend(int argc, char* argv[]);
} } // namespace test
#endif #endif

View File

@@ -23,30 +23,44 @@ namespace test {
using namespace filament::backend; using namespace filament::backend;
Shader::Shader(DriverApi& api, Cleanup& cleanup, ShaderConfig config) { Shader::Shader(DriverApi& api, Cleanup& cleanup, ShaderConfig config) : mCleanup(cleanup) {
utils::FixedCapacityVector<DescriptorSetLayoutBinding> kLayouts(config.uniformNames.size()); utils::FixedCapacityVector<DescriptorSetLayoutBinding> kLayouts(config.uniforms.size());
for (unsigned char i = 0; i < config.uniformNames.size(); ++i) { for (unsigned char i = 0; i < config.uniforms.size(); ++i) {
kLayouts[i] = kLayouts[i] = {
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, i }; config.uniforms[i].type.value_or(DescriptorType::UNIFORM_BUFFER),
}; ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, i };
}
// This assumes that the uniforms will all be in a single descriptor set at index 1.
// If there are shaders with uniforms in other sets then ShaderConfig will need to be expanded
// to accommodate that.
size_t kDescriptorSetIndex = 1;
filamat::DescriptorSets descriptors; filamat::DescriptorSets descriptors;
for (unsigned char i = 0; i < config.uniformNames.size(); ++i) { descriptors[kDescriptorSetIndex] = filamat::DescriptorSetInfo(config.uniforms.size());
descriptors[i + 1] = {{ config.uniformNames[i], kLayouts[i], {}}}; for (unsigned char i = 0; i < config.uniforms.size(); ++i) {
descriptors[kDescriptorSetIndex][i] = {
config.uniforms[i].name, kLayouts[i], config.uniforms[i].samplerInfo };
} }
ShaderGenerator shaderGen( ShaderGenerator shaderGen(
std::move(config.vertexShader), std::move(config.fragmentShader), BackendTest::sBackend, std::move(config.vertexShader), std::move(config.fragmentShader), BackendTest::sBackend,
BackendTest::sIsMobilePlatform, std::move(descriptors)); BackendTest::sIsMobilePlatform, std::move(descriptors));
Program prog = shaderGen.getProgram(api); Program prog = shaderGen.getProgram(api);
for (unsigned char i = 0; i < config.uniformNames.size(); ++i) {
prog.descriptorBindings(1, {{ config.uniformNames[i], DescriptorType::UNIFORM_BUFFER, i }}); Program::DescriptorBindingsInfo bindingsInfo(config.uniforms.size());
for (unsigned char i = 0; i < config.uniforms.size(); ++i) {
bindingsInfo[i] = {
config.uniforms[i].name,
config.uniforms[i].type.value_or(DescriptorType::UNIFORM_BUFFER), i };
} }
prog.descriptorBindings(1, bindingsInfo);
mProgram = cleanup.add(api.createProgram(std::move(prog))); mProgram = cleanup.add(api.createProgram(std::move(prog)));
mDescriptorSetLayout = cleanup.add( mDescriptorSetLayout = cleanup.add(
api.createDescriptorSetLayout(DescriptorSetLayout{ kLayouts })); api.createDescriptorSetLayout(DescriptorSetLayout{ kLayouts }));
}
mDescriptorSet = cleanup.add(api.createDescriptorSet(mDescriptorSetLayout)); filament::backend::DescriptorSetHandle Shader::createDescriptorSet(DriverApi& api) const {
return mCleanup.add(api.createDescriptorSet(mDescriptorSetLayout));
} }
filament::backend::ProgramHandle Shader::getProgram() const { filament::backend::ProgramHandle Shader::getProgram() const {
@@ -57,8 +71,4 @@ filament::backend::DescriptorSetLayoutHandle Shader::getDescriptorSetLayout() co
return mDescriptorSetLayout; return mDescriptorSetLayout;
} }
filament::backend::DescriptorSetHandle Shader::getDescriptorSet() const {
return mDescriptorSet;
}
} // namespace test } // namespace test

View File

@@ -18,14 +18,22 @@
#define TNT_SHADER_H #define TNT_SHADER_H
#include "Lifetimes.h" #include "Lifetimes.h"
#include "private/filament/SamplerInterfaceBlock.h"
namespace test { namespace test {
struct UniformConfig {
utils::CString name;
// If not specified this will be DescriptorType::UNIFORM_BUFFER
std::optional<filament::backend::DescriptorType> type;
std::optional<filament::SamplerInterfaceBlock::SamplerInfo> samplerInfo;
};
// All describing a shader that should be created. // All describing a shader that should be created.
struct ShaderConfig { struct ShaderConfig {
std::string vertexShader; std::string vertexShader;
std::string fragmentShader; std::string fragmentShader;
std::vector<utils::CString> uniformNames; std::vector<UniformConfig> uniforms;
}; };
// All values describing a uniform. // All values describing a uniform.
@@ -35,6 +43,7 @@ struct ResolvedUniformBindingConfig {
uint32_t byteOffset; uint32_t byteOffset;
filament::backend::descriptor_set_t set; filament::backend::descriptor_set_t set;
filament::backend::descriptor_binding_t binding; filament::backend::descriptor_binding_t binding;
std::optional<filament::backend::DescriptorSetHandle> descriptorSet;
}; };
// An equivalent to ResolvedUniformBindingConfig with all fields optional. // An equivalent to ResolvedUniformBindingConfig with all fields optional.
@@ -46,6 +55,7 @@ struct UniformBindingConfig {
std::optional<uint32_t> byteOffset; std::optional<uint32_t> byteOffset;
std::optional<filament::backend::descriptor_set_t> set; std::optional<filament::backend::descriptor_set_t> set;
std::optional<filament::backend::descriptor_binding_t> binding; std::optional<filament::backend::descriptor_binding_t> binding;
std::optional<filament::backend::DescriptorSetHandle> descriptorSet;
template<typename UniformType> template<typename UniformType>
ResolvedUniformBindingConfig resolve(); ResolvedUniformBindingConfig resolve();
@@ -79,12 +89,14 @@ public:
filament::backend::ProgramHandle getProgram() const; filament::backend::ProgramHandle getProgram() const;
filament::backend::DescriptorSetLayoutHandle getDescriptorSetLayout() const; filament::backend::DescriptorSetLayoutHandle getDescriptorSetLayout() const;
filament::backend::DescriptorSetHandle getDescriptorSet() const;
filament::backend::DescriptorSetHandle createDescriptorSet(
filament::backend::DriverApi& api) const;
protected: protected:
Cleanup& mCleanup;
filament::backend::ProgramHandle mProgram; filament::backend::ProgramHandle mProgram;
filament::backend::DescriptorSetLayoutHandle mDescriptorSetLayout; filament::backend::DescriptorSetLayoutHandle mDescriptorSetLayout;
filament::backend::DescriptorSetHandle mDescriptorSet;
}; };
template<typename UniformType> template<typename UniformType>
@@ -95,7 +107,8 @@ ResolvedUniformBindingConfig UniformBindingConfig::resolve() {
.bufferSize = bufferSize.value_or(resolvedDataSize), .bufferSize = bufferSize.value_or(resolvedDataSize),
.byteOffset = byteOffset.value_or(0), .byteOffset = byteOffset.value_or(0),
.set = set.value_or(1), .set = set.value_or(1),
.binding = binding.value_or(0) .binding = binding.value_or(0),
.descriptorSet = descriptorSet
}; };
} }
@@ -120,9 +133,16 @@ void Shader::bindUniform(filament::backend::DriverApi& api,
UniformBindingConfig config) const { UniformBindingConfig config) const {
auto resolvedConfig = config.resolve<UniformType>(); auto resolvedConfig = config.resolve<UniformType>();
api.updateDescriptorSetBuffer(getDescriptorSet(), resolvedConfig.binding, hwBuffer, 0, filament::backend::DescriptorSetHandle descriptorSet;
if (resolvedConfig.descriptorSet.has_value()) {
descriptorSet = *resolvedConfig.descriptorSet;
} else {
descriptorSet = createDescriptorSet(api);
}
api.updateDescriptorSetBuffer(descriptorSet, resolvedConfig.binding, hwBuffer, 0,
resolvedConfig.bufferSize); resolvedConfig.bufferSize);
api.bindDescriptorSet(getDescriptorSet(), resolvedConfig.set, {}); api.bindDescriptorSet(descriptorSet, resolvedConfig.set, {});
} }
template<typename UniformType> template<typename UniformType>

View File

@@ -0,0 +1,253 @@
/*
* Copyright (C) 2021 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 "SharedShaders.h"
#include "Shader.h"
#include "absl/strings/str_format.h"
#include "gtest/gtest.h"
namespace test {
using namespace filament::backend;
namespace {
// A shader stored in pieces so that uniform declarations can be injected.
struct ShaderText {
std::string mPrefix;
std::string mBody;
std::string withUniform(const std::string& uniformText) const {
return absl::StrFormat("%s\n%s\n%s", mPrefix.c_str(), uniformText.c_str(), mBody.c_str());
}
};
std::optional<ShaderText> GetGlslVertexShader(VertexShaderType type) {
switch (type) {
case VertexShaderType::Noop: {
return ShaderText{
R"(
#version 450 core
layout(location = 0) in vec4 mesh_position;
)", R"(
void main() {
gl_Position = vec4(mesh_position.xy, 0.0, 1.0);
#if defined(TARGET_VULKAN_ENVIRONMENT)
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
gl_Position.y = -gl_Position.y;
#endif
})" };
}
case VertexShaderType::Simple: {
return ShaderText{
R"(
#version 450 core
layout(location = 0) in vec4 mesh_position;
)", R"(
void main() {
gl_Position = vec4(
mesh_position.xy * (params.scaleMinusOne.xy + 1.0) + params.offset.xy,
params.scaleMinusOne.z + 1.0,
1.0);
#if defined(TARGET_VULKAN_ENVIRONMENT)
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
gl_Position.y = -gl_Position.y;
#endif
})" };
}
case VertexShaderType::Textured: {
return ShaderText{
R"(
#version 450 core
layout(location = 0) in vec4 mesh_position;
layout(location = 0) out vec2 uv;
)", R"(
void main() {
gl_Position = vec4(mesh_position.xy, 0.0, 1.0);
uv = (mesh_position.xy * 0.5 + 0.5);
#if defined(TARGET_VULKAN_ENVIRONMENT)
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
gl_Position.y = -gl_Position.y;
#endif
})" };
}
default:
return std::nullopt;
}
}
std::optional<ShaderText> GetGlslFragmentShader(FragmentShaderType type) {
switch (type) {
case FragmentShaderType::White: {
return ShaderText{
R"(
#version 450 core
precision mediump int; precision highp float;
layout(location = 0) out vec4 fragColor;
)", R"(
void main() {
fragColor = vec4(1.0, 1.0, 1.0, 1.0);
})" };
}
case FragmentShaderType::SolidColored: {
return ShaderText{
R"(
#version 450 core
precision mediump int; precision highp float;
layout(location = 0) out vec4 fragColor;
)", R"(
void main() {
fragColor = params.color;
})" };
}
case FragmentShaderType::Textured: {
return ShaderText{
R"(
#version 450 core
precision mediump int; precision highp float;
layout(location = 0) out vec4 fragColor;
layout(location = 0) in vec2 uv;
)", R"(
void main() {
fragColor = texture(test_tex, uv);
})" };
}
default:
return std::nullopt;
}
}
std::optional<std::string> GetGlslUniform(ShaderUniformType type) {
switch (type) {
case ShaderUniformType::None: {
return "";
}
case ShaderUniformType::Simple: {
return R"(
layout(binding = 0, set = 1) uniform Params {
highp vec4 color;
// Use scaleMinusOne instead of scale so that a 0 initialized value is a good default
highp vec4 scaleMinusOne;
highp vec4 offset;
} params;
)";
}
case ShaderUniformType::SimpleWithPadding: {
return R"(
layout(binding = 0, set = 1) uniform Params {
highp vec4 padding[4]; // offset of 64 bytes
highp vec4 color;
// Use scaleMinusOne instead of scale so that a 0 initialized value is a good default
highp vec4 scaleMinusOne;
highp vec4 offset;
} params;
)";
}
case ShaderUniformType::Sampler: {
return R"(
layout(location = 0, set = 1) uniform sampler2D test_tex;
)";
}
default:
return std::nullopt;
}
}
std::vector<UniformConfig> GetUniformConfig(ShaderUniformType type) {
switch (type) {
case ShaderUniformType::None: {
return {};
}
case ShaderUniformType::Simple: {
return {{ "Params" }};
}
case ShaderUniformType::SimpleWithPadding: {
return {{ "Params" }};
}
case ShaderUniformType::Sampler: {
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo{
"backend_test", "test_tex", 0,
SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH, false };
return {{
"test_tex", DescriptorType::SAMPLER, samplerInfo
}};
}
default:
abort();
}
}
ShaderLanguage getShaderLanguage(const Backend& backend) {
switch (backend) {
case Backend::METAL:
return ShaderLanguage::MSL;
case Backend::WEBGPU:
return ShaderLanguage::WGSL;
case Backend::VULKAN:
case Backend::NOOP:
case Backend::OPENGL:
default: {
return ShaderLanguage::GLSL;
}
}
}
} // namespace
Shader SharedShaders::makeShader(filament::backend::DriverApi& api, Cleanup& cleanup,
ShaderRequest request) {
std::optional<ShaderText> vertex;
std::optional<ShaderText> fragment;
std::optional<std::string> uniform;
if (getShaderLanguage(BackendTest::sBackend) != ShaderLanguage::GLSL) {
// TODO: If any shaders need backend/shader language specific shaders rather than transpiled
// versions of the GLSL shader, check environment.
}
vertex = GetGlslVertexShader(request.mVertexType);
fragment = GetGlslFragmentShader(request.mFragmentType);
uniform = GetGlslUniform(request.mUniformType);
if (vertex.has_value() && fragment.has_value() && uniform.has_value()) {
return Shader(
api, cleanup, ShaderConfig{
vertex->withUniform(*uniform), fragment->withUniform(*uniform),
GetUniformConfig(request.mUniformType)}
);
}
abort();
}
std::string SharedShaders::getVertexShaderText(VertexShaderType vertex, ShaderUniformType uniform) {
std::optional<ShaderText> vertexText = GetGlslVertexShader(vertex);
std::optional<std::string> uniformText = GetGlslUniform(uniform);
if (!vertexText.has_value() || !uniformText.has_value()) {
abort();
}
return vertexText->withUniform(*uniformText);
}
std::string SharedShaders::getFragmentShaderText(FragmentShaderType fragment,
ShaderUniformType uniform) {
std::optional<ShaderText> fragmentText = GetGlslFragmentShader(fragment);
std::optional<std::string> uniformText = GetGlslUniform(uniform);
if (!fragmentText.has_value() || !uniformText.has_value()) {
abort();
}
return fragmentText->withUniform(*uniformText);
}
} // namespace test

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2021 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_SHAREDSHADERS_H
#define TNT_SHAREDSHADERS_H
#include "Shader.h"
#include "SharedShadersConstants.h"
#include "Lifetimes.h"
#include "PlatformRunner.h"
namespace test {
enum class ShaderLanguage : uint8_t {
GLSL,
MSL,
WGSL
};
struct ShaderRequest {
VertexShaderType mVertexType;
FragmentShaderType mFragmentType;
ShaderUniformType mUniformType;
};
class SharedShaders {
public:
static Shader makeShader(filament::backend::DriverApi& api, Cleanup& cleanup,
ShaderRequest request);
static std::string getVertexShaderText(VertexShaderType vertex, ShaderUniformType uniform);
static std::string getFragmentShaderText(FragmentShaderType fragment,
ShaderUniformType uniform);
};
} // namespace test
#endif //TNT_SHAREDSHADERS_H

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2021 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_SHAREDSHADERSCONSTANTS_H
#define TNT_SHAREDSHADERSCONSTANTS_H
#include "math/mathfwd.h"
enum class ShaderUniformType : uint8_t {
None,
Simple,
SimpleWithPadding,
Sampler,
};
struct SimpleMaterialParams {
filament::math::float4 color;
// 1.0 will be added to this value before use.
// The XY values are used to scale position inputs and the Z value is used to set the output
// position's Z value.
filament::math::float4 scaleMinusOne;
// Offset will be applied after scale
filament::math::float4 offset;
};
struct SimpleWithPaddingMaterialParams {
// The associated uniform structure in the shader will have 64 bytes of padding at the beginning
// So users of this struct will need to add 64 bytes to its size and offset all uniform writes.
filament::math::float4 color;
// 1.0 will be added to this value before use.
// The XY values are used to scale position inputs and the Z value is used to set the output
// position's Z value.
filament::math::float4 scaleMinusOne;
// Offset will be applied after scale
filament::math::float4 offset;
};
enum class VertexShaderType : uint8_t {
Noop,
Simple,
Textured
};
enum class FragmentShaderType : uint8_t {
White,
SolidColored,
Textured
};
#endif //TNT_SHAREDSHADERSCONSTANTS_H

View File

@@ -0,0 +1,96 @@
/*
* 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 "Skip.h"
#include <sstream>
namespace test {
SkipEnvironment::SkipEnvironment(test::Backend backend) : backend(backend) {}
SkipEnvironment::SkipEnvironment(test::OperatingSystem os) : os(os) {}
SkipEnvironment::SkipEnvironment(test::OperatingSystem os, test::Backend backend)
: backend(backend),
os(os) {}
bool SkipEnvironment::matches() {
bool backendMatches = !backend.has_value() || *backend == BackendTest::sBackend;
bool osMatches = !os.has_value() || *os == BackendTest::sOperatingSystem;
bool isMobileMatches = !isMobile.has_value() || *isMobile == BackendTest::sIsMobilePlatform;
return backendMatches && osMatches && isMobileMatches;
}
std::string SkipEnvironment::describe() {
std::stringstream result;
if (matches()) {
result << "environment matches because " << describe_actual_environment() << ".";
} else {
result << "environment does not match because " << describe_requirements() << " but "
<< describe_actual_environment() << ".";
}
return result.str();
}
std::string SkipEnvironment::describe_actual_environment() {
bool resultWritten = false;
std::stringstream reality;
if (backend.has_value()) {
reality << "backend was " << utils::to_string(BackendTest::sBackend).c_str();
resultWritten = true;
}
if (os.has_value()) {
if (resultWritten) {
reality << ", and ";
}
reality << "operating system was "
<< utils::to_string(BackendTest::sOperatingSystem).c_str();
resultWritten = true;
}
if (isMobile.has_value()) {
if (resultWritten) {
reality << ", and ";
}
reality << "device " << (BackendTest::sIsMobilePlatform ? "was" : "was not") << " mobile";
resultWritten = true;
}
return reality.str();
}
std::string SkipEnvironment::describe_requirements() {
bool resultWritten = false;
std::stringstream requirement;
if (backend.has_value()) {
requirement << "backend needs to be " << utils::to_string(*backend).c_str();
resultWritten = true;
}
if (os.has_value()) {
if (resultWritten) {
requirement << ", and ";
}
requirement << "operating system needs to be " << utils::to_string(*os).c_str();
resultWritten = true;
}
if (isMobile.has_value() && BackendTest::sIsMobilePlatform != isMobile) {
if (resultWritten) {
requirement << ", and ";
}
requirement << "device needs to " << (*isMobile ? "be" : "not be") << " mobile";
resultWritten = true;
}
return requirement.str();
}
} // namespace test

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_SKIP_H
#define TNT_SKIP_H
#include <gtest/gtest.h>
#include "BackendTest.h"
// skipEnvironment must be a test::SkipEnvironment
#define SKIP_IF(skipEnvironment) \
do { \
SkipEnvironment skip(skipEnvironment); \
if (skip.matches()) { \
GTEST_SKIP() << "Skipping test as the " << skip.describe(); \
} \
} while (false)
namespace test {
struct SkipEnvironment {
SkipEnvironment(const SkipEnvironment&) = default;
explicit SkipEnvironment(test::Backend backend);
explicit SkipEnvironment(test::OperatingSystem os);
SkipEnvironment(test::OperatingSystem os, test::Backend backend);
std::optional<test::Backend> backend;
std::optional<test::OperatingSystem> os;
std::optional<bool> isMobile;
bool matches();
// Describes the current state of either matching or mismatching.
std::string describe();
// Describe all the non-null requirements.
std::string describe_requirements();
// Describes the environment's status for all the attributes that are non-null.
std::string describe_actual_environment();
};
} // namespace test
#endif// TNT_SKIP_H

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

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