Compare commits

..
Author SHA1 Message Date
bridgewaterrobbie 3e1499d1a1 Sampler creation as part of the driver 2025-04-30 12:14:05 -04:00
bridgewaterrobbie 586c82ef10 Add comment explaining public texture format enum conversion 2025-04-30 12:13:32 -04:00
bridgewaterrobbie 7ece76a679 Add a required feature for Filament to requestDevice 2025-04-30 11:46:47 -04:00
bridgewaterrobbie 9959b104f9 Fix mistake- entries size is smaller than the max index, need to fill one at a time with emplace back 2025-04-30 11:44:56 -04:00
bridgewaterrobbie f024214621 Texture and Texture View creation 2025-04-30 11:44:56 -04:00
Andy Hovingh 8a3bf04eef webgpu: initial render pipeline creation 2025-04-29 16:22:09 -05:00
Matthew Hoffman 44840a481e Working backend tests on MacOS for Metal as well as OpenGL (#8662)
BUGS=[409099263]
2025-04-29 12:21:36 -07:00
Powei Feng 8070643ba5 ci: refactor to centralize version definitions (#8663)
- move CI only prerequisites to github actions
 - Add linux, mac prereq actions
 - Add an action for indicating dependency versions
 - Move ninja installation into its own script
2025-04-29 10:02:51 -07:00
Doris Wu 0c33f9f2a3 [fgviewer] Add graphviz view mode to the web page (#8673)
* Export graphviz data to the webview

* Fix some errors

* Fix colors

* Only show toggle when view selected

* Update

* Merge graphviz data into json string

* Mark export_graphviz function as const

* Set SERVE_FROM_SOURCE_TREE to 0

* Remove unnecessary import

* Refactor

* Refactor

* New line when early return
2025-04-29 02:39:37 +00:00
Juan Caldas db72bd024b wgpu: Update callback mode to WaitAnyOnly (#8677) 2025-04-28 20:29:38 -04:00
Powei Feng 7c6b470650 Improve code-correctness script (#8659)
- Sort failures alphabetically
- Add way to suppress error when necessary
- Add way to run test on a subset of files for testing
- Fix case where number of files is less than number of workers
2025-04-28 17:00:40 +00:00
Syed Idris Shah 68a99ef54c metal driver cleanup
Remove the unused functions and varibles from render primitve and target api's.
2025-04-25 23:49:12 -04:00
Powei Feng 39fabdbc3b vk: fix exception-escape failures (#8656)
And one drive-by fix to VulkanYcbcrConversionCache.h
2025-04-25 23:03:47 +00:00
KeremTAN 83967fdb1b std type trait specialization of is_arithmetic and is_floating_type removed 2025-04-25 12:14:26 -07:00
KeremTAN b88c2d80a4 Fix Clang 20 compile error: invalid std type trait specializations 2025-04-25 12:14:26 -07:00
bridgewaterrobbie 4a36193f04 Uncomment updateDescriptorSetBuffer. What we have may actually suffice as-is, and no longer crashes 2025-04-25 14:28:39 -04:00
bridgewaterrobbie 8df19cb6b2 Indicate we do not support texture swizzling 2025-04-25 14:28:39 -04:00
bridgewaterrobbie 783c1c9c83 Upload wgpu cpp header to easily reference in discussions 2025-04-25 12:11:42 -04:00
Syed Idris Shah a92771c5ef webgpu: create and bind render primitives 2025-04-25 10:15:27 -04:00
Syed Idris Shah c98a76135b webgpu: Implement update buffer object methods in driver
Move queue instantion to the constructor. Its needed by the update buffer methods.
2025-04-25 10:15:27 -04:00
Syed Idris Shah 7f2836bb43 webgpu: Implement R functions for vertex and index buffers
Also create BufferObject and use it to update vertex buffers

Updating buffers will be in the next upcoming patch.
2025-04-25 10:15:27 -04:00
Syed Idris Shah b60d02919b webgpu: Update VertexBufferInfo to populate VertexBufferLayout 2025-04-25 10:15:27 -04:00
bridgewaterrobbie 156dde6872 We can't cast handles that weren't actually constructed, so can't use the update descriptor funcs yet. 2025-04-24 14:47:16 -04:00
bridgewaterrobbie dd2fbf01aa wgpu: Initial bindgroup and descriptorset setup.
Likely not handling textures correctly, need more understanding there to proceed.
2025-04-24 14:47:16 -04:00
Ajmal Kunnummal beba1f63f1 Revert initializing the transform 2025-04-24 10:27:35 -07:00
Ajmal Kunnummal e20dd9ec93 Fix bug where the transform parameter was mistakenly unused in setAcquiredImage
- Also initialized the transform field to identity
2025-04-24 10:27:35 -07:00
Ajmal Kunnummal 7f80d956b6 Fix bug where BufferInterfaceBlock::getTransformFieldOffset the offset should be reported in bytes instead of words 2025-04-24 10:26:59 -07:00
Rasmus Munk Larsen 3c941863dc Revert change to pow. 2025-04-23 16:25:46 -07:00
Rasmus Munk Larsen 57e2007aaf Fix typo again. 2025-04-23 16:25:46 -07:00
Rasmus Munk Larsen f88f0607c7 Fix typo. 2025-04-23 16:25:46 -07:00
Rasmus Munk Larsen 91b98964cc Optimize pow and slerp for quaternions by using the identies
sin(acos(x)) = sqrt(1-x^2)
  cos(acos(x)) = x.
2025-04-23 16:25:46 -07:00
rafadevai d92a90e22b VK: Small improvements in different places (#8651)
- Add markers to more VulkanDriver calls and other
functions related to VulkanDriver::collectGarbage
- Fix validation layer error calling vkWaitForFences
with a count of 0.
- In VulkanTextureState, call clearCachedImageViews
before destroying the vkImage.
- In VulkanStagePool, use a std::vector for keeping
track of used images and buffers. Also set the
TIME_BEFORE_EVICTION to 3 frames instead of the
FVK_MAX_COMMAND_BUFFERS.
2025-04-23 22:15:29 +00:00
Powei Feng 79a548ecae Fix broken ios build (#8658)
Missing include after #8654
2025-04-23 20:28:11 +00:00
Mathias Agopian 3c4c8940b2 clang-tidy cleanups 2025-04-23 09:42:02 -07:00
Mathias Agopian 75d2252d33 Add UTILS_VERY_[UN]LIKELY macros
clang optimizes code differently with very likely or unlikely 
conditions, so we add a `VERY` version of these macros, and we
make use of it for assertions.
2025-04-23 09:23:21 -07:00
Powei Feng 9d3a55291d Fix broken header check (#8655)
- PR #8641 introduced a requirement for including Systrace.h that
  wasn't covered by the header-check test.
- Make sure temp.cpp contains only one include
- Add a missing include for Package.h
2025-04-22 22:18:50 +00:00
Powei Feng 170dec0945 vk: external sampler work (#8608)
- Add a manager class for handling external image and sampling
 - The logic is to enable a slow path if the descriptor set
   layout has external samplers and an external image has been
   assigned to the corresponding descriptor set
 - The slow path will deduce the format of the external image to
   and create the corresponding YUV conversion if necessary.
 - Clean-up VulkanPlatform
2025-04-22 15:01:18 -07: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
180 changed files with 255842 additions and 2242 deletions
+7
View File
@@ -0,0 +1,7 @@
name: 'Set up dependency versions'
runs:
using: "composite"
steps:
- name: Set up dependency versions
shell: bash
run: cat ./build/common/versions >> $GITHUB_ENV
+38
View File
@@ -0,0 +1,38 @@
name: 'Linux Preqrequisites'
runs:
using: "composite"
steps:
- uses: ./.github/actions/dep-versions
- name: Install Linux Prerequisites
shell: bash
run: |
set -xe
# See https://askubuntu.com/questions/272248/processing-triggers-for-man-db/1476024#1476024
echo "set man-db/auto-update false" | sudo debconf-communicate
sudo dpkg-reconfigure man-db
# Install ninja
source ./build/common/get-ninja.sh
# Install CMake
mkdir -p cmake
cd cmake
sudo wget https://github.com/Kitware/CMake/releases/download/v$GITHUB_CMAKE_VERSION/cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh
sudo chmod +x ./cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh
sudo ./cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh --skip-license > /dev/null
sudo update-alternatives --install /usr/bin/cmake cmake $(pwd)/bin/cmake 1000 --force
cd ..
sudo wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install clang-$GITHUB_CLANG_VERSION libc++-$GITHUB_CLANG_VERSION-dev libc++abi-$GITHUB_CLANG_VERSION-dev
sudo apt-get install mesa-common-dev libxi-dev libxxf86vm-dev
# For dawn
sudo apt-get install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libx11-xcb-dev
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-${GITHUB_CLANG_VERSION} 100
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-${GITHUB_CLANG_VERSION} 100
set +xe
+23
View File
@@ -0,0 +1,23 @@
name: 'Mac Preqrequisites'
runs:
using: "composite"
steps:
- uses: ./.github/actions/dep-versions
- 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: Cache Brew
id: brew-cache
uses: actions/cache@v4 # Use a specific version
with:
path: $HOME/Library/Caches/Homebrew
key: ${{ runner.os }}-brew-20250424
- name: Install Mac Prerequisites
shell: bash
run: |
# Install ninja
source ./build/common/get-ninja.sh
+3
View File
@@ -16,6 +16,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/linux-prereq
- name: Run Android Continuous - name: Run Android Continuous
uses: ./.github/actions/android-continuous uses: ./.github/actions/android-continuous
with: with:
+3
View File
@@ -14,6 +14,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/mac-prereq
- name: Run build script - name: Run build script
run: | run: |
cd build/ios && printf "y" | ./build.sh continuous cd build/ios && printf "y" | ./build.sh continuous
+3
View File
@@ -14,6 +14,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/linux-prereq
- name: Run build script - name: Run build script
run: | run: |
cd build/linux && printf "y" | ./build.sh continuous cd build/linux && printf "y" | ./build.sh continuous
+3
View File
@@ -14,6 +14,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/mac-prereq
- name: Run build script - name: Run build script
run: | run: |
cd build/mac && printf "y" | ./build.sh continuous cd build/mac && printf "y" | ./build.sh continuous
+49 -35
View File
@@ -9,22 +9,32 @@ on:
- main - main
jobs: jobs:
build-desktop: build-desktop-mac:
name: build-desktop name: build-mac
runs-on: ${{ matrix.os }} runs-on: macos-14-xlarge
strategy:
matrix:
os: [macos-14-xlarge, ubuntu-22.04-16core]
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: ./.github/actions/mac-prereq
- name: Run build script - name: Run build script
run: | run: |
WORKFLOW_OS=`echo \`uname\` | sed "s/Darwin/mac/" | tr [:upper:] [:lower:]` cd build/mac && printf "y" | ./build.sh presubmit
cd build/$WORKFLOW_OS && printf "y" | ./build.sh presubmit - name: Test material parser
run: |
out/cmake-release/filament/test/test_material_parser
build-desktop-linux:
name: build-linux
runs-on: ubuntu-22.04-16core
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/linux-prereq
- name: Run build script
run: |
cd build/linux && printf "y" | ./build.sh presubmit
- name: Test material parser - name: Test material parser
run: | run: |
out/cmake-release/filament/test/test_material_parser out/cmake-release/filament/test/test_material_parser
@@ -35,6 +45,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: |
build\windows\build-github.bat presubmit build\windows\build-github.bat presubmit
@@ -48,6 +60,7 @@ jobs:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: ./.github/actions/linux-prereq
- uses: actions/setup-java@v3 - uses: actions/setup-java@v3
with: with:
distribution: 'temurin' distribution: 'temurin'
@@ -66,6 +79,7 @@ jobs:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: ./.github/actions/mac-prereq
- 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
@@ -79,6 +93,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/linux-prereq
- name: Run build script - name: Run build script
run: | run: |
cd build/web && printf "y" | ./build.sh presubmit cd build/web && printf "y" | ./build.sh presubmit
@@ -101,18 +118,20 @@ jobs:
runs-on: macos-14-xlarge runs-on: macos-14-xlarge
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master
- name: Set up Python
uses: actions/setup-python@v5
with: with:
python-version: '3.x' fetch-depth: 0
- name: Install python prereqs - uses: ./.github/actions/mac-prereq
run: pip install mako setuptools pyyaml - name: Cache Mesa and deps
- name: Run script id: mesa-cache
run: | uses: actions/cache@v4 # Use a specific version
bash test/renderdiff/test.sh with:
path: mesa
key: ${{ runner.os }}-mesa-deps-2-${{ vars.MESA_VERSION }}
- name: Get Mesa
id: mesa-prereq
run: bash test/utils/get_mesa.sh
- name: Run Test
run: bash test/renderdiff/test.sh
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
name: presubmit-renderdiff-result name: presubmit-renderdiff-result
@@ -121,11 +140,13 @@ jobs:
validate-wgsl-webgpu: validate-wgsl-webgpu:
name: validate-wgsl-webgpu name: validate-wgsl-webgpu
runs-on: 'ubuntu-24.04-8core' runs-on: 'ubuntu-24.04-8core'
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/linux-prereq
- name: Run build script - name: Run build script
run: source ./build/linux/ci-common.sh && ./build.sh -W debug test_filamat filament run: ./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*
@@ -136,23 +157,16 @@ jobs:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Homebrew - uses: ./.github/actions/mac-prereq
id: set-up-homebrew - name: Install clang-tidy and deps
uses: Homebrew/actions/setup-homebrew@master
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install prerequisites
run: | run: |
pip install pyyaml pip install pyyaml
brew install llvm brew install llvm@${GITHUB_LLVM_VERSION}
sudo ln -s "$(brew --prefix llvm)/bin/clang-tidy" "/usr/local/bin/clang-tidy" sudo ln -s "$(brew --prefix llvm)@${GITHUB_LLVM_VERSION}/bin/clang-tidy" "/usr/local/bin/clang-tidy"
- name: Run build script - name: Run build script
# We need to build before clang-tidy can run analysis # We need to build before clang-tidy can run analysis
run: | run: |
# This will build for all three desktop backends on mac # This will build for all three desktop backends on mac
./build.sh -p desktop debug gltf_viewer ./build.sh -p desktop debug gltf_viewer
- name: Run test - name: Run test
run: | run: bash test/code-correctness/test.sh
bash test/code-correctness/test.sh
+42 -6
View File
@@ -29,8 +29,42 @@ on:
types: [created] types: [created]
jobs: jobs:
build-desktop: build-linux:
name: build-desktop name: build-linux
runs-on: ubuntu-22.04-32core
if: github.event_name == 'release' || github.event.inputs.platform == 'desktop'
steps:
- name: Decide Git ref
id: git_ref
run: |
REF=${RELEASE_TAG:-${GITHUB_REF}}
TAG=${REF##*/}
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- uses: actions/checkout@v4.1.6
with:
ref: ${{ steps.git_ref.outputs.ref }}
- uses: ./.github/actions/linux-prereq
- name: Run build script
env:
TAG: ${{ steps.git_ref.outputs.tag }}
run: |
cd build/linux && printf "y" | ./build.sh release
cd ../..
mv out/filament-release-linux.tgz out/filament-${TAG}-linux.tgz
- uses: actions/github-script@v6
env:
TAG: ${{ steps.git_ref.outputs.tag }}
with:
script: |
const upload = require('./build/common/upload-release-assets');
const { TAG } = process.env;
const globber = await glob.create('out/*.tgz');
await upload({ github, context }, await globber.glob(), TAG);
build-mac:
name: build-mac
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
if: github.event_name == 'release' || github.event.inputs.platform == 'desktop' if: github.event_name == 'release' || github.event.inputs.platform == 'desktop'
@@ -49,15 +83,14 @@ jobs:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
with: with:
ref: ${{ steps.git_ref.outputs.ref }} ref: ${{ steps.git_ref.outputs.ref }}
- uses: ./.github/actions/mac-prereq
- name: Run build script - name: Run build script
env: env:
TAG: ${{ steps.git_ref.outputs.tag }} TAG: ${{ steps.git_ref.outputs.tag }}
run: | run: |
WORKFLOW_OS=`echo \`uname\` | sed "s/Darwin/mac/" | tr [:upper:] [:lower:]` cd build/mac && printf "y" | ./build.sh release
cd build/$WORKFLOW_OS && printf "y" | ./build.sh release
cd ../.. cd ../..
if [ -f out/filament-release-darwin.tgz ]; then mv out/filament-release-darwin.tgz out/filament-${TAG}-mac.tgz; fi; mv out/filament-release-darwin.tgz out/filament-${TAG}-mac.tgz
if [ -f out/filament-release-linux.tgz ]; then mv out/filament-release-linux.tgz out/filament-${TAG}-linux.tgz; fi;
- uses: actions/github-script@v6 - uses: actions/github-script@v6
env: env:
TAG: ${{ steps.git_ref.outputs.tag }} TAG: ${{ steps.git_ref.outputs.tag }}
@@ -84,6 +117,7 @@ jobs:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
with: with:
ref: ${{ steps.git_ref.outputs.ref }} ref: ${{ steps.git_ref.outputs.ref }}
- uses: ./.github/actions/linux-prereq
- name: Run build script - name: Run build script
env: env:
TAG: ${{ steps.git_ref.outputs.tag }} TAG: ${{ steps.git_ref.outputs.tag }}
@@ -121,6 +155,7 @@ jobs:
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '17' java-version: '17'
- uses: ./.github/actions/linux-prereq
- name: Run build script - name: Run build script
env: env:
TAG: ${{ steps.git_ref.outputs.tag }} TAG: ${{ steps.git_ref.outputs.tag }}
@@ -171,6 +206,7 @@ jobs:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
with: with:
ref: ${{ steps.git_ref.outputs.ref }} ref: ${{ steps.git_ref.outputs.ref }}
- uses: ./.github/actions/mac-prereq
- name: Run build script - name: Run build script
env: env:
TAG: ${{ steps.git_ref.outputs.tag }} TAG: ${{ steps.git_ref.outputs.tag }}
+3
View File
@@ -14,6 +14,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v4.1.6 - uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/linux-prereq
- name: Run build script - name: Run build script
run: | run: |
cd build/web && printf "y" | ./build.sh continuous cd build/web && printf "y" | ./build.sh continuous
+1
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)
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
} }
dependencies { dependencies {
implementation 'com.google.android.filament:filament-android:1.59.2' 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.2' pod 'Filament', '~> 1.59.3'
``` ```
## Documentation ## Documentation
+3
View File
@@ -7,6 +7,9 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to 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.3
+6
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>
+6
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:
+6 -1
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)
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament GROUP=com.google.android.filament
VERSION_NAME=1.59.2 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.
+11 -14
View File
@@ -14,12 +14,12 @@ echo "Please refer to BUILDING.md for more information."
read -r -p "Do you wish to proceed (y/n)? " choice read -r -p "Do you wish to proceed (y/n)? " choice
case "${choice}" in case "${choice}" in
y|Y) y|Y)
echo "Build will proceed..." echo "Build will proceed..."
;; ;;
n|N) n|N)
exit 0 exit 0
;; ;;
*) *)
exit 0 exit 0
;; ;;
esac esac
@@ -30,30 +30,27 @@ set -x
UNAME=`echo $(uname)` UNAME=`echo $(uname)`
LC_UNAME=`echo $UNAME | tr '[:upper:]' '[:lower:]'` LC_UNAME=`echo $UNAME | tr '[:upper:]' '[:lower:]'`
FILAMENT_ANDROID_CI_BUILD=true
# build-common.sh will generate the following variables: # build-common.sh will generate the following variables:
# $GENERATE_ARCHIVES # $GENERATE_ARCHIVES
# $BUILD_DEBUG # $BUILD_DEBUG
# $BUILD_RELEASE # $BUILD_RELEASE
source `dirname $0`/../common/ci-common.sh source `dirname $0`/../common/ci-common.sh
if [[ "$LC_UNAME" == "linux" ]]; then
source `dirname $0`/../linux/ci-common.sh
elif [[ "$LC_UNAME" == "darwin" ]]; then
source `dirname $0`/../mac/ci-common.sh
fi
source `dirname $0`/../common/build-common.sh source `dirname $0`/../common/build-common.sh
if [[ "$GITHUB_WORKFLOW" ]]; then if [[ "$GITHUB_WORKFLOW" ]]; then
java_version=$(java -version 2>&1 | head -1 | cut -d'"' -f2 | sed '/^1\./s///' | cut -d'.' -f1) java_version=$(java -version 2>&1 | head -1 | cut -d'"' -f2 | sed '/^1\./s///' | cut -d'.' -f1)
if [[ "$java_version" < 17 ]]; then if [[ "$java_version" < 17 ]]; then
echo "Android builds require Java 17, found version ${java_version} instead" echo "Android builds require Java 17, found version ${java_version} instead"
exit 0 exit 1
fi fi
fi fi
# Unless explicitly specified, NDK version will be set to match exactly the required one # Unless explicitly specified, NDK version will be set to match exactly the required one
FILAMENT_NDK_VERSION=${FILAMENT_NDK_VERSION:-$(cat `dirname $0`/ndk.version)} FILAMENT_NDK_VERSION=${GITHUB_NDK_VERSION:-27.0.11718014}
(! grep "${FILAMENT_NDK_VERSION}" `dirname $0`/../../android/build.gradle > /dev/null) &&
echo "Mismatch of NDK versions: want ${FILAMENT_NDK_VERSION} and not found in android/build.gradle" &&
exit 1
# Install the required NDK version specifically (if not present) # Install the required NDK version specifically (if not present)
if [[ ! -d "${ANDROID_HOME}/ndk/$FILAMENT_NDK_VERSION" ]]; then if [[ ! -d "${ANDROID_HOME}/ndk/$FILAMENT_NDK_VERSION" ]]; then
-1
View File
@@ -1 +0,0 @@
27.0.11718014
+8 -2
View File
@@ -56,10 +56,16 @@ popd >/dev/null
rm -rf out/check-headers rm -rf out/check-headers
mkdir -p out/check-headers mkdir -p out/check-headers
TMP_FILE=out/check-headers/temp.cpp
echo "Checking that public headers compile independently..." echo "Checking that public headers compile independently..."
for include in "${includes[@]}"; do for include in "${includes[@]}"; do
rm -f ${TMP_FILE}
echo "Checking ${include}" echo "Checking ${include}"
echo "#include <${include}>" >> out/check-headers/temp.cpp if [[ "${include}" == "utils/Systrace.h" ]]; then
clang -std=c++17 -I "${FILAMENT_HEADERS}" out/check-headers/temp.cpp -c -o /dev/null # A necessary define before we can include utils/Systrace.h
echo "#define SYSTRACE_TAG SYSTRACE_TAG_DISABLED" >> ${TMP_FILE}
fi
echo "#include <${include}>" >> ${TMP_FILE}
clang -std=c++17 -I "${FILAMENT_HEADERS}" ${TMP_FILE} -c -o /dev/null
done done
echo "Done!" echo "Done!"
-1
View File
@@ -2,5 +2,4 @@
if [[ "$GITHUB_WORKFLOW" ]]; then if [[ "$GITHUB_WORKFLOW" ]]; then
echo "Running workflow $GITHUB_WORKFLOW (event: $GITHUB_EVENT_NAME, action: $GITHUB_ACTION)" echo "Running workflow $GITHUB_WORKFLOW (event: $GITHUB_EVENT_NAME, action: $GITHUB_ACTION)"
CONTINUOUS_INTEGRATION=true
fi fi
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
if [[ "$GITHUB_WORKFLOW" ]]; then
OS_NAME=$(uname -s)
NINJA_DL_DIR=/tmp/ninja-dl
NINJA_PLATFORM=
if [[ "$OS_NAME" == "Linux" ]]; then
NINJA_PLATFORM=linux
elif [[ "$OS_NAME" == "Darwin" ]]; then
NINJA_PLATFORM=mac
fi
curl -L -o /tmp/ninja-dl.zip https://github.com/ninja-build/ninja/releases/download/v${GITHUB_NINJA_VERSION}/ninja-${NINJA_PLATFORM}.zip
mkdir -p $NINJA_DL_DIR
unzip -q /tmp/ninja-dl.zip -d $NINJA_DL_DIR
chmod +x ${NINJA_DL_DIR}/ninja
# Install ninja globally for AGP
sudo cp ${NINJA_DL_DIR}/ninja /usr/local/bin/
fi
+6
View File
@@ -0,0 +1,6 @@
GITHUB_CLANG_VERSION=14
GITHUB_CMAKE_VERSION=3.19.5
GITHUB_NINJA_VERSION=1.10.2
GITHUB_MESA_VERSION=24.2.1
GITHUB_LLVM_VERSION=16
GITHUB_NDK_VERSION=27.0.11718014
-2
View File
@@ -28,7 +28,6 @@ set -e
set -x set -x
source `dirname $0`/../common/ci-common.sh source `dirname $0`/../common/ci-common.sh
source `dirname $0`/ci-common.sh
source `dirname $0`/../common/build-common.sh source `dirname $0`/../common/build-common.sh
pushd `dirname $0`/../.. > /dev/null pushd `dirname $0`/../.. > /dev/null
@@ -41,4 +40,3 @@ if [[ "${GENERATE_ARCHIVES}" ]]; then
fi fi
./build.sh -i -p ios -c $BUILD_SIMULATOR $GENERATE_ARCHIVES $BUILD_DEBUG $BUILD_RELEASE ./build.sh -i -p ios -c $BUILD_SIMULATOR $GENERATE_ARCHIVES $BUILD_DEBUG $BUILD_RELEASE
-6
View File
@@ -1,6 +0,0 @@
#!/bin/bash
curl -OL https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-mac.zip
unzip -q ninja-mac.zip
chmod +x ninja
export PATH="$PWD:$PATH"
-1
View File
@@ -32,7 +32,6 @@ set -x
# $BUILD_DEBUG # $BUILD_DEBUG
# $BUILD_RELEASE # $BUILD_RELEASE
source `dirname $0`/../common/ci-common.sh source `dirname $0`/../common/ci-common.sh
source `dirname $0`/ci-common.sh
source `dirname $0`/../common/build-common.sh source `dirname $0`/../common/build-common.sh
pushd `dirname $0`/../.. > /dev/null pushd `dirname $0`/../.. > /dev/null
-38
View File
@@ -1,38 +0,0 @@
#!/bin/bash
# version of clang we want to use
export GITHUB_CLANG_VERSION=14
# version of CMake to use instead of the default one
export GITHUB_CMAKE_VERSION=3.19.5
# version of ninja to use
export GITHUB_NINJA_VERSION=1.10.2
# Steps for GitHub Workflows
if [[ "$GITHUB_WORKFLOW" ]]; then
# Install ninja
wget -q https://github.com/ninja-build/ninja/releases/download/v$GITHUB_NINJA_VERSION/ninja-linux.zip
unzip -q ninja-linux.zip
export PATH="$PWD:$PATH"
# Install CMake
mkdir -p cmake
cd cmake
sudo wget https://github.com/Kitware/CMake/releases/download/v$GITHUB_CMAKE_VERSION/cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh
sudo chmod +x ./cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh
sudo ./cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh --skip-license > /dev/null
sudo update-alternatives --install /usr/bin/cmake cmake $(pwd)/bin/cmake 1000 --force
cd ..
sudo wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install clang-$GITHUB_CLANG_VERSION libc++-$GITHUB_CLANG_VERSION-dev libc++abi-$GITHUB_CLANG_VERSION-dev
sudo apt-get install mesa-common-dev libxi-dev libxxf86vm-dev
# For dawn
sudo apt-get install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libx11-xcb-dev
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-${GITHUB_CLANG_VERSION} 100
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-${GITHUB_CLANG_VERSION} 100
fi
-1
View File
@@ -28,7 +28,6 @@ set -e
set -x set -x
source `dirname $0`/../common/ci-common.sh source `dirname $0`/../common/ci-common.sh
source `dirname $0`/ci-common.sh
source `dirname $0`/../common/build-common.sh source `dirname $0`/../common/build-common.sh
pushd `dirname $0`/../.. > /dev/null pushd `dirname $0`/../.. > /dev/null
-8
View File
@@ -1,8 +0,0 @@
#!/bin/bash
curl -OL https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-mac.zip
unzip -q ninja-mac.zip
chmod +x ninja
# Install ninja globally for AGP
cp ninja /usr/local/bin
export PATH="$PWD:$PATH"
+8 -2
View File
@@ -31,10 +31,16 @@ set(DIST_ARCH arm64-v8a)
string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} HOST_NAME_L) string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} HOST_NAME_L)
file(TO_CMAKE_PATH $ENV{ANDROID_HOME} ANDROID_HOME_UNIX) file(TO_CMAKE_PATH $ENV{ANDROID_HOME} ANDROID_HOME_UNIX)
message(STATUS "Try using NDK \'${FILAMENT_NDK_VERSION}\'")
if (NOT FILAMENT_NDK_VERSION) if (NOT FILAMENT_NDK_VERSION)
file(READ "${CMAKE_CURRENT_LIST_DIR}/android/ndk.version" FILAMENT_NDK_VERSION) file(READ "${CMAKE_CURRENT_LIST_DIR}/common/versions" VERSIONS_STR)
string(REGEX MATCH "^\\d+" FILAMENT_NDK_VERSION ${FILAMENT_NDK_VERSION}) string(REGEX MATCH "GITHUB_NDK_VERSION=(\\d+)" _UNUSED ${VERSIONS_STR})
if(CMAKE_MATCH_1)
set(FILAMENT_NDK_VERSION "${CMAKE_MATCH_1}")
endif()
endif() endif()
message(STATUS "Using NDK \'${FILAMENT_NDK_VERSION}\'")
file(GLOB NDK_VERSIONS LIST_DIRECTORIES true ${ANDROID_HOME_UNIX}/ndk/${FILAMENT_NDK_VERSION}*) file(GLOB NDK_VERSIONS LIST_DIRECTORIES true ${ANDROID_HOME_UNIX}/ndk/${FILAMENT_NDK_VERSION}*)
list(SORT NDK_VERSIONS) list(SORT NDK_VERSIONS)
list(GET NDK_VERSIONS -1 NDK_VERSION) list(GET NDK_VERSIONS -1 NDK_VERSION)
+8 -2
View File
@@ -32,10 +32,16 @@ set(DIST_ARCH armeabi-v7a)
string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} HOST_NAME_L) string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} HOST_NAME_L)
file(TO_CMAKE_PATH $ENV{ANDROID_HOME} ANDROID_HOME_UNIX) file(TO_CMAKE_PATH $ENV{ANDROID_HOME} ANDROID_HOME_UNIX)
message(STATUS "Try using NDK \'${FILAMENT_NDK_VERSION}\'")
if (NOT FILAMENT_NDK_VERSION) if (NOT FILAMENT_NDK_VERSION)
file(READ "${CMAKE_CURRENT_LIST_DIR}/android/ndk.version" FILAMENT_NDK_VERSION) file(READ "${CMAKE_CURRENT_LIST_DIR}/common/versions" VERSIONS_STR)
string(REGEX MATCH "^\\d+" FILAMENT_NDK_VERSION ${FILAMENT_NDK_VERSION}) string(REGEX MATCH "GITHUB_NDK_VERSION=(\\d+)" _UNUSED ${VERSIONS_STR})
if(CMAKE_MATCH_1)
set(FILAMENT_NDK_VERSION "${CMAKE_MATCH_1}")
endif()
endif() endif()
message(STATUS "Using NDK \'${FILAMENT_NDK_VERSION}\'")
file(GLOB NDK_VERSIONS LIST_DIRECTORIES true ${ANDROID_HOME_UNIX}/ndk/${FILAMENT_NDK_VERSION}*) file(GLOB NDK_VERSIONS LIST_DIRECTORIES true ${ANDROID_HOME_UNIX}/ndk/${FILAMENT_NDK_VERSION}*)
list(SORT NDK_VERSIONS) list(SORT NDK_VERSIONS)
list(GET NDK_VERSIONS -1 NDK_VERSION) list(GET NDK_VERSIONS -1 NDK_VERSION)
+8 -2
View File
@@ -31,10 +31,16 @@ set(DIST_ARCH x86)
string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} HOST_NAME_L) string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} HOST_NAME_L)
file(TO_CMAKE_PATH $ENV{ANDROID_HOME} ANDROID_HOME_UNIX) file(TO_CMAKE_PATH $ENV{ANDROID_HOME} ANDROID_HOME_UNIX)
message(STATUS "Try using NDK \'${FILAMENT_NDK_VERSION}\'")
if (NOT FILAMENT_NDK_VERSION) if (NOT FILAMENT_NDK_VERSION)
file(READ "${CMAKE_CURRENT_LIST_DIR}/android/ndk.version" FILAMENT_NDK_VERSION) file(READ "${CMAKE_CURRENT_LIST_DIR}/common/versions" VERSIONS_STR)
string(REGEX MATCH "^\\d+" FILAMENT_NDK_VERSION ${FILAMENT_NDK_VERSION}) string(REGEX MATCH "GITHUB_NDK_VERSION=(\\d+)" _UNUSED ${VERSIONS_STR})
if(CMAKE_MATCH_1)
set(FILAMENT_NDK_VERSION "${CMAKE_MATCH_1}")
endif()
endif() endif()
message(STATUS "Using NDK \'${FILAMENT_NDK_VERSION}\'")
file(GLOB NDK_VERSIONS LIST_DIRECTORIES true ${ANDROID_HOME_UNIX}/ndk/${FILAMENT_NDK_VERSION}*) file(GLOB NDK_VERSIONS LIST_DIRECTORIES true ${ANDROID_HOME_UNIX}/ndk/${FILAMENT_NDK_VERSION}*)
list(SORT NDK_VERSIONS) list(SORT NDK_VERSIONS)
list(GET NDK_VERSIONS -1 NDK_VERSION) list(GET NDK_VERSIONS -1 NDK_VERSION)
+8 -2
View File
@@ -31,10 +31,16 @@ set(DIST_ARCH x86_64)
string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} HOST_NAME_L) string(TOLOWER ${CMAKE_HOST_SYSTEM_NAME} HOST_NAME_L)
file(TO_CMAKE_PATH $ENV{ANDROID_HOME} ANDROID_HOME_UNIX) file(TO_CMAKE_PATH $ENV{ANDROID_HOME} ANDROID_HOME_UNIX)
message(STATUS "Try using NDK \'${FILAMENT_NDK_VERSION}\'")
if (NOT FILAMENT_NDK_VERSION) if (NOT FILAMENT_NDK_VERSION)
file(READ "${CMAKE_CURRENT_LIST_DIR}/android/ndk.version" FILAMENT_NDK_VERSION) file(READ "${CMAKE_CURRENT_LIST_DIR}/common/versions" VERSIONS_STR)
string(REGEX MATCH "^\\d+" FILAMENT_NDK_VERSION ${FILAMENT_NDK_VERSION}) string(REGEX MATCH "GITHUB_NDK_VERSION=(\\d+)" _UNUSED ${VERSIONS_STR})
if(CMAKE_MATCH_1)
set(FILAMENT_NDK_VERSION "${CMAKE_MATCH_1}")
endif()
endif() endif()
message(STATUS "Using NDK \'${FILAMENT_NDK_VERSION}\'")
file(GLOB NDK_VERSIONS LIST_DIRECTORIES true ${ANDROID_HOME_UNIX}/ndk/${FILAMENT_NDK_VERSION}*) file(GLOB NDK_VERSIONS LIST_DIRECTORIES true ${ANDROID_HOME_UNIX}/ndk/${FILAMENT_NDK_VERSION}*)
list(SORT NDK_VERSIONS) list(SORT NDK_VERSIONS)
list(GET NDK_VERSIONS -1 NDK_VERSION) list(GET NDK_VERSIONS -1 NDK_VERSION)
-12
View File
@@ -1,16 +1,4 @@
#!/bin/bash #!/bin/bash
if [ `uname` == "Linux" ];then
source `dirname $0`/../linux/ci-common.sh
elif [ `uname` == "Darwin" ];then
curl -OL https://github.com/ninja-build/ninja/releases/download/v1.10.2/ninja-mac.zip
unzip -q ninja-mac.zip
else
echo "Unsupported OS"
exit 1
fi
chmod +x ninja
export PATH="$PWD:$PATH"
# Install emscripten. # Install emscripten.
curl -L https://github.com/emscripten-core/emsdk/archive/refs/tags/3.1.60.zip > emsdk.zip curl -L https://github.com/emscripten-core/emsdk/archive/refs/tags/3.1.60.zip > emsdk.zip
+8
View File
@@ -194,6 +194,8 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanDriver.cpp src/vulkan/VulkanDriver.cpp
src/vulkan/VulkanDriver.h src/vulkan/VulkanDriver.h
src/vulkan/VulkanDriverFactory.h src/vulkan/VulkanDriverFactory.h
src/vulkan/VulkanExternalImageManager.cpp
src/vulkan/VulkanExternalImageManager.h
src/vulkan/VulkanFboCache.cpp src/vulkan/VulkanFboCache.cpp
src/vulkan/VulkanFboCache.h src/vulkan/VulkanFboCache.h
src/vulkan/VulkanHandles.cpp src/vulkan/VulkanHandles.cpp
@@ -257,8 +259,11 @@ if (FILAMENT_SUPPORTS_WEBGPU)
src/webgpu/WebGPUDriver.h src/webgpu/WebGPUDriver.h
src/webgpu/WebGPUHandles.cpp src/webgpu/WebGPUHandles.cpp
src/webgpu/WebGPUHandles.h src/webgpu/WebGPUHandles.h
src/webgpu/WebGPUPipelineCreation.cpp
src/webgpu/WebGPUPipelineCreation.h
src/webgpu/WebGPUSwapChain.cpp src/webgpu/WebGPUSwapChain.cpp
src/webgpu/WebGPUSwapChain.h 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)
@@ -534,6 +539,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
@@ -294,6 +294,16 @@ public:
VkQueue getProtectedGraphicsQueue() const noexcept; VkQueue getProtectedGraphicsQueue() const noexcept;
struct ExternalImageMetadata { struct ExternalImageMetadata {
/**
* The Filament texture format.
*/
TextureFormat filamentFormat;
/**
* The Filament texture usage.
*/
TextureUsage filamentUsage;
/** /**
* The width of the external image * The width of the external image
*/ */
@@ -304,11 +314,6 @@ public:
*/ */
uint32_t height; uint32_t height;
/**
* The layerCount of the external image
*/
uint32_t layerCount;
/** /**
* The layer count of the external image * The layer count of the external image
*/ */
@@ -324,11 +329,6 @@ public:
*/ */
VkFormat format; VkFormat format;
/**
* An external buffer can be protected. This tells you if it is.
*/
bool isProtected;
/** /**
* The type of external format (opaque int) if used. * The type of external format (opaque int) if used.
*/ */
@@ -348,20 +348,61 @@ public:
* Heap information * Heap information
*/ */
uint32_t memoryTypeBits; uint32_t memoryTypeBits;
/**
* Ycbcr conversion components
*/
VkComponentMapping ycbcrConversionComponents;
/**
* Ycbcr model
*/
VkSamplerYcbcrModelConversion ycbcrModel;
/**
* Ycbcr range
*/
VkSamplerYcbcrRange ycbcrRange;
/**
* Ycbcr x chroma offset
*/
VkChromaLocation xChromaOffset;
/**
* Ycbcr y chroma offset
*/
VkChromaLocation yChromaOffset;
}; };
virtual ExternalImageMetadata getExternalImageMetadata(ExternalImageHandleRef externalImage);
using ImageData = std::pair<VkImage, VkDeviceMemory>;
virtual ImageData createExternalImageData(ExternalImageHandleRef externalImage,
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex,
VkImageUsageFlags usage);
virtual VkSampler createExternalSampler(SamplerYcbcrConversion chroma, // Note that the image metadata might change per-frame, hence we need a method for extracting
SamplerParams sampler, uint32_t internalFormat); // it.
virtual ExternalImageMetadata extractExternalImageMetadata(ExternalImageHandleRef image) const {
return {};
}
virtual VkImageView createExternalImageView(SamplerYcbcrConversion chroma, struct ImageData {
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range, struct Bundle {
VkImageViewType viewType, VkComponentMapping swizzle); VkImage image = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
inline bool valid() const noexcept {
return image != VK_NULL_HANDLE;
}
};
// It's possible for the external image to also have a known VK format. We need to create an
// image for that in case we are not looking to use an external "sampler" with this image.
Bundle internal;
// If we get a externalFormat in the metadata, then we should create an image with
// VK_FORMAT_UNDEFINED
Bundle external;
};
virtual ImageData createVkImageFromExternal(ExternalImageHandleRef image) const {
return {};
}
protected: protected:
virtual ExtensionSet getSwapchainInstanceExtensions() const; virtual ExtensionSet getSwapchainInstanceExtensions() const;
@@ -374,20 +415,6 @@ private:
// Platform dependent helper methods // Platform dependent helper methods
static ExtensionSet getSwapchainInstanceExtensionsImpl(); static ExtensionSet getSwapchainInstanceExtensionsImpl();
static ExternalImageMetadata getExternalImageMetadataImpl(ExternalImageHandleRef externalImage,
VkDevice device);
static ImageData createExternalImageDataImpl(ExternalImageHandleRef externalImage,
VkDevice device, const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex,
VkImageUsageFlags usage);
static VkSampler createExternalSamplerImpl(VkDevice device,
SamplerYcbcrConversion chroma, SamplerParams sampler,
uint32_t internalFormat);
static VkImageView createExternalImageViewImpl(VkDevice device,
SamplerYcbcrConversion chroma, uint32_t internalFormat, VkImage image,
VkImageSubresourceRange range, VkImageViewType viewType,
VkComponentMapping swizzle);
// Platform dependent helper methods // Platform dependent helper methods
static SurfaceBundle createVkSurfaceKHRImpl(void* nativeWindow, VkInstance instance, static SurfaceBundle createVkSurfaceKHRImpl(void* nativeWindow, VkInstance instance,
uint64_t flags) noexcept; uint64_t flags) noexcept;
@@ -26,7 +26,7 @@ namespace filament::backend {
class VulkanPlatformAndroid : public VulkanPlatform { class VulkanPlatformAndroid : public VulkanPlatform {
public: public:
Platform::ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer, ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer,
bool sRGB) noexcept; bool sRGB) noexcept;
struct UTILS_PUBLIC ExternalImageDescAndroid { struct UTILS_PUBLIC ExternalImageDescAndroid {
@@ -39,31 +39,26 @@ public:
ExternalImageDescAndroid UTILS_PUBLIC getExternalImageDesc( ExternalImageDescAndroid UTILS_PUBLIC getExternalImageDesc(
ExternalImageHandleRef externalImage) const noexcept; ExternalImageHandleRef externalImage) const noexcept;
virtual ExternalImageMetadata extractExternalImageMetadata(
ExternalImageHandleRef image) const override;
virtual ImageData createVkImageFromExternal(ExternalImageHandleRef image) const override;
protected: protected:
virtual ExtensionSet getSwapchainInstanceExtensions() const override;
using SurfaceBundle = VulkanPlatform::SurfaceBundle;
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
uint64_t flags) const noexcept override;
private:
struct ExternalImageVulkanAndroid : public Platform::ExternalImage { struct ExternalImageVulkanAndroid : public Platform::ExternalImage {
AHardwareBuffer* aHardwareBuffer = nullptr; AHardwareBuffer* aHardwareBuffer = nullptr;
bool sRGB = false; bool sRGB = false;
unsigned int width; // Texture width
unsigned int height; // Texture height
TextureFormat format;// Texture format
TextureUsage usage; // Texture usage flags
protected: protected:
~ExternalImageVulkanAndroid() override; ~ExternalImageVulkanAndroid() override;
}; };
virtual ExternalImageMetadata getExternalImageMetadata(ExternalImageHandleRef externalImage);
using ImageData = VulkanPlatform::ImageData;
virtual ImageData createExternalImageData(ExternalImageHandleRef externalImage,
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex,
VkImageUsageFlags usage);
virtual ExtensionSet getSwapchainInstanceExtensions() const;
using SurfaceBundle = VulkanPlatform::SurfaceBundle;
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
uint64_t flags) const noexcept;
}; };
}// namespace filament::backend }// namespace filament::backend
+2 -2
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();
+2 -2
View File
@@ -1408,8 +1408,8 @@ void MetalDriver::setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph, Primit
auto primitive = handle_cast<MetalRenderPrimitive>(rph); auto primitive = handle_cast<MetalRenderPrimitive>(rph);
auto vertexBuffer = handle_cast<MetalVertexBuffer>(vbh); auto vertexBuffer = handle_cast<MetalVertexBuffer>(vbh);
auto indexBuffer = handle_cast<MetalIndexBuffer>(ibh); auto indexBuffer = handle_cast<MetalIndexBuffer>(ibh);
MetalVertexBufferInfo const* const vbi = handle_cast<MetalVertexBufferInfo>(vertexBuffer->vbih); primitive->vertexBuffer = vertexBuffer;
primitive->setBuffers(vbi, vertexBuffer, indexBuffer); primitive->indexBuffer = indexBuffer;
primitive->type = pt; primitive->type = pt;
} }
@@ -194,12 +194,8 @@ struct MetalIndexBuffer : public HwIndexBuffer {
}; };
struct MetalRenderPrimitive : public HwRenderPrimitive { struct MetalRenderPrimitive : public HwRenderPrimitive {
MetalRenderPrimitive();
void setBuffers(MetalVertexBufferInfo const* const vbi,
MetalVertexBuffer* vertexBuffer, MetalIndexBuffer* indexBuffer);
// The pointers to MetalVertexBuffer and MetalIndexBuffer are "weak". // The pointers to MetalVertexBuffer and MetalIndexBuffer are "weak".
// The MetalVertexBuffer and MetalIndexBuffer must outlive the MetalRenderPrimitive. // The MetalVertexBuffer and MetalIndexBuffer must outlive the MetalRenderPrimitive.
MetalVertexBuffer* vertexBuffer = nullptr; MetalVertexBuffer* vertexBuffer = nullptr;
MetalIndexBuffer* indexBuffer = nullptr; MetalIndexBuffer* indexBuffer = nullptr;
}; };
@@ -380,7 +376,6 @@ public:
math::uint2 getAttachmentSize() noexcept; math::uint2 getAttachmentSize() noexcept;
bool isDefaultRenderTarget() const { return defaultRenderTarget; }
uint8_t getSamples() const { return samples; } uint8_t getSamples() const { return samples; }
Attachment getDrawColorAttachment(size_t index); Attachment getDrawColorAttachment(size_t index);
@@ -536,15 +536,6 @@ MetalIndexBuffer::MetalIndexBuffer(MetalContext& context, BufferUsage usage, uin
uint32_t indexCount) : HwIndexBuffer(elementSize, indexCount), uint32_t indexCount) : HwIndexBuffer(elementSize, indexCount),
buffer(context, BufferObjectBinding::VERTEX, usage, elementSize * indexCount, true) { } buffer(context, BufferObjectBinding::VERTEX, usage, elementSize * indexCount, true) { }
MetalRenderPrimitive::MetalRenderPrimitive() {
}
void MetalRenderPrimitive::setBuffers(MetalVertexBufferInfo const* const vbi,
MetalVertexBuffer* vertexBuffer, MetalIndexBuffer* indexBuffer) {
this->vertexBuffer = vertexBuffer;
this->indexBuffer = indexBuffer;
}
MetalProgram::MetalProgram(MetalContext& context, Program&& program) noexcept MetalProgram::MetalProgram(MetalContext& context, Program&& program) noexcept
: HwProgram(program.getName()), mContext(context) { : HwProgram(program.getName()), mContext(context) {
mToken = context.shaderCompiler->createProgram(program.getName(), std::move(program)); mToken = context.shaderCompiler->createProgram(program.getName(), std::move(program));
+29 -37
View File
@@ -16,9 +16,15 @@
#include "GLUtils.h" #include "GLUtils.h"
#include "private/backend/Driver.h"
#include <utils/compiler.h>
#include <utils/ostream.h>
#include <utils/trap.h> #include <utils/trap.h>
#include "private/backend/Driver.h" #include <string_view>
#include <stddef.h>
namespace filament::backend { namespace filament::backend {
@@ -28,38 +34,31 @@ using namespace utils;
namespace GLUtils { namespace GLUtils {
UTILS_NOINLINE UTILS_NOINLINE
const char* getGLError(GLenum error) noexcept { std::string_view getGLErrorString(GLenum error) noexcept {
const char* string = "unknown";
switch (error) { switch (error) {
case GL_NO_ERROR: case GL_NO_ERROR:
string = "GL_NO_ERROR"; return "GL_NO_ERROR";
break;
case GL_INVALID_ENUM: case GL_INVALID_ENUM:
string = "GL_INVALID_ENUM"; return "GL_INVALID_ENUM";
break;
case GL_INVALID_VALUE: case GL_INVALID_VALUE:
string = "GL_INVALID_VALUE"; return "GL_INVALID_VALUE";
break;
case GL_INVALID_OPERATION: case GL_INVALID_OPERATION:
string = "GL_INVALID_OPERATION"; return "GL_INVALID_OPERATION";
break;
case GL_INVALID_FRAMEBUFFER_OPERATION: case GL_INVALID_FRAMEBUFFER_OPERATION:
string = "GL_INVALID_FRAMEBUFFER_OPERATION"; return "GL_INVALID_FRAMEBUFFER_OPERATION";
break;
case GL_OUT_OF_MEMORY: case GL_OUT_OF_MEMORY:
string = "GL_OUT_OF_MEMORY"; return "GL_OUT_OF_MEMORY";
break;
default: default:
break; break;
} }
return string; return "unknown";
} }
UTILS_NOINLINE UTILS_NOINLINE
GLenum checkGLError(io::ostream& out, const char* function, size_t line) noexcept { GLenum checkGLError(io::ostream& out, const char* function, size_t line) noexcept {
GLenum const error = glGetError(); GLenum const error = glGetError();
if (error != GL_NO_ERROR) { if (UTILS_VERY_UNLIKELY(error != GL_NO_ERROR)) {
const char* string = getGLError(error); auto const string = getGLErrorString(error);
out << "OpenGL error " << io::hex << error << " (" << string << ") in \"" out << "OpenGL error " << io::hex << error << " (" << string << ") in \""
<< function << "\" at line " << io::dec << line << io::endl; << function << "\" at line " << io::dec << line << io::endl;
} }
@@ -69,46 +68,39 @@ GLenum checkGLError(io::ostream& out, const char* function, size_t line) noexcep
UTILS_NOINLINE UTILS_NOINLINE
void assertGLError(io::ostream& out, const char* function, size_t line) noexcept { void assertGLError(io::ostream& out, const char* function, size_t line) noexcept {
GLenum const err = checkGLError(out, function, line); GLenum const err = checkGLError(out, function, line);
if (err != GL_NO_ERROR) { if (UTILS_VERY_UNLIKELY(err != GL_NO_ERROR)) {
debug_trap(); debug_trap();
} }
} }
UTILS_NOINLINE UTILS_NOINLINE
const char* getFramebufferStatus(GLenum status) noexcept { std::string_view getFramebufferStatusString(GLenum status) noexcept {
const char* string = "unknown";
switch (status) { switch (status) {
case GL_FRAMEBUFFER_COMPLETE: case GL_FRAMEBUFFER_COMPLETE:
string = "GL_FRAMEBUFFER_COMPLETE"; return "GL_FRAMEBUFFER_COMPLETE";
break;
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
string = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
string = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
break;
case GL_FRAMEBUFFER_UNSUPPORTED: case GL_FRAMEBUFFER_UNSUPPORTED:
string = "GL_FRAMEBUFFER_UNSUPPORTED"; return "GL_FRAMEBUFFER_UNSUPPORTED";
break;
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
case GL_FRAMEBUFFER_UNDEFINED: case GL_FRAMEBUFFER_UNDEFINED:
string = "GL_FRAMEBUFFER_UNDEFINED"; return "GL_FRAMEBUFFER_UNDEFINED";
break;
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
string = "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"; return "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE";
break;
#endif #endif
default: default:
break; break;
} }
return string; return "unknown";
} }
UTILS_NOINLINE UTILS_NOINLINE
GLenum checkFramebufferStatus(io::ostream& out, GLenum target, const char* function, size_t line) noexcept { GLenum checkFramebufferStatus(io::ostream& out, GLenum target, const char* function, size_t line) noexcept {
GLenum const status = glCheckFramebufferStatus(target); GLenum const status = glCheckFramebufferStatus(target);
if (status != GL_FRAMEBUFFER_COMPLETE) { if (UTILS_VERY_UNLIKELY(status != GL_FRAMEBUFFER_COMPLETE)) {
const char* string = getFramebufferStatus(status); auto const string = getFramebufferStatusString(status);
out << "OpenGL framebuffer error " << io::hex << status << " (" << string << ") in \"" out << "OpenGL framebuffer error " << io::hex << status << " (" << string << ") in \""
<< function << "\" at line " << io::dec << line << io::endl; << function << "\" at line " << io::dec << line << io::endl;
} }
@@ -118,7 +110,7 @@ GLenum checkFramebufferStatus(io::ostream& out, GLenum target, const char* funct
UTILS_NOINLINE UTILS_NOINLINE
void assertFramebufferStatus(io::ostream& out, GLenum target, const char* function, size_t line) noexcept { void assertFramebufferStatus(io::ostream& out, GLenum target, const char* function, size_t line) noexcept {
GLenum const status = checkFramebufferStatus(out, target, function, line); GLenum const status = checkFramebufferStatus(out, target, function, line);
if (status != GL_FRAMEBUFFER_COMPLETE) { if (UTILS_VERY_UNLIKELY(status != GL_FRAMEBUFFER_COMPLETE)) {
debug_trap(); debug_trap();
} }
} }
+61 -36
View File
@@ -17,29 +17,26 @@
#ifndef TNT_FILAMENT_BACKEND_OPENGL_GLUTILS_H #ifndef TNT_FILAMENT_BACKEND_OPENGL_GLUTILS_H
#define TNT_FILAMENT_BACKEND_OPENGL_GLUTILS_H #define TNT_FILAMENT_BACKEND_OPENGL_GLUTILS_H
#include <utils/compiler.h>
#include <utils/debug.h> #include <utils/debug.h>
#include <utils/Log.h> #include <utils/ostream.h>
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include <string_view> #include <string_view>
#include <unordered_set> #include <unordered_set>
#include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include <string.h> #include <stdint.h>
#include "gl_headers.h" #include "gl_headers.h"
namespace filament::backend { namespace filament::backend::GLUtils {
namespace GLUtils {
const char* getGLError(GLenum error) noexcept; std::string_view getGLErrorString(GLenum error) noexcept;
GLenum checkGLError(utils::io::ostream& out, const char* function, size_t line) noexcept; GLenum checkGLError(utils::io::ostream& out, const char* function, size_t line) noexcept;
void assertGLError(utils::io::ostream& out, const char* function, size_t line) noexcept; void assertGLError(utils::io::ostream& out, const char* function, size_t line) noexcept;
const char* getFramebufferStatus(GLenum err) noexcept; std::string_view getFramebufferStatusString(GLenum err) noexcept;
GLenum checkFramebufferStatus(utils::io::ostream& out, GLenum target, const char* function, size_t line) noexcept; GLenum checkFramebufferStatus(utils::io::ostream& out, GLenum target, const char* function, size_t line) noexcept;
void assertFramebufferStatus(utils::io::ostream& out, GLenum target, const char* function, size_t line) noexcept; void assertFramebufferStatus(utils::io::ostream& out, GLenum target, const char* function, size_t line) noexcept;
@@ -53,7 +50,7 @@ void assertFramebufferStatus(utils::io::ostream& out, GLenum target, const char*
# define CHECK_GL_FRAMEBUFFER_STATUS(out, target) { GLUtils::checkFramebufferStatus(out, target, __func__, __LINE__); } # define CHECK_GL_FRAMEBUFFER_STATUS(out, target) { GLUtils::checkFramebufferStatus(out, target, __func__, __LINE__); }
#endif #endif
constexpr GLuint getComponentCount(ElementType type) noexcept { constexpr GLuint getComponentCount(ElementType const type) noexcept {
using ElementType = ElementType; using ElementType = ElementType;
switch (type) { switch (type) {
case ElementType::BYTE: case ElementType::BYTE:
@@ -87,27 +84,29 @@ constexpr GLuint getComponentCount(ElementType type) noexcept {
case ElementType::USHORT4: case ElementType::USHORT4:
return 4; return 4;
} }
// should never happen
return 1;
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// Our enums to GLenum conversions // Our enums to GLenum conversions
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
constexpr GLbitfield getAttachmentBitfield(TargetBufferFlags flags) noexcept { constexpr GLbitfield getAttachmentBitfield(TargetBufferFlags const flags) noexcept {
GLbitfield mask = 0; GLbitfield mask = 0;
if (any(flags & TargetBufferFlags::COLOR_ALL)) { if (any(flags & TargetBufferFlags::COLOR_ALL)) {
mask |= (GLbitfield)GL_COLOR_BUFFER_BIT; mask |= GLbitfield(GL_COLOR_BUFFER_BIT);
} }
if (any(flags & TargetBufferFlags::DEPTH)) { if (any(flags & TargetBufferFlags::DEPTH)) {
mask |= (GLbitfield)GL_DEPTH_BUFFER_BIT; mask |= GLbitfield(GL_DEPTH_BUFFER_BIT);
} }
if (any(flags & TargetBufferFlags::STENCIL)) { if (any(flags & TargetBufferFlags::STENCIL)) {
mask |= (GLbitfield)GL_STENCIL_BUFFER_BIT; mask |= GLbitfield(GL_STENCIL_BUFFER_BIT);
} }
return mask; return mask;
} }
constexpr GLenum getBufferUsage(BufferUsage usage) noexcept { constexpr GLenum getBufferUsage(BufferUsage const usage) noexcept {
switch (usage) { switch (usage) {
case BufferUsage::STATIC: case BufferUsage::STATIC:
return GL_STATIC_DRAW; return GL_STATIC_DRAW;
@@ -116,7 +115,7 @@ constexpr GLenum getBufferUsage(BufferUsage usage) noexcept {
} }
} }
constexpr GLenum getBufferBindingType(BufferObjectBinding bindingType) noexcept { constexpr GLenum getBufferBindingType(BufferObjectBinding const bindingType) noexcept {
switch (bindingType) { switch (bindingType) {
case BufferObjectBinding::VERTEX: case BufferObjectBinding::VERTEX:
return GL_ARRAY_BUFFER; return GL_ARRAY_BUFFER;
@@ -135,13 +134,15 @@ constexpr GLenum getBufferBindingType(BufferObjectBinding bindingType) noexcept
return 0x90D2; // just to return something return 0x90D2; // just to return something
#endif #endif
} }
// should never happen
return GL_ARRAY_BUFFER;
} }
constexpr GLboolean getNormalization(bool normalized) noexcept { constexpr GLboolean getNormalization(bool const normalized) noexcept {
return GLboolean(normalized ? GL_TRUE : GL_FALSE); return GLboolean(normalized ? GL_TRUE : GL_FALSE);
} }
constexpr GLenum getComponentType(ElementType type) noexcept { constexpr GLenum getComponentType(ElementType const type) noexcept {
using ElementType = ElementType; using ElementType = ElementType;
switch (type) { switch (type) {
case ElementType::BYTE: case ElementType::BYTE:
@@ -184,9 +185,11 @@ constexpr GLenum getComponentType(ElementType type) noexcept {
return GL_HALF_FLOAT_OES; return GL_HALF_FLOAT_OES;
#endif #endif
} }
// should never happen
return GL_INT;
} }
constexpr GLenum getTextureTargetNotExternal(SamplerType target) noexcept { constexpr GLenum getTextureTargetNotExternal(SamplerType const target) noexcept {
switch (target) { switch (target) {
case SamplerType::SAMPLER_2D: case SamplerType::SAMPLER_2D:
return GL_TEXTURE_2D; return GL_TEXTURE_2D;
@@ -202,14 +205,16 @@ constexpr GLenum getTextureTargetNotExternal(SamplerType target) noexcept {
// we should never be here // we should never be here
return GL_TEXTURE_2D; return GL_TEXTURE_2D;
} }
// should never happen
return GL_TEXTURE_2D;
} }
constexpr GLenum getCubemapTarget(uint16_t layer) noexcept { constexpr GLenum getCubemapTarget(uint16_t const layer) noexcept {
assert_invariant(layer <= 5); assert_invariant(layer <= 5);
return GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer; return GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer;
} }
constexpr GLenum getWrapMode(SamplerWrapMode mode) noexcept { constexpr GLenum getWrapMode(SamplerWrapMode const mode) noexcept {
using SamplerWrapMode = SamplerWrapMode; using SamplerWrapMode = SamplerWrapMode;
switch (mode) { switch (mode) {
case SamplerWrapMode::REPEAT: case SamplerWrapMode::REPEAT:
@@ -219,6 +224,8 @@ constexpr GLenum getWrapMode(SamplerWrapMode mode) noexcept {
case SamplerWrapMode::MIRRORED_REPEAT: case SamplerWrapMode::MIRRORED_REPEAT:
return GL_MIRRORED_REPEAT; return GL_MIRRORED_REPEAT;
} }
// should never happen
return GL_CLAMP_TO_EDGE;
} }
constexpr GLenum getTextureFilter(SamplerMinFilter filter) noexcept { constexpr GLenum getTextureFilter(SamplerMinFilter filter) noexcept {
@@ -234,6 +241,8 @@ constexpr GLenum getTextureFilter(SamplerMinFilter filter) noexcept {
return GL_NEAREST_MIPMAP_NEAREST return GL_NEAREST_MIPMAP_NEAREST
- GLenum(SamplerMinFilter::NEAREST_MIPMAP_NEAREST) + GLenum(filter); - GLenum(SamplerMinFilter::NEAREST_MIPMAP_NEAREST) + GLenum(filter);
} }
// should never happen
return GL_NEAREST;
} }
constexpr GLenum getTextureFilter(SamplerMagFilter filter) noexcept { constexpr GLenum getTextureFilter(SamplerMagFilter filter) noexcept {
@@ -241,7 +250,7 @@ constexpr GLenum getTextureFilter(SamplerMagFilter filter) noexcept {
} }
constexpr GLenum getBlendEquationMode(BlendEquation mode) noexcept { constexpr GLenum getBlendEquationMode(BlendEquation const mode) noexcept {
using BlendEquation = BlendEquation; using BlendEquation = BlendEquation;
switch (mode) { switch (mode) {
case BlendEquation::ADD: return GL_FUNC_ADD; case BlendEquation::ADD: return GL_FUNC_ADD;
@@ -250,9 +259,11 @@ constexpr GLenum getBlendEquationMode(BlendEquation mode) noexcept {
case BlendEquation::MIN: return GL_MIN; case BlendEquation::MIN: return GL_MIN;
case BlendEquation::MAX: return GL_MAX; case BlendEquation::MAX: return GL_MAX;
} }
// should never happen
return GL_FUNC_ADD;
} }
constexpr GLenum getBlendFunctionMode(BlendFunction mode) noexcept { constexpr GLenum getBlendFunctionMode(BlendFunction const mode) noexcept {
using BlendFunction = BlendFunction; using BlendFunction = BlendFunction;
switch (mode) { switch (mode) {
case BlendFunction::ZERO: return GL_ZERO; case BlendFunction::ZERO: return GL_ZERO;
@@ -267,9 +278,11 @@ constexpr GLenum getBlendFunctionMode(BlendFunction mode) noexcept {
case BlendFunction::ONE_MINUS_DST_ALPHA: return GL_ONE_MINUS_DST_ALPHA; case BlendFunction::ONE_MINUS_DST_ALPHA: return GL_ONE_MINUS_DST_ALPHA;
case BlendFunction::SRC_ALPHA_SATURATE: return GL_SRC_ALPHA_SATURATE; case BlendFunction::SRC_ALPHA_SATURATE: return GL_SRC_ALPHA_SATURATE;
} }
// should never happen
return GL_ONE;
} }
constexpr GLenum getCompareFunc(SamplerCompareFunc func) noexcept { constexpr GLenum getCompareFunc(SamplerCompareFunc const func) noexcept {
switch (func) { switch (func) {
case SamplerCompareFunc::LE: return GL_LEQUAL; case SamplerCompareFunc::LE: return GL_LEQUAL;
case SamplerCompareFunc::GE: return GL_GEQUAL; case SamplerCompareFunc::GE: return GL_GEQUAL;
@@ -280,28 +293,30 @@ constexpr GLenum getCompareFunc(SamplerCompareFunc func) noexcept {
case SamplerCompareFunc::A: return GL_ALWAYS; case SamplerCompareFunc::A: return GL_ALWAYS;
case SamplerCompareFunc::N: return GL_NEVER; case SamplerCompareFunc::N: return GL_NEVER;
} }
// should never happen
return GL_LEQUAL;
} }
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
constexpr GLenum getTextureCompareMode(SamplerCompareMode mode) noexcept { constexpr GLenum getTextureCompareMode(SamplerCompareMode const mode) noexcept {
return mode == SamplerCompareMode::NONE ? return mode == SamplerCompareMode::NONE ?
GL_NONE : GL_COMPARE_REF_TO_TEXTURE; GL_NONE : GL_COMPARE_REF_TO_TEXTURE;
} }
constexpr GLenum getTextureCompareFunc(SamplerCompareFunc func) noexcept { constexpr GLenum getTextureCompareFunc(SamplerCompareFunc const func) noexcept {
return getCompareFunc(func); return getCompareFunc(func);
} }
#endif #endif
constexpr GLenum getDepthFunc(SamplerCompareFunc func) noexcept { constexpr GLenum getDepthFunc(SamplerCompareFunc const func) noexcept {
return getCompareFunc(func); return getCompareFunc(func);
} }
constexpr GLenum getStencilFunc(SamplerCompareFunc func) noexcept { constexpr GLenum getStencilFunc(SamplerCompareFunc const func) noexcept {
return getCompareFunc(func); return getCompareFunc(func);
} }
constexpr GLenum getStencilOp(StencilOperation op) noexcept { constexpr GLenum getStencilOp(StencilOperation const op) noexcept {
switch (op) { switch (op) {
case StencilOperation::KEEP: return GL_KEEP; case StencilOperation::KEEP: return GL_KEEP;
case StencilOperation::ZERO: return GL_ZERO; case StencilOperation::ZERO: return GL_ZERO;
@@ -312,9 +327,11 @@ constexpr GLenum getStencilOp(StencilOperation op) noexcept {
case StencilOperation::DECR_WRAP: return GL_DECR_WRAP; case StencilOperation::DECR_WRAP: return GL_DECR_WRAP;
case StencilOperation::INVERT: return GL_INVERT; case StencilOperation::INVERT: return GL_INVERT;
} }
// should never happen
return GL_KEEP;
} }
constexpr GLenum getFormat(PixelDataFormat format) noexcept { constexpr GLenum getFormat(PixelDataFormat const format) noexcept {
using PixelDataFormat = PixelDataFormat; using PixelDataFormat = PixelDataFormat;
switch (format) { switch (format) {
case PixelDataFormat::RGB: return GL_RGB; case PixelDataFormat::RGB: return GL_RGB;
@@ -336,9 +353,11 @@ constexpr GLenum getFormat(PixelDataFormat format) noexcept {
default: return GL_NONE; default: return GL_NONE;
#endif #endif
} }
// should never happen
return GL_RGBA;
} }
constexpr GLenum getType(PixelDataType type) noexcept { constexpr GLenum getType(PixelDataType const type) noexcept {
using PixelDataType = PixelDataType; using PixelDataType = PixelDataType;
switch (type) { switch (type) {
case PixelDataType::UBYTE: return GL_UNSIGNED_BYTE; case PixelDataType::UBYTE: return GL_UNSIGNED_BYTE;
@@ -360,10 +379,12 @@ constexpr GLenum getType(PixelDataType type) noexcept {
default: return GL_NONE; default: return GL_NONE;
#endif #endif
} }
// should never happen
return GL_UNSIGNED_INT;
} }
#if !defined(__EMSCRIPTEN__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2) #if !defined(__EMSCRIPTEN__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2)
constexpr GLenum getSwizzleChannel(TextureSwizzle c) noexcept { constexpr GLenum getSwizzleChannel(TextureSwizzle const c) noexcept {
using TextureSwizzle = TextureSwizzle; using TextureSwizzle = TextureSwizzle;
switch (c) { switch (c) {
case TextureSwizzle::SUBSTITUTE_ZERO: return GL_ZERO; case TextureSwizzle::SUBSTITUTE_ZERO: return GL_ZERO;
@@ -373,10 +394,12 @@ constexpr GLenum getSwizzleChannel(TextureSwizzle c) noexcept {
case TextureSwizzle::CHANNEL_2: return GL_BLUE; case TextureSwizzle::CHANNEL_2: return GL_BLUE;
case TextureSwizzle::CHANNEL_3: return GL_ALPHA; case TextureSwizzle::CHANNEL_3: return GL_ALPHA;
} }
// should never happen
return GL_RED;
} }
#endif #endif
constexpr GLenum getCullingMode(CullingMode mode) noexcept { constexpr GLenum getCullingMode(CullingMode const mode) noexcept {
switch (mode) { switch (mode) {
case CullingMode::NONE: case CullingMode::NONE:
// should never happen // should never happen
@@ -388,11 +411,13 @@ constexpr GLenum getCullingMode(CullingMode mode) noexcept {
case CullingMode::FRONT_AND_BACK: case CullingMode::FRONT_AND_BACK:
return GL_FRONT_AND_BACK; return GL_FRONT_AND_BACK;
} }
// should never happen
return GL_FRONT_AND_BACK;
} }
// ES2 supported internal formats for texturing and how they map to a format/type // ES2 supported internal formats for texturing and how they map to a format/type
constexpr std::pair<GLenum, GLenum> textureFormatToFormatAndType( constexpr std::pair<GLenum, GLenum> textureFormatToFormatAndType(
TextureFormat format) noexcept { TextureFormat const format) noexcept {
switch (format) { switch (format) {
case TextureFormat::R8: return { 0x1909 /*GL_LUMINANCE*/, GL_UNSIGNED_BYTE }; case TextureFormat::R8: return { 0x1909 /*GL_LUMINANCE*/, GL_UNSIGNED_BYTE };
case TextureFormat::RGB8: return { GL_RGB, GL_UNSIGNED_BYTE }; case TextureFormat::RGB8: return { GL_RGB, GL_UNSIGNED_BYTE };
@@ -413,7 +438,7 @@ constexpr std::pair<GLenum, GLenum> textureFormatToFormatAndType(
// clang loses it on this one, and generates a huge jump table when // clang loses it on this one, and generates a huge jump table when
// inlined. So we don't mark it as inline (only constexpr) which solves the problem, // inlined. So we don't mark it as inline (only constexpr) which solves the problem,
// strangely, when not inlined, clang simply generates an array lookup. // strangely, when not inlined, clang simply generates an array lookup.
constexpr /* inline */ GLenum getInternalFormat(TextureFormat format) noexcept { constexpr /* inline */ GLenum getInternalFormat(TextureFormat const format) noexcept {
switch (format) { switch (format) {
/* Formats supported by our ES2 implementations */ /* Formats supported by our ES2 implementations */
@@ -661,7 +686,7 @@ public:
unordered_string_set split(const char* extensions) noexcept; unordered_string_set split(const char* extensions) noexcept;
} // namespace GLUtils } // namespace filament::backend::GLUtils
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_OPENGL_GLUTILS_H #endif // TNT_FILAMENT_BACKEND_OPENGL_GLUTILS_H
@@ -2098,6 +2098,7 @@ void OpenGLDriver::setAcquiredImage(Handle<HwStream> sh, void* hwbuffer, const m
glstream->user_thread.pending = mPlatform.transformAcquiredImage({ glstream->user_thread.pending = mPlatform.transformAcquiredImage({
hwbuffer, cb, userData, handler }); hwbuffer, cb, userData, handler });
glstream->user_thread.transform = transform;
if (glstream->user_thread.pending.image != nullptr) { if (glstream->user_thread.pending.image != nullptr) {
// If there's no pending image, do nothing. Note that GL_OES_EGL_image does not let you pass // If there's no pending image, do nothing. Note that GL_OES_EGL_image does not let you pass
@@ -85,6 +85,7 @@ OpenGLProgram::~OpenGLProgram() noexcept {
delete lazyInitializationData; delete lazyInitializationData;
ShaderCompilerService::terminate(mToken); ShaderCompilerService::terminate(mToken);
assert_invariant(!mToken);
} }
delete [] mUniformsRecords; delete [] mUniformsRecords;
@@ -17,11 +17,15 @@
#include "ShaderCompilerService.h" #include "ShaderCompilerService.h"
#include "BlobCacheKey.h" #include "BlobCacheKey.h"
#include "CallbackManager.h"
#include "CompilerThreadPool.h"
#include "OpenGLBlobCache.h" #include "OpenGLBlobCache.h"
#include "OpenGLDriver.h" #include "OpenGLDriver.h"
#include <iterator>
#include <private/backend/BackendUtils.h> #include <private/backend/BackendUtils.h>
#include <backend/DriverEnums.h>
#include <backend/Program.h> #include <backend/Program.h>
#include <utils/compiler.h> #include <utils/compiler.h>
@@ -34,9 +38,9 @@
#include <utils/Panic.h> #include <utils/Panic.h>
#include <utils/Systrace.h> #include <utils/Systrace.h>
#include <algorithm>
#include <array> #include <array>
#include <cctype> #include <cctype>
#include <chrono>
#include <mutex> #include <mutex>
#include <memory> #include <memory>
#include <string> #include <string>
@@ -47,6 +51,7 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <string.h>
namespace filament::backend { namespace filament::backend {
@@ -54,17 +59,17 @@ using namespace utils;
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
static inline std::string to_string(bool b) noexcept { return b ? "true" : "false"; } static std::string to_string(bool const b) { return b ? "true" : "false"; }
static inline std::string to_string(int i) noexcept { return std::to_string(i); } static std::string to_string(int const i) { return std::to_string(i); }
static inline std::string to_string(float f) noexcept { return "float(" + std::to_string(f) + ")"; } static std::string to_string(float const f) { return "float(" + std::to_string(f) + ")"; }
static void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name, static void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name,
GLuint shaderId, CString const& sourceCode) noexcept; GLuint shaderId, CString const& sourceCode) noexcept;
static void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept; static void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept;
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context, char* source, static void process_GOOGLE_cpp_style_line_directive(OpenGLContext const& context, char* source,
size_t len) noexcept; size_t len) noexcept;
static void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount, char* source, static void process_OVR_multiview2(OpenGLContext const& context, int32_t eyeCount, char* source,
size_t len) noexcept; size_t len) noexcept;
static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) 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 std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept;
@@ -72,20 +77,15 @@ static std::array<std::string_view, 3> splitShaderSource(std::string_view source
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
struct ShaderCompilerService::OpenGLProgramToken : ProgramToken { struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
struct ProgramData {
GLuint program{};
shaders_t shaders{};
};
~OpenGLProgramToken() override; ~OpenGLProgramToken() override;
OpenGLProgramToken(ShaderCompilerService& compiler, utils::CString const& name) noexcept OpenGLProgramToken(ShaderCompilerService& compiler, CString const& name) noexcept
: compiler(compiler), name(name) { : compiler(compiler), name(name), handle(compiler.issueCallbackHandle()) {
} }
ShaderCompilerService& compiler; ShaderCompilerService& compiler;
utils::CString const& name; CString const& name;
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes; FixedCapacityVector<std::pair<CString, uint8_t>> attributes;
shaders_source_t shaderSourceCode; shaders_source_t shaderSourceCode;
void* user = nullptr; void* user = nullptr;
struct { struct {
@@ -93,48 +93,34 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
GLuint program = 0; GLuint program = 0;
} gl; // 12 bytes } gl; // 12 bytes
// Used in THREAD_POOL mode. The job from ThreadPool should call this when the token is ready to
// Sets the programData, typically from the compiler thread, and signal the main thread. // be used. It sends a signal to the engine thread being blocked upon the `wait` call, so that
// This is similar to std::promise::set_value. // the engine thread resumes its processing with the token.
void set(ProgramData const& data) noexcept { void signal() noexcept {
std::unique_lock const l(lock); std::unique_lock const l(lock);
programData = data;
signaled = true; signaled = true;
cond.notify_one(); cond.notify_one();
} }
// Get the programBinary, wait if necessary. // Used in THREAD_POOL mode. The engine thread should call this before accessing token's fields.
// This is similar to std::future::get // This may block until the token is ready to be used.
ProgramData const& get() const noexcept {
std::unique_lock l(lock);
cond.wait(l, [this](){ return signaled; });
return programData;
}
void wait() const noexcept { void wait() const noexcept {
std::unique_lock l(lock); std::unique_lock l(lock);
cond.wait(l, [this](){ return signaled; }); cond.wait(l, [this] { return signaled; });
}
// Checks if the programBinary is ready.
// This is similar to std::future::wait_for(0s)
bool isReady() const noexcept {
std::unique_lock l(lock);
using namespace std::chrono_literals;
return cond.wait_for(l, 0s, [this](){ return signaled; });
} }
CallbackManager::Handle handle{}; CallbackManager::Handle handle{};
BlobCacheKey key; BlobCacheKey key;
mutable utils::Mutex lock;
mutable utils::Condition cond;
ProgramData programData;
bool signaled = false;
bool canceled = false; // not part of the signaling // Used for the `THREAD_POOL` mode.
mutable Mutex lock;
mutable Condition cond;
bool signaled = false;
}; };
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() = default; ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() {
compiler.submitCallbackHandle(handle);
}
/* static */ void ShaderCompilerService::setUserData(const program_token_t& token, /* static */ void ShaderCompilerService::setUserData(const program_token_t& token,
void* user) noexcept { void* user) noexcept {
@@ -189,9 +175,9 @@ void ShaderCompilerService::init() noexcept {
if (mMode == Mode::THREAD_POOL) { if (mMode == Mode::THREAD_POOL) {
// - on Adreno there is a single compiler object. We can't use a pool > 1 // - on Adreno there is a single compiler object. We can't use a pool > 1
// also glProgramBinary blocks if other threads are compiling. // also glProgramBinary blocks if other threads are compiling.
// - on Mali shader compilation can be multi-threaded, but program linking happens on // - on Mali shader compilation can be multithreaded, but program linking happens on
// a single service thread, so we don't bother using more than one thread either. // a single service thread, so we don't bother using more than one thread either.
// - on PowerVR shader compilation and linking can be multi-threaded. // - on PowerVR shader compilation and linking can be multithreaded.
// How many threads should we use? // How many threads should we use?
// - on macOS (M1 MacBook Pro/Ventura) there is global lock around all GL APIs when using // - on macOS (M1 MacBook Pro/Ventura) there is global lock around all GL APIs when using
// a shared context, so parallel shader compilation yields no benefit. // a shared context, so parallel shader compilation yields no benefit.
@@ -220,7 +206,7 @@ void ShaderCompilerService::init() noexcept {
mShaderCompilerThreadCount = poolSize; mShaderCompilerThreadCount = poolSize;
mCompilerThreadPool.init(mShaderCompilerThreadCount, mCompilerThreadPool.init(mShaderCompilerThreadCount,
[&platform = mDriver.mPlatform, priority]() { [&platform = mDriver.mPlatform, priority] {
// give the thread a name // give the thread a name
JobSystem::setThreadName("CompilerThreadPool"); JobSystem::setThreadName("CompilerThreadPool");
// run at a slightly lower priority than other filament threads // run at a slightly lower priority than other filament threads
@@ -228,7 +214,7 @@ void ShaderCompilerService::init() noexcept {
// create a gl context current to this thread // create a gl context current to this thread
platform.createContext(true); platform.createContext(true);
}, },
[&platform = mDriver.mPlatform]() { [&platform = mDriver.mPlatform] {
// release context and thread state // release context and thread state
platform.releaseContext(); platform.releaseContext();
}); });
@@ -249,119 +235,65 @@ void ShaderCompilerService::terminate() noexcept {
} }
ShaderCompilerService::program_token_t ShaderCompilerService::createProgram( ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
utils::CString const& name, Program&& program) { CString const& name, Program&& program) {
auto& gl = mDriver.getContext(); auto& gl = mDriver.getContext();
// Create a token. A callback condition (handle) is internally created upon token creation.
auto token = std::make_shared<OpenGLProgramToken>(*this, name); auto token = std::make_shared<OpenGLProgramToken>(*this, name);
if (UTILS_UNLIKELY(gl.isES2())) { if (UTILS_UNLIKELY(gl.isES2())) {
token->attributes = std::move(program.getAttributes()); token->attributes = std::move(program.getAttributes());
} }
// Try retrieving the cached program blob if available.
token->gl.program = mBlobCache.retrieve(&token->key, mDriver.mPlatform, program); token->gl.program = mBlobCache.retrieve(&token->key, mDriver.mPlatform, program);
if (token->gl.program) { if (token->gl.program) {
return token; return token;
} }
token->handle = mCallbackManager.get(); // Initiate program compilation.
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue(); CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
switch (mMode) { switch (mMode) {
case Mode::THREAD_POOL: { case Mode::THREAD_POOL: {
// queue a compile job
mCompilerThreadPool.queue(priorityQueue, token, mCompilerThreadPool.queue(priorityQueue, token,
[this, &gl, program = std::move(program), token]() mutable { [this, &gl, program = std::move(program), token]() mutable {
// compile the shaders compileShaders(gl, std::move(program.getShadersSource()),
shaders_t shaders{}; program.getSpecializationConstants(), program.isMultiview(), token);
compileShaders(gl, linkProgram(gl, token);
std::move(program.getShadersSource()), // Now `token->gl.program` must be populated, so we signal the completion
program.getSpecializationConstants(), // of the linking. We don't need to check the result of the program here
program.isMultiview(), // because it'll be done in the engine thread.
shaders, token->signal();
token->shaderSourceCode); // We try caching the program blob after sending the signal. This allows us
// to unblock the engine thread as soon as the token is ready while
// link the program // performing an expensive caching operation still in the pool.
GLuint const glProgram = linkProgram(gl, shaders, token->attributes); tryCachingProgram(mBlobCache, mDriver.mPlatform, token);
OpenGLProgramToken::ProgramData programData;
programData.shaders = shaders;
// We need to query the link status here to guarantee that the
// program is compiled and linked now (we don't want this to be
// deferred to later). We don't care about the result at this point.
GLint status = GL_FALSE;
glGetProgramiv(glProgram, GL_LINK_STATUS, &status);
programData.program = glProgram;
// we don't need to check for success here, it'll be done on the
// main thread side.
token->set(programData);
mCallbackManager.put(token->handle);
// caching must be the last thing we do
if (token->key && status == GL_TRUE) {
// Attempt to cache. This calls glGetProgramBinary.
mBlobCache.insert(mDriver.mPlatform, token->key, glProgram);
}
}); });
break; break;
} }
case Mode::SYNCHRONOUS: case Mode::SYNCHRONOUS:
case Mode::ASYNCHRONOUS: { case Mode::ASYNCHRONOUS: {
// this cannot fail because we check compilation status after linking the program compileShaders(gl, std::move(program.getShadersSource()),
// shaders[] is filled with id of shader stages present. program.getSpecializationConstants(), program.isMultiview(), token);
compileShaders(gl,
std::move(program.getShadersSource()),
program.getSpecializationConstants(),
program.isMultiview(),
token->gl.shaders,
token->shaderSourceCode);
runAtNextTick(priorityQueue, token, [this, token](Job const&) { runAtNextTick(priorityQueue, token, [this, token](Job const&) {
assert_invariant(mMode != Mode::THREAD_POOL); assert_invariant(mMode != Mode::THREAD_POOL);
if (mMode == Mode::ASYNCHRONOUS) { if (mMode == Mode::ASYNCHRONOUS) {
// don't attempt to link this program if all shaders are not done compiling // Check link completion if link was initiated.
GLint status;
if (token->gl.program) { if (token->gl.program) {
glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status); return isLinkCompleted(token);
if (status == GL_FALSE) {
return false;
}
} else {
for (auto shader: token->gl.shaders) {
if (shader) {
glGetShaderiv(shader, GL_COMPLETION_STATUS, &status);
if (status == GL_FALSE) {
return false;
}
}
}
} }
} // Link hasn't been initiated, then check compile completion.
if (!isCompileCompleted(token)) {
if (!token->gl.program) {
// link the program, this also cannot fail because status is checked later.
token->gl.program = linkProgram(mDriver.getContext(),
token->gl.shaders, token->attributes);
if (mMode == Mode::ASYNCHRONOUS) {
// wait until the link finishes...
return false; return false;
} }
} }
if (!token->gl.program) {
assert_invariant(token->gl.program); linkProgram(mDriver.getContext(), token);
if (mMode == Mode::ASYNCHRONOUS) {
mCallbackManager.put(token->handle); return false;// Wait until the link finishes.
}
if (token->key) {
// TODO: technically we don't have to cache right now. Is it advantageous to
// do this later, maybe depending on CPU usage?
// attempt to cache if we don't have a thread pool (otherwise it's done
// by the pool).
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
} }
return true; return true;
}); });
break; break;
@@ -375,7 +307,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
return token; return token;
} }
GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t& token) { GLuint ShaderCompilerService::getProgram(program_token_t& token) {
GLuint const program = initialize(token); GLuint const program = initialize(token);
assert_invariant(token == nullptr); assert_invariant(token == nullptr);
#if !FILAMENT_ENABLE_MATDBG #if !FILAMENT_ENABLE_MATDBG
@@ -384,43 +316,29 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t&
return program; return program;
} }
/*
* Cancel program compilation. This function is responsible for cleaning up the ongoing
* compilation & link process. If the process is already completed by calling `initialize(token)`,
* this function is not called.
*/
/* static */ void ShaderCompilerService::terminate(program_token_t& token) { /* static */ void ShaderCompilerService::terminate(program_token_t& token) {
assert_invariant(token);
token->canceled = true; assert_invariant(token);// This function should be called when the token is still alive.
bool const isTickOpCanceled = token->compiler.cancelTickOp(token);
if (token->compiler.mMode == Mode::THREAD_POOL) { if (token->compiler.mMode == Mode::THREAD_POOL) {
auto job = token->compiler.mCompilerThreadPool.dequeue(token); auto const job = token->compiler.mCompilerThreadPool.dequeue(token);
if (!job) { if (!job) {
// The job is being executed right now. We need to wait for it to finish to avoid a // It's likely that the job was already completed. But it may be still being
// race. // executed at this moment. Just try waiting for it to avoid a race.
token->wait(); token->wait();
} else {
// The job has not been executed, but we still need to inform the callback manager in
// order for future callbacks to be successfully called.
token->compiler.mCallbackManager.put(token->handle);
}
} else if (isTickOpCanceled) {
// Since the tick op was canceled, we need to .put the token here.
token->compiler.mCallbackManager.put(token->handle);
}
for (GLuint& shader: token->gl.shaders) {
if (shader) {
if (token->gl.program) {
glDetachShader(token->gl.program, shader);
}
glDeleteShader(shader);
shader = 0;
} }
} }
if (token->gl.program) {
glDeleteProgram(token->gl.program);
}
token.reset(); cleanupProgramAndShaders(token);
// Cleanup the token.
token->compiler.cancelTickOp(token);
token = nullptr;// This will submit a callback condition (handle) to the callback manager.
} }
void ShaderCompilerService::tick() { void ShaderCompilerService::tick() {
@@ -430,8 +348,16 @@ void ShaderCompilerService::tick() {
} }
} }
CallbackManager::Handle ShaderCompilerService::issueCallbackHandle() const noexcept {
return mCallbackManager.get();
}
void ShaderCompilerService::submitCallbackHandle(CallbackManager::Handle handle) noexcept {
mCallbackManager.put(handle);
}
void ShaderCompilerService::notifyWhenAllProgramsAreReady( void ShaderCompilerService::notifyWhenAllProgramsAreReady(
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) { CallbackHandler* handler, CallbackHandler::Callback const callback, void* user) {
if (callback) { if (callback) {
mCallbackManager.setCallback(handler, callback, user); mCallbackManager.setCallback(handler, callback, user);
} }
@@ -439,117 +365,137 @@ void ShaderCompilerService::notifyWhenAllProgramsAreReady(
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
/* static */ void ShaderCompilerService::getProgramFromCompilerPool( GLuint ShaderCompilerService::initialize(program_token_t& token) {
program_token_t& token) noexcept {
OpenGLProgramToken::ProgramData const& programData{ token->get() };
if (!token->canceled) {
token->gl.shaders = programData.shaders;
token->gl.program = programData.program;
}
}
GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
SYSTRACE_CALL(); SYSTRACE_CALL();
if (!token->gl.program) {
switch (mMode) {
case Mode::THREAD_POOL: {
// we need this program right now, remove it from the queue
auto job = mCompilerThreadPool.dequeue(token);
if (job) {
// if we were able to remove it, we execute the job now, otherwise it means
// it's being executed right now.
job();
}
if (!token->canceled) { assert_invariant(token);// This function should be called when the token is still alive.
token->compiler.cancelTickOp(token);
}
// Block until we get the program from the pool. Generally this wouldn't block ensureTokenIsReady(token);
// because we just compiled the program above, when executing job.
ShaderCompilerService::getProgramFromCompilerPool(token);
break;
}
case Mode::ASYNCHRONOUS: {
// we force the program link -- which might stall, either here or below in
// checkProgramStatus(), but we don't have a choice, we need to use the program now.
token->compiler.cancelTickOp(token);
token->gl.program =
linkProgram(mDriver.getContext(), token->gl.shaders, token->attributes);
assert_invariant(token->gl.program);
mCallbackManager.put(token->handle);
if (token->key) {
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
}
break;
}
case Mode::SYNCHRONOUS: {
// if we don't have a program yet, block until we get it.
tick();
break;
}
case Mode::UNDEFINED: {
assert_invariant(false);
}
}
}
// by this point we must have a GL program
assert_invariant(token->gl.program); assert_invariant(token->gl.program);
GLuint program = 0; // Check status of program linking. If it failed, errors will be logged.
bool const linked = checkLinkStatusAndCleanupShaders(token);
// check status of program linking and shader compilation, logs error and free all resources // We panic if it failed to create the program.
// in case of error. FILAMENT_CHECK_POSTCONDITION(linked)
bool const success = checkProgramStatus(token);
// Unless we have matdbg, we panic if a program is invalid. Otherwise, we'd get a UB.
// The compilation error has been logged to log.e by this point.
FILAMENT_CHECK_POSTCONDITION(FILAMENT_ENABLE_MATDBG || success)
<< "OpenGL program " << token->name.c_str_safe() << " failed to link or compile"; << "OpenGL program " << token->name.c_str_safe() << " failed to link or compile";
if (UTILS_LIKELY(success)) { // The program is successfully created. Try caching the program blob. In the THREAD_POOL mode,
program = token->gl.program; // caching is performed in the pool.
// no need to keep the shaders around if (mMode != Mode::THREAD_POOL) {
UTILS_NOUNROLL tryCachingProgram(mBlobCache, mDriver.mPlatform, token);
for (GLuint& shader: token->gl.shaders) {
if (shader) {
glDetachShader(program, shader);
glDeleteShader(shader);
shader = 0;
}
}
} }
// and destroy all temporary init data GLuint const program = token->gl.program;
token = nullptr;
// Cleanup the token.
token->compiler.cancelTickOp(token);
token = nullptr;// This will submit a callback condition (handle) to the callback manager.
return program; return program;
} }
void ShaderCompilerService::ensureTokenIsReady(program_token_t const& token) {
if (token->gl.program) {
return;// It's ready.
}
switch (mMode) {
case Mode::THREAD_POOL: {
// We need this program right now, make sure the job is finished.
if (auto job = mCompilerThreadPool.dequeue(token)) {
job();// The job hasn't started yet, so execute it now.
}
// This may block if the job was already taken by a thread ahead of the `dequeue`
// above and currently being executed. Otherwise, the job must have already been
// completed by this point from either the code above or the other thread.
token->wait();
break;
}
case Mode::ASYNCHRONOUS: {
// Technically the shader compilation may not have finished yet. To deal with the case,
// ideally, we should wait here until the compilation is finished. However, for now, we
// just log warnings here instead of repeatedly checking compile status. If this turns
// out to be a real issue later, we would need to consider doing the canonical way.
if (!isCompileCompleted(token)) {
slog.w << "Shader compilation for OpenGL program " << token->name.c_str_safe()
<< " is not completed yet. The following program link may not succeed.";
}
linkProgram(mDriver.getContext(), token);
break;
}
case Mode::SYNCHRONOUS: {
// We must not have called the TickOp yet until now. Call now to have
// `token->gl.program` ready to use.
tick();
break;
}
case Mode::UNDEFINED: {
assert_invariant(false);
}
}
}
// ------------------------------------------------------------------------------------------------
void ShaderCompilerService::runAtNextTick(CompilerPriorityQueue priority,
program_token_t const& token, Job job) noexcept {
// insert items in order of priority and at the end of the range
auto& ops = mRunAtNextTickOps;
auto const pos = std::lower_bound(ops.begin(), ops.end(), priority,
[](ContainerType const& lhs, CompilerPriorityQueue const priorityQueue) {
return std::get<0>(lhs) < priorityQueue;
});
ops.emplace(pos, priority, token, std::move(job));
SYSTRACE_CONTEXT();
SYSTRACE_VALUE32("ShaderCompilerService Jobs", mRunAtNextTickOps.size());
}
bool ShaderCompilerService::cancelTickOp(program_token_t const& token) noexcept {
// We do a linear search here, but this is rare, and we know the list is pretty small.
auto& ops = mRunAtNextTickOps;
auto const pos = std::find_if(ops.begin(), ops.end(), [&](const auto& item) {
return std::get<1>(item) == token;
});
if (pos != ops.end()) {
ops.erase(pos);
return true;
}
SYSTRACE_CONTEXT();
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
return false;
}
void ShaderCompilerService::executeTickOps() noexcept {
auto& ops = mRunAtNextTickOps;
auto it = ops.begin();
while (it != ops.end()) {
Job const& job = std::get<2>(*it);
bool const remove = job.fn(job);
if (remove) {
it = ops.erase(it);
} else {
++it;
}
}
SYSTRACE_CONTEXT();
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
}
/*
* Compile shaders in the ShaderSource. This cannot fail because compilation failures are not
* checked until after the program is linked.
* This always returns the GL shader IDs or zero a shader stage is not present.
*/
/* static */ void ShaderCompilerService::compileShaders(OpenGLContext& context, /* static */ void ShaderCompilerService::compileShaders(OpenGLContext& context,
Program::ShaderSource shadersSource, Program::ShaderSource shadersSource,
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants, FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
bool multiview, bool multiview, program_token_t const& token) noexcept {
shaders_t& outShaders,
UTILS_UNUSED_IN_RELEASE shaders_source_t& outShaderSourceCode) noexcept {
SYSTRACE_CALL(); SYSTRACE_CALL();
auto appendSpecConstantString = +[](std::string& s, Program::SpecializationConstant const& sc) { auto const appendSpecConstantString = +[](std::string& s, Program::SpecializationConstant const& sc) {
s += "#define SPIRV_CROSS_CONSTANT_ID_" + std::to_string(sc.id) + ' '; s += "#define SPIRV_CROSS_CONSTANT_ID_" + std::to_string(sc.id) + ' ';
s += std::visit([](auto&& arg) { return to_string(arg); }, sc.value); s += std::visit([](auto&& arg) { return to_string(arg); }, sc.value);
s += '\n'; s += '\n';
@@ -558,7 +504,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
std::string specializationConstantString; std::string specializationConstantString;
int32_t numViews = 2; int32_t numViews = 2;
for (auto const& sc : specializationConstants) { for (auto const& sc: specializationConstants) {
appendSpecConstantString(specializationConstantString, sc); appendSpecConstantString(specializationConstantString, sc);
if (sc.id == 8) { if (sc.id == 8) {
// This constant must match // This constant must match
@@ -596,7 +542,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
if (UTILS_LIKELY(!shadersSource[i].empty())) { if (UTILS_LIKELY(!shadersSource[i].empty())) {
Program::ShaderBlob& shader = shadersSource[i]; Program::ShaderBlob& shader = shadersSource[i];
char* shader_src = reinterpret_cast<char*>(shader.data()); char* shader_src = reinterpret_cast<char*>(shader.data());
size_t shader_len = shader.size(); size_t const shader_len = shader.size();
// remove GOOGLE_cpp_style_line_directive // remove GOOGLE_cpp_style_line_directive
process_GOOGLE_cpp_style_line_directive(context, shader_src, shader_len); process_GOOGLE_cpp_style_line_directive(context, shader_src, shader_len);
@@ -619,178 +565,191 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
} }
std::array<std::string_view, 5> sources = { std::array<std::string_view, 5> sources = {
version, version, prolog, specializationConstantString, packingFunctions,
prolog, { body.data(), body.size() - 1 }// null-terminated
specializationConstantString,
packingFunctions,
{ body.data(), body.size() - 1 } // null-terminated
}; };
// Some of the sources may be zero-length. Remove them as to avoid passing lengths of // Some of the sources may be zero-length. Remove them as to avoid passing lengths of
// zero to glShaderSource(). glShaderSource should work with lengths of zero, but some // zero to glShaderSource(). glShaderSource should work with lengths of zero, but some
// drivers instead interpret zero as a sentinel for a null-terminated string. // drivers instead interpret zero as a sentinel for a null-terminated string.
auto partitionPoint = std::stable_partition( auto const partitionPoint = std::stable_partition(sources.begin(), sources.end(),
sources.begin(), sources.end(), [](std::string_view s) { return !s.empty(); }); [](std::string_view s) { return !s.empty(); });
size_t count = std::distance(sources.begin(), partitionPoint); size_t const count = std::distance(sources.begin(), partitionPoint);
std::array<const char*, 5> shaderStrings; std::array<const char*, 5> shaderStrings;
std::array<GLint, 5> lengths; std::array<GLint, 5> lengths;
for (size_t i = 0; i < count; i++) { for (size_t j = 0; j < count; j++) {
shaderStrings[i] = sources[i].data(); shaderStrings[j] = sources[j].data();
lengths[i] = sources[i].size(); lengths[j] = GLint(sources[j].size());
} }
GLuint const shaderId = glCreateShader(glShaderType); GLuint const shaderId = glCreateShader(glShaderType);
glShaderSource(shaderId, count, shaderStrings.data(), lengths.data()); glShaderSource(shaderId, GLsizei(count), shaderStrings.data(), lengths.data());
glCompileShader(shaderId); glCompileShader(shaderId);
#ifndef NDEBUG #ifndef NDEBUG
// for debugging we return the original shader source (without the modifications we // for debugging we return the original shader source (without the modifications we
// made here), otherwise the line numbers wouldn't match. // made here), otherwise the line numbers wouldn't match.
outShaderSourceCode[i] = { shader_src, shader_len }; token->shaderSourceCode[i] = { shader_src, shader_len };
#endif #endif
token->gl.shaders[i] = shaderId;
outShaders[i] = shaderId;
} }
} }
} }
/* /* static */ bool ShaderCompilerService::isCompileCompleted(program_token_t const& token) noexcept {
* Create a program from the given shader IDs and links it. This cannot fail because errors GLenum param = GL_COMPLETION_STATUS;
* are checked later. This always returns a valid GL program ID (which doesn't mean the if (UTILS_UNLIKELY(token->compiler.mMode != Mode::ASYNCHRONOUS)) {
* program itself is valid). param = GL_COMPILE_STATUS;
*/ }
/* static */ GLuint ShaderCompilerService::linkProgram(OpenGLContext& context,
shaders_t const& shaders,
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& attributes) noexcept {
for (auto shader: token->gl.shaders) {
if (!shader) {
continue;
}
GLint status;
glGetShaderiv(shader, param, &status);
if (status == GL_FALSE) {
return false;
}
}
return true;
}
/* static */ void ShaderCompilerService::checkCompileStatus(program_token_t const& token) noexcept {
SYSTRACE_CALL(); SYSTRACE_CALL();
UTILS_NOUNROLL
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
const GLuint shader = token->gl.shaders[i];
if (!shader) {
continue;// We're not using this shader stage.
}
// GL_COMPILE_STATUS may block until the compilation is completed.
GLint status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (UTILS_LIKELY(status == GL_TRUE)) {
continue;// Succeeded in compilation.
}
// Something went wrong. Log the error message.
const ShaderStage type = static_cast<ShaderStage>(i);
logCompilationError(slog.e, type, token->name.c_str_safe(), shader,
token->shaderSourceCode[i]);
}
}
/* static */ void ShaderCompilerService::linkProgram(OpenGLContext const& context,
program_token_t const& token) noexcept {
SYSTRACE_CALL();
// Shader compilation should be completed by now. Check the status and log errors on failure.
checkCompileStatus(token);
// Link program
GLuint const program = glCreateProgram(); GLuint const program = glCreateProgram();
for (auto shader : shaders) { for (auto const shader: token->gl.shaders) {
if (shader) { if (shader) {
glAttachShader(program, shader); glAttachShader(program, shader);
} }
} }
if (UTILS_UNLIKELY(context.isES2())) { if (UTILS_UNLIKELY(context.isES2())) {
for (auto const& [ name, loc ] : attributes) { for (auto const& [name, loc]: token->attributes) {
glBindAttribLocation(program, loc, name.c_str()); glBindAttribLocation(program, loc, name.c_str());
} }
} }
glLinkProgram(program); glLinkProgram(program);
token->gl.program = program;
return program;
} }
// ------------------------------------------------------------------------------------------------ /* static */ bool ShaderCompilerService::isLinkCompleted(program_token_t const& token) noexcept {
void ShaderCompilerService::runAtNextTick(CompilerPriorityQueue priority,
const program_token_t& token, Job job) noexcept {
// insert items in order of priority and at the end of the range
auto& ops = mRunAtNextTickOps;
auto const pos = std::lower_bound(ops.begin(), ops.end(), priority,
[](ContainerType const& lhs, CompilerPriorityQueue priorityQueue) {
return std::get<0>(lhs) < priorityQueue;
});
ops.emplace(pos, priority, token, std::move(job));
SYSTRACE_CONTEXT();
SYSTRACE_VALUE32("ShaderCompilerService Jobs", mRunAtNextTickOps.size());
}
bool ShaderCompilerService::cancelTickOp(program_token_t token) noexcept {
// We do a linear search here, but this is rare, and we know the list is pretty small.
auto& ops = mRunAtNextTickOps;
auto pos = std::find_if(ops.begin(), ops.end(), [&](const auto& item) {
return std::get<1>(item) == token;
});
if (pos != ops.end()) {
ops.erase(pos);
return true;
}
SYSTRACE_CONTEXT();
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
return false;
}
void ShaderCompilerService::executeTickOps() noexcept {
auto& ops = mRunAtNextTickOps;
auto it = ops.begin();
while (it != ops.end()) {
Job const& job = std::get<2>(*it);
bool const remove = job.fn(job);
if (remove) {
it = ops.erase(it);
} else {
++it;
}
}
SYSTRACE_CONTEXT();
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
}
// ------------------------------------------------------------------------------------------------
/*
* Checks a program link status and logs errors and frees resources on failure.
* Returns true on success.
*/
/* static */ bool ShaderCompilerService::checkProgramStatus(program_token_t const& token) noexcept {
SYSTRACE_CALL();
assert_invariant(token->gl.program); assert_invariant(token->gl.program);
GLint status; GLenum param = GL_COMPLETION_STATUS;
glGetProgramiv(token->gl.program, GL_LINK_STATUS, &status); if (UTILS_UNLIKELY(token->compiler.mMode != Mode::ASYNCHRONOUS)) {
if (UTILS_LIKELY(status == GL_TRUE)) { param = GL_LINK_STATUS;
return true;
} }
// only if the link fails, we check the compilation status GLint status;
glGetProgramiv(token->gl.program, param, &status);
return (status == GL_TRUE);
}
/* static */ bool ShaderCompilerService::checkLinkStatusAndCleanupShaders(
program_token_t const& token) noexcept {
SYSTRACE_CALL();
assert_invariant(token->gl.program);
bool linked = true;
GLint status;
// GL_LINK_STATUS may block until the link is completed.
glGetProgramiv(token->gl.program, GL_LINK_STATUS, &status);
if (UTILS_UNLIKELY(status != GL_TRUE)) {
// Something went wrong. Log the error message.
logProgramLinkError(slog.e, token->name.c_str_safe(), token->gl.program);
linked = false;
}
// No need to keep the shaders around regardless of the result of the program linking.
UTILS_NOUNROLL UTILS_NOUNROLL
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) { for (GLuint& shader: token->gl.shaders) {
const ShaderStage type = static_cast<ShaderStage>(i);
const GLuint shader = token->gl.shaders[i];
if (shader) { if (shader) {
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status != GL_TRUE) {
logCompilationError(slog.e, type,
token->name.c_str_safe(), shader, token->shaderSourceCode[i]);
}
glDetachShader(token->gl.program, shader); glDetachShader(token->gl.program, shader);
glDeleteShader(shader); glDeleteShader(shader);
token->gl.shaders[i] = 0; shader = 0;
} }
} }
// log the link error as well return linked;
logProgramLinkError(slog.e, token->name.c_str_safe(), token->gl.program); }
glDeleteProgram(token->gl.program);
token->gl.program = 0; /* static */ void ShaderCompilerService::tryCachingProgram(OpenGLBlobCache& cache,
return false; OpenGLPlatform& platform, program_token_t const& token) noexcept {
if (!token->key || !token->gl.program) {
return; // Invalid params
}
GLint status = GL_FALSE;
glGetProgramiv(token->gl.program, GL_LINK_STATUS, &status);
if (status == GL_FALSE) {
return;// Link failure
}
cache.insert(platform, token->key, token->gl.program);
}
/* static */ void ShaderCompilerService::cleanupProgramAndShaders(
program_token_t const& token) noexcept {
for (GLuint& shader: token->gl.shaders) {
if (!shader) {
continue;
}
if (token->gl.program) {
glDetachShader(token->gl.program, shader);
}
glDeleteShader(shader);
shader = 0;
}
if (token->gl.program) {
glDeleteProgram(token->gl.program);
token->gl.program = 0;
}
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
UTILS_NOINLINE UTILS_NOINLINE
/* static */ void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name, /* static */ void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name,
GLuint shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept { GLuint const shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
auto to_string = [](ShaderStage type) -> const char* { { // scope for the temporary string storage
switch (type) { auto to_string = [](ShaderStage type) -> const char* {
case ShaderStage::VERTEX: switch (type) {
return "vertex"; case ShaderStage::VERTEX:
case ShaderStage::FRAGMENT: return "vertex";
return "fragment"; case ShaderStage::FRAGMENT:
case ShaderStage::COMPUTE: return "fragment";
return "compute"; case ShaderStage::COMPUTE:
} return "compute";
}; }
return "unknown";
};
{// scope for the temporary string storage
GLint length = 0; GLint length = 0;
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &length); glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &length);
@@ -837,8 +796,8 @@ UTILS_NOINLINE
// If usages of the Google-style line directive are present, remove them, as some // If usages of the Google-style line directive are present, remove them, as some
// drivers don't allow the quotation marks. This source modification happens in-place. // drivers don't allow the quotation marks. This source modification happens in-place.
/* static */ void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context, char* source, /* static */ void process_GOOGLE_cpp_style_line_directive(OpenGLContext const& context,
size_t len) noexcept { char* source, size_t len) noexcept {
if (!context.ext.GOOGLE_cpp_style_line_directive) { if (!context.ext.GOOGLE_cpp_style_line_directive) {
if (UTILS_UNLIKELY(requestsGoogleLineDirectivesExtension({ source, len }))) { if (UTILS_UNLIKELY(requestsGoogleLineDirectivesExtension({ source, len }))) {
removeGoogleLineDirectives(source, len);// length is unaffected removeGoogleLineDirectives(source, len);// length is unaffected
@@ -850,13 +809,13 @@ UTILS_NOINLINE
// necessary for OpenGL because OpenGL relies on the number specified in shader files to determine // necessary for OpenGL because OpenGL relies on the number specified in shader files to determine
// the number of views, which is assumed as a single digit, for multiview. // the number of views, which is assumed as a single digit, for multiview.
// This source modification happens in-place. // This source modification happens in-place.
/* static */ void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount, char* source, /* static */ void process_OVR_multiview2(OpenGLContext const& context, int32_t const eyeCount,
size_t len) noexcept { char* source, size_t const len) noexcept {
// We don't use regular expression in favor of performance. // We don't use regular expression in favor of performance.
if (context.ext.OVR_multiview2) { if (context.ext.OVR_multiview2) {
const std::string_view shader{ source, len }; const std::string_view shader{ source, len };
const std::string_view layout = "layout"; constexpr std::string_view layout = "layout";
const std::string_view num_views = "num_views"; constexpr std::string_view num_views = "num_views";
size_t found = 0; size_t found = 0;
while (true) { while (true) {
found = shader.find(layout, found); found = shader.find(layout, found);
@@ -1011,20 +970,20 @@ mediump vec4 unpackSnorm4x8(highp uint v) {
// - extensions // - extensions
// - everything else // - everything else
/* static */ std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept { /* static */ std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept {
auto version_start = source.find("#version"); auto const version_start = source.find("#version");
assert_invariant(version_start != std::string_view::npos); assert_invariant(version_start != std::string_view::npos);
auto version_eol = source.find('\n', version_start) + 1; auto const version_eol = source.find('\n', version_start) + 1;
assert_invariant(version_eol != std::string_view::npos); assert_invariant(version_eol != std::string_view::npos);
auto prolog_start = version_eol; auto const prolog_start = version_eol;
auto prolog_eol = source.rfind("\n#extension");// last #extension line auto prolog_eol = source.rfind("\n#extension");// last #extension line
if (prolog_eol == std::string_view::npos) { if (prolog_eol == std::string_view::npos) {
prolog_eol = prolog_start; prolog_eol = prolog_start;
} else { } else {
prolog_eol = source.find('\n', prolog_eol + 1) + 1; prolog_eol = source.find('\n', prolog_eol + 1) + 1;
} }
auto body_start = prolog_eol; auto const body_start = prolog_eol;
std::string_view const version = source.substr(version_start, version_eol - version_start); std::string_view const version = source.substr(version_start, version_eol - version_start);
std::string_view const prolog = source.substr(prolog_start, prolog_eol - prolog_start); std::string_view const prolog = source.substr(prolog_start, prolog_eol - prolog_start);
@@ -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;
@@ -84,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
@@ -92,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);
@@ -99,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) {
@@ -128,26 +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, shaders_t& outShaders, shaders_source_t& outShaderSourceCode) noexcept;
static GLuint linkProgram(OpenGLContext& context, shaders_t const& 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
@@ -17,9 +17,11 @@
#define COREVIDEO_SILENCE_GL_DEPRECATION #define COREVIDEO_SILENCE_GL_DEPRECATION
#include "CocoaExternalImage.h" #include "CocoaExternalImage.h"
#include <utils/Panic.h>
#include "../GLUtils.h" #include "../GLUtils.h"
#include <utils/Panic.h>
#include <utils/Log.h>
namespace filament::backend { namespace filament::backend {
static const char *s_vertex = R"SHADER(#version 410 core static const char *s_vertex = R"SHADER(#version 410 core
@@ -28,6 +28,7 @@
#include <utils/compiler.h> #include <utils/compiler.h>
#include <utils/Panic.h> #include <utils/Panic.h>
#include <utils/debug.h> #include <utils/debug.h>
#include <utils/Log.h>
namespace filament::backend { namespace filament::backend {
@@ -295,6 +295,8 @@ VulkanCommandBuffer& CommandBufferPool::getRecording() {
} }
void CommandBufferPool::gc() { void CommandBufferPool::gc() {
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_START("CommandBufferPool::gc");
ActiveBuffers reclaimed; ActiveBuffers reclaimed;
mSubmitted.forEachSetBit([this,&reclaimed] (size_t index) { mSubmitted.forEachSetBit([this,&reclaimed] (size_t index) {
auto& buffer = mBuffers[index]; auto& buffer = mBuffers[index];
@@ -304,6 +306,7 @@ void CommandBufferPool::gc() {
} }
}); });
mSubmitted &= ~reclaimed; mSubmitted &= ~reclaimed;
FVK_SYSTRACE_END();
} }
void CommandBufferPool::update() { void CommandBufferPool::update() {
@@ -333,7 +336,9 @@ void CommandBufferPool::wait() {
mSubmitted.forEachSetBit([this, &count, &fences] (size_t index) { mSubmitted.forEachSetBit([this, &count, &fences] (size_t index) {
fences[count++] = mBuffers[index]->getVkFence(); fences[count++] = mBuffers[index]->getVkFence();
}); });
vkWaitForFences(mDevice, count, fences, VK_TRUE, UINT64_MAX); if (count) {
vkWaitForFences(mDevice, count, fences, VK_TRUE, UINT64_MAX);
}
update(); update();
} }
@@ -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()
@@ -31,8 +31,8 @@ namespace filament::backend {
namespace { namespace {
using DescriptorCount = VulkanDescriptorSetLayout::Count;
using DescriptorSetLayoutArray = VulkanDescriptorSetCache::DescriptorSetLayoutArray; using DescriptorSetLayoutArray = VulkanDescriptorSetCache::DescriptorSetLayoutArray;
using DescriptorCount = VulkanDescriptorSetCache::DescriptorCount;
// We create a pool for each layout as defined by the number of descriptors of each type. For // We create a pool for each layout as defined by the number of descriptors of each type. For
// example, a layout of // example, a layout of
@@ -203,11 +203,10 @@ public:
DescriptorInfinitePool(VkDevice device) DescriptorInfinitePool(VkDevice device)
: mDevice(device) {} : mDevice(device) {}
VkDescriptorSet obtainSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) { VkDescriptorSet obtainSet(DescriptorCount const& count, VkDescriptorSetLayout vklayout) {
auto const vklayout = layout->getVkLayout();
DescriptorPool* sameTypePool = nullptr; DescriptorPool* sameTypePool = nullptr;
for (auto& pool: mPools) { for (auto& pool: mPools) {
if (!pool->canAllocate(layout->count)) { if (!pool->canAllocate(count)) {
continue; continue;
} }
if (auto set = pool->obtainSet(vklayout); set != VK_NULL_HANDLE) { if (auto set = pool->obtainSet(vklayout); set != VK_NULL_HANDLE) {
@@ -225,8 +224,7 @@ public:
} }
// We need to increase the set of pools by one. // We need to increase the set of pools by one.
mPools.push_back(std::make_unique<DescriptorPool>(mDevice, mPools.push_back(std::make_unique<DescriptorPool>(mDevice, count, capacity));
DescriptorCount::fromLayoutBitmask(layout->bitmask), capacity));
auto& pool = mPools.back(); auto& pool = mPools.back();
auto ret = pool->obtainSet(vklayout); auto ret = pool->obtainSet(vklayout);
assert_invariant(ret != VK_NULL_HANDLE && "failed to obtain a set?"); assert_invariant(ret != VK_NULL_HANDLE && "failed to obtain a set?");
@@ -276,39 +274,36 @@ void VulkanDescriptorSetCache::unbind(uint8_t setIndex) {
} }
void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands, void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
VkPipelineLayout pipelineLayout, fvkutils::DescriptorSetMask const& setMask) { VkPipelineLayout pipelineLayout, fvkutils::DescriptorSetMask const& useExternalSamplers,
fvkutils::DescriptorSetMask const& setMask) {
// setMask indicates the set of descriptor sets the driver wants to bind, curMask is the // setMask indicates the set of descriptor sets the driver wants to bind, curMask is the
// actual set of sets that *needs* to be bound. // actual set of sets that *needs* to be bound.
fvkutils::DescriptorSetMask curMask = setMask; fvkutils::DescriptorSetMask curMask = setMask;
auto& updateSets = mStashedSets; auto const& updateSets = mStashedSets;
bool const pipelineLayoutIsSame = mLastBoundInfo.pipelineLayout == pipelineLayout; curMask.forEachSetBit([&](size_t index) {
if (!updateSets[index]) {
if (pipelineLayoutIsSame) { curMask.unset(index);
auto& lastBoundSets = mLastBoundInfo.boundSets;
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 (!updateSets[index]) { if (mLastBoundInfo.pipelineLayout == pipelineLayout) {
auto& lastBoundSets = mLastBoundInfo.boundSets;
curMask.forEachSetBit([&](size_t index) {
if (updateSets[index] == lastBoundSets[index] && !useExternalSamplers[index]) {
curMask.unset(index); curMask.unset(index);
} }
}); });
} }
curMask.forEachSetBit([&updateSets, commands, pipelineLayout](size_t index) { curMask.forEachSetBit([&](size_t index) {
// This code actually binds the descriptor sets. // This code actually binds the descriptor sets.
auto set = updateSets[index]; auto set = updateSets[index];
VkCommandBuffer const cmdbuffer = commands->buffer(); VkCommandBuffer const cmdbuffer = commands->buffer();
VkDescriptorSet vkset = useExternalSamplers[index] ? set->getExternalSamplerVkSet() :
set->getVkSet();
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index, vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index,
1, &set->getVkSet(), set->uniqueDynamicUboCount, set->getOffsets()->data()); 1, &vkset, set->uniqueDynamicUboCount, set->getOffsets()->data());
commands->acquire(set); commands->acquire(set);
}); });
@@ -334,7 +329,7 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
if (set->dynamicUboMask.test(binding)) { if (set->dynamicUboMask.test(binding)) {
type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
} }
VkWriteDescriptorSet const descriptorWrite = { VkWriteDescriptorSet descriptorWrite = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = set->getVkSet(), .dstSet = set->getVkSet(),
.dstBinding = binding, .dstBinding = binding,
@@ -343,12 +338,17 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
.pBufferInfo = &info, .pBufferInfo = &info,
}; };
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr); vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
if (auto externalSamplerSet = set->getExternalSamplerVkSet();
externalSamplerSet != VK_NULL_HANDLE) {
descriptorWrite.dstSet = externalSamplerSet;
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
}
set->acquire(bufferObject); set->acquire(bufferObject);
} }
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set, void VulkanDescriptorSetCache::updateSamplerImpl(VkDescriptorSet vkset, uint8_t binding,
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture, fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept {
VkSampler sampler) noexcept {
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) &&
@@ -364,16 +364,29 @@ void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescr
.imageLayout = fvkutils::getVkLayout(texture->getDefaultLayout()), .imageLayout = fvkutils::getVkLayout(texture->getDefaultLayout()),
}; };
VkWriteDescriptorSet const descriptorWrite = { VkWriteDescriptorSet descriptorWrite = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr, .pNext = nullptr,
.dstSet = set->getVkSet(), .dstSet = vkset,
.dstBinding = binding, .dstBinding = binding,
.descriptorCount = 1, .descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = &info, .pImageInfo = &info,
}; };
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr); vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
}
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
VkSampler sampler) noexcept {
updateSamplerImpl(set->getVkSet(), binding, texture, sampler);
set->acquire(texture);
}
void VulkanDescriptorSetCache::updateSamplerForExternalSamplerSet(
fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
fvkmemory::resource_ptr<VulkanTexture> texture) noexcept {
updateSamplerImpl(set->getExternalSamplerVkSet(), binding, texture, VK_NULL_HANDLE);
set->acquire(texture); set->acquire(texture);
} }
@@ -383,32 +396,32 @@ void VulkanDescriptorSetCache::updateInputAttachment(
// TOOD: fill this in. // TOOD: fill this in.
} }
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 = getVkSet(layout); auto const vkSet = mDescriptorPool->obtainSet(layout->count, layout->getVkLayout());
auto const& count = layout->count; auto const& count = layout->count;
auto const vklayout = layout->getVkLayout(); auto const vklayout = layout->getVkLayout();
auto set = fvkmemory::resource_ptr<VulkanDescriptorSet>::make(mResourceManager, handle, auto set = fvkmemory::resource_ptr<VulkanDescriptorSet>::make(
layout->bitmask.dynamicUbo, layout->count.dynamicUbo, mResourceManager, handle, layout->bitmask.dynamicUbo, layout->count.dynamicUbo,
[vkSet, count, vklayout, this](VulkanDescriptorSet*) { [vkSet, count, vklayout, this](
// Note that mDescriptorPool could be gone due to terminate (when the backend shuts VulkanDescriptorSet*) { this->manualRecycle(count, vklayout, vkSet); },
// down). vkSet);
if (mDescriptorPool) {
mDescriptorPool->recycle(count, vklayout, vkSet);
}
});
set->setVkSet(vkSet);
return set; return set;
} }
VkDescriptorSet VulkanDescriptorSetCache::getVkSet( VkDescriptorSet VulkanDescriptorSetCache::getVkSet(DescriptorCount const& count,
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) { VkDescriptorSetLayout vklayout) {
return mDescriptorPool->obtainSet(layout); return mDescriptorPool->obtainSet(count, vklayout);
} }
void VulkanDescriptorSetCache::manualRecyle(VulkanDescriptorSetLayout::Count const& count, void VulkanDescriptorSetCache::manualRecycle(VulkanDescriptorSetLayout::Count const& count,
VkDescriptorSetLayout vklayout, VkDescriptorSet vkSet) { VkDescriptorSetLayout vklayout, VkDescriptorSet vkSet) {
mDescriptorPool->recycle(count, vklayout, vkSet); // Note that mDescriptorPool could be gone due to terminate (when the backend shuts
// down).
if (mDescriptorPool) {
mDescriptorPool->recycle(count, vklayout, vkSet);
}
} }
void VulkanDescriptorSetCache::gc() { mStashedSets = {}; } void VulkanDescriptorSetCache::gc() { mStashedSets = {}; }
@@ -43,6 +43,7 @@ public:
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray; using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
using DescriptorSetArray = using DescriptorSetArray =
std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>, UNIQUE_DESCRIPTOR_SET_COUNT>; std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>, UNIQUE_DESCRIPTOR_SET_COUNT>;
using DescriptorCount = VulkanDescriptorSetLayout::Count;
VulkanDescriptorSetCache(VkDevice device, fvkmemory::ResourceManager* resourceManager); VulkanDescriptorSetCache(VkDevice device, fvkmemory::ResourceManager* resourceManager);
~VulkanDescriptorSetCache(); ~VulkanDescriptorSetCache();
@@ -56,6 +57,10 @@ public:
void updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding, void updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept; fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept;
void updateSamplerForExternalSamplerSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
fvkmemory::resource_ptr<VulkanTexture> texture) noexcept;
void updateInputAttachment(fvkmemory::resource_ptr<VulkanDescriptorSet> set, void updateInputAttachment(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
VulkanAttachment const& attachment) noexcept; VulkanAttachment const& attachment) noexcept;
@@ -65,17 +70,17 @@ public:
void unbind(uint8_t setIndex); void unbind(uint8_t setIndex);
void commit(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout, void commit(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout,
fvkutils::DescriptorSetMask const& useExternalSamplerMask,
fvkutils::DescriptorSetMask const& setMask); fvkutils::DescriptorSetMask const& setMask);
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 // This method is meant to be used with external samplers
// class). VkDescriptorSet getVkSet(DescriptorCount const& count, VkDescriptorSetLayout vklayout);
VkDescriptorSet getVkSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
// This method is only meant to be used with external samplers. // This method is meant to be used with external samplers
void manualRecyle(VulkanDescriptorSetLayout::Count const& count, VkDescriptorSetLayout vklayout, void manualRecycle(VulkanDescriptorSetLayout::Count const& count, VkDescriptorSetLayout vklayout,
VkDescriptorSet vkSet); VkDescriptorSet vkSet);
DescriptorSetArray const& getBoundSets() const { return mStashedSets; } DescriptorSetArray const& getBoundSets() const { return mStashedSets; }
@@ -83,6 +88,9 @@ public:
void gc(); void gc();
private: private:
void updateSamplerImpl(VkDescriptorSet set, uint8_t binding,
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept;
class DescriptorInfinitePool; class DescriptorInfinitePool;
VkDevice mDevice; VkDevice mDevice;
@@ -127,6 +127,7 @@ void VulkanDescriptorSetLayoutCache::terminate() noexcept {
VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout( VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
VulkanDescriptorSetLayout::Bitmask const& bitmasks, VulkanDescriptorSetLayout::Bitmask const& bitmasks,
fvkutils::SamplerBitmask externalSamplers,
utils::FixedCapacityVector<VkSampler> immutableSamplers) { utils::FixedCapacityVector<VkSampler> immutableSamplers) {
LayoutKey key = { LayoutKey key = {
.bitmask = bitmasks, .bitmask = bitmasks,
@@ -141,7 +142,7 @@ VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
bitmasks.dynamicUbo); bitmasks.dynamicUbo);
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bitmasks.ubo); count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bitmasks.ubo);
count += appendSamplerBindings(&toBind[count], bitmasks.sampler, bitmasks.externalSampler, count += appendSamplerBindings(&toBind[count], bitmasks.sampler, externalSamplers,
immutableSamplers); immutableSamplers);
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
bitmasks.inputAttachment); bitmasks.inputAttachment);
@@ -160,9 +161,9 @@ VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> VulkanDescriptorSetLayoutCache::createLayout( fvkmemory::resource_ptr<VulkanDescriptorSetLayout> VulkanDescriptorSetLayoutCache::createLayout(
Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info) { Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info) {
BitmaskGroup maskGroup = VulkanDescriptorSetLayout::Bitmask::fromLayoutDescription(info);
auto layout = fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::make(mResourceManager, handle, auto layout = fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::make(mResourceManager, handle,
info); std::move(info), getVkLayout(maskGroup, maskGroup.externalSampler));
layout->setVkLayout(getVkLayout(layout->bitmask));
return layout; return layout;
} }
@@ -46,6 +46,7 @@ public:
// This method is meant to be used with external samplers // This method is meant to be used with external samplers
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout::Bitmask const& bitmasks, VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout::Bitmask const& bitmasks,
fvkutils::SamplerBitmask externalSamplers,
utils::FixedCapacityVector<VkSampler> immutableSamplers = {}); utils::FixedCapacityVector<VkSampler> immutableSamplers = {});
private: private:
+177 -75
View File
@@ -30,7 +30,6 @@
#include "vulkan/memory/ResourcePointer.h" #include "vulkan/memory/ResourcePointer.h"
#include "vulkan/utils/Conversion.h" #include "vulkan/utils/Conversion.h"
#include "vulkan/utils/Definitions.h" #include "vulkan/utils/Definitions.h"
#include "vulkan/vulkan_core.h"
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include <backend/platforms/VulkanPlatform.h> #include <backend/platforms/VulkanPlatform.h>
@@ -198,11 +197,9 @@ Dispatcher VulkanDriver::getDispatcher() const noexcept {
} }
VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& context, VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& context,
Platform::DriverConfig const& driverConfig) noexcept Platform::DriverConfig const& driverConfig)
: mPlatform(platform), : mPlatform(platform),
mResourceManager( mResourceManager(driverConfig.handleArenaSize, driverConfig.disableHandleUseAfterFreeCheck,
driverConfig.handleArenaSize,
driverConfig.disableHandleUseAfterFreeCheck,
driverConfig.disableHeapHandleTags), driverConfig.disableHeapHandleTags),
mAllocator(createAllocator(mPlatform->getInstance(), mPlatform->getPhysicalDevice(), mAllocator(createAllocator(mPlatform->getInstance(), mPlatform->getPhysicalDevice(),
mPlatform->getDevice())), mPlatform->getDevice())),
@@ -221,6 +218,8 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
mDescriptorSetLayoutCache(mPlatform->getDevice(), &mResourceManager), mDescriptorSetLayoutCache(mPlatform->getDevice(), &mResourceManager),
mDescriptorSetCache(mPlatform->getDevice(), &mResourceManager), mDescriptorSetCache(mPlatform->getDevice(), &mResourceManager),
mQueryManager(mPlatform->getDevice()), mQueryManager(mPlatform->getDevice()),
mExternalImageManager(platform, &mSamplerCache, &mYcbcrConversionCache, &mDescriptorSetCache,
&mDescriptorSetLayoutCache),
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported), mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
mStereoscopicType(driverConfig.stereoscopicType) { mStereoscopicType(driverConfig.stereoscopicType) {
@@ -251,7 +250,7 @@ VulkanDriver::~VulkanDriver() noexcept = default;
UTILS_NOINLINE UTILS_NOINLINE
Driver* VulkanDriver::create(VulkanPlatform* platform, VulkanContext const& context, Driver* VulkanDriver::create(VulkanPlatform* platform, VulkanContext const& context,
Platform::DriverConfig const& driverConfig) noexcept { Platform::DriverConfig const& driverConfig) {
#if 0 #if 0
// this is useful for development, but too verbose even for debug builds // this is useful for development, but too verbose even for debug builds
// For reference on a 64-bits machine in Release mode: // For reference on a 64-bits machine in Release mode:
@@ -313,7 +312,7 @@ void VulkanDriver::terminate() {
mCurrentSwapChain = {}; mCurrentSwapChain = {};
mDefaultRenderTarget = {}; mDefaultRenderTarget = {};
mBoundPipeline = {}; mPipelineState = {};
mQueryManager.terminate(); mQueryManager.terminate();
@@ -325,6 +324,10 @@ void VulkanDriver::terminate() {
mCommands.terminate(); mCommands.terminate();
// Must come before samplerCache, ycbcrConversionCache, descriptorSetCache,
// descriptorSetLayoutCache
mExternalImageManager.terminate();
mStagePool.terminate(); mStagePool.terminate();
mPipelineCache.terminate(); mPipelineCache.terminate();
mFramebufferCache.terminate(); mFramebufferCache.terminate();
@@ -381,6 +384,10 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
int64_t refreshIntervalNs, uint32_t frameId) { int64_t refreshIntervalNs, uint32_t frameId) {
FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME); FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
// Do nothing. // Do nothing.
if (mAppState.hasExternalSamplers()) {
mExternalImageManager.onBeginFrame();
}
} }
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler, void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
@@ -421,12 +428,17 @@ void VulkanDriver::updateDescriptorSetTexture(
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh); auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th); auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
// TODO: YcbcrConversion? if (mExternalImageManager.isExternallySampledTexture(texture)) {
VulkanSamplerCache::Params cacheParams = { mExternalImageManager.bindExternallySampledTexture(set, binding, texture, params);
.sampler = params, mAppState.hasBoundExternalImages = true;
}; } else {
VkSampler const vksampler = mSamplerCache.getSampler(cacheParams); VulkanSamplerCache::Params cacheParams = {
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler); .sampler = params,
};
VkSampler const vksampler = mSamplerCache.getSampler(cacheParams);
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
mExternalImageManager.clearTextureBinding(set, binding);
}
} }
void VulkanDriver::flush(int) { void VulkanDriver::flush(int) {
@@ -446,6 +458,7 @@ void VulkanDriver::finish(int dummy) {
void VulkanDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph, void VulkanDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh, Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh,
PrimitiveType pt) { PrimitiveType pt) {
FVK_SYSTRACE_SCOPE();
auto vb = resource_ptr<VulkanVertexBuffer>::cast(&mResourceManager, vbh); auto vb = resource_ptr<VulkanVertexBuffer>::cast(&mResourceManager, vbh);
auto ib = resource_ptr<VulkanIndexBuffer>::cast(&mResourceManager, ibh); auto ib = resource_ptr<VulkanIndexBuffer>::cast(&mResourceManager, ibh);
auto ptr = resource_ptr<VulkanRenderPrimitive>::make(&mResourceManager, rph, pt, vb, ib); auto ptr = resource_ptr<VulkanRenderPrimitive>::make(&mResourceManager, rph, pt, vb, ib);
@@ -456,12 +469,14 @@ void VulkanDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
if (!rph) { if (!rph) {
return; return;
} }
FVK_SYSTRACE_SCOPE();
auto ptr = resource_ptr<VulkanRenderPrimitive>::cast(&mResourceManager, rph); auto ptr = resource_ptr<VulkanRenderPrimitive>::cast(&mResourceManager, rph);
ptr.dec(); ptr.dec();
} }
void VulkanDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vbih, uint8_t bufferCount, void VulkanDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vbih, uint8_t bufferCount,
uint8_t attributeCount, AttributeArray attributes) { uint8_t attributeCount, AttributeArray attributes) {
FVK_SYSTRACE_SCOPE();
auto vbi = resource_ptr<VulkanVertexBufferInfo>::make(&mResourceManager, vbih, bufferCount, auto vbi = resource_ptr<VulkanVertexBufferInfo>::make(&mResourceManager, vbih, bufferCount,
attributeCount, attributes); attributeCount, attributes);
vbi.inc(); vbi.inc();
@@ -471,12 +486,14 @@ void VulkanDriver::destroyVertexBufferInfo(Handle<HwVertexBufferInfo> vbih) {
if (!vbih) { if (!vbih) {
return; return;
} }
FVK_SYSTRACE_SCOPE();
auto vbi = resource_ptr<VulkanVertexBufferInfo>::cast(&mResourceManager, vbih); auto vbi = resource_ptr<VulkanVertexBufferInfo>::cast(&mResourceManager, vbih);
vbi.dec(); vbi.dec();
} }
void VulkanDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint32_t vertexCount, void VulkanDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint32_t vertexCount,
Handle<HwVertexBufferInfo> vbih) { Handle<HwVertexBufferInfo> vbih) {
FVK_SYSTRACE_SCOPE();
auto vbi = resource_ptr<VulkanVertexBufferInfo>::cast(&mResourceManager, vbih); auto vbi = resource_ptr<VulkanVertexBufferInfo>::cast(&mResourceManager, vbih);
auto vb = resource_ptr<VulkanVertexBuffer>::make(&mResourceManager, vbh, mContext, mStagePool, auto vb = resource_ptr<VulkanVertexBuffer>::make(&mResourceManager, vbh, mContext, mStagePool,
vertexCount, vbi); vertexCount, vbi);
@@ -487,12 +504,14 @@ void VulkanDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vbh) {
if (!vbh) { if (!vbh) {
return; return;
} }
FVK_SYSTRACE_SCOPE();
auto vb = resource_ptr<VulkanVertexBuffer>::cast(&mResourceManager, vbh); auto vb = resource_ptr<VulkanVertexBuffer>::cast(&mResourceManager, vbh);
vb.dec(); vb.dec();
} }
void VulkanDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType, void VulkanDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType,
uint32_t indexCount, BufferUsage usage) { uint32_t indexCount, BufferUsage usage) {
FVK_SYSTRACE_SCOPE();
auto elementSize = (uint8_t) getElementTypeSize(elementType); auto elementSize = (uint8_t) getElementTypeSize(elementType);
auto ib = resource_ptr<VulkanIndexBuffer>::make(&mResourceManager, ibh, mAllocator, mStagePool, auto ib = resource_ptr<VulkanIndexBuffer>::make(&mResourceManager, ibh, mAllocator, mStagePool,
elementSize, indexCount); elementSize, indexCount);
@@ -503,12 +522,14 @@ void VulkanDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
if (!ibh) { if (!ibh) {
return; return;
} }
FVK_SYSTRACE_SCOPE();
auto ib = resource_ptr<VulkanIndexBuffer>::cast(&mResourceManager, ibh); auto ib = resource_ptr<VulkanIndexBuffer>::cast(&mResourceManager, ibh);
ib.dec(); ib.dec();
} }
void VulkanDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount, void VulkanDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage) { BufferObjectBinding bindingType, BufferUsage usage) {
FVK_SYSTRACE_SCOPE();
auto bo = resource_ptr<VulkanBufferObject>::make(&mResourceManager, boh, mAllocator, mStagePool, auto bo = resource_ptr<VulkanBufferObject>::make(&mResourceManager, boh, mAllocator, mStagePool,
byteCount, bindingType); byteCount, bindingType);
bo.inc(); bo.inc();
@@ -518,6 +539,7 @@ void VulkanDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
if (!boh) { if (!boh) {
return; return;
} }
FVK_SYSTRACE_SCOPE();
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh); auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
bo.dec(); bo.dec();
} }
@@ -563,41 +585,48 @@ void VulkanDriver::createTextureExternalImage2R(Handle<HwTexture> th, backend::S
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
Platform::ExternalImageHandleRef externalImage) { Platform::ExternalImageHandleRef externalImage) {
FVK_SYSTRACE_SCOPE(); FVK_SYSTRACE_SCOPE();
auto const& metadata = mPlatform->getExternalImageMetadata(externalImage); auto metadata = mPlatform->extractExternalImageMetadata(externalImage);
if (metadata.isProtected) {
usage |= backend::TextureUsage::PROTECTED;
}
VkImageUsageFlags vkUsage = metadata.usage;
if (any(usage & TextureUsage::BLIT_SRC)) {
vkUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
if (any(usage & (TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE))) {
vkUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
assert_invariant(width == metadata.width); assert_invariant(width == metadata.width);
assert_invariant(height == metadata.height); assert_invariant(height == metadata.height);
assert_invariant(fvkutils::getVkFormat(format) == metadata.format);
VkMemoryPropertyFlags const requiredMemoryFlags = any(usage & TextureUsage::UPLOADABLE) // We do not check the format since AHB could return both a known format and an external format.
? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT // In which case, we choose one or the other, but this choice is not known to the client.
: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; // Therefore the following lines are commented out.
uint32_t const memoryTypeIndex = // assert_invariant(format == metadata.filamentFormat);
mContext.selectMemoryType(metadata.memoryTypeBits, requiredMemoryFlags); // assert_invariant(fvkutils::getVkFormat(format) == metadata.format);
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex != VK_MAX_MEMORY_TYPES)
<< "failed to find a valid memory type for external image memory.";
VkImage vkimg; auto imgData = mPlatform->createVkImageFromExternal(externalImage);
VkDeviceMemory deviceMemory;
std::tie(vkimg, deviceMemory) =
mPlatform->createExternalImageData(externalImage, metadata, memoryTypeIndex, vkUsage);
assert_invariant(imgData.internal.valid() || imgData.external.valid());
VkFormat vkformat = metadata.format;
VkImage vkimage = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
if (imgData.internal.valid()) {
metadata.externalFormat = 0;
vkimage = imgData.internal.image;
memory = imgData.internal.memory;
} else { // imgData.external.valid()
vkformat = VK_FORMAT_UNDEFINED;
vkimage = imgData.external.image;
memory = imgData.external.memory;
}
VkSamplerYcbcrConversion const conversion =
mExternalImageManager.getVkSamplerYcbcrConversion(metadata);
auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th, mContext, auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th, mContext,
mPlatform->getDevice(), mAllocator, &mResourceManager, &mCommands, vkimg, deviceMemory, mPlatform->getDevice(), mAllocator, &mResourceManager, &mCommands, vkimage, memory,
metadata.format, VK_NULL_HANDLE, metadata.samples, metadata.width, metadata.height, vkformat, conversion, metadata.samples, metadata.width, metadata.height,
metadata.layerCount, usage, mStagePool); metadata.layers, usage, mStagePool);
auto& commands = mCommands.get();
// Unlike uploaded textures or swapchains, we need to explicit transition this
// texture into the read layout.
texture->transitionLayout(&commands, texture->getPrimaryViewRange(), VulkanLayout::READ_ONLY);
if (imgData.external.valid()) {
mExternalImageManager.addExternallySampledTexture(texture, externalImage);
}
texture.inc(); texture.inc();
} }
@@ -630,6 +659,8 @@ void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
} }
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th); auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
texture.dec(); texture.dec();
mExternalImageManager.removeExternallySampledTexture(texture);
} }
void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program) { void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
@@ -746,6 +777,7 @@ void VulkanDriver::createFenceR(Handle<HwFence> fh, int) {
} }
void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) { void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
FVK_SYSTRACE_SCOPE();
// Running gc() to guard against an edge case where the old swapchains need to have been // Running gc() to guard against an edge case where the old swapchains need to have been
// destroyed before the new swapchain can be created. Otherwise, we would fail // destroyed before the new swapchain can be created. Otherwise, we would fail
// vkCreateSwapchainKHR with VK_ERROR_NATIVE_WINDOW_IN_USE_KHR. // vkCreateSwapchainKHR with VK_ERROR_NATIVE_WINDOW_IN_USE_KHR.
@@ -787,6 +819,7 @@ void VulkanDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
void VulkanDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh, void VulkanDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
backend::DescriptorSetLayout&& info) { backend::DescriptorSetLayout&& info) {
FVK_SYSTRACE_SCOPE();
auto layout = mDescriptorSetLayoutCache.createLayout(dslh, std::move(info)); auto layout = mDescriptorSetLayoutCache.createLayout(dslh, std::move(info));
layout.inc(); layout.inc();
} }
@@ -798,6 +831,10 @@ void VulkanDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, dslh); fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, dslh);
auto set = mDescriptorSetCache.createSet(dsh, layout); auto set = mDescriptorSetCache.createSet(dsh, layout);
set.inc(); set.inc();
if (layout->hasExternalSamplers()) {
mAppState.hasExternalSamplerLayouts = true;
}
} }
Handle<HwVertexBufferInfo> VulkanDriver::createVertexBufferInfoS() noexcept { Handle<HwVertexBufferInfo> VulkanDriver::createVertexBufferInfoS() noexcept {
@@ -922,6 +959,10 @@ void VulkanDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> dslh
void VulkanDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) { void VulkanDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh); auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
set.dec(); set.dec();
if (mAppState.hasExternalSamplers() && set->getExternalSamplerVkSet() != VK_NULL_HANDLE) {
mExternalImageManager.removeDescriptorSet(set);
}
} }
Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream) { Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream) {
@@ -1482,7 +1523,6 @@ void VulkanDriver::endRenderPass(int) {
// pipeline barrier between framebuffer writes and shader reads. // pipeline barrier between framebuffer writes and shader reads.
rt->emitBarriersEndRenderPass(*mCurrentRenderPass.commandBuffer); rt->emitBarriersEndRenderPass(*mCurrentRenderPass.commandBuffer);
mRenderPassFboInfo = {};
mCurrentRenderPass.renderTarget = {}; mCurrentRenderPass.renderTarget = {};
mCurrentRenderPass.renderPass = VK_NULL_HANDLE; mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
@@ -1542,10 +1582,10 @@ void VulkanDriver::commit(Handle<HwSwapChain> sch) {
void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index, void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
backend::PushConstantVariant value) { backend::PushConstantVariant value) {
assert_invariant(mBoundPipeline.program && "Expect a program when writing to push constants"); assert_invariant(mPipelineState.program && "Expect a program when writing to push constants");
assert_invariant(mCurrentRenderPass.commandBuffer && "Should be called within a renderpass"); assert_invariant(mCurrentRenderPass.commandBuffer && "Should be called within a renderpass");
mBoundPipeline.program->writePushConstant(mCurrentRenderPass.commandBuffer->buffer(), mPipelineState.program->writePushConstant(mCurrentRenderPass.commandBuffer->buffer(),
mBoundPipeline.pipelineLayout, stage, index, value); mPipelineState.pipelineLayout, stage, index, value);
} }
void VulkanDriver::insertEventMarker(char const* string) { void VulkanDriver::insertEventMarker(char const* string) {
@@ -1724,16 +1764,69 @@ void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
} }
void VulkanDriver::bindPipeline(PipelineState const& pipelineState) { void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
// This resets all of the pipeline states; the most relevant (needing reset) is .bindInDraw.
mPipelineState = {};
auto& setLayouts = pipelineState.pipelineLayout.setLayout;
DescriptorSetLayoutHandleList layoutHandles;
uint8_t layoutCount = 0;
std::transform(setLayouts.begin(), setLayouts.end(), layoutHandles.begin(),
[&](auto const& handle) -> resource_ptr<VulkanDescriptorSetLayout> {
if (!handle) {
return {};
}
layoutCount++;
return resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, handle);
});
constexpr uint8_t descriptorSetMaskTable[4] = {0x1, 0x3, 0x7, 0xF};
fvkutils::DescriptorSetMask const descriptorSetMask =
fvkutils::DescriptorSetMask(descriptorSetMaskTable[layoutCount]);
if (mAppState.hasExternalSamplers()) {
auto const haveExternalSamplers = [&](auto layoutHandle) {
if (!layoutHandle) {
return false;
}
return layoutHandle->hasExternalSamplers();
};
if (std::any_of(layoutHandles.begin(), layoutHandles.end(), haveExternalSamplers)) {
BindInDrawBundle bundle = {
.pipelineState = pipelineState,
.dsLayoutHandles = layoutHandles,
.descriptorSetMask = descriptorSetMask,
};
mPipelineState.bindInDraw = { true, bundle };
return;
}
}
// The normal, non-external sampler path
using VkDescriptorSetLayoutArray = VulkanPipelineLayoutCache::DescriptorSetLayoutArray;
VkDescriptorSetLayoutArray vkLayouts;
std::transform(layoutHandles.begin(), layoutHandles.end(), vkLayouts.begin(),
[](auto const& layout) -> VkDescriptorSetLayout {
if (!layout) {
return VK_NULL_HANDLE;
}
return layout->getVkLayout();
});
auto program = resource_ptr<VulkanProgram>::cast(&mResourceManager, pipelineState.program);
auto pipelineLayout = mPipelineLayoutCache.getLayout(vkLayouts, program);
bindPipelineImpl(pipelineState, pipelineLayout, descriptorSetMask);
}
void VulkanDriver::bindPipelineImpl(PipelineState const& pipelineState,
VkPipelineLayout pipelineLayout, fvkutils::DescriptorSetMask descriptorSetMask) {
FVK_SYSTRACE_SCOPE(); FVK_SYSTRACE_SCOPE();
auto commands = mCurrentRenderPass.commandBuffer; auto commands = mCurrentRenderPass.commandBuffer;
auto vbi = resource_ptr<VulkanVertexBufferInfo>::cast(&mResourceManager, auto vbi = resource_ptr<VulkanVertexBufferInfo>::cast(&mResourceManager,
pipelineState.vertexBufferInfo); pipelineState.vertexBufferInfo);
Handle<HwProgram> programHandle = pipelineState.program;
RasterState const& rasterState = pipelineState.rasterState; RasterState const& rasterState = pipelineState.rasterState;
PolygonOffset const& depthOffset = pipelineState.polygonOffset; PolygonOffset const& depthOffset = pipelineState.polygonOffset;
auto program = resource_ptr<VulkanProgram>::cast(&mResourceManager, programHandle); auto program = resource_ptr<VulkanProgram>::cast(&mResourceManager, pipelineState.program);
commands->acquire(program); commands->acquire(program);
// Update the VK raster state. // Update the VK raster state.
@@ -1775,28 +1868,11 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
mPipelineCache.bindPrimitiveTopology(topology); mPipelineCache.bindPrimitiveTopology(topology);
mPipelineCache.bindVertexArray(attribDesc, bufferDesc, vbi->getAttributeCount()); mPipelineCache.bindVertexArray(attribDesc, bufferDesc, vbi->getAttributeCount());
auto& setLayouts = pipelineState.pipelineLayout.setLayout; // Note that we cannot reinit mPipeline because the .bindInDraw metadata that needs to carry
VulkanDescriptorSetLayout::DescriptorSetLayoutArray layoutList; // over even on bind.
uint8_t layoutCount = 0; mPipelineState.program = program;
std::transform(setLayouts.begin(), setLayouts.end(), layoutList.begin(), mPipelineState.pipelineLayout = pipelineLayout;
[&](Handle<HwDescriptorSetLayout> handle) -> VkDescriptorSetLayout { mPipelineState.descriptorSetMask = descriptorSetMask;
if (!handle) {
return VK_NULL_HANDLE;
}
auto layout =
resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, handle);
layoutCount++;
return layout->getVkLayout();
});
auto pipelineLayout = mPipelineLayoutCache.getLayout(layoutList, program);
constexpr uint8_t descriptorSetMaskTable[4] = {0x1, 0x3, 0x7, 0xF};
mBoundPipeline = {
.program = program,
.pipelineLayout = pipelineLayout,
.descriptorSetMask = fvkutils::DescriptorSetMask(descriptorSetMaskTable[layoutCount]),
};
mPipelineCache.bindLayout(pipelineLayout); mPipelineCache.bindLayout(pipelineLayout);
mPipelineCache.bindPipeline(mCurrentRenderPass.commandBuffer); mPipelineCache.bindPipeline(mCurrentRenderPass.commandBuffer);
@@ -1841,15 +1917,41 @@ void VulkanDriver::bindDescriptorSet(
void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) { void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
FVK_SYSTRACE_SCOPE(); FVK_SYSTRACE_SCOPE();
VkCommandBuffer cmdbuffer = mCurrentRenderPass.commandBuffer->buffer(); VkCommandBuffer cmdbuffer = mCurrentRenderPass.commandBuffer->buffer();
auto const& [doBindInDraw, bundle] = mPipelineState.bindInDraw;
mDescriptorSetCache.commit(mCurrentRenderPass.commandBuffer, fvkutils::DescriptorSetMask setsWithExternalSamplers = {};
mBoundPipeline.pipelineLayout, if (doBindInDraw) {
mBoundPipeline.descriptorSetMask); auto& layoutHandles = bundle.dsLayoutHandles;
setsWithExternalSamplers = mExternalImageManager.prepareBindSets(layoutHandles,
mDescriptorSetCache.getBoundSets());
VulkanDescriptorSetLayout::DescriptorSetLayoutArray vklayouts;
for (size_t i = 0; i < layoutHandles.size(); i++) {
if (!layoutHandles[i]) {
vklayouts[i] = VK_NULL_HANDLE;
continue;
}
if (setsWithExternalSamplers[i]) {
vklayouts[i] = layoutHandles[i]->getExternalSamplerVkLayout();
} else {
vklayouts[i] = layoutHandles[i]->getVkLayout();
}
}
auto program =
resource_ptr<VulkanProgram>::cast(&mResourceManager, bundle.pipelineState.program);
VkPipelineLayout const pipelineLayout = mPipelineLayoutCache.getLayout(vklayouts, program);
if (pipelineLayout != mPipelineState.pipelineLayout) {
bindPipelineImpl(bundle.pipelineState, pipelineLayout, bundle.descriptorSetMask);
}
mPipelineState.bindInDraw.first = false;
}
mDescriptorSetCache.commit(mCurrentRenderPass.commandBuffer, mPipelineState.pipelineLayout,
setsWithExternalSamplers, mPipelineState.descriptorSetMask);
// Finally, make the actual draw call. TODO: support subranges // Finally, make the actual draw call. TODO: support subranges
const uint32_t firstIndex = indexOffset; uint32_t const firstIndex = indexOffset;
const int32_t vertexOffset = 0; constexpr int32_t vertexOffset = 0;
const uint32_t firstInstId = 0; constexpr uint32_t firstInstId = 0;
vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId); vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId);
} }
+35 -14
View File
@@ -30,6 +30,7 @@
#include "VulkanYcbcrConversionCache.h" #include "VulkanYcbcrConversionCache.h"
#include "vulkan/VulkanDescriptorSetCache.h" #include "vulkan/VulkanDescriptorSetCache.h"
#include "vulkan/VulkanDescriptorSetLayoutCache.h" #include "vulkan/VulkanDescriptorSetLayoutCache.h"
#include "vulkan/VulkanExternalImageManager.h"
#include "vulkan/VulkanPipelineLayoutCache.h" #include "vulkan/VulkanPipelineLayoutCache.h"
#include "vulkan/memory/ResourceManager.h" #include "vulkan/memory/ResourceManager.h"
#include "vulkan/memory/ResourcePointer.h" #include "vulkan/memory/ResourcePointer.h"
@@ -54,7 +55,7 @@ constexpr uint8_t MAX_RENDERTARGET_ATTACHMENT_TEXTURES =
class VulkanDriver final : public DriverBase { class VulkanDriver final : public DriverBase {
public: public:
static Driver* create(VulkanPlatform* platform, VulkanContext const& context, static Driver* create(VulkanPlatform* platform, VulkanContext const& context,
Platform::DriverConfig const& driverConfig) noexcept; Platform::DriverConfig const& driverConfig);
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) #if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
// Encapsulates the VK_EXT_debug_utils extension. In particular, we use // Encapsulates the VK_EXT_debug_utils extension. In particular, we use
@@ -89,8 +90,8 @@ private:
void debugCommandBegin(CommandStream* cmds, bool synchronous, void debugCommandBegin(CommandStream* cmds, bool synchronous,
const char* methodName) noexcept override; const char* methodName) noexcept override;
inline VulkanDriver(VulkanPlatform* platform, VulkanContext const& context, VulkanDriver(VulkanPlatform* platform, VulkanContext const& context,
Platform::DriverConfig const& driverConfig) noexcept; Platform::DriverConfig const& driverConfig);
~VulkanDriver() noexcept override; ~VulkanDriver() noexcept override;
@@ -119,6 +120,8 @@ private:
private: private:
void collectGarbage(); void collectGarbage();
void bindPipelineImpl(PipelineState const& pipelineState, VkPipelineLayout pipelineLayout,
fvkutils::DescriptorSetMask descriptorSetMask);
VulkanPlatform* mPlatform = nullptr; VulkanPlatform* mPlatform = nullptr;
fvkmemory::ResourceManager mResourceManager; fvkmemory::ResourceManager mResourceManager;
@@ -143,21 +146,39 @@ private:
VulkanDescriptorSetLayoutCache mDescriptorSetLayoutCache; VulkanDescriptorSetLayoutCache mDescriptorSetLayoutCache;
VulkanDescriptorSetCache mDescriptorSetCache; VulkanDescriptorSetCache mDescriptorSetCache;
VulkanQueryManager mQueryManager; VulkanQueryManager mQueryManager;
VulkanExternalImageManager mExternalImageManager;
// This is necessary for us to write to push constants after binding a pipeline. // This is necessary for us to write to push constants after binding a pipeline.
struct { using DescriptorSetLayoutHandleList = std::array<resource_ptr<VulkanDescriptorSetLayout>,
resource_ptr<VulkanProgram> program; VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
VkPipelineLayout pipelineLayout;
fvkutils::DescriptorSetMask descriptorSetMask; struct BindInDrawBundle {
} mBoundPipeline = {}; PipelineState pipelineState = {};
DescriptorSetLayoutHandleList dsLayoutHandles = {};
fvkutils::DescriptorSetMask descriptorSetMask = {};
resource_ptr<VulkanProgram> program = {};
};
// We need to store information about a render pass to enable better barriers at the end of a
// renderpass.
struct { struct {
using AttachmentArray = // For push constant
fvkutils::StaticVector<VulkanAttachment, MAX_RENDERTARGET_ATTACHMENT_TEXTURES>; resource_ptr<VulkanProgram> program = {};
AttachmentArray attachments; // For push commiting dynamic ubos in draw()
} mRenderPassFboInfo = {}; VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
fvkutils::DescriptorSetMask descriptorSetMask = {};
std::pair<bool, BindInDrawBundle> bindInDraw = {false, {}};
} mPipelineState = {};
struct {
// This tracks whether the app has seen external samplers bound to a the descriptor set.
// This will force bindPipeline to take a slow path.
bool hasExternalSamplerLayouts = false;
bool hasBoundExternalImages = false;
bool hasExternalSamplers() const noexcept {
return hasExternalSamplerLayouts && hasBoundExternalImages;
}
} mAppState;
bool const mIsSRGBSwapChainSupported; bool const mIsSRGBSwapChainSupported;
backend::StereoscopicType const mStereoscopicType; backend::StereoscopicType const mStereoscopicType;
@@ -0,0 +1,294 @@
/*
* 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 "VulkanExternalImageManager.h"
#include "VulkanDescriptorSetCache.h"
#include "VulkanDescriptorSetLayoutCache.h"
#include "VulkanSamplerCache.h"
#include "VulkanYcbcrConversionCache.h"
#include "vulkan/memory/ResourcePointer.h"
#include "vulkan/utils/Conversion.h"
#include <backend/platforms/VulkanPlatform.h>
#include <algorithm>
namespace filament::backend {
namespace {
using Bitmask = fvkutils::UniformBufferBitmask;
static_assert(sizeof(Bitmask) * 8 == fvkutils::MAX_DESCRIPTOR_SET_BITMASK_BITS);
template<typename T>
void erasep(std::vector<T>& v, std::function<bool(T const&)> f) {
auto newEnd = std::remove_if(v.begin(), v.end(), f);
v.erase(newEnd, v.end());
}
using ImageData = VulkanExternalImageManager::VulkanExternalImageManager::ImageData;
ImageData& findImage(std::vector<ImageData>& images,
fvkmemory::resource_ptr<VulkanTexture> texture) {
auto itr = std::find_if(images.begin(), images.end(), [&](ImageData const& data) {
return data.image == texture;
});
assert_invariant(itr != images.end());
return *itr;
}
void copySet(VkDevice device, VkDescriptorSet srcSet, VkDescriptorSet dstSet, Bitmask bindings) {
// TODO: fix the size for better memory management
std::vector<VkCopyDescriptorSet> copies;
bindings.forEachSetBit([&](size_t index) {
copies.push_back({
.sType = VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET,
.srcSet = srcSet,
.srcBinding = (uint32_t) index,
.dstSet = dstSet,
.dstBinding = (uint32_t) index,
.descriptorCount = 1,
});
});
vkUpdateDescriptorSets(device, 0, nullptr, copies.size(), copies.data());
}
Bitmask foldBitsInHalf(Bitmask bitset) {
Bitmask outBitset;
bitset.forEachSetBit([&](size_t index) {
constexpr size_t BITMASK_LOWER_BITS_LEN = sizeof(outBitset) * 4;
outBitset.set(index % BITMASK_LOWER_BITS_LEN);
});
return outBitset;
}
}// namespace
VulkanExternalImageManager::VulkanExternalImageManager(VulkanPlatform* platform,
VulkanSamplerCache* samplerCache, VulkanYcbcrConversionCache* ycbcrConversionCache,
VulkanDescriptorSetCache* setCache, VulkanDescriptorSetLayoutCache* layoutCache)
: mPlatform(platform),
mSamplerCache(samplerCache),
mYcbcrConversionCache(ycbcrConversionCache),
mDescriptorSetCache(setCache),
mDescriptorSetLayoutCache(layoutCache) {
}
VulkanExternalImageManager::~VulkanExternalImageManager() = default;
void VulkanExternalImageManager::terminate() {
mSetBindings.clear();
mImages.clear();
}
void VulkanExternalImageManager::onBeginFrame() {
std::for_each(mImages.begin(), mImages.end(), [](ImageData& image) {
image.hasBeenValidated = false;
});
std::for_each(mSetBindings.begin(), mSetBindings.end(), [](SetBindingInfo& info) {
info.bound = false;
});
}
fvkutils::DescriptorSetMask VulkanExternalImageManager::prepareBindSets(LayoutArray const& layouts,
SetArray const& sets) {
fvkutils::DescriptorSetMask shouldUseExternalSampler{};
for (uint8_t i = 0; i < sets.size(); i++) {
auto set = sets[i];
auto layout = layouts[i];
if (!set || !layout) {
continue;
}
if (hasExternalSampler(set)) {
updateSetAndLayout(set, layout);
shouldUseExternalSampler.set(i);
}
}
return shouldUseExternalSampler;
}
bool VulkanExternalImageManager::hasExternalSampler(
fvkmemory::resource_ptr<VulkanDescriptorSet> set) {
auto itr = std::find_if(mSetBindings.begin(), mSetBindings.end(),
[&](SetBindingInfo const& info) { return info.set == set; });
return itr != mSetBindings.end();
}
void VulkanExternalImageManager::updateSetAndLayout(
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
utils::FixedCapacityVector<
std::tuple<uint8_t, VkSampler, fvkmemory::resource_ptr<VulkanTexture>>>
samplerAndBindings;
samplerAndBindings.reserve(MAX_SAMPLER_COUNT);
fvkutils::SamplerBitmask actualExternalSamplers;
for (auto& bindingInfo : mSetBindings) {
if (bindingInfo.set != set || bindingInfo.bound) {
continue;
}
auto& imageData = findImage(mImages, bindingInfo.image);
updateImage(&imageData);
auto samplerParams = bindingInfo.samplerParams;
// according to spec, these must match chromaFilter
// https://registry.khronos.org/vulkan/specs/latest/man/html/VkSamplerCreateInfo.html#VUID-VkSamplerCreateInfo-minFilter-01645
samplerParams.filterMag = SamplerMagFilter::NEAREST;
samplerParams.filterMin = SamplerMinFilter::NEAREST;
auto sampler = mSamplerCache->getSampler({
.sampler = samplerParams,
.conversion = imageData.conversion,
});
actualExternalSamplers.set(bindingInfo.binding);
samplerAndBindings.push_back({ bindingInfo.binding, sampler, bindingInfo.image });
bindingInfo.bound = true;
}
if (samplerAndBindings.empty()) {
return;
}
// Sort by binding number
std::sort(samplerAndBindings.begin(), samplerAndBindings.end(), [](auto const& a, auto const& b) {
return std::get<0>(a) < std::get<0>(b);
});
utils::FixedCapacityVector<VkSampler> outSamplers;
outSamplers.reserve(MAX_SAMPLER_COUNT);
std::for_each(samplerAndBindings.begin(), samplerAndBindings.end(),
[&](auto const& b) { outSamplers.push_back(std::get<1>(b)); });
VkDescriptorSetLayout const oldLayout = layout->getExternalSamplerVkLayout();
VkDescriptorSetLayout const newLayout = mDescriptorSetLayoutCache->getVkLayout(layout->bitmask,
actualExternalSamplers, outSamplers);
// Need to copy the set
VkDescriptorSet const oldSet = set->getExternalSamplerVkSet();
if (oldLayout != newLayout || oldSet == VK_NULL_HANDLE) {
// Build a new descriptor set from the new layout
VkDescriptorSet const newSet = mDescriptorSetCache->getVkSet(layout->count, newLayout);
auto const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
auto const samplers = layout->bitmask.sampler & (~actualExternalSamplers);
// Each bitmask denotes a binding index, and separated into two stages - vertex and buffer
// We fold the two stages into just the lower half of the bits to denote a combined set of
// bindings.
Bitmask const copyBindings = foldBitsInHalf(ubo | samplers);
VkDescriptorSet const srcSet = oldSet != VK_NULL_HANDLE ? oldSet : set->getVkSet();
copySet(mPlatform->getDevice(), srcSet, newSet, copyBindings);
set->setExternalSamplerVkSet(newSet, [&](VulkanDescriptorSet*) {
mDescriptorSetCache->manualRecycle(layout->count, newLayout, newSet);
});
if (oldLayout != newLayout) {
layout->setExternalSamplerVkLayout(newLayout);
}
}
// Update the external samplers in the set
for (auto& [binding, sampler, image]: samplerAndBindings) {
mDescriptorSetCache->updateSamplerForExternalSamplerSet(set, binding, image);
}
}
VkSamplerYcbcrConversion VulkanExternalImageManager::getVkSamplerYcbcrConversion(
VulkanPlatform::ExternalImageMetadata const& metadata) {
// This external image does not require external sampler (YUV conversion).
if (metadata.externalFormat == 0 && !fvkutils::isVKYcbcrConversionFormat(metadata.format)) {
return VK_NULL_HANDLE;
}
VulkanYcbcrConversionCache::Params ycbcrParams = {
.conversion = {
.ycbcrModel = fvkutils::getYcbcrModelConversionFilament(metadata.ycbcrModel),
.r = fvkutils::getSwizzleFilament(metadata.ycbcrConversionComponents.r, 0),
.g = fvkutils::getSwizzleFilament(metadata.ycbcrConversionComponents.g, 1),
.b = fvkutils::getSwizzleFilament(metadata.ycbcrConversionComponents.b, 2),
.a = fvkutils::getSwizzleFilament(metadata.ycbcrConversionComponents.a, 3),
.ycbcrRange = fvkutils::getYcbcrRangeFilament(metadata.ycbcrRange),
.xChromaOffset = fvkutils::getChromaLocationFilament(metadata.xChromaOffset),
.yChromaOffset = fvkutils::getChromaLocationFilament(metadata.yChromaOffset),
// Unclear where to get the chromaFilter, we just assume it's nearest.
.chromaFilter = SamplerMagFilter::NEAREST,
},
.format = metadata.format,
.externalFormat = metadata.externalFormat,
};
return mYcbcrConversionCache->getConversion(ycbcrParams);
}
void VulkanExternalImageManager::updateImage(ImageData* image) {
if (image->hasBeenValidated) {
return;
}
image->hasBeenValidated = true;
auto metadata = mPlatform->extractExternalImageMetadata(image->platformHandle);
auto vkYcbcr = getVkSamplerYcbcrConversion(metadata);
if (vkYcbcr == image->conversion) {
return;
}
image->image->setYcbcrConversion(vkYcbcr);
image->conversion = vkYcbcr;
return;
}
void VulkanExternalImageManager::removeDescriptorSet(
fvkmemory::resource_ptr<VulkanDescriptorSet> inSet) {
erasep<SetBindingInfo>(mSetBindings,
[&](auto const& bindingInfo) { return (bindingInfo.set == inSet); });
}
void VulkanExternalImageManager::bindExternallySampledTexture(
fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t bindingPoint,
fvkmemory::resource_ptr<VulkanTexture> image, SamplerParams samplerParams) {
// Should we do duplicate validation here?
auto& imageData = findImage(mImages, image);
mSetBindings.push_back({ bindingPoint, imageData.image, set, samplerParams });
}
void VulkanExternalImageManager::addExternallySampledTexture(
fvkmemory::resource_ptr<VulkanTexture> image,
Platform::ExternalImageHandleRef platformHandleRef) {
mImages.push_back({ image, platformHandleRef, false });
}
void VulkanExternalImageManager::removeExternallySampledTexture(
fvkmemory::resource_ptr<VulkanTexture> image) {
erasep<SetBindingInfo>(mSetBindings,
[&](auto const& bindingInfo) { return (bindingInfo.image == image); });
erasep<ImageData>(mImages, [&](auto const& imageData) {
return imageData.image == image;
});
}
bool VulkanExternalImageManager::isExternallySampledTexture(
fvkmemory::resource_ptr<VulkanTexture> image) const {
return std::find_if(mImages.begin(), mImages.end(), [&](auto const& imageData) {
return imageData.image == image;
}) != mImages.end();
}
void VulkanExternalImageManager::clearTextureBinding(
fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t bindingPoint) {
erasep<SetBindingInfo>(mSetBindings, [&](auto const& bindingInfo) {
return (bindingInfo.set == set && bindingInfo.binding == bindingPoint);
});
}
} // namesapce filament::backend
@@ -0,0 +1,121 @@
/*
* 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_CACHING_VULKANEXTERNALIMAGEMANAGER_H
#define TNT_FILAMENT_BACKEND_CACHING_VULKANEXTERNALIMAGEMANAGER_H
#include "VulkanHandles.h"
#include <backend/DriverEnums.h>
#include <array>
#include <vector>
namespace filament::backend {
class VulkanYcbcrConversionCache;
class VulkanSamplerCache;
class VulkanDescriptorSetLayoutCache;
class VulkanDescriptorSetCache;
// Manages the logic of external images and their quirks wrt Vulikan.
class VulkanExternalImageManager {
public:
VulkanExternalImageManager(
VulkanPlatform* platform,
VulkanSamplerCache* samplerCache,
VulkanYcbcrConversionCache* ycbcrConversionCache,
VulkanDescriptorSetCache* setCache,
VulkanDescriptorSetLayoutCache* layoutCache);
~VulkanExternalImageManager();
void terminate();
void onBeginFrame();
using SetArray = std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>,
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
using LayoutArray = std::array<fvkmemory::resource_ptr<VulkanDescriptorSetLayout>,
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
using VkLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
// Returns bitmask to indicate whether or not to use the external sampler version of each
// descriptor set.
fvkutils::DescriptorSetMask prepareBindSets(LayoutArray const& layouts, SetArray const& sets);
void removeDescriptorSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set);
void bindExternallySampledTexture(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
uint8_t bindingPoint, fvkmemory::resource_ptr<VulkanTexture> image,
SamplerParams samplerParams);
void clearTextureBinding(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
uint8_t bindingPoint);
void addExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> external,
Platform::ExternalImageHandleRef platformHandleRef);
void removeExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> image);
bool isExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> image) const;
VkSamplerYcbcrConversion getVkSamplerYcbcrConversion(
VulkanPlatform::ExternalImageMetadata const& metadata);
struct ImageData {
fvkmemory::resource_ptr<VulkanTexture> image;
Platform::ExternalImageHandle platformHandle;
bool hasBeenValidated = false; // indicates whether the image has been validated *this frame*
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
};
private:
bool hasExternalSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set);
void updateSetAndLayout(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
void updateImage(ImageData* imageData);
VulkanPlatform* mPlatform;
VulkanSamplerCache* mSamplerCache;
VulkanYcbcrConversionCache* mYcbcrConversionCache;
VulkanDescriptorSetCache* mDescriptorSetCache;
VulkanDescriptorSetLayoutCache* mDescriptorSetLayoutCache;
using SetAndLayout = std::pair<fvkmemory::resource_ptr<VulkanDescriptorSet>,
fvkmemory::resource_ptr<VulkanDescriptorSetLayout>>;
struct SetBindingInfo {
uint8_t binding = 0;
fvkmemory::resource_ptr<VulkanTexture> image;
fvkmemory::resource_ptr<VulkanDescriptorSet> set;
SamplerParams samplerParams;
bool bound = false;
};
// Use vectors instead of hash maps because we only expect small number of entries.
std::vector<SetBindingInfo> mSetBindings;
std::vector<ImageData> mImages;
};
} // filament::backend
#endif // TNT_FILAMENT_BACKEND_CACHING_VULKANEXTERNALIMAGEMANAGER_H
+14 -6
View File
@@ -330,12 +330,20 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept
mRenderPassCache[config] = {renderPass, mCurrentTime}; mRenderPassCache[config] = {renderPass, mCurrentTime};
#if FVK_ENABLED(FVK_DEBUG_FBO_CACHE) #if FVK_ENABLED(FVK_DEBUG_FBO_CACHE)
FVK_LOGD << "Created render pass " << renderPass << " with " FVK_LOGD << "Created render pass " << renderPass << " with ";
<< "samples = " << int(config.samples) << ", " for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; ++i) {
<< "depth = " << (hasDepth ? 1 : 0) << ", " FVK_LOGD << (int) config.colorFormat[i] << " ";
<< "colorAttachmentCount[0] = " << subpasses[0].colorAttachmentCount }
<< utils::io::endl; FVK_LOGD << ", "
#endif << "depth = " << config.depthFormat << ", "
<< "initialDepthLayout = " << (int) config.initialDepthLayout << ", "
<< "samples = " << int(config.samples) << ", "
<< "needsResolveMask = " << int(config.needsResolveMask) << ", "
<< "usesLazilyAllocatedMemory = " << int(config.usesLazilyAllocatedMemory) << ", "
<< "viewCount = " << int(config.viewCount) << ", "
<< "colorAttachmentCount[0] = " << subpasses[0].colorAttachmentCount
<< utils::io::endl;
#endif
return renderPass; return renderPass;
} }
+10 -3
View File
@@ -149,11 +149,18 @@ void VulkanDescriptorSet::acquire(fvkmemory::resource_ptr<VulkanBufferObject> ob
mResources.push_back(obj); mResources.push_back(obj);
} }
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout const& layout) VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout&& layout,
VkDescriptorSetLayout vkLayout)
: bitmask(fromBackendLayout(layout)), : bitmask(fromBackendLayout(layout)),
count(Count::fromLayoutBitmask(bitmask)) {} count(Count::fromLayoutBitmask(bitmask)),
mVkLayout(vkLayout) {}
PushConstantDescription::PushConstantDescription(backend::Program const& program) noexcept { VulkanDescriptorSetLayout::Bitmask VulkanDescriptorSetLayout::Bitmask::fromLayoutDescription(
DescriptorSetLayout const& layout) {
return fromBackendLayout(layout);
}
PushConstantDescription::PushConstantDescription(backend::Program const& program) {
mRangeCount = 0; mRangeCount = 0;
for (auto stage : { ShaderStage::VERTEX, ShaderStage::FRAGMENT, ShaderStage::COMPUTE }) { for (auto stage : { ShaderStage::VERTEX, ShaderStage::FRAGMENT, ShaderStage::COMPUTE }) {
auto const& constants = program.getPushConstants(stage); auto const& constants = program.getPushConstants(stage);
+40 -13
View File
@@ -71,7 +71,7 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
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. // This is a subset of the sampler field.
fvkutils::SamplerBitmask externalSampler; // 8 bytes fvkutils::SamplerBitmask externalSampler; // 8 bytes
bool operator==(Bitmask const& right) const { bool operator==(Bitmask const& right) const {
@@ -79,6 +79,8 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
inputAttachment == right.inputAttachment && inputAttachment == right.inputAttachment &&
externalSampler == right.externalSampler; externalSampler == right.externalSampler;
} }
static Bitmask fromLayoutDescription(DescriptorSetLayout const& layout);
}; };
static_assert(sizeof(Bitmask) == 40); static_assert(sizeof(Bitmask) == 40);
@@ -120,16 +122,20 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
} }
}; };
VulkanDescriptorSetLayout(DescriptorSetLayout const& layout); VulkanDescriptorSetLayout(DescriptorSetLayout&& layout, VkDescriptorSetLayout vkLayout);
// Note that we don't destroy the vklayout. This is done by the layout cache. // Note that we don't destroy the vklayout. This is done by the layout cache.
~VulkanDescriptorSetLayout() = default; ~VulkanDescriptorSetLayout() = default;
VkDescriptorSetLayout const& getVkLayout() const noexcept { return mVkLayout; } VkDescriptorSetLayout getVkLayout() const noexcept { return mVkLayout; }
// It is possible to have the layout switch out due to AHardwarebuffer (external image) format VkDescriptorSetLayout getExternalSamplerVkLayout() const noexcept {
// changes. return mExternalSamplerVkLayout;
void setVkLayout(VkDescriptorSetLayout vklayout) noexcept { mVkLayout = vklayout; } }
void setExternalSamplerVkLayout(VkDescriptorSetLayout vklayout) noexcept {
mExternalSamplerVkLayout = vklayout;
}
bool hasExternalSamplers() const noexcept { return bitmask.externalSampler.count() > 0; } bool hasExternalSamplers() const noexcept { return bitmask.externalSampler.count() > 0; }
@@ -137,7 +143,11 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
Count const count; Count const count;
private: private:
VkDescriptorSetLayout mVkLayout = VK_NULL_HANDLE; // This is the layout without any immutable samplers.
VkDescriptorSetLayout const mVkLayout = VK_NULL_HANDLE;
// This is the layout with immutable samplers, and can be updated.
VkDescriptorSetLayout mExternalSamplerVkLayout = VK_NULL_HANDLE;
}; };
struct VulkanDescriptorSet : public HwDescriptorSet, fvkmemory::Resource { struct VulkanDescriptorSet : public HwDescriptorSet, fvkmemory::Resource {
@@ -149,23 +159,37 @@ public:
VulkanDescriptorSet( VulkanDescriptorSet(
fvkutils::UniformBufferBitmask const& dynamicUboMask, fvkutils::UniformBufferBitmask const& dynamicUboMask,
uint8_t uniqueDynamicUboCount, uint8_t uniqueDynamicUboCount,
OnRecycle&& onRecycleFn) OnRecycle&& onRecycleFn, VkDescriptorSet vkSet)
: dynamicUboMask(dynamicUboMask), : dynamicUboMask(dynamicUboMask),
uniqueDynamicUboCount(uniqueDynamicUboCount), uniqueDynamicUboCount(uniqueDynamicUboCount),
mVkSet(vkSet),
mOnRecycleFn(std::move(onRecycleFn)) {} mOnRecycleFn(std::move(onRecycleFn)) {}
// NOLINTNEXTLINE(bugprone-exception-escape)
~VulkanDescriptorSet() { ~VulkanDescriptorSet() {
if (mOnRecycleFn) { if (mOnRecycleFn) {
mOnRecycleFn(this); mOnRecycleFn(this);
} }
if (mOnRecycleExternalSamplerFn) {
mOnRecycleExternalSamplerFn(this);
}
} }
VkDescriptorSet const& getVkSet() const noexcept { VkDescriptorSet getVkSet() const noexcept {
return mVkSet; return mVkSet;
} }
// Note that the only case where you'd set it more than once is with external images/samplers. VkDescriptorSet getExternalSamplerVkSet() const noexcept {
void setVkSet(VkDescriptorSet vkset) noexcept { mVkSet = vkset; } return mExternalSamplerVkSet;
}
void setExternalSamplerVkSet(VkDescriptorSet vkset, OnRecycle onRecycle) {
mExternalSamplerVkSet = vkset;
if (mOnRecycleExternalSamplerFn) {
mOnRecycleExternalSamplerFn(this);
}
mOnRecycleExternalSamplerFn = onRecycle;
}
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept { void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
mOffsets = std::move(offsets); mOffsets = std::move(offsets);
@@ -182,17 +206,20 @@ public:
uint8_t const uniqueDynamicUboCount; uint8_t const uniqueDynamicUboCount;
private: private:
VkDescriptorSet mVkSet = VK_NULL_HANDLE; VkDescriptorSet const mVkSet;
VkDescriptorSet mExternalSamplerVkSet = 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;
OnRecycle mOnRecycleExternalSamplerFn;
}; };
using PushConstantNameArray = utils::FixedCapacityVector<char const*>; using PushConstantNameArray = utils::FixedCapacityVector<char const*>;
using PushConstantNameByStage = std::array<PushConstantNameArray, Program::SHADER_TYPE_COUNT>; using PushConstantNameByStage = std::array<PushConstantNameArray, Program::SHADER_TYPE_COUNT>;
struct PushConstantDescription { struct PushConstantDescription {
explicit PushConstantDescription(backend::Program const& program) noexcept; explicit PushConstantDescription(backend::Program const& program);
VkPushConstantRange const* getVkRanges() const noexcept { return mRanges; } VkPushConstantRange const* getVkRanges() const noexcept { return mRanges; }
uint32_t getVkRangeCount() const noexcept { return mRangeCount; } uint32_t getVkRangeCount() const noexcept { return mRangeCount; }
@@ -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;
@@ -61,8 +66,12 @@ void VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) {
// If an error occurred, allow higher levels to handle it gracefully. // If an error occurred, allow higher levels to handle it gracefully.
assert_invariant(cacheEntry != nullptr && "Failed to create/find pipeline"); assert_invariant(cacheEntry != nullptr && "Failed to create/find pipeline");
mBoundPipeline = mPipelineRequirements;
vkCmdBindPipeline(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, cacheEntry->handle); static PipelineEqual equal;
if (!equal(mBoundPipeline, mPipelineRequirements)) {
mBoundPipeline = mPipelineRequirements;
vkCmdBindPipeline(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, cacheEntry->handle);
}
} }
VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() noexcept { VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() noexcept {
@@ -215,7 +224,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 +280,8 @@ void VulkanPipelineCache::terminate() noexcept {
} }
mPipelines.clear(); mPipelines.clear();
mBoundPipeline = {}; mBoundPipeline = {};
vkDestroyPipelineCache(mDevice, mPipelineCache, VKALLOC);
} }
void VulkanPipelineCache::gc() noexcept { void VulkanPipelineCache::gc() noexcept {
@@ -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 = {};
@@ -335,7 +335,7 @@ void VulkanReadPixels::run(fvkmemory::resource_ptr<VulkanRenderTarget> srcTarget
mTaskHandler->post(std::move(waitFenceFunc), std::move(cleanPbdFunc)); mTaskHandler->post(std::move(waitFenceFunc), std::move(cleanPbdFunc));
} }
void VulkanReadPixels::runUntilComplete() noexcept { void VulkanReadPixels::runUntilComplete() {
if (!mTaskHandler) { if (!mTaskHandler) {
return; return;
} }
@@ -79,7 +79,7 @@ public:
OnReadCompleteFunction const& readCompleteFunc); OnReadCompleteFunction const& readCompleteFunc);
// This method will block until all of the in-flight requests are complete. // This method will block until all of the in-flight requests are complete.
void runUntilComplete() noexcept; void runUntilComplete();
private: private:
VkDevice mDevice = VK_NULL_HANDLE; VkDevice mDevice = VK_NULL_HANDLE;
@@ -28,7 +28,7 @@ namespace filament::backend {
VulkanSamplerCache::VulkanSamplerCache(VkDevice device) VulkanSamplerCache::VulkanSamplerCache(VkDevice device)
: mDevice(device) {} : mDevice(device) {}
VkSampler VulkanSamplerCache::getSampler(Params params) noexcept { VkSampler VulkanSamplerCache::getSampler(Params params) {
auto iter = mCache.find(params); auto iter = mCache.find(params);
if (UTILS_LIKELY(iter != mCache.end())) { if (UTILS_LIKELY(iter != mCache.end())) {
return iter->second; return iter->second;
@@ -38,7 +38,7 @@ public:
static_assert(sizeof(Params) == 16); static_assert(sizeof(Params) == 16);
explicit VulkanSamplerCache(VkDevice device); explicit VulkanSamplerCache(VkDevice device);
VkSampler getSampler(Params params) noexcept; VkSampler getSampler(Params params);
void terminate() noexcept; void terminate() noexcept;
private: private:
VkDevice mDevice; VkDevice mDevice;
@@ -24,7 +24,7 @@
#include <utils/Panic.h> #include <utils/Panic.h>
static constexpr uint32_t TIME_BEFORE_EVICTION = FVK_MAX_COMMAND_BUFFERS; static constexpr uint32_t TIME_BEFORE_EVICTION = 3;
namespace filament::backend { namespace filament::backend {
@@ -39,7 +39,7 @@ VulkanStage const* VulkanStagePool::acquireStage(uint32_t numBytes) {
auto stage = iter->second; auto stage = iter->second;
mFreeStages.erase(iter); mFreeStages.erase(iter);
stage->lastAccessed = mCurrentFrame; stage->lastAccessed = mCurrentFrame;
mUsedStages.insert(stage); mUsedStages.push_back(stage);
return stage; return stage;
} }
// We were not able to find a sufficiently large stage, so create a new one. // We were not able to find a sufficiently large stage, so create a new one.
@@ -51,7 +51,7 @@ VulkanStage const* VulkanStagePool::acquireStage(uint32_t numBytes) {
}); });
// Create the VkBuffer. // Create the VkBuffer.
mUsedStages.insert(stage); mUsedStages.push_back(stage);
VkBufferCreateInfo bufferInfo { VkBufferCreateInfo bufferInfo {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = numBytes, .size = numBytes,
@@ -77,7 +77,7 @@ VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, Pi
if (image->format == vkformat && image->width == width && image->height == height) { if (image->format == vkformat && image->width == width && image->height == height) {
mFreeImages.erase(image); mFreeImages.erase(image);
image->lastAccessed = mCurrentFrame; image->lastAccessed = mCurrentFrame;
mUsedImages.insert(image); mUsedImages.push_back(image);
return image; return image;
} }
} }
@@ -89,7 +89,7 @@ VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, Pi
.lastAccessed = mCurrentFrame, .lastAccessed = mCurrentFrame,
}); });
mUsedImages.insert(image); mUsedImages.push_back(image);
const VkImageCreateInfo imageInfo = { const VkImageCreateInfo imageInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
@@ -161,7 +161,7 @@ void VulkanStagePool::gc() noexcept {
stage->lastAccessed = mCurrentFrame; stage->lastAccessed = mCurrentFrame;
mFreeStages.insert(std::make_pair(stage->capacity, stage)); mFreeStages.insert(std::make_pair(stage->capacity, stage));
} else { } else {
mUsedStages.insert(stage); mUsedStages.push_back(stage);
} }
} }
@@ -185,7 +185,7 @@ void VulkanStagePool::gc() noexcept {
image->lastAccessed = mCurrentFrame; image->lastAccessed = mCurrentFrame;
mFreeImages.insert(image); mFreeImages.insert(image);
} else { } else {
mUsedImages.insert(image); mUsedImages.push_back(image);
} }
} }
FVK_SYSTRACE_END(); FVK_SYSTRACE_END();
@@ -22,6 +22,7 @@
#include <map> #include <map>
#include <unordered_set> #include <unordered_set>
#include <vector>
namespace filament::backend { namespace filament::backend {
@@ -73,10 +74,10 @@ private:
std::multimap<uint32_t, VulkanStage const*> mFreeStages; std::multimap<uint32_t, VulkanStage const*> mFreeStages;
// Simple unordered set for stashing a list of in-use stages that can be reclaimed later. // Simple unordered set for stashing a list of in-use stages that can be reclaimed later.
std::unordered_set<VulkanStage const*> mUsedStages; std::vector<VulkanStage const*> mUsedStages;
std::unordered_set<VulkanStageImage const*> mFreeImages; std::unordered_set<VulkanStageImage const*> mFreeImages;
std::unordered_set<VulkanStageImage const*> mUsedImages; std::vector<VulkanStageImage const*> mUsedImages;
// Store the current "time" (really just a frame count) and LRU eviction parameters. // Store the current "time" (really just a frame count) and LRU eviction parameters.
uint64_t mCurrentFrame = 0; uint64_t mCurrentFrame = 0;
+6 -11
View File
@@ -223,8 +223,7 @@ VkImageUsageFlags getUsage(VulkanContext const& context, uint8_t samples,
VulkanTextureState::VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands, VulkanTextureState::VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands,
VmaAllocator allocator, VkDevice device, VkImage image, VkDeviceMemory deviceMemory, VmaAllocator allocator, VkDevice device, VkImage image, VkDeviceMemory deviceMemory,
VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount, VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount,
VkSamplerYcbcrConversion ycbcrConversion, bool isExternalFormat, VkImageUsageFlags usage, VkSamplerYcbcrConversion ycbcrConversion, VkImageUsageFlags usage, bool isProtected)
bool isProtected)
: mStagePool(stagePool), : mStagePool(stagePool),
mCommands(commands), mCommands(commands),
mAllocator(allocator), mAllocator(allocator),
@@ -234,17 +233,17 @@ VulkanTextureState::VulkanTextureState(VulkanStagePool& stagePool, VulkanCommand
mVkFormat(format), mVkFormat(format),
mViewType(viewType), mViewType(viewType),
mFullViewRange{ fvkutils::getImageAspect(format), 0, levels, 0, layerCount }, mFullViewRange{ fvkutils::getImageAspect(format), 0, levels, 0, layerCount },
mYcbcr{ ycbcrConversion, isExternalFormat }, mYcbcr{ ycbcrConversion },
mDefaultLayout(getDefaultLayoutImpl(usage)), mDefaultLayout(getDefaultLayoutImpl(usage)),
mUsage(usage), mUsage(usage),
mIsProtected(isProtected) {} mIsProtected(isProtected) {}
VulkanTextureState::~VulkanTextureState() { VulkanTextureState::~VulkanTextureState() {
clearCachedImageViews();
if (mTextureImageMemory != VK_NULL_HANDLE) { if (mTextureImageMemory != VK_NULL_HANDLE) {
vkDestroyImage(mDevice, mTextureImage, VKALLOC); vkDestroyImage(mDevice, mTextureImage, VKALLOC);
vkFreeMemory(mDevice, mTextureImageMemory, VKALLOC); vkFreeMemory(mDevice, mTextureImageMemory, VKALLOC);
} }
clearCachedImageViews();
} }
void VulkanTextureState::clearCachedImageViews() noexcept { void VulkanTextureState::clearCachedImageViews() noexcept {
@@ -272,7 +271,7 @@ VkImageView VulkanTextureState::getImageView(VkImageSubresourceRange range, VkIm
.flags = 0, .flags = 0,
.image = mTextureImage, .image = mTextureImage,
.viewType = viewType, .viewType = viewType,
.format = mYcbcr.isExternalFormat ? VK_FORMAT_UNDEFINED : mVkFormat, .format = mYcbcr.conversion != VK_NULL_HANDLE ? VK_FORMAT_UNDEFINED : mVkFormat,
.components = swizzle, .components = swizzle,
.subresourceRange = range, .subresourceRange = range,
}; };
@@ -294,7 +293,6 @@ VulkanTexture::VulkanTexture(VulkanContext const& context, VkDevice device, VmaA
commands, allocator, device, image, memory, format, commands, allocator, device, image, memory, format,
fvkutils::getViewType(SamplerType::SAMPLER_2D), fvkutils::getViewType(SamplerType::SAMPLER_2D),
/*mipLevels=*/1, getLayerCountFromDepth(depth), conversion, /*mipLevels=*/1, getLayerCountFromDepth(depth), conversion,
/*isExternalFormat=*/false,
getUsage(context, samples, VK_NULL_HANDLE, format, tusage), getUsage(context, samples, VK_NULL_HANDLE, format, tusage),
any(usage & TextureUsage::PROTECTED))) { any(usage & TextureUsage::PROTECTED))) {
mPrimaryViewRange = mState->mFullViewRange; mPrimaryViewRange = mState->mFullViewRange;
@@ -424,8 +422,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
mState = fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, stagePool, mState = fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, stagePool,
commands, allocator, device, textureImage, textureImageMemory, vkFormat, commands, allocator, device, textureImage, textureImageMemory, vkFormat,
fvkutils::getViewType(target), levels, getLayerCount(target, depth), fvkutils::getViewType(target), levels, getLayerCount(target, depth),
VK_NULL_HANDLE /* ycbcrConversion */, false /*isExternalFormat*/, imageInfo.usage, VK_NULL_HANDLE /* ycbcrConversion */, imageInfo.usage, isProtected);
isProtected);
// Spec out the "primary" VkImageView that shaders use to sample from the image. // Spec out the "primary" VkImageView that shaders use to sample from the image.
mPrimaryViewRange = mState->mFullViewRange; mPrimaryViewRange = mState->mFullViewRange;
@@ -764,15 +761,13 @@ void VulkanTexture::setLayout(VkImageSubresourceRange const& range, VulkanLayout
} }
} }
void VulkanTexture::setYcbcrConversion(VkSamplerYcbcrConversion conversion, bool isExternalFormat) { void VulkanTexture::setYcbcrConversion(VkSamplerYcbcrConversion conversion) {
// Note that this comparison is valid because we only ever create VkSamplerYcbcrConversion from // Note that this comparison is valid because we only ever create VkSamplerYcbcrConversion from
// a cache. So for each set of parameters, there is exactly one conversion (similar to // a cache. So for each set of parameters, there is exactly one conversion (similar to
// samplers). // samplers).
VulkanTextureState::Ycbcr ycbcr = { VulkanTextureState::Ycbcr ycbcr = {
.conversion = conversion, .conversion = conversion,
.isExternalFormat = isExternalFormat,
}; };
if (mState->mYcbcr != ycbcr) { if (mState->mYcbcr != ycbcr) {
mState->mYcbcr = ycbcr; mState->mYcbcr = ycbcr;
mState->clearCachedImageViews(); mState->clearCachedImageViews();
+3 -5
View File
@@ -40,8 +40,7 @@ struct VulkanTextureState : public fvkmemory::Resource {
VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands, VmaAllocator allocator, VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands, VmaAllocator allocator,
VkDevice device, VkImage image, VkDeviceMemory deviceMemory, VkFormat format, VkDevice device, VkImage image, VkDeviceMemory deviceMemory, VkFormat format,
VkImageViewType viewType, uint8_t levels, uint8_t layerCount, VkImageViewType viewType, uint8_t levels, uint8_t layerCount,
VkSamplerYcbcrConversion ycbcrConversion, bool isExternalFormat, VkSamplerYcbcrConversion ycbcrConversion, VkImageUsageFlags usage, bool isProtected);
VkImageUsageFlags usage, bool isProtected);
~VulkanTextureState(); ~VulkanTextureState();
@@ -86,10 +85,9 @@ private:
// conversion matrix per-frame. // conversion matrix per-frame.
struct Ycbcr { struct Ycbcr {
VkSamplerYcbcrConversion conversion; VkSamplerYcbcrConversion conversion;
bool isExternalFormat;
bool operator==(Ycbcr const& other) const { bool operator==(Ycbcr const& other) const {
return conversion == other.conversion && isExternalFormat == other.isExternalFormat; return conversion == other.conversion;
} }
bool operator!=(Ycbcr const& other) const { bool operator!=(Ycbcr const& other) const {
@@ -209,7 +207,7 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
// This is used in the case of external images and external samplers. AHB might update the // This is used in the case of external images and external samplers. AHB might update the
// conversion per-frame. This implies that we need to invalidate the view cache when that // conversion per-frame. This implies that we need to invalidate the view cache when that
// happens. // happens.
void setYcbcrConversion(VkSamplerYcbcrConversion conversion, bool isExternal); void setYcbcrConversion(VkSamplerYcbcrConversion conversion);
#if FVK_ENABLED(FVK_DEBUG_TEXTURE) #if FVK_ENABLED(FVK_DEBUG_TEXTURE)
void print() const; void print() const;
@@ -33,7 +33,7 @@ VulkanYcbcrConversionCache::VulkanYcbcrConversionCache(VkDevice device)
: mDevice(device) {} : mDevice(device) {}
VkSamplerYcbcrConversion VulkanYcbcrConversionCache::getConversion( VkSamplerYcbcrConversion VulkanYcbcrConversionCache::getConversion(
VulkanYcbcrConversionCache::Params params) noexcept { VulkanYcbcrConversionCache::Params params) {
auto iter = mCache.find(params); auto iter = mCache.find(params);
if (UTILS_LIKELY(iter != mCache.end())) { if (UTILS_LIKELY(iter != mCache.end())) {
return iter->second; return iter->second;
@@ -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 = fvkutils::getVkFormat(params.format), .format = 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),
@@ -30,15 +30,14 @@ namespace filament::backend {
class VulkanYcbcrConversionCache { class VulkanYcbcrConversionCache {
public: public:
struct Params { struct Params {
SamplerYcbcrConversion conversion = {}; SamplerYcbcrConversion conversion = {}; // 4
TextureFormat format = {}; VkFormat format; // 4
uint16_t padding = 0; uint64_t externalFormat = 0; // 8
uint64_t externalFormat = 0;
}; };
static_assert(sizeof(Params) == 16); static_assert(sizeof(Params) == 16);
explicit VulkanYcbcrConversionCache(VkDevice device); explicit VulkanYcbcrConversionCache(VkDevice device);
VkSamplerYcbcrConversion getConversion(Params params) noexcept; VkSamplerYcbcrConversion getConversion(Params params);
void terminate() noexcept; void terminate() noexcept;
private: private:
@@ -48,7 +47,8 @@ private:
bool operator()(Params lhs, Params rhs) const noexcept { bool operator()(Params lhs, Params rhs) const noexcept {
SamplerYcbcrConversion::EqualTo equal; SamplerYcbcrConversion::EqualTo equal;
return equal(lhs.conversion, rhs.conversion) && return equal(lhs.conversion, rhs.conversion) &&
lhs.externalFormat == rhs.externalFormat; lhs.externalFormat == rhs.externalFormat &&
lhs.format == rhs.format;
} }
}; };
using ConversionHashFn = utils::hash::MurmurHashFn<Params>; using ConversionHashFn = utils::hash::MurmurHashFn<Params>;
@@ -31,6 +31,8 @@ ResourceManager::ResourceManager(size_t arenaSize, bool disableUseAfterFreeCheck
: mHandleAllocatorImpl("Handles", arenaSize, disableUseAfterFreeCheck, disablePoolHandleTags) {} : mHandleAllocatorImpl("Handles", arenaSize, disableUseAfterFreeCheck, disablePoolHandleTags) {}
void ResourceManager::gc() noexcept { void ResourceManager::gc() noexcept {
FVK_SYSTRACE_CONTEXT();
FVK_SYSTRACE_START("ResourceManager::gc");
auto destroyAll = [this](GcList& list) { auto destroyAll = [this](GcList& list) {
for (auto const& [type, id]: list) { for (auto const& [type, id]: list) {
destroyWithType(type, id); destroyWithType(type, id);
@@ -49,6 +51,7 @@ void ResourceManager::gc() noexcept {
GcList gcs; GcList gcs;
std::swap(gcs, mGcList); std::swap(gcs, mGcList);
destroyAll(gcs); destroyAll(gcs);
FVK_SYSTRACE_END();
} }
void ResourceManager::terminate() noexcept { void ResourceManager::terminate() noexcept {
@@ -132,6 +132,10 @@ public:
return id() == other.id() && type() == other.type(); return id() == other.id() && type() == other.type();
} }
inline bool operator!=(resource_ptr<D> const& other) const {
return !((*this) == other);
}
inline explicit operator bool() const { inline explicit operator bool() const {
return bool(mRef); return bool(mRef);
} }
@@ -212,6 +212,8 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
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, VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
// This is needed for external images. See VulkanPlatformAndroid
VK_KHR_IMAGE_FORMAT_LIST_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__)
@@ -993,30 +995,6 @@ VkQueue VulkanPlatform::getProtectedGraphicsQueue() const noexcept {
return mImpl->mProtectedGraphicsQueue; return mImpl->mProtectedGraphicsQueue;
} }
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadata(
ExternalImageHandleRef externalImage) {
return getExternalImageMetadataImpl(externalImage, mImpl->mDevice);
}
VulkanPlatform::ImageData VulkanPlatform::createExternalImageData(
ExternalImageHandleRef externalImage, const ExternalImageMetadata& metadata,
uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
return createExternalImageDataImpl(externalImage, mImpl->mDevice, metadata, memoryTypeIndex,
usage);
}
VkSampler VulkanPlatform::createExternalSampler(SamplerYcbcrConversion chroma,
SamplerParams sampler, uint32_t internalFormat) {
return createExternalSamplerImpl(mImpl->mDevice, chroma, sampler, internalFormat);
}
VkImageView VulkanPlatform::createExternalImageView(SamplerYcbcrConversion chroma,
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
VkImageViewType viewType, VkComponentMapping swizzle) {
return createExternalImageViewImpl(mImpl->mDevice, chroma, internalFormat, image, range,
viewType, swizzle);
}
ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() const { ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() const {
return getSwapchainInstanceExtensionsImpl(); return getSwapchainInstanceExtensionsImpl();
} }
@@ -15,14 +15,14 @@
*/ */
#include <backend/platforms/VulkanPlatformAndroid.h> #include <backend/platforms/VulkanPlatformAndroid.h>
#include "vulkan/VulkanConstants.h"
#include "vulkan/VulkanContext.h"
#include <backend/DriverEnums.h> #include <backend/DriverEnums.h>
#include <private/backend/BackendUtilsAndroid.h> #include <private/backend/BackendUtilsAndroid.h>
#include "vulkan/VulkanConstants.h"
#include <utils/Panic.h> #include <utils/Panic.h>
#include "vulkan/utils/Image.h" #include "vulkan/utils/Image.h"
#include "vulkan/utils/Conversion.h"
#include <bluevk/BlueVK.h> #include <bluevk/BlueVK.h>
@@ -39,6 +39,14 @@ namespace {
VkFormat transformVkFormat(VkFormat format, bool sRGB) { VkFormat transformVkFormat(VkFormat format, bool sRGB) {
if (!sRGB) { if (!sRGB) {
switch (format) {
case VK_FORMAT_R8G8B8A8_SRGB:
return VK_FORMAT_R8G8B8A8_UNORM;
case VK_FORMAT_R8G8B8_SRGB:
return VK_FORMAT_R8G8B8_UNORM;
default:
break;
}
return format; return format;
} }
@@ -57,7 +65,7 @@ VkFormat transformVkFormat(VkFormat format, bool sRGB) {
} }
bool isProtectedFromUsage(uint64_t usage) { bool isProtectedFromUsage(uint64_t usage) {
return (usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) ? true : false; return usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT;
} }
std::pair<VkFormat, VkImageUsageFlags> getVKFormatAndUsage(const AHardwareBuffer_Desc& desc, std::pair<VkFormat, VkImageUsageFlags> getVKFormatAndUsage(const AHardwareBuffer_Desc& desc,
@@ -120,7 +128,9 @@ std::pair<VkFormat, VkImageUsageFlags> getVKFormatAndUsage(const AHardwareBuffer
usage = 0; usage = 0;
if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE) { if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE) {
usage |= VK_IMAGE_USAGE_SAMPLED_BIT; usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
// We shouldn't be using external samplers as input attachments
// usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
} }
if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER) { if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER) {
if (isDepthFormat) { if (isDepthFormat) {
@@ -136,87 +146,37 @@ std::pair<VkFormat, VkImageUsageFlags> getVKFormatAndUsage(const AHardwareBuffer
return { format, usage }; return { format, usage };
} }
VulkanPlatform::ImageData allocateExternalImage(AHardwareBuffer* buffer, VkDevice device, std::pair<TextureFormat, TextureUsage> getFilamentFormatAndUsage(const AHardwareBuffer_Desc& desc,
VulkanPlatform::ExternalImageMetadata const& metadata, uint32_t memoryTypeIndex, bool sRGB) {
VkImageUsageFlags usage) { auto const format = mapToFilamentFormat(desc.format, sRGB);
VulkanPlatform::ImageData data; return {
format,
// if external format we need to specifiy it in the allocation mapToFilamentUsage(desc.usage, format),
const bool useExternalFormat = metadata.format == VK_FORMAT_UNDEFINED;
const VkExternalFormatANDROID externalFormat = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
.pNext = nullptr,
// pass down the format (external means we don't have it VK defined)
.externalFormat = metadata.externalFormat,
}; };
const VkExternalMemoryImageCreateInfo externalCreateInfo = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
.pNext = useExternalFormat ? &externalFormat : nullptr,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
};
VkImageCreateInfo imageInfo{ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
imageInfo.pNext = &externalCreateInfo;
imageInfo.format = metadata.format;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent = {
metadata.width,
metadata.height,
1u,
};
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = metadata.layers;
imageInfo.samples = metadata.samples;
imageInfo.usage = usage;
VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &data.first);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkCreateImage failed with error=" << static_cast<int32_t>(result);
// Allocate the memory
VkImportAndroidHardwareBufferInfoANDROID androidHardwareBufferInfo = {
.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID,
.pNext = nullptr,
.buffer = buffer,
};
VkMemoryDedicatedAllocateInfo memoryDedicatedAllocateInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
.pNext = &androidHardwareBufferInfo,
.image = data.first,
.buffer = VK_NULL_HANDLE,
};
VkMemoryAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &memoryDedicatedAllocateInfo,
.allocationSize = metadata.allocationSize,
.memoryTypeIndex = memoryTypeIndex,
};
result = vkAllocateMemory(device, &allocInfo, VKALLOC, &data.second);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkAllocateMemory failed with error=" << static_cast<int32_t>(result);
return data;
} }
}// namespace }// namespace
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() = default; VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() {
if (__builtin_available(android 26, *)) {
if (aHardwareBuffer) {
AHardwareBuffer_release(aHardwareBuffer);
}
}
}
Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage( Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
AHardwareBuffer const* buffer, bool sRGB) noexcept { AHardwareBuffer const* buffer, bool sRGB) noexcept {
if (__builtin_available(android 26, *)) { if (__builtin_available(android 26, *)) {
auto bufferImpl = const_cast<AHardwareBuffer*>(buffer);
AHardwareBuffer_acquire(bufferImpl);
AHardwareBuffer_Desc hardwareBufferDescription = {}; AHardwareBuffer_Desc hardwareBufferDescription = {};
AHardwareBuffer_describe(buffer, &hardwareBufferDescription); AHardwareBuffer_describe(buffer, &hardwareBufferDescription);
auto* const p = new (std::nothrow) ExternalImageVulkanAndroid; auto* const p = new (std::nothrow) ExternalImageVulkanAndroid;
p->aHardwareBuffer = const_cast<AHardwareBuffer*>(buffer); p->aHardwareBuffer = const_cast<AHardwareBuffer*>(buffer);
p->sRGB = sRGB; p->sRGB = sRGB;
p->height = hardwareBufferDescription.height;
p->width = hardwareBufferDescription.width;
TextureFormat textureFormat = mapToFilamentFormat(hardwareBufferDescription.format, sRGB);
p->format = textureFormat;
p->usage = mapToFilamentUsage(hardwareBufferDescription.usage, textureFormat);
return Platform::ExternalImageHandle{ p }; return Platform::ExternalImageHandle{ p };
} }
@@ -225,23 +185,20 @@ Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
VulkanPlatformAndroid::ExternalImageDescAndroid VulkanPlatformAndroid::getExternalImageDesc( VulkanPlatformAndroid::ExternalImageDescAndroid VulkanPlatformAndroid::getExternalImageDesc(
ExternalImageHandleRef externalImage) const noexcept { ExternalImageHandleRef externalImage) const noexcept {
auto const* fvkExternalImage = auto metadata = extractExternalImageMetadata(externalImage);
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
return { return {
.width = fvkExternalImage->width, .width = metadata.width,
.height = fvkExternalImage->height, .height = metadata.height,
.format = fvkExternalImage->format, .format = metadata.filamentFormat,
.usage = fvkExternalImage->usage, .usage = metadata.filamentUsage,
}; };
} }
VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMetadata( VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::extractExternalImageMetadata(
ExternalImageHandleRef externalImage) { ExternalImageHandleRef image) const {
auto const* fvkExternalImage = auto const* fvkExternalImage = static_cast<ExternalImageVulkanAndroid const*>(image.get());
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
ExternalImageMetadata metadata; ExternalImageMetadata metadata = {};
AHardwareBuffer* buffer = fvkExternalImage->aHardwareBuffer; AHardwareBuffer* buffer = fvkExternalImage->aHardwareBuffer;
if (__builtin_available(android 26, *)) { if (__builtin_available(android 26, *)) {
AHardwareBuffer_Desc bufferDesc; AHardwareBuffer_Desc bufferDesc;
@@ -249,16 +206,29 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMet
metadata.width = bufferDesc.width; metadata.width = bufferDesc.width;
metadata.height = bufferDesc.height; metadata.height = bufferDesc.height;
metadata.layers = bufferDesc.layers; metadata.layers = bufferDesc.layers;
metadata.isProtected = isProtectedFromUsage(bufferDesc.usage);
std::tie(metadata.format, metadata.usage) = std::tie(metadata.format, metadata.usage) =
getVKFormatAndUsage(bufferDesc, fvkExternalImage->sRGB); getVKFormatAndUsage(bufferDesc, fvkExternalImage->sRGB);
} std::tie(metadata.filamentFormat, metadata.filamentUsage) =
getFilamentFormatAndUsage(bufferDesc, fvkExternalImage->sRGB);
if (isProtectedFromUsage(bufferDesc.usage)) {
metadata.filamentUsage |= TextureUsage::PROTECTED;
}
// TODO: The following seems unnecessary. we should be able to discern directly from the
// bufferDesc.
if (any(metadata.filamentUsage & TextureUsage::BLIT_SRC)) {
metadata.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
if (any(metadata.filamentUsage & (TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE))) {
metadata.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
}
metadata.samples = VK_SAMPLE_COUNT_1_BIT; metadata.samples = VK_SAMPLE_COUNT_1_BIT;
VkAndroidHardwareBufferFormatPropertiesANDROID formatInfo = { VkAndroidHardwareBufferFormatPropertiesANDROID formatInfo = {
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID, .sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID,
.pNext = nullptr,
}; };
VkAndroidHardwareBufferPropertiesANDROID properties = { VkAndroidHardwareBufferPropertiesANDROID properties = {
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID, .sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
@@ -268,138 +238,155 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMet
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkGetAndroidHardwareBufferProperties failed with error=" << "vkGetAndroidHardwareBufferProperties failed with error="
<< static_cast<int32_t>(result); << static_cast<int32_t>(result);
VkFormat bufferPropertiesFormat = transformVkFormat(formatInfo.format, fvkExternalImage->sRGB); VkFormat bufferPropertiesFormat = transformVkFormat(formatInfo.format, fvkExternalImage->sRGB);
FILAMENT_CHECK_POSTCONDITION(metadata.format == bufferPropertiesFormat) FILAMENT_CHECK_POSTCONDITION(metadata.format == bufferPropertiesFormat)
<< "mismatched image format( " << metadata.format << ") and queried format(" << "mismatched image format( " << metadata.format << ") and queried format("
<< bufferPropertiesFormat << ") for external image (AHB)"; << bufferPropertiesFormat << ") for external image (AHB)";
metadata.externalFormat = formatInfo.externalFormat;
bool const requiresConversion =
metadata.format == VK_FORMAT_UNDEFINED ||
fvkutils::isVKYcbcrConversionFormat(metadata.format);
if (requiresConversion) {
metadata.format = VK_FORMAT_UNDEFINED;
metadata.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
metadata.externalFormat = formatInfo.externalFormat;
} else {
metadata.externalFormat = 0;
}
metadata.allocationSize = properties.allocationSize; metadata.allocationSize = properties.allocationSize;
metadata.memoryTypeBits = properties.memoryTypeBits; metadata.memoryTypeBits = properties.memoryTypeBits;
metadata.ycbcrConversionComponents = formatInfo.samplerYcbcrConversionComponents;
metadata.ycbcrModel = formatInfo.suggestedYcbcrModel;
metadata.ycbcrRange = formatInfo.suggestedYcbcrRange;
metadata.xChromaOffset = formatInfo.suggestedXChromaOffset;
metadata.yChromaOffset = formatInfo.suggestedYChromaOffset;
return metadata; return metadata;
} }
VulkanPlatformAndroid::ImageData VulkanPlatformAndroid::createExternalImageData( VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
ExternalImageHandleRef externalImage, const ExternalImageMetadata& metadata, ExternalImageHandleRef externalImage) const {
uint32_t memoryTypeIndex, VkImageUsageFlags usage) { auto metadata = extractExternalImageMetadata(externalImage);
auto const* fvkExternalImage = auto const* fvkExternalImage =
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get()); static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
ImageData data = allocateExternalImage(fvkExternalImage->aHardwareBuffer, getDevice(), metadata, AHardwareBuffer* buffer = fvkExternalImage->aHardwareBuffer;
memoryTypeIndex, usage);
VkResult result = vkBindImageMemory(getDevice(), data.first, data.second, 0);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkBindImageMemory error=" << static_cast<int32_t>(result);
return data;
}
VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device, SamplerYcbcrConversion chroma, VkDevice const device = getDevice();
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range, VkPhysicalDevice const physicalDevice = getPhysicalDevice();
VkImageViewType viewType, VkComponentMapping swizzle){ auto buildImage = [&](ExternalImageMetadata const& metadata) {
VkExternalFormatANDROID externalFormat = { bool const isExternal = metadata.externalFormat != 0;
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID, VkExternalFormatANDROID const externalFormat = {
.externalFormat = internalFormat, .sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
.pNext = nullptr,
.externalFormat = metadata.externalFormat,
};
VkExternalMemoryImageCreateInfo externalCreateInfo = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
.pNext = isExternal ? &externalFormat : nullptr,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
};
VkFormat formats[2] = {};
VkImageFormatListCreateInfo imageFormatListInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO,
.pNext = nullptr,
.viewFormatCount = 2,
.pViewFormats = formats,
};
if (fvkExternalImage->sRGB) {
formats[0] = metadata.format;
formats[1] = transformVkFormat(metadata.format, /*sRGB=*/false);
imageFormatListInfo.pNext = externalCreateInfo.pNext;
externalCreateInfo.pNext = &imageFormatListInfo;
}
VkImageCreateInfo const imageInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = &externalCreateInfo,
.flags = fvkExternalImage->sRGB ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0u,
.imageType = VK_IMAGE_TYPE_2D,
// For non external images, use the same format as the AHB, which isn't in SRGB
// Fix VUID-VkMemoryAllocateInfo-pNext-02387
.format = transformVkFormat(metadata.format, /*sRGB=*/false),
.extent = {
metadata.width,
metadata.height,
1u,
},
.mipLevels = 1,
.arrayLayers = metadata.layers,
.samples = metadata.samples,
.usage = metadata.usage,
};
VkImage image;
VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &image);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkCreateImage failed with error=" << static_cast<int32_t>(result);
return image;
}; };
TextureSwizzle const swizzleArray[] = {chroma.r, chroma.g, chroma.b, chroma.a}; auto allocMem = [&](VkImage image, ExternalImageMetadata const& metadata) {
VkSamplerYcbcrConversionCreateInfo conversionInfo = { bool const isExternal = metadata.externalFormat != 0;
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO, // Allocate the memory
.pNext = &externalFormat, VkImportAndroidHardwareBufferInfoANDROID const androidHardwareBufferInfo = {
.format = VK_FORMAT_UNDEFINED, .sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID,
.ycbcrModel = fvkutils::getYcbcrModelConversion(chroma.ycbcrModel), .buffer = buffer,
.ycbcrRange = fvkutils::getYcbcrRange(chroma.ycbcrRange), };
.components = fvkutils::getSwizzleMap(swizzleArray), VkMemoryDedicatedAllocateInfo const memoryDedicatedAllocateInfo = {
.xChromaOffset = fvkutils::getChromaLocation(chroma.xChromaOffset), .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
.yChromaOffset = fvkutils::getChromaLocation(chroma.yChromaOffset), .pNext = &androidHardwareBufferInfo,
.chromaFilter = fvkutils::getFilter(chroma.chromaFilter), .image = image,
}; .buffer = VK_NULL_HANDLE,
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE; };
VkResult result = vkCreateSamplerYcbcrConversion(device, &conversionInfo, VkPhysicalDeviceMemoryProperties memoryProperties;
nullptr, &conversion); vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) VkMemoryPropertyFlags requiredMemoryFlags =
<< "Unable to create Ycbcr Conversion." !isExternal && any(metadata.filamentUsage & TextureUsage::UPLOADABLE)
<< " error=" << static_cast<int32_t>(result); ? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
VkSamplerYcbcrConversionInfo samplerYcbcrConversionInfo = { if (any(metadata.filamentUsage & TextureUsage::PROTECTED)) {
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO, requiredMemoryFlags |= VK_MEMORY_PROPERTY_PROTECTED_BIT;
.conversion = conversion, }
uint32_t const memoryTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
metadata.memoryTypeBits, requiredMemoryFlags);
VkMemoryAllocateInfo const allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &memoryDedicatedAllocateInfo,
.allocationSize = metadata.allocationSize,
.memoryTypeIndex = memoryTypeIndex,
};
VkDeviceMemory memory;
VkResult result = vkAllocateMemory(device, &allocInfo, VKALLOC, &memory);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkAllocateMemory failed with error=" << static_cast<int32_t>(result);
result = vkBindImageMemory(getDevice(), image, memory, 0);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkBindImageMemory error=" << static_cast<int32_t>(result);
return memory;
}; };
VkImageViewCreateInfo viewInfo = { VulkanPlatform::ImageData::Bundle internal = {}, external = {};
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, auto img = buildImage(metadata);
.pNext = &samplerYcbcrConversionInfo, auto mem = allocMem(img, metadata);
.flags = 0,
.image = image,
.viewType = viewType,
.format = VK_FORMAT_UNDEFINED,
.components = swizzle,
.subresourceRange = range,
};
VkImageView imageView;
result = vkCreateImageView(device, &viewInfo, VKALLOC, &imageView);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "Unable to create VkImageView."
<< " error=" << static_cast<int32_t>(result);
return imageView; // Note that we're always choosing a non-externally sampled format if it exists.
} if (metadata.externalFormat == 0) {
internal = { img, mem };
} else {
external = { img, mem };
}
VkSampler VulkanPlatform::createExternalSamplerImpl( return {
VkDevice device, SamplerYcbcrConversion chroma, SamplerParams params, .internal = internal,
uint32_t internalFormat) { .external = external,
VkExternalFormatANDROID externalFormat = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
.externalFormat = internalFormat,
}; };
TextureSwizzle const swizzleArray[] = {chroma.r, chroma.g, chroma.b, chroma.a};
VkSamplerYcbcrConversionCreateInfo conversionInfo = {
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO,
.pNext = &externalFormat,
.format = VK_FORMAT_UNDEFINED,
.ycbcrModel = fvkutils::getYcbcrModelConversion(chroma.ycbcrModel),
.ycbcrRange = fvkutils::getYcbcrRange(chroma.ycbcrRange),
.components = fvkutils::getSwizzleMap(swizzleArray),
.xChromaOffset = fvkutils::getChromaLocation(chroma.xChromaOffset),
.yChromaOffset = fvkutils::getChromaLocation(chroma.yChromaOffset),
.chromaFilter = fvkutils::getFilter(chroma.chromaFilter),
};
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
VkResult result = vkCreateSamplerYcbcrConversion(device, &conversionInfo,
nullptr, &conversion);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "Unable to create Ycbcr Conversion."
<< " error=" << static_cast<int32_t>(result);
VkSamplerYcbcrConversionInfo samplerYcbcrConversionInfo = {
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
.pNext = nullptr,
.conversion = conversion,
};
VkSamplerCreateInfo samplerInfo = {
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.pNext = &samplerYcbcrConversionInfo,
.magFilter = fvkutils::getFilter(params.filterMag),
.minFilter = fvkutils::getFilter(params.filterMin),
.mipmapMode = fvkutils::getMipmapMode(params.filterMin),
.addressModeU = fvkutils::getWrapMode(params.wrapS),
.addressModeV = fvkutils::getWrapMode(params.wrapT),
.addressModeW = fvkutils::getWrapMode(params.wrapR),
.anisotropyEnable = params.anisotropyLog2 == 0 ? VK_FALSE : VK_TRUE,
.maxAnisotropy = (float)(1u << params.anisotropyLog2),
.compareEnable = fvkutils::getCompareEnable(params.compareMode),
.compareOp = fvkutils::getCompareOp(params.compareFunc),
.minLod = 0.0f,
.maxLod = fvkutils::getMaxLod(params.filterMin),
.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
.unnormalizedCoordinates = VK_FALSE,
};
VkSampler sampler;
result = vkCreateSampler(device, &samplerInfo, VKALLOC, &sampler);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "Unable to create sampler."
<< " error=" << static_cast<int32_t>(result);
return sampler;
} }
VulkanPlatform::ExtensionSet VulkanPlatformAndroid::getSwapchainInstanceExtensions() const { VulkanPlatform::ExtensionSet VulkanPlatformAndroid::getSwapchainInstanceExtensions() const {
@@ -413,7 +400,7 @@ VulkanPlatform::SurfaceBundle VulkanPlatformAndroid::createVkSurfaceKHR(void* na
VkSurfaceKHR surface; VkSurfaceKHR surface;
VkExtent2D extent; VkExtent2D extent;
VkAndroidSurfaceCreateInfoKHR const createInfo{ VkAndroidSurfaceCreateInfoKHR const createInfo = {
.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR,
.window = (ANativeWindow*) nativeWindow, .window = (ANativeWindow*) nativeWindow,
}; };
@@ -427,20 +414,9 @@ VulkanPlatform::SurfaceBundle VulkanPlatformAndroid::createVkSurfaceKHR(void* na
// Deprecated platform dependent helper methods // Deprecated platform dependent helper methods
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() { return {}; } VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() { return {}; }
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataImpl(
ExternalImageHandleRef externalImage, VkDevice device) {
return ExternalImageMetadata{};
}
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
ExternalImageHandleRef externalImage, VkDevice device,
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
return ImageData{};
}
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow, VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
VkInstance instance, uint64_t flags) noexcept { VkInstance instance, uint64_t flags) noexcept {
return SurfaceBundle{}; return SurfaceBundle{};
} }
}// namespace filament::backend } // namespace filament::backend
@@ -43,30 +43,6 @@ VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl(
return ret; return ret;
} }
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataImpl(
ExternalImageHandleRef externalImage, VkDevice device) {
return {};
}
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
ExternalImageHandleRef externalImage, VkDevice device,
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
return {};
}
VkSampler VulkanPlatform::createExternalSamplerImpl(VkDevice device,
SamplerYcbcrConversion chroma,
SamplerParams sampler,
uint32_t internalFormat) {
return VK_NULL_HANDLE;
}
VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device,
SamplerYcbcrConversion chroma, uint32_t internalFormat, VkImage image,
VkImageSubresourceRange range, VkImageViewType viewType, VkComponentMapping swizzle) {
return VK_NULL_HANDLE;
}
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;
@@ -84,30 +84,6 @@ using namespace bluevk;
namespace filament::backend { namespace filament::backend {
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataImpl(
ExternalImageHandleRef externalImage, VkDevice device) {
return {};
}
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
ExternalImageHandleRef externalImage, VkDevice device,
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
return {};
}
VkSampler VulkanPlatform::createExternalSamplerImpl(VkDevice device,
SamplerYcbcrConversion chroma,
SamplerParams sampler,
uint32_t internalFormat) {
return VK_NULL_HANDLE;
}
VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device,
SamplerYcbcrConversion chroma, uint32_t internalFormat, VkImage image,
VkImageSubresourceRange range, VkImageViewType viewType, VkComponentMapping swizzle) {
return VK_NULL_HANDLE;
}
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() { VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
VulkanPlatform::ExtensionSet const ret = { VulkanPlatform::ExtensionSet const ret = {
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND) #if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
@@ -51,6 +51,8 @@ VkFormat getVkFormat(ElementType type, bool normalized, bool integer) {
return VK_FORMAT_UNDEFINED; return VK_FORMAT_UNDEFINED;
} }
} }
// Non-normalized case
switch (type) { switch (type) {
// Single Component Types // Single Component Types
case ElementType::BYTE: return integer ? VK_FORMAT_R8_SINT : VK_FORMAT_R8_SSCALED; case ElementType::BYTE: return integer ? VK_FORMAT_R8_SINT : VK_FORMAT_R8_SSCALED;
@@ -184,6 +184,46 @@ bool isVkStencilFormat(VkFormat format) {
return (getImageAspect(format) & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; return (getImageAspect(format) & VK_IMAGE_ASPECT_STENCIL_BIT) != 0;
} }
bool isVKYcbcrConversionFormat(VkFormat format) {
switch (format) {
case VK_FORMAT_G8B8G8R8_422_UNORM:
case VK_FORMAT_B8G8R8G8_422_UNORM:
case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM:
case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
case VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM:
case VK_FORMAT_G8_B8R8_2PLANE_422_UNORM:
case VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM:
case VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16:
case VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16:
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16:
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16:
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16:
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16:
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16:
case VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16:
case VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16:
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16:
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16:
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16:
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16:
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16:
case VK_FORMAT_G16B16G16R16_422_UNORM:
case VK_FORMAT_B16G16R16G16_422_UNORM:
case VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM:
case VK_FORMAT_G16_B16R16_2PLANE_420_UNORM:
case VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM:
case VK_FORMAT_G16_B16R16_2PLANE_422_UNORM:
case VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM:
case VK_FORMAT_G8_B8R8_2PLANE_444_UNORM:
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16:
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16:
case VK_FORMAT_G16_B16R16_2PLANE_444_UNORM:
return true;
default:
return false;
}
}
static uint32_t mostSignificantBit(uint32_t x) { return 1ul << (31ul - utils::clz(x)); } static uint32_t mostSignificantBit(uint32_t x) { return 1ul << (31ul - utils::clz(x)); }
uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask) { uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask) {
@@ -98,6 +98,8 @@ bool isVkDepthFormat(VkFormat format);
bool isVkStencilFormat(VkFormat format); bool isVkStencilFormat(VkFormat format);
bool isVKYcbcrConversionFormat(VkFormat format);
VkImageAspectFlags getImageAspect(VkFormat format); VkImageAspectFlags getImageAspect(VkFormat format);
uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask); uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask);
File diff suppressed because it is too large Load Diff
+159
View File
@@ -0,0 +1,159 @@
/*
* 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;
wgpu::Instance instance = device.GetAdapter().GetInstance();
instance.WaitAny(
module.GetCompilationInfo(wgpu::CallbackMode::WaitAnyOnly,
[&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;
}),
UINT16_MAX);
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
+267 -112
View File
@@ -16,8 +16,8 @@
#include "webgpu/WebGPUDriver.h" #include "webgpu/WebGPUDriver.h"
#include "WebGPUPipelineCreation.h"
#include "WebGPUSwapChain.h" #include "WebGPUSwapChain.h"
#include "webgpu/WebGPUConstants.h"
#include <backend/platforms/WebGPUPlatform.h> #include <backend/platforms/WebGPUPlatform.h>
#include "CommandStreamDispatcher.h" #include "CommandStreamDispatcher.h"
@@ -25,18 +25,20 @@
#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>
#include <backend/TargetBufferInfo.h>
#include <math/mat3.h> #include <math/mat3.h>
#include <utils/CString.h> #include <utils/CString.h>
#include <utils/Panic.h>
#include <utils/ostream.h> #include <utils/ostream.h>
#include <dawn/webgpu_cpp_print.h> #include <dawn/webgpu_cpp_print.h>
#include <webgpu/webgpu_cpp.h> #include <webgpu/webgpu_cpp.h>
#include <algorithm> #include <algorithm>
#include <array>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <memory>
#include <sstream> #include <sstream>
#include <string_view> #include <string_view>
#include <utility> #include <utility>
@@ -184,8 +186,6 @@ void printAdapterDetails(wgpu::Adapter const& adapter) {
} }
#endif #endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) #if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printDeviceDetails(wgpu::Device const& device) { void printDeviceDetails(wgpu::Device const& device) {
wgpu::SupportedFeatures supportedFeatures{}; wgpu::SupportedFeatures supportedFeatures{};
@@ -237,6 +237,7 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfi
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) #if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printDeviceDetails(mDevice); printDeviceDetails(mDevice);
#endif #endif
mQueue = mDevice.GetQueue();
} }
WebGPUDriver::~WebGPUDriver() noexcept = default; WebGPUDriver::~WebGPUDriver() noexcept = default;
@@ -319,12 +320,18 @@ void WebGPUDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
} }
void WebGPUDriver::destroyBufferObject(Handle<HwBufferObject> boh) { void WebGPUDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
if (boh) {
destructHandle<WGPUBufferObject>(boh);
}
} }
void WebGPUDriver::destroyTexture(Handle<HwTexture> th) { 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) {
@@ -350,6 +357,9 @@ void WebGPUDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> tqh)
} }
void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> tqh) { void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> tqh) {
if (tqh) {
destructHandle<WebGPUDescriptorSet>(tqh);
}
} }
Handle<HwSwapChain> WebGPUDriver::createSwapChainS() noexcept { Handle<HwSwapChain> WebGPUDriver::createSwapChainS() noexcept {
@@ -364,12 +374,10 @@ Handle<HwTexture> WebGPUDriver::createTextureS() noexcept {
return allocHandle<WGPUTexture>(); return allocHandle<WGPUTexture>();
} }
Handle<HwTexture> WebGPUDriver::importTextureS() noexcept { Handle<HwTexture> WebGPUDriver::importTextureS() noexcept { return allocHandle<WGPUTexture>(); }
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++);
}
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 {
@@ -385,7 +393,7 @@ Handle<HwIndexBuffer> WebGPUDriver::createIndexBufferS() noexcept {
} }
Handle<HwTexture> WebGPUDriver::createTextureViewS() noexcept { Handle<HwTexture> WebGPUDriver::createTextureViewS() noexcept {
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++); return allocHandle<WGPUTexture>();
} }
Handle<HwBufferObject> WebGPUDriver::createBufferObjectS() noexcept { Handle<HwBufferObject> WebGPUDriver::createBufferObjectS() noexcept {
@@ -401,7 +409,7 @@ Handle<HwVertexBuffer> WebGPUDriver::createVertexBufferS() noexcept {
} }
Handle<HwDescriptorSet> WebGPUDriver::createDescriptorSetS() noexcept { Handle<HwDescriptorSet> WebGPUDriver::createDescriptorSetS() noexcept {
return Handle<HwDescriptorSet>((Handle<HwDescriptorSet>::HandleId) mNextFakeHandle++); return allocHandle<WebGPUDescriptorSet>();
} }
Handle<HwRenderPrimitive> WebGPUDriver::createRenderPrimitiveS() noexcept { Handle<HwRenderPrimitive> WebGPUDriver::createRenderPrimitiveS() noexcept {
@@ -413,7 +421,7 @@ Handle<HwVertexBufferInfo> WebGPUDriver::createVertexBufferInfoS() noexcept {
} }
Handle<HwTexture> WebGPUDriver::createTextureViewSwizzleS() noexcept { Handle<HwTexture> WebGPUDriver::createTextureViewSwizzleS() noexcept {
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++); return allocHandle<WGPUTexture>();
} }
Handle<HwRenderTarget> WebGPUDriver::createDefaultRenderTargetS() noexcept { Handle<HwRenderTarget> WebGPUDriver::createDefaultRenderTargetS() noexcept {
@@ -425,15 +433,15 @@ Handle<HwDescriptorSetLayout> WebGPUDriver::createDescriptorSetLayoutS() noexcep
} }
Handle<HwTexture> WebGPUDriver::createTextureExternalImageS() noexcept { Handle<HwTexture> WebGPUDriver::createTextureExternalImageS() noexcept {
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++); return allocHandle<WGPUTexture>();
} }
Handle<HwTexture> WebGPUDriver::createTextureExternalImage2S() noexcept { Handle<HwTexture> WebGPUDriver::createTextureExternalImage2S() noexcept {
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++); return allocHandle<WGPUTexture>();
} }
Handle<HwTexture> WebGPUDriver::createTextureExternalImagePlaneS() noexcept { Handle<HwTexture> WebGPUDriver::createTextureExternalImagePlaneS() noexcept {
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++); return allocHandle<WGPUTexture>();
} }
void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) { void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
@@ -441,7 +449,6 @@ void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
assert_invariant(!mSwapChain); assert_invariant(!mSwapChain);
wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags); wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags);
mQueue = mDevice.GetQueue();
wgpu::Extent2D surfaceSize = mPlatform.getSurfaceExtent(mNativeWindow); wgpu::Extent2D surfaceSize = mPlatform.getSurfaceExtent(mNativeWindow);
mSwapChain = constructHandle<WebGPUSwapChain>(sch, std::move(surface), surfaceSize, mAdapter, mSwapChain = constructHandle<WebGPUSwapChain>(sch, std::move(surface), surfaceSize, mAdapter,
mDevice, flags); mDevice, flags);
@@ -471,60 +478,79 @@ void WebGPUDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vbih, uint
void WebGPUDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint32_t vertexCount, void WebGPUDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint32_t vertexCount,
Handle<HwVertexBufferInfo> vbih) { Handle<HwVertexBufferInfo> vbih) {
auto* vertexBufferInfo = handleCast<WGPUVertexBufferInfo>(vbih); auto* vertexBufferInfo = handleCast<WGPUVertexBufferInfo>(vbih);
constructHandle<WGPUVertexBuffer>(vbh, mDevice, vertexCount,vertexBufferInfo->bufferCount, vbih); constructHandle<WGPUVertexBuffer>(vbh, mDevice, vertexCount, vertexBufferInfo->bufferCount,
vbih);
} }
void WebGPUDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType, void WebGPUDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType,
uint32_t indexCount, BufferUsage usage) { uint32_t indexCount, BufferUsage usage) {
auto elementSize = (uint8_t)getElementTypeSize(elementType); auto elementSize = static_cast<uint8_t>(getElementTypeSize(elementType));
constructHandle<WGPUIndexBuffer>(ibh, mDevice, elementSize, indexCount); constructHandle<WGPUIndexBuffer>(ibh, mDevice, elementSize, indexCount);
} }
void WebGPUDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount, void WebGPUDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage) {} BufferObjectBinding bindingType, BufferUsage usage) {
constructHandle<WGPUBufferObject>(boh, mDevice, bindingType, byteCount);
}
void WebGPUDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint8_t levels, void WebGPUDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage usage) {} TextureUsage usage) {
constructHandle<WGPUTexture>(th, target, levels, format, samples, w, h, depth, usage, mDevice);
}
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) {
auto source = handleCast<WGPUTexture>(srch);
constructHandle<WGPUTexture>(th, source, 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,
backend::TextureSwizzle a) {} backend::TextureSwizzle a) {
PANIC_POSTCONDITION("Swizzle WebGPU Texture is not supported");
}
void WebGPUDriver::createTextureExternalImage2R(Handle<HwTexture> th, backend::SamplerType target, void WebGPUDriver::createTextureExternalImage2R(Handle<HwTexture> th, backend::SamplerType target,
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
Platform::ExternalImageHandleRef externalImage) {} Platform::ExternalImageHandleRef externalImage) {
PANIC_POSTCONDITION("External WebGPU Texture is not supported");
}
void WebGPUDriver::createTextureExternalImageR(Handle<HwTexture> th, backend::SamplerType target, void WebGPUDriver::createTextureExternalImageR(Handle<HwTexture> th, backend::SamplerType target,
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
void* externalImage) {} void* externalImage) {
PANIC_POSTCONDITION("External WebGPU Texture is not supported");
}
void WebGPUDriver::createTextureExternalImagePlaneR(Handle<HwTexture> th, void WebGPUDriver::createTextureExternalImagePlaneR(Handle<HwTexture> th,
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
void* image, uint32_t plane) {} void* image, uint32_t plane) {
PANIC_POSTCONDITION("External WebGPU Texture is not supported");
}
void WebGPUDriver::importTextureR(Handle<HwTexture> th, intptr_t id, SamplerType target, void WebGPUDriver::importTextureR(Handle<HwTexture> th, intptr_t id, SamplerType target,
uint8_t levels, TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t w, uint32_t h,
uint32_t depth, TextureUsage usage) {} uint32_t depth, TextureUsage usage) {
PANIC_POSTCONDITION("Import WebGPU Texture is not supported");
}
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) {
assert_invariant(mDevice); assert_invariant(mDevice);
auto* renderPrimitive = handleCast<WGPURenderPrimitive>(rph); auto* renderPrimitive = constructHandle<WGPURenderPrimitive>(rph);
auto* vertexBuffer = handleCast<WGPUVertexBuffer>(vbh); auto* vertexBuffer = handleCast<WGPUVertexBuffer>(vbh);
auto* indexBuffer = handleCast<WGPUIndexBuffer>(ibh); auto* indexBuffer = handleCast<WGPUIndexBuffer>(ibh);
// auto* vertexBufferInfo = handleCast<WGPUVertexBufferInfo>(vertexBuffer->vbih);
// renderPrimitive->setBuffers(vertexBufferInfo, vertexBuffer, indexBuffer);
renderPrimitive->vertexBuffer = vertexBuffer; renderPrimitive->vertexBuffer = vertexBuffer;
renderPrimitive->indexBuffer = indexBuffer; renderPrimitive->indexBuffer = indexBuffer;
renderPrimitive->type = pt; renderPrimitive->type = 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);
@@ -542,11 +568,14 @@ 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); 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) {
auto layout = handleCast<WebGPUDescriptorSetLayout>(dslh);
constructHandle<WebGPUDescriptorSet>(dsh, layout->getLayout(), layout->getLayoutSize());
}
Handle<HwStream> WebGPUDriver::createStreamNative(void* nativeStream) { Handle<HwStream> WebGPUDriver::createStreamNative(void* nativeStream) {
return {}; return {};
@@ -582,11 +611,11 @@ FenceStatus WebGPUDriver::getFenceStatus(Handle<HwFence> fh) {
// We create all textures using VK_IMAGE_TILING_OPTIMAL, so our definition of "supported" is that // We create all textures using VK_IMAGE_TILING_OPTIMAL, so our definition of "supported" is that
// the GPU supports the given texture format with non-zero optimal tiling features. // the GPU supports the given texture format with non-zero optimal tiling features.
bool WebGPUDriver::isTextureFormatSupported(TextureFormat format) { bool WebGPUDriver::isTextureFormatSupported(TextureFormat format) {
return true; return WGPUTexture::fToWGPUTextureFormat(format) != wgpu::TextureFormat::Undefined;
} }
bool WebGPUDriver::isTextureSwizzleSupported() { bool WebGPUDriver::isTextureSwizzleSupported() {
return true; return false;
} }
bool WebGPUDriver::isTextureFormatMipmappable(TextureFormat format) { bool WebGPUDriver::isTextureFormatMipmappable(TextureFormat format) {
@@ -675,20 +704,21 @@ size_t WebGPUDriver::getMaxArrayTextureLayers() {
void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p, void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
uint32_t byteOffset) { uint32_t byteOffset) {
scheduleDestroy(std::move(p)); updateGPUBuffer(handleCast<WGPUIndexBuffer>(ibh), std::move(p), byteOffset);
} }
void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> ibh, BufferDescriptor&& p, void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> ibh, BufferDescriptor&& p,
uint32_t byteOffset) { uint32_t byteOffset) {
scheduleDestroy(std::move(p)); updateGPUBuffer(handleCast<WGPUBufferObject>(ibh), std::move(p), byteOffset);
} }
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> ibh, BufferDescriptor&& p, void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> ibh,
uint32_t byteOffset) { BufferDescriptor&& p, uint32_t byteOffset) {
scheduleDestroy(std::move(p)); updateGPUBuffer(handleCast<WGPUBufferObject>(ibh), std::move(p), byteOffset);
} }
void WebGPUDriver::resetBufferObject(Handle<HwBufferObject> boh) { void WebGPUDriver::resetBufferObject(Handle<HwBufferObject> boh) {
// Is there something that needs to be done here? Vulkan has left it unimplemented.
} }
void WebGPUDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t index, void WebGPUDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t index,
@@ -696,7 +726,8 @@ void WebGPUDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t in
auto* vertexBuffer = handleCast<WGPUVertexBuffer>(vbh); auto* vertexBuffer = handleCast<WGPUVertexBuffer>(vbh);
auto* bufferObject = handleCast<WGPUBufferObject>(boh); auto* bufferObject = handleCast<WGPUBufferObject>(boh);
assert_invariant(index < vertexBuffer->buffers.size()); assert_invariant(index < vertexBuffer->buffers.size());
vertexBuffer->setBuffer(bufferObject, index); assert_invariant(bufferObject->buffer.GetUsage() & wgpu::BufferUsage::Vertex);
vertexBuffer->buffers[index] = bufferObject->buffer;
} }
void WebGPUDriver::update3DImage(Handle<HwTexture> th, void WebGPUDriver::update3DImage(Handle<HwTexture> th,
@@ -822,7 +853,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));
} }
@@ -845,73 +876,60 @@ void WebGPUDriver::blit(
} }
void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) { void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
const auto* program = handleCast<WGPUProgram>(pipelineState.program);
assert_invariant(program);
assert_invariant(program->computeShaderModule == nullptr &&
"WebGPU backend does not (yet) support compute pipelines.");
FILAMENT_CHECK_POSTCONDITION(program->vertexShaderModule)
<< "WebGPU backend requires a vertex shader module for a render pipeline";
std::array<wgpu::BindGroupLayout, MAX_DESCRIPTOR_SET_COUNT> bindGroupLayouts{};
assert_invariant(bindGroupLayouts.size() >= pipelineState.pipelineLayout.setLayout.size());
size_t bindGroupLayoutCount = 0;
for (size_t i = 0; i < bindGroupLayouts.size(); i++) {
const auto handle = pipelineState.pipelineLayout.setLayout[bindGroupLayoutCount];
if (handle.getId() == HandleBase::nullid) {
continue;
}
bindGroupLayouts[bindGroupLayoutCount++] =
handleCast<WebGPUDescriptorSetLayout>(handle)->getLayout();
}
std::stringstream layoutLabelStream;
layoutLabelStream << program->name.c_str() << " layout";
const auto layoutLabel = layoutLabelStream.str();
const wgpu::PipelineLayoutDescriptor layoutDescriptor{
.label = wgpu::StringView(layoutLabel),
.bindGroupLayoutCount = bindGroupLayoutCount,
.bindGroupLayouts = bindGroupLayouts.data()
// TODO investigate immediateDataRangeByteSize
};
const wgpu::PipelineLayout layout = mDevice.CreatePipelineLayout(&layoutDescriptor);
FILAMENT_CHECK_POSTCONDITION(layout)
<< "Failed to create wgpu::PipelineLayout for render pipeline for "
<< layoutDescriptor.label;
auto const* vertexBufferInfo = handleCast<WGPUVertexBufferInfo>(pipelineState.vertexBufferInfo);
assert_invariant(vertexBufferInfo);
const wgpu::RenderPipeline pipeline = createWebGPURenderPipeline(mDevice, *program,
*vertexBufferInfo, layout, pipelineState.rasterState, pipelineState.stencilState,
pipelineState.polygonOffset, pipelineState.primitiveType, mSwapChain->getColorFormat(),
mSwapChain->getDepthFormat());
// TODO: uncomment once we have a valid pipeline to set
// mRenderPassEncoder.SetPipeline(pipeline);
} }
void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) { void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
// VulkanCommandBuffer* commands = mCurrentRenderPass.commandBuffer;
// VkCommandBuffer cmdbuffer = commands->buffer();
// auto prim = resource_ptr<VulkanRenderPrimitive>::cast(&mResourceManager, rph);
auto* renderPrimitive = handleCast<WGPURenderPrimitive>(rph); auto* renderPrimitive = handleCast<WGPURenderPrimitive>(rph);
// commands->acquire(prim);
// This *must* match the VulkanVertexBufferInfo that was bound in bindPipeline(). But we want // This *must* match the WGPUVertexBufferInfo that was bound in bindPipeline(). But we want
// to allow to call this before bindPipeline(), so the validation can only happen in draw() // to allow to call this before bindPipeline(), so the validation can only happen in draw()
auto vbi = handleCast<WGPUVertexBufferInfo>(renderPrimitive->vertexBuffer->vbih); auto vbi = handleCast<WGPUVertexBufferInfo>(renderPrimitive->vertexBuffer->vbih);
(void) vbi; assert_invariant(
vbi->getVertexBufferLayoutSize() == renderPrimitive->vertexBuffer->buffers.size());
for (uint32_t i = 0; i < vbi->getVertexBufferLayoutSize(); i++) {
mRenderPassEncoder.SetVertexBuffer(i, renderPrimitive->vertexBuffer->buffers[i]);
}
// mRenderPassEncoder.SetVertexBuffer(uint32_t slot, Buffer const& buffer = nullptr, uint64_t offset = 0, uint64_t size = kWholeSize); mRenderPassEncoder.SetIndexBuffer(renderPrimitive->indexBuffer->buffer,
mRenderPassEncoder.SetIndexBuffer(renderPrimitive->indexBuffer->buffer, renderPrimitive->indexBuffer->indexFormat); renderPrimitive->indexBuffer->indexFormat);
// uint32_t const bufferCount = vbi->getAttributeCount();
// VkDeviceSize const* offsets = vbi->getOffsets();
// VkBuffer const* buffers = prim->vertexBuffer->getVkBuffers();
//
// // Next bind the vertex buffers and index buffer. One potential performance improvement is to
// // avoid rebinding these if they are already bound, but since we do not (yet) support subranges
// // it would be rare for a client to make consecutive draw calls with the same render primitive.
// vkCmdBindVertexBuffers(cmdbuffer, 0, bufferCount, buffers, offsets);
// vkCmdBindIndexBuffer(cmdbuffer, prim->indexBuffer->buffer.getGpuBuffer(), 0,
// prim->indexBuffer->indexType);
// METAL
// if (UTILS_UNLIKELY(mContext->currentRenderPassAbandoned)) {
// return;
// }
// FILAMENT_CHECK_PRECONDITION(mContext->currentRenderPassEncoder != nullptr)
// << "bindRenderPrimitive() without a valid command encoder.";
//
// // Bind the user vertex buffers.
// MetalBuffer* vertexBuffers[MAX_VERTEX_BUFFER_COUNT] = {};
// size_t vertexBufferOffsets[MAX_VERTEX_BUFFER_COUNT] = {};
// size_t maxBufferIndex = 0;
//
// MetalRenderPrimitive const* const primitive = handle_cast<MetalRenderPrimitive>(rph);
// MetalVertexBufferInfo const* const vbi =
// handle_cast<MetalVertexBufferInfo>(primitive->vertexBuffer->vbih);
//
// mContext->currentRenderPrimitive = rph;
//
// auto vb = primitive->vertexBuffer;
// for (auto m : vbi->bufferMapping) {
// assert_invariant(
// m.bufferArgumentIndex >= USER_VERTEX_BUFFER_BINDING_START &&
// m.bufferArgumentIndex < USER_VERTEX_BUFFER_BINDING_START + MAX_VERTEX_BUFFER_COUNT);
// size_t const vertexBufferIndex = m.bufferArgumentIndex - USER_VERTEX_BUFFER_BINDING_START;
// vertexBuffers[vertexBufferIndex] = vb->buffers[m.sourceBufferIndex];
// maxBufferIndex = std::max(maxBufferIndex, vertexBufferIndex);
// }
//
// const auto bufferCount = maxBufferIndex + 1;
// MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), mContext->currentRenderPassEncoder,
// USER_VERTEX_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX, vertexBuffers,
// vertexBufferOffsets, bufferCount);
//
// // Bind the zero buffer, used for missing vertex attributes.
// static const char bytes[16] = { 0 };
// [mContext->currentRenderPassEncoder setVertexBytes:bytes
// length:16
// atIndex:ZERO_VERTEX_BUFFER_BINDING];
} }
void WebGPUDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) { void WebGPUDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
@@ -937,28 +955,165 @@ void WebGPUDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
void WebGPUDriver::resetState(int) { void WebGPUDriver::resetState(int) {
} }
void WebGPUDriver::updateDescriptorSetBuffer( void WebGPUDriver::updateDescriptorSetBuffer(Handle<HwDescriptorSet> dsh,
backend::DescriptorSetHandle dsh, backend::descriptor_binding_t binding, Handle<HwBufferObject> boh, uint32_t offset,
backend::descriptor_binding_t binding,
backend::BufferObjectHandle boh,
uint32_t offset,
uint32_t size) { uint32_t size) {
auto bindGroup = handleCast<WebGPUDescriptorSet>(dsh);
auto buffer = handleCast<WGPUBufferObject>(boh);
if (!bindGroup->getIsLocked()) {
// TODO making assumptions that size and offset mean the same thing here.
wgpu::BindGroupEntry entry{ .binding = static_cast<uint32_t>(binding * 2),
.buffer = buffer->buffer,
.offset = offset,
.size = size };
bindGroup->addEntry(entry.binding, std::move(entry));
}
} }
void WebGPUDriver::updateDescriptorSetTexture( void WebGPUDriver::updateDescriptorSetTexture(Handle<HwDescriptorSet> dsh,
backend::DescriptorSetHandle dsh, backend::descriptor_binding_t binding, Handle<HwTexture> th, SamplerParams params) {
backend::descriptor_binding_t binding, auto bindGroup = handleCast<WebGPUDescriptorSet>(dsh);
backend::TextureHandle th, auto texture = handleCast<WGPUTexture>(th);
SamplerParams params) {
if (!bindGroup->getIsLocked()) {
// Dawn will cache duplicate samplers, so we don't strictly need to maintain a cache.
// Making a cache might save us minor perf by reducing param translation
auto sampler = makeSampler(params);
// TODO making assumptions that size and offset mean the same thing here.
wgpu::BindGroupEntry tEntry{ .binding = static_cast<uint32_t>(binding * 2),
.textureView = texture->getTexView() };
bindGroup->addEntry(tEntry.binding, std::move(tEntry));
wgpu::BindGroupEntry sEntry{ .binding = static_cast<uint32_t>(binding * 2 + 1),
.sampler = sampler };
bindGroup->addEntry(sEntry.binding, std::move(sEntry));
}
} }
void WebGPUDriver::bindDescriptorSet( void WebGPUDriver::bindDescriptorSet(Handle<HwDescriptorSet> dsh, backend::descriptor_set_t set,
backend::DescriptorSetHandle dsh,
backend::descriptor_set_t set,
backend::DescriptorSetOffsetArray&& offsets) { backend::DescriptorSetOffsetArray&& offsets) {
auto bindGroup = handleCast<WebGPUDescriptorSet>(dsh);
// TODO: presume we need this, use it. Probably Encoder::SetBindGroup
auto wbg = bindGroup->lockAndReturn(mDevice);
} }
void WebGPUDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) { void WebGPUDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
} }
wgpu::Sampler WebGPUDriver::makeSampler(SamplerParams const& params) {
wgpu::SamplerDescriptor desc;
desc.label = "TODO";
desc.addressModeU = fWrapModeToWAddressMode(params.wrapS);
desc.addressModeV = fWrapModeToWAddressMode(params.wrapR);
desc.addressModeW = fWrapModeToWAddressMode(params.wrapT);
switch (params.filterMag) {
case SamplerMagFilter::NEAREST: {
desc.magFilter = wgpu::FilterMode::Nearest;
break;
}
case SamplerMagFilter::LINEAR: {
desc.magFilter = wgpu::FilterMode::Linear;
break;
}
}
switch (params.filterMin) {
case SamplerMinFilter::NEAREST: {
desc.minFilter = wgpu::FilterMode::Nearest;
// Metal Driver uses an explicit not-mipmapped value webgpu lacks. Nearest should
// suffice
desc.mipmapFilter = wgpu::MipmapFilterMode::Nearest;
break;
}
case SamplerMinFilter::LINEAR: {
desc.minFilter = wgpu::FilterMode::Linear;
// Metal Driver uses an explicit not-mipmapped value webgpu lacks. Nearest should
// suffice
desc.mipmapFilter = wgpu::MipmapFilterMode::Nearest;
break;
}
case SamplerMinFilter::NEAREST_MIPMAP_NEAREST: {
desc.minFilter = wgpu::FilterMode::Nearest;
desc.mipmapFilter = wgpu::MipmapFilterMode::Nearest;
break;
}
case SamplerMinFilter::LINEAR_MIPMAP_NEAREST: {
desc.minFilter = wgpu::FilterMode::Linear;
desc.mipmapFilter = wgpu::MipmapFilterMode::Nearest;
break;
}
case SamplerMinFilter::NEAREST_MIPMAP_LINEAR: {
desc.minFilter = wgpu::FilterMode::Nearest;
desc.mipmapFilter = wgpu::MipmapFilterMode::Linear;
break;
}
case SamplerMinFilter::LINEAR_MIPMAP_LINEAR: {
desc.minFilter = wgpu::FilterMode::Linear;
desc.mipmapFilter = wgpu::MipmapFilterMode::Linear;
break;
}
}
switch (params.compareFunc) {
case SamplerCompareFunc::LE: {
desc.compare = wgpu::CompareFunction::LessEqual;
break;
}
case SamplerCompareFunc::GE: {
desc.compare = wgpu::CompareFunction::GreaterEqual;
break;
}
case SamplerCompareFunc::L: {
desc.compare = wgpu::CompareFunction::Less;
break;
}
case SamplerCompareFunc::G: {
desc.compare = wgpu::CompareFunction::Greater;
break;
}
case SamplerCompareFunc::E: {
desc.compare = wgpu::CompareFunction::Equal;
break;
}
case SamplerCompareFunc::NE: {
desc.compare = wgpu::CompareFunction::NotEqual;
break;
}
case SamplerCompareFunc::A: {
desc.compare = wgpu::CompareFunction::Always;
break;
}
case SamplerCompareFunc::N: {
desc.compare = wgpu::CompareFunction::Never;
break;
}
}
desc.maxAnisotropy = 1u << params.anisotropyLog2;
// Unused: Filament's compareMode, WGPU lodMinClamp/lodMaxClamp
return mDevice.CreateSampler();
}
wgpu::AddressMode WebGPUDriver::fWrapModeToWAddressMode(const SamplerWrapMode& fWrapMode) {
switch (fWrapMode) {
case SamplerWrapMode::CLAMP_TO_EDGE: {
return wgpu::AddressMode::ClampToEdge;
break;
}
case SamplerWrapMode::REPEAT: {
return wgpu::AddressMode::Repeat;
break;
}
case SamplerWrapMode::MIRRORED_REPEAT: {
return wgpu::AddressMode::MirrorRepeat;
break;
}
}
return wgpu::AddressMode::Undefined;
}
} // namespace filament } // namespace filament
+26 -3
View File
@@ -18,6 +18,7 @@
#define TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H #define TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
#include "WebGPUHandles.h" #include "WebGPUHandles.h"
#include "webgpu/WebGPUConstants.h"
#include <backend/platforms/WebGPUPlatform.h> #include <backend/platforms/WebGPUPlatform.h>
#include "DriverBase.h" #include "DriverBase.h"
@@ -34,7 +35,7 @@
#include <memory> #include <memory>
#ifndef FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB #ifndef FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB
# define FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB 8 #define FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB 8
#endif #endif
namespace filament::backend { namespace filament::backend {
@@ -55,6 +56,28 @@ private:
explicit WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept; explicit WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept;
[[nodiscard]] ShaderModel getShaderModel() const noexcept final; [[nodiscard]] ShaderModel getShaderModel() const noexcept final;
[[nodiscard]] ShaderLanguage getShaderLanguage() const noexcept final; [[nodiscard]] ShaderLanguage getShaderLanguage() const noexcept final;
[[nodiscard]] wgpu::Sampler makeSampler(SamplerParams const& params);
[[nodiscard]] static wgpu::AddressMode fWrapModeToWAddressMode(const filament::backend::SamplerWrapMode& fUsage);
template<typename GPUBufferObject>
void updateGPUBuffer(GPUBufferObject* gpuBufferObject, BufferDescriptor&& bufferDescriptor,
uint32_t byteOffset) {
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
<< "copyIntoBuffer called with a null buffer";
FILAMENT_CHECK_PRECONDITION(
bufferDescriptor.size + byteOffset <= gpuBufferObject->buffer.GetSize())
<< "Attempting to copy " << bufferDescriptor.size << " bytes into a buffer of size "
<< gpuBufferObject->buffer.GetSize() << " at offset " << byteOffset;
// TODO: All buffer objects are created with CopyDst usage.
// This may have some performance implications. That should be investigated later.
assert_invariant(gpuBufferObject->buffer.GetUsage() & wgpu::BufferUsage::CopyDst);
// WriteBuffer is an async call. But cpu buffer data is already written to the staging
// buffer on return from the WriteBuffer.
mQueue.WriteBuffer(gpuBufferObject->buffer, byteOffset, bufferDescriptor.buffer,
bufferDescriptor.size);
scheduleDestroy(std::move(bufferDescriptor));
}
// the platform (e.g. OS) specific aspects of the WebGPU backend are strictly only // the platform (e.g. OS) specific aspects of the WebGPU backend are strictly only
// handled in the WebGPUPlatform // handled in the WebGPUPlatform
@@ -99,8 +122,8 @@ private:
return mHandleAllocator.allocate<D>(); return mHandleAllocator.allocate<D>();
} }
template<typename D, typename B, typename ... ARGS> template<typename D, typename B, typename... ARGS>
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)...);
} }
+480 -88
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2025 The Android Open Source Project * Copyright (C) 2025 The Android Open Source Project
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,46 +19,58 @@
#include <utility> #include <utility>
namespace { namespace {
constexpr wgpu::BufferUsage getBufferObjectUsage(
filament::backend::BufferObjectBinding bindingType) noexcept {
switch (bindingType) {
case filament::backend::BufferObjectBinding::VERTEX:
return wgpu::BufferUsage::Vertex;
case filament::backend::BufferObjectBinding::UNIFORM:
return wgpu::BufferUsage::Uniform;
case filament::backend::BufferObjectBinding::SHADER_STORAGE:
return wgpu::BufferUsage::Storage;
}
}
wgpu::Buffer createIndexBuffer(wgpu::Device const& device, uint8_t elementSize, uint32_t indexCount) { wgpu::Buffer createBuffer(wgpu::Device const& device, wgpu::BufferUsage usage, uint32_t size,
wgpu::BufferDescriptor descriptor{ .label = "index_buffer", char const* label) {
.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Index, wgpu::BufferDescriptor descriptor{ .label = label,
.size = elementSize * indexCount, .usage = usage,
.size = size,
.mappedAtCreation = false }; .mappedAtCreation = false };
return device.CreateBuffer(&descriptor); return device.CreateBuffer(&descriptor);
} }
wgpu::VertexFormat getVertexFormat(filament::backend::ElementType type, bool normalized, bool integer) { wgpu::VertexFormat getVertexFormat(filament::backend::ElementType type, bool normalized, bool integer) {
using ElementType = filament::backend::ElementType; using ElementType = filament::backend::ElementType;
// using VertexFormat = wgpu::VertexFormat; using VertexFormat = wgpu::VertexFormat;
if (normalized) { if (normalized) {
switch (type) { switch (type) {
// Single Component Types // Single Component Types
case ElementType::BYTE: return wgpu::VertexFormat::Snorm8; case ElementType::BYTE: return VertexFormat::Snorm8;
case ElementType::UBYTE: return wgpu::VertexFormat::Unorm8; case ElementType::UBYTE: return VertexFormat::Unorm8;
case ElementType::SHORT: return wgpu::VertexFormat::Snorm16; case ElementType::SHORT: return VertexFormat::Snorm16;
case ElementType::USHORT: return wgpu::VertexFormat::Unorm16; case ElementType::USHORT: return VertexFormat::Unorm16;
// Two Component Types // Two Component Types
case ElementType::BYTE2: return wgpu::VertexFormat::Snorm8x2; case ElementType::BYTE2: return VertexFormat::Snorm8x2;
case ElementType::UBYTE2: return wgpu::VertexFormat::Unorm8x2; case ElementType::UBYTE2: return VertexFormat::Unorm8x2;
case ElementType::SHORT2: return wgpu::VertexFormat::Snorm16x2; case ElementType::SHORT2: return VertexFormat::Snorm16x2;
case ElementType::USHORT2: return wgpu::VertexFormat::Unorm16x2; case ElementType::USHORT2: return VertexFormat::Unorm16x2;
// Three Component Types // Three Component Types
// There is no vertex format type for 3 byte data in webgpu. Use // There is no vertex format type for 3 byte data in webgpu. Use
// 4 byte signed normalized type and ignore the last byte. // 4 byte signed normalized type and ignore the last byte.
// TODO: This is to be verified. // TODO: This is to be verified.
case ElementType::BYTE3: return wgpu::VertexFormat::Snorm8x4; // NOT MINSPEC case ElementType::BYTE3: return VertexFormat::Snorm8x4; // NOT MINSPEC
case ElementType::UBYTE3: return wgpu::VertexFormat::Unorm8x4; // NOT MINSPEC case ElementType::UBYTE3: return VertexFormat::Unorm8x4; // NOT MINSPEC
case ElementType::SHORT3: return wgpu::VertexFormat::Snorm16x4; // NOT MINSPEC case ElementType::SHORT3: return VertexFormat::Snorm16x4; // NOT MINSPEC
case ElementType::USHORT3: return wgpu::VertexFormat::Unorm16x4; // NOT MINSPEC case ElementType::USHORT3: return VertexFormat::Unorm16x4; // NOT MINSPEC
// Four Component Types // Four Component Types
case ElementType::BYTE4: return wgpu::VertexFormat::Snorm8x4; case ElementType::BYTE4: return VertexFormat::Snorm8x4;
case ElementType::UBYTE4: return wgpu::VertexFormat::Unorm8x4; case ElementType::UBYTE4: return VertexFormat::Unorm8x4;
case ElementType::SHORT4: return wgpu::VertexFormat::Snorm16x4; case ElementType::SHORT4: return VertexFormat::Snorm16x4;
case ElementType::USHORT4: return wgpu::VertexFormat::Unorm8x4; case ElementType::USHORT4: return VertexFormat::Unorm8x4;
default: default:
FILAMENT_CHECK_POSTCONDITION(false) << "Normalized format does not exist."; FILAMENT_CHECK_POSTCONDITION(false) << "Normalized format does not exist.";
return wgpu::VertexFormat::Float32x3; return VertexFormat::Float32x3;
} }
} }
switch (type) { switch (type) {
@@ -66,41 +78,39 @@ wgpu::VertexFormat getVertexFormat(filament::backend::ElementType type, bool nor
// There is no direct alternative for SSCALED in webgpu. Convert them to Float32 directly. // There is no direct alternative for SSCALED in webgpu. Convert them to Float32 directly.
// This will result in increased memory on the cpu side. // This will result in increased memory on the cpu side.
// TODO: Is Float16 acceptable instead with some potential accuracy errors? // TODO: Is Float16 acceptable instead with some potential accuracy errors?
case ElementType::BYTE: return integer ? wgpu::VertexFormat::Sint8 : wgpu::VertexFormat::Float32; case ElementType::BYTE: return integer ? VertexFormat::Sint8 : VertexFormat::Float32;
case ElementType::UBYTE: return integer ? wgpu::VertexFormat::Uint8 : wgpu::VertexFormat::Float32; case ElementType::UBYTE: return integer ? VertexFormat::Uint8 : VertexFormat::Float32;
case ElementType::SHORT: return integer ? wgpu::VertexFormat::Sint16 : wgpu::VertexFormat::Float32; case ElementType::SHORT: return integer ? VertexFormat::Sint16 : VertexFormat::Float32;
case ElementType::USHORT: return integer ? wgpu::VertexFormat::Uint16 : wgpu::VertexFormat::Float32; case ElementType::USHORT: return integer ? VertexFormat::Uint16 : VertexFormat::Float32;
case ElementType::HALF: return wgpu::VertexFormat::Float16; case ElementType::HALF: return VertexFormat::Float16;
case ElementType::INT: return wgpu::VertexFormat::Sint32; case ElementType::INT: return VertexFormat::Sint32;
case ElementType::UINT: return wgpu::VertexFormat::Uint32; case ElementType::UINT: return VertexFormat::Uint32;
case ElementType::FLOAT: return wgpu::VertexFormat::Float32; case ElementType::FLOAT: return VertexFormat::Float32;
// Two Component Types // Two Component Types
case ElementType::BYTE2: return integer ? wgpu::VertexFormat::Sint8x2 : wgpu::VertexFormat::Float32x2; case ElementType::BYTE2: return integer ? VertexFormat::Sint8x2 : VertexFormat::Float32x2;
case ElementType::UBYTE2: return integer ? wgpu::VertexFormat::Uint8x2 : wgpu::VertexFormat::Float32x2; case ElementType::UBYTE2: return integer ? VertexFormat::Uint8x2 : VertexFormat::Float32x2;
case ElementType::SHORT2: return integer ? wgpu::VertexFormat::Sint16x2 : wgpu::VertexFormat::Float32x2; case ElementType::SHORT2: return integer ? VertexFormat::Sint16x2 : VertexFormat::Float32x2;
case ElementType::USHORT2: return integer ? wgpu::VertexFormat::Uint16x2 : wgpu::VertexFormat::Float32x2; case ElementType::USHORT2: return integer ? VertexFormat::Uint16x2 : VertexFormat::Float32x2;
case ElementType::HALF2: return wgpu::VertexFormat::Float16x2; case ElementType::HALF2: return VertexFormat::Float16x2;
case ElementType::FLOAT2: return wgpu::VertexFormat::Float32x2; case ElementType::FLOAT2: return VertexFormat::Float32x2;
// Three Component Types // Three Component Types
case ElementType::BYTE3: return wgpu::VertexFormat::Sint8x4; // NOT MINSPEC case ElementType::BYTE3: return VertexFormat::Sint8x4; // NOT MINSPEC
case ElementType::UBYTE3: return wgpu::VertexFormat::Uint8x4; // NOT MINSPEC case ElementType::UBYTE3: return VertexFormat::Uint8x4; // NOT MINSPEC
case ElementType::SHORT3: return wgpu::VertexFormat::Sint16x4; // NOT MINSPEC case ElementType::SHORT3: return VertexFormat::Sint16x4; // NOT MINSPEC
case ElementType::USHORT3: return wgpu::VertexFormat::Uint16x4; // NOT MINSPEC case ElementType::USHORT3: return VertexFormat::Uint16x4; // NOT MINSPEC
case ElementType::HALF3: return wgpu::VertexFormat::Float16x4; // NOT MINSPEC case ElementType::HALF3: return VertexFormat::Float16x4; // NOT MINSPEC
case ElementType::FLOAT3: return wgpu::VertexFormat::Float32x3; case ElementType::FLOAT3: return VertexFormat::Float32x3;
// Four Component Types // Four Component Types
case ElementType::BYTE4: return integer ? wgpu::VertexFormat::Sint8x4 : wgpu::VertexFormat::Float32x4; case ElementType::BYTE4: return integer ? VertexFormat::Sint8x4 : VertexFormat::Float32x4;
case ElementType::UBYTE4: return integer ? wgpu::VertexFormat::Uint8x4 : wgpu::VertexFormat::Float32x4; case ElementType::UBYTE4: return integer ? VertexFormat::Uint8x4 : VertexFormat::Float32x4;
case ElementType::SHORT4: return integer ? wgpu::VertexFormat::Sint16x4 : wgpu::VertexFormat::Float32x4; case ElementType::SHORT4: return integer ? VertexFormat::Sint16x4 : VertexFormat::Float32x4;
case ElementType::USHORT4: return integer ? wgpu::VertexFormat::Uint16x4 : wgpu::VertexFormat::Float32x4; case ElementType::USHORT4: return integer ? VertexFormat::Uint16x4 : VertexFormat::Float32x4;
case ElementType::HALF4: return wgpu::VertexFormat::Float16x4; case ElementType::HALF4: return VertexFormat::Float16x4;
case ElementType::FLOAT4: return wgpu::VertexFormat::Float32x4; case ElementType::FLOAT4: return VertexFormat::Float32x4;
} }
FILAMENT_CHECK_POSTCONDITION(false) << "Vertex format should always be defined.";
return wgpu::VertexFormat::Float32x3;
} }
} // namespace }// namespace
namespace filament::backend { namespace filament::backend {
@@ -109,8 +119,10 @@ WGPUVertexBufferInfo::WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attribut
: HwVertexBufferInfo(bufferCount, attributeCount), : HwVertexBufferInfo(bufferCount, attributeCount),
mVertexBufferLayout(bufferCount), mVertexBufferLayout(bufferCount),
mAttributes(bufferCount) { mAttributes(bufferCount) {
assert_invariant(attributeCount > 0);
assert_invariant(bufferCount > 0);
for (uint32_t attribIndex = 0; attribIndex < attributes.size(); attribIndex++) { for (uint32_t attribIndex = 0; attribIndex < attributes.size(); attribIndex++) {
Attribute attrib = attributes[attribIndex]; Attribute const& attrib = attributes[attribIndex];
// Ignore the attributes which are not bind to vertex buffers. // Ignore the attributes which are not bind to vertex buffers.
if (attrib.buffer == Attribute::BUFFER_UNUSED) { if (attrib.buffer == Attribute::BUFFER_UNUSED) {
continue; continue;
@@ -121,52 +133,45 @@ WGPUVertexBufferInfo::WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attribut
bool const isNormalized = attrib.flags & Attribute::FLAG_NORMALIZED; bool const isNormalized = attrib.flags & Attribute::FLAG_NORMALIZED;
wgpu::VertexFormat vertexFormat = getVertexFormat(attrib.type, isNormalized, isInteger); wgpu::VertexFormat vertexFormat = getVertexFormat(attrib.type, isNormalized, isInteger);
// Attributes are sequential per buffer
mAttributes[attrib.buffer].push_back({ mAttributes[attrib.buffer].push_back({
.format = vertexFormat, .format = vertexFormat,
.offset = attrib.offset, .offset = attrib.offset,
.shaderLocation = attribIndex, .shaderLocation = static_cast<uint32_t>(mAttributes[attrib.buffer].size()),
}); });
mVertexBufferLayout[attrib.buffer] = {
.arrayStride = attrib.stride, mVertexBufferLayout[attrib.buffer].stepMode = wgpu::VertexStepMode::Vertex;
}; if (mVertexBufferLayout[attrib.buffer].arrayStride == 0) {
mVertexBufferLayout[attrib.buffer].arrayStride = attrib.stride;
} else {
assert_invariant(mVertexBufferLayout[attrib.buffer].arrayStride == attrib.stride);
}
} }
for (uint32_t bufferIndex = 0; bufferIndex < bufferCount; bufferIndex++) { for (uint32_t bufferIndex = 0; bufferIndex < bufferCount; bufferIndex++) {
mVertexBufferLayout[bufferIndex] = { mVertexBufferLayout[bufferIndex].attributeCount = mAttributes[bufferIndex].size();
.attributeCount = mAttributes[bufferIndex].size(), mVertexBufferLayout[bufferIndex].attributes = mAttributes[bufferIndex].data();
.attributes = mAttributes[bufferIndex].data(),
};
} }
} }
WGPUIndexBuffer::WGPUIndexBuffer(wgpu::Device const& device, uint8_t elementSize, WGPUIndexBuffer::WGPUIndexBuffer(wgpu::Device const& device, uint8_t elementSize,
uint32_t indexCount) uint32_t indexCount)
: buffer(createIndexBuffer(device, elementSize, indexCount)), : buffer(createBuffer(device, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Index,
elementSize * indexCount, "index_buffer")),
indexFormat(elementSize == 2 ? wgpu::IndexFormat::Uint16 : wgpu::IndexFormat::Uint32) {} indexFormat(elementSize == 2 ? wgpu::IndexFormat::Uint16 : wgpu::IndexFormat::Uint32) {}
WGPUVertexBuffer::WGPUVertexBuffer(wgpu::Device const &device, uint32_t vextexCount, uint32_t bufferCount, WGPUVertexBuffer::WGPUVertexBuffer(wgpu::Device const& device, uint32_t vertexCount,
Handle<HwVertexBufferInfo> vbih) uint32_t bufferCount, Handle<HwVertexBufferInfo> vbih)
: HwVertexBuffer(vextexCount), : HwVertexBuffer(vertexCount),
vbih(vbih), vbih(vbih),
buffers(bufferCount) { 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) { WGPUBufferObject::WGPUBufferObject(wgpu::Device const& device, BufferObjectBinding bindingType,
descriptor.label = ("vertex_buffer_" + std::to_string(i)).c_str(); uint32_t byteCount)
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) {}
WGPUBufferObject::WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount)
: HwBufferObject(byteCount), : HwBufferObject(byteCount),
buffer(createBuffer(device, wgpu::BufferUsage::CopyDst | getBufferObjectUsage(bindingType),
byteCount, "buffer_object")),
bufferObjectBinding(bindingType) {} bufferObjectBinding(bindingType) {}
wgpu::ShaderStage WebGPUDescriptorSetLayout::filamentStageToWGPUStage(ShaderStageFlags fFlags) { wgpu::ShaderStage WebGPUDescriptorSetLayout::filamentStageToWGPUStage(ShaderStageFlags fFlags) {
@@ -184,14 +189,13 @@ wgpu::ShaderStage WebGPUDescriptorSetLayout::filamentStageToWGPUStage(ShaderStag
} }
WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout, WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout,
wgpu::Device const* device) { wgpu::Device const& device) {
assert_invariant(device->Get()); assert_invariant(device);
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for // TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
// debugging. For now, hack an incrementing value. // debugging. For now, hack an incrementing value.
static int layoutNum = 0; static int layoutNum = 0;
uint samplerCount = uint samplerCount =
std::count_if(layout.bindings.begin(), layout.bindings.end(), [](auto& fEntry) { std::count_if(layout.bindings.begin(), layout.bindings.end(), [](auto& fEntry) {
return fEntry.type == DescriptorType::SAMPLER || return fEntry.type == DescriptorType::SAMPLER ||
@@ -255,13 +259,401 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
.entries = wEntries.data() .entries = wEntries.data()
}; };
// TODO Do we need to defer this until we have more info on textures and samplers?? // TODO Do we need to defer this until we have more info on textures and samplers??
mLayout = device->CreateBindGroupLayout(&layoutDescriptor); mLayoutSize = wEntries.size();
mLayout = device.CreateBindGroupLayout(&layoutDescriptor);
} }
WebGPUDescriptorSetLayout::~WebGPUDescriptorSetLayout() {} WebGPUDescriptorSetLayout::~WebGPUDescriptorSetLayout() {}
void WGPURenderPrimitive::setBuffers(WGPUVertexBufferInfo const* const vbi, WebGPUDescriptorSet::WebGPUDescriptorSet(const wgpu::BindGroupLayout& layout, uint layoutSize)
WGPUVertexBuffer* vertexBuffer, WGPUIndexBuffer* indexBuffer) { : mLayout(layout),
entries(layoutSize, wgpu::BindGroupEntry{.buffer = nullptr, .sampler = nullptr, .textureView = nullptr}) {
// Establish the size of entries based on the layout. This should be reliable and efficient.
}
WebGPUDescriptorSet::~WebGPUDescriptorSet() {
mBindGroup = nullptr;
mLayout = nullptr;
entries.clear();
}
wgpu::BindGroup WebGPUDescriptorSet::lockAndReturn(const wgpu::Device& device) {
if (mBindGroup) {
return mBindGroup;
}
// TODO label? Should we just copy layout label?
wgpu::BindGroupDescriptor desc{ .layout = mLayout,
.entryCount = entries.size(),
.entries = entries.data() };
mBindGroup = device.CreateBindGroup(&desc);
return mBindGroup;
}
void WebGPUDescriptorSet::addEntry(uint index, wgpu::BindGroupEntry&& entry) {
if (mBindGroup) {
// We will keep getting hits from future updates, but shouldn't do anything
// Filament guarantees this won't change after things have locked.
return;
}
// TODO: Putting some level of trust that Filament is not going to reuse indexes or go past the
// layout index for efficiency. Add guards if wrong.
entries.emplace_back(std::move(entry));
}
// From createTextureR
WGPUTexture::WGPUTexture(SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples,
uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage,
wgpu::Device device) noexcept {
// First the texture aspect
wgpu::TextureDescriptor desc;
switch (target) {
case SamplerType::SAMPLER_CUBEMAP:
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
case SamplerType::SAMPLER_2D:
case SamplerType::SAMPLER_2D_ARRAY:
// Should be safe to assume external is 2d
case SamplerType::SAMPLER_EXTERNAL: {
desc.dimension = wgpu::TextureDimension::e2D;
break;
}
case SamplerType::SAMPLER_3D: {
desc.dimension = wgpu::TextureDimension::e3D;
break;
}
}
desc.size = { .width = width, .height = height, .depthOrArrayLayers = depth };
desc.format = fToWGPUTextureFormat(format);
assert_invariant(desc.format != wgpu::TextureFormat::Undefined);
// WGPU requires this to be true. Filament should comply
assert(samples == 1 || samples || 4);
desc.sampleCount = samples;
desc.usage = fToWGPUTextureUsage(usage);
desc.mipLevelCount = levels;
// TODO Is this fine? Could do all-the-things, a naive mapping or get something from Filament
desc.viewFormats = nullptr;
texture = device.CreateTexture(&desc);
// TODO should a default levelCount be something other than 0? Sample count?
texView = makeTextureView(0, 1);
}
// From createTextureViewR
WGPUTexture::WGPUTexture(WGPUTexture* src, uint8_t baseLevel, uint8_t levelCount) noexcept {
texture = src->texture;
texView = makeTextureView(baseLevel, levelCount);
}
wgpu::TextureUsage WGPUTexture::fToWGPUTextureUsage(const TextureUsage& fUsage) {
wgpu::TextureUsage retUsage = wgpu::TextureUsage::None;
// Basing this mapping off of VulkanTexture.cpp's getUsage func and suggestions from Gemini
// TODO Validate assumptions, revisit if issues.
if (any(TextureUsage::BLIT_SRC & fUsage)) {
retUsage |= wgpu::TextureUsage::CopySrc;
}
if (any((TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE) & fUsage)) {
retUsage |= wgpu::TextureUsage::CopyDst;
}
if (any(TextureUsage::SAMPLEABLE & fUsage)) {
retUsage |= wgpu::TextureUsage::TextureBinding;
}
// WGPU Render attachment covers either color or stencil situation dependant
// NOTE: Depth attachment isn't used this way in Vulkan but logically maps to WGPU docs. If
// issues, investigate here.
if (any((TextureUsage::COLOR_ATTACHMENT | TextureUsage::STENCIL_ATTACHMENT |
TextureUsage::DEPTH_ATTACHMENT) &
fUsage)) {
retUsage |= wgpu::TextureUsage::RenderAttachment;
} }
// This is from Vulkan logic- if there are any issues try disabling this first, allows perf
// benefit though
const bool useTransientAttachment =
// Usage consists of attachment flags only.
none(fUsage & ~TextureUsage::ALL_ATTACHMENTS) &&
// Usage contains at least one attachment flag.
any(fUsage & TextureUsage::ALL_ATTACHMENTS) &&
// Depth resolve cannot use transient attachment because it uses a custom shader.
// TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this
// restriction.
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
// and above to be able to support stencil resolve (along with depth).
!(any(fUsage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1);
if (useTransientAttachment) {
retUsage |= wgpu::TextureUsage::TransientAttachment;
}
// NOTE: Unused wgpu flags:
// StorageBinding
// StorageAttachment
// NOTE: Unused Filament flags:
// SUBPASS_INPUT VK goes to input attachment which we don't support right now
// PROTECTED
return retUsage;
}
wgpu::TextureFormat WGPUTexture::fToWGPUTextureFormat(const TextureFormat& fUsage) {
switch (fUsage) {
case filament::backend::TextureFormat::R8:
return wgpu::TextureFormat::R8Unorm;
case filament::backend::TextureFormat::R8_SNORM:
return wgpu::TextureFormat::R8Snorm;
case filament::backend::TextureFormat::R8UI:
return wgpu::TextureFormat::R8Uint;
case filament::backend::TextureFormat::R8I:
return wgpu::TextureFormat::R8Sint;
case filament::backend::TextureFormat::STENCIL8:
return wgpu::TextureFormat::Stencil8;
case filament::backend::TextureFormat::R16F:
return wgpu::TextureFormat::R16Float;
case filament::backend::TextureFormat::R16UI:
return wgpu::TextureFormat::R16Uint;
case filament::backend::TextureFormat::R16I:
return wgpu::TextureFormat::R16Sint;
case filament::backend::TextureFormat::RG8:
return wgpu::TextureFormat::RG8Unorm;
case filament::backend::TextureFormat::RG8_SNORM:
return wgpu::TextureFormat::RG8Snorm;
case filament::backend::TextureFormat::RG8UI:
return wgpu::TextureFormat::RG8Uint;
case filament::backend::TextureFormat::RG8I:
return wgpu::TextureFormat::RG8Sint;
case filament::backend::TextureFormat::R32F:
return wgpu::TextureFormat::R32Float;
case filament::backend::TextureFormat::R32UI:
return wgpu::TextureFormat::R32Uint;
case filament::backend::TextureFormat::R32I:
return wgpu::TextureFormat::R32Sint;
case filament::backend::TextureFormat::RG16F:
return wgpu::TextureFormat::RG16Float;
case filament::backend::TextureFormat::RG16UI:
return wgpu::TextureFormat::RG16Uint;
case filament::backend::TextureFormat::RG16I:
return wgpu::TextureFormat::RG16Sint;
case filament::backend::TextureFormat::RGBA8:
return wgpu::TextureFormat::RGBA8Unorm;
case filament::backend::TextureFormat::SRGB8_A8:
return wgpu::TextureFormat::RGBA8UnormSrgb;
case filament::backend::TextureFormat::RGBA8_SNORM:
return wgpu::TextureFormat::RGBA8Snorm;
case filament::backend::TextureFormat::RGBA8UI:
return wgpu::TextureFormat::RGBA8Uint;
case filament::backend::TextureFormat::RGBA8I:
return wgpu::TextureFormat::RGBA8Sint;
case filament::backend::TextureFormat::DEPTH16:
return wgpu::TextureFormat::Depth16Unorm;
case filament::backend::TextureFormat::DEPTH24:
return wgpu::TextureFormat::Depth24Plus;
case filament::backend::TextureFormat::DEPTH32F:
return wgpu::TextureFormat::Depth32Float;
case filament::backend::TextureFormat::DEPTH24_STENCIL8:
return wgpu::TextureFormat::Depth24PlusStencil8;
case filament::backend::TextureFormat::DEPTH32F_STENCIL8:
return wgpu::TextureFormat::Depth32FloatStencil8;
case filament::backend::TextureFormat::RG32F:
return wgpu::TextureFormat::RG32Float;
case filament::backend::TextureFormat::RG32UI:
return wgpu::TextureFormat::RG32Uint;
case filament::backend::TextureFormat::RG32I:
return wgpu::TextureFormat::RG32Sint;
case filament::backend::TextureFormat::RGBA16F:
return wgpu::TextureFormat::RGBA16Float;
case filament::backend::TextureFormat::RGBA16UI:
return wgpu::TextureFormat::RGBA16Uint;
case filament::backend::TextureFormat::RGBA16I:
return wgpu::TextureFormat::RGBA16Sint;
case filament::backend::TextureFormat::RGBA32F:
return wgpu::TextureFormat::RGBA32Float;
case filament::backend::TextureFormat::RGBA32UI:
return wgpu::TextureFormat::RGBA32Uint;
case filament::backend::TextureFormat::RGBA32I:
return wgpu::TextureFormat::RGBA32Sint;
case filament::backend::TextureFormat::EAC_R11:
return wgpu::TextureFormat::EACR11Unorm;
case filament::backend::TextureFormat::EAC_R11_SIGNED:
return wgpu::TextureFormat::EACR11Snorm;
case filament::backend::TextureFormat::EAC_RG11:
return wgpu::TextureFormat::EACRG11Unorm;
case filament::backend::TextureFormat::EAC_RG11_SIGNED:
return wgpu::TextureFormat::EACRG11Snorm;
case filament::backend::TextureFormat::ETC2_RGB8:
return wgpu::TextureFormat::ETC2RGB8Unorm;
case filament::backend::TextureFormat::ETC2_SRGB8:
return wgpu::TextureFormat::ETC2RGB8UnormSrgb;
case filament::backend::TextureFormat::ETC2_RGB8_A1:
return wgpu::TextureFormat::ETC2RGB8A1Unorm;
case filament::backend::TextureFormat::ETC2_SRGB8_A1:
return wgpu::TextureFormat::ETC2RGB8A1UnormSrgb;
case filament::backend::TextureFormat::ETC2_EAC_RGBA8:
return wgpu::TextureFormat::ETC2RGBA8Unorm;
case filament::backend::TextureFormat::ETC2_EAC_SRGBA8:
return wgpu::TextureFormat::ETC2RGBA8UnormSrgb;
case filament::backend::TextureFormat::RGBA_ASTC_4x4:
return wgpu::TextureFormat::ASTC4x4Unorm;
case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_4x4:
return wgpu::TextureFormat::ASTC4x4UnormSrgb;
case filament::backend::TextureFormat::RGBA_ASTC_5x4:
return wgpu::TextureFormat::ASTC5x4Unorm;
case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_5x4:
return wgpu::TextureFormat::ASTC5x4UnormSrgb;
case filament::backend::TextureFormat::RGBA_ASTC_5x5:
return wgpu::TextureFormat::ASTC5x5Unorm;
case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_5x5:
return wgpu::TextureFormat::ASTC5x5UnormSrgb;
case filament::backend::TextureFormat::RGBA_ASTC_6x5:
return wgpu::TextureFormat::ASTC6x5Unorm;
case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_6x5:
return wgpu::TextureFormat::ASTC6x5UnormSrgb;
case filament::backend::TextureFormat::RGBA_ASTC_6x6:
return wgpu::TextureFormat::ASTC6x6Unorm;
case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_6x6:
return wgpu::TextureFormat::ASTC6x6UnormSrgb;
case filament::backend::TextureFormat::RGBA_ASTC_8x5:
return wgpu::TextureFormat::ASTC8x5Unorm;
case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_8x5:
return wgpu::TextureFormat::ASTC8x5UnormSrgb;
case filament::backend::TextureFormat::RGBA_ASTC_8x6:
return wgpu::TextureFormat::ASTC8x6Unorm;
case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_8x6:
return wgpu::TextureFormat::ASTC8x6UnormSrgb;
case filament::backend::TextureFormat::RGBA_ASTC_8x8:
return wgpu::TextureFormat::ASTC8x8Unorm;
case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_8x8:
return wgpu::TextureFormat::ASTC8x8UnormSrgb;
case filament::backend::TextureFormat::RGBA_ASTC_10x5:
return wgpu::TextureFormat::ASTC10x5Unorm;
case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_10x5:
return wgpu::TextureFormat::ASTC10x5UnormSrgb;
case filament::backend::TextureFormat::RGBA_ASTC_10x6:
return wgpu::TextureFormat::ASTC10x6Unorm;
case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_10x6:
return wgpu::TextureFormat::ASTC10x6UnormSrgb;
case filament::backend::TextureFormat::RGBA_ASTC_10x8:
return wgpu::TextureFormat::ASTC10x8Unorm;
case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_10x8:
return wgpu::TextureFormat::ASTC10x8UnormSrgb;
case filament::backend::TextureFormat::RGBA_ASTC_10x10:
return wgpu::TextureFormat::ASTC10x10Unorm;
case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_10x10:
return wgpu::TextureFormat::ASTC10x10UnormSrgb;
case filament::backend::TextureFormat::RGBA_ASTC_12x10:
return wgpu::TextureFormat::ASTC12x10Unorm;
case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_12x10:
return wgpu::TextureFormat::ASTC12x10UnormSrgb;
case filament::backend::TextureFormat::RGBA_ASTC_12x12:
return wgpu::TextureFormat::ASTC12x12Unorm;
case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_12x12:
return wgpu::TextureFormat::ASTC12x12UnormSrgb;
case filament::backend::TextureFormat::RED_RGTC1:
return wgpu::TextureFormat::BC4RUnorm;
case filament::backend::TextureFormat::SIGNED_RED_RGTC1:
return wgpu::TextureFormat::BC4RSnorm;
case filament::backend::TextureFormat::RED_GREEN_RGTC2:
return wgpu::TextureFormat::BC5RGUnorm;
case filament::backend::TextureFormat::SIGNED_RED_GREEN_RGTC2:
return wgpu::TextureFormat::BC5RGSnorm;
case filament::backend::TextureFormat::RGB_BPTC_UNSIGNED_FLOAT:
return wgpu::TextureFormat::BC6HRGBUfloat;
case filament::backend::TextureFormat::RGB_BPTC_SIGNED_FLOAT:
return wgpu::TextureFormat::BC6HRGBFloat;
case filament::backend::TextureFormat::RGBA_BPTC_UNORM:
return wgpu::TextureFormat::BC7RGBAUnorm;
case filament::backend::TextureFormat::SRGB_ALPHA_BPTC_UNORM:
return wgpu::TextureFormat::BC7RGBAUnormSrgb;
case filament::backend::TextureFormat::RGB565:
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
// and discard the alpha and lower precision.
return wgpu::TextureFormat::Undefined;
case filament::backend::TextureFormat::RGB9_E5:
return wgpu::TextureFormat::RGB9E5Ufloat;
case filament::backend::TextureFormat::RGB5_A1:
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
// and handle the packing/unpacking in shaders.
return wgpu::TextureFormat::Undefined;
case filament::backend::TextureFormat::RGBA4:
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
// and handle the packing/unpacking in shaders.
return wgpu::TextureFormat::Undefined;
case filament::backend::TextureFormat::RGB8:
// No direct sRGB equivalent in wgpu without alpha.
return wgpu::TextureFormat::RGBA8Unorm;
case filament::backend::TextureFormat::SRGB8:
// No direct sRGB equivalent in wgpu without alpha.
return wgpu::TextureFormat::RGBA8UnormSrgb;
case filament::backend::TextureFormat::RGB8_SNORM:
// No direct mapping in wgpu without alpha.
return wgpu::TextureFormat::RGBA8Snorm;
case filament::backend::TextureFormat::RGB8UI:
// No direct mapping in wgpu without alpha.
return wgpu::TextureFormat::RGBA8Uint;
case filament::backend::TextureFormat::RGB8I:
// No direct mapping in wgpu without alpha.
return wgpu::TextureFormat::RGBA8Sint;
case filament::backend::TextureFormat::R11F_G11F_B10F:
return wgpu::TextureFormat::RG11B10Ufloat;
case filament::backend::TextureFormat::UNUSED:
return wgpu::TextureFormat::Undefined;
case filament::backend::TextureFormat::RGB10_A2:
return wgpu::TextureFormat::RGB10A2Unorm;
case filament::backend::TextureFormat::RGB16F:
// No direct mapping in wgpu without alpha.
return wgpu::TextureFormat::RGBA16Float;
case filament::backend::TextureFormat::RGB16UI:
// No direct mapping in wgpu without alpha.
return wgpu::TextureFormat::RGBA16Uint;
case filament::backend::TextureFormat::RGB16I:
// No direct mapping in wgpu without alpha.
return wgpu::TextureFormat::RGBA16Sint;
case filament::backend::TextureFormat::RGB32F:
// No direct mapping in wgpu without alpha.
return wgpu::TextureFormat::RGBA32Float;
case filament::backend::TextureFormat::RGB32UI:
// No direct mapping in wgpu without alpha.
return wgpu::TextureFormat::RGBA32Uint;
case filament::backend::TextureFormat::RGB32I:
// No direct mapping in wgpu without alpha.
return wgpu::TextureFormat::RGBA32Sint;
case filament::backend::TextureFormat::DXT1_RGB:
return wgpu::TextureFormat::BC1RGBAUnorm;
case filament::backend::TextureFormat::DXT1_RGBA:
return wgpu::TextureFormat::BC1RGBAUnorm;
case filament::backend::TextureFormat::DXT3_RGBA:
return wgpu::TextureFormat::BC2RGBAUnorm;
case filament::backend::TextureFormat::DXT5_RGBA:
return wgpu::TextureFormat::BC3RGBAUnorm;
case filament::backend::TextureFormat::DXT1_SRGB:
return wgpu::TextureFormat::BC1RGBAUnormSrgb;
case filament::backend::TextureFormat::DXT1_SRGBA:
return wgpu::TextureFormat::BC1RGBAUnormSrgb;
case filament::backend::TextureFormat::DXT3_SRGBA:
return wgpu::TextureFormat::BC2RGBAUnormSrgb;
case filament::backend::TextureFormat::DXT5_SRGBA:
return wgpu::TextureFormat::BC3RGBAUnormSrgb;
}
}
wgpu::TextureView WGPUTexture::makeTextureView(const uint8_t& baseLevel,
const uint8_t& levelCount) {
wgpu::TextureViewDescriptor desc;
desc.baseMipLevel = baseLevel;
desc.mipLevelCount = levelCount;
// baseArrayLayer is required, making a guess
desc.baseArrayLayer = 0;
// Have not found an analouge to aspect in other drivers, but ALL should be unrestrictive.
// TODO Can we make this better?
desc.aspect = wgpu::TextureAspect::All;
// The rest of the properties should be fine to leave as default, using the texture params.
desc.label = "TODO";
desc.format = wgpu::TextureFormat::Undefined;
desc.dimension = wgpu::TextureViewDimension::Undefined;
desc.usage = wgpu::TextureUsage::None;
return texture.CreateView(&desc);
}
}// namespace filament::backend }// namespace filament::backend
+68 -24
View File
@@ -24,48 +24,62 @@
#include <backend/Handle.h> #include <backend/Handle.h>
#include <utils/FixedCapacityVector.h> #include <utils/FixedCapacityVector.h>
// #include <utils/StructureOfArrays.h>
#include <webgpu/webgpu_cpp.h> #include <webgpu/webgpu_cpp.h>
#include <cstdint> #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;
// VertexBufferInfo contains layout info for Vertex Buffer based on WebGPU structs. In WebGPU each // VertexBufferInfo contains layout info for Vertex Buffer based on WebGPU structs. In WebGPU each
// VertexBufferLayout is associated with a single vertex buffer. So number of mVertexBufferLayout // VertexBufferLayout is associated with a single vertex buffer. So number of mVertexBufferLayout
// is equal to bufferCount. Each VertexBufferLayout can contain multiple VertexAttribute. Bind index // is equal to bufferCount. Each VertexBufferLayout can contain multiple VertexAttribute. Bind index
// of vertex buffer is implicitly calculated by the position of VertexBufferLayout in an array. // of vertex buffer is implicitly calculated by the position of VertexBufferLayout in an array.
struct WGPUVertexBufferInfo : public HwVertexBufferInfo { class WGPUVertexBufferInfo : public HwVertexBufferInfo {
public:
WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount, WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
AttributeArray const& attributes); AttributeArray const& attributes);
inline wgpu::VertexBufferLayout const* getVertexBufferLayout() const { inline wgpu::VertexBufferLayout const* getVertexBufferLayout() const {
return mVertexBufferLayout.data(); return mVertexBufferLayout.data();
} }
inline uint32_t getVertexBufferLayoutSize() const { inline uint32_t getVertexBufferLayoutSize() const {
return mVertexBufferLayout.size(); return mVertexBufferLayout.size();
} }
inline wgpu::VertexAttribute const* getVertexAttributeForIndex(uint32_t index) const { inline wgpu::VertexAttribute const* getVertexAttributeForIndex(uint32_t index) const {
assert_invariant(index < mAttributes.size());
return mAttributes[index].data(); return mAttributes[index].data();
} }
inline uint32_t getVertexAttributeSize(uint32_t index) const { inline uint32_t getVertexAttributeSize(uint32_t index) const {
assert_invariant(index < mAttributes.size());
return mAttributes[index].size(); return mAttributes[index].size();
} }
private: private:
std::vector<wgpu::VertexBufferLayout> mVertexBufferLayout; // TODO: can we do better in terms on heap management.
std::vector<std::vector<wgpu::VertexAttribute>> mAttributes; std::vector<wgpu::VertexBufferLayout> mVertexBufferLayout {};
std::vector<std::vector<wgpu::VertexAttribute>> mAttributes {};
}; };
struct WGPUVertexBuffer : public HwVertexBuffer { struct WGPUVertexBuffer : public HwVertexBuffer {
WGPUVertexBuffer(wgpu::Device const &device, uint32_t vextexCount, uint32_t bufferCount, WGPUVertexBuffer(wgpu::Device const &device, uint32_t vertexCount, uint32_t bufferCount,
Handle<HwVertexBufferInfo> vbih); Handle<HwVertexBufferInfo> vbih);
void setBuffer(WGPUBufferObject *bufferObject, uint32_t index);
Handle<HwVertexBufferInfo> vbih; Handle<HwVertexBufferInfo> vbih;
utils::FixedCapacityVector<wgpu::Buffer> buffers; utils::FixedCapacityVector<wgpu::Buffer> buffers;
}; };
@@ -78,42 +92,72 @@ struct WGPUIndexBuffer : public HwIndexBuffer {
wgpu::IndexFormat indexFormat; wgpu::IndexFormat indexFormat;
}; };
// TODO: Currently WGPUBufferObject 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(wgpu::Device const &device, BufferObjectBinding bindingType, uint32_t byteCount);
wgpu::Buffer buffer; wgpu::Buffer buffer = nullptr;
const BufferObjectBinding bufferObjectBinding; const BufferObjectBinding bufferObjectBinding;
}; };
class WebGPUDescriptorSetLayout final : public HwDescriptorSetLayout {
class WebGPUDescriptorSetLayout : public HwDescriptorSetLayout {
public: public:
WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout, wgpu::Device const* device); WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout, wgpu::Device const& device);
~WebGPUDescriptorSetLayout(); ~WebGPUDescriptorSetLayout();
[[nodiscard]] const wgpu::BindGroupLayout& getLayout() const { return mLayout; }
[[nodiscard]] uint getLayoutSize() const { return mLayoutSize; }
private: private:
// TODO: If this is useful elsewhere, remove it from this class // TODO: If this is useful elsewhere, remove it from this class
// Convert Filament Shader Stage Flags bitmask to webgpu equivilant // Convert Filament Shader Stage Flags bitmask to webgpu equivilant
static wgpu::ShaderStage filamentStageToWGPUStage(ShaderStageFlags fFlags); static wgpu::ShaderStage filamentStageToWGPUStage(ShaderStageFlags fFlags);
uint mLayoutSize;
wgpu::BindGroupLayout mLayout; wgpu::BindGroupLayout mLayout;
}; };
// TODO: Currently WGPUTexture is not used by WebGPU for useful task. class WebGPUDescriptorSet final : public HwDescriptorSet {
// Update the struct when used by WebGPU driver. public:
struct WGPUTexture : public HwTexture { WebGPUDescriptorSet(const wgpu::BindGroupLayout& layout, uint layoutSize);
~WebGPUDescriptorSet();
wgpu::BindGroup lockAndReturn(wgpu::Device const& device);
void addEntry(uint index, wgpu::BindGroupEntry&& entry);
[[nodiscard]] bool getIsLocked() const { return mBindGroup != nullptr; }
private:
// TODO: Consider storing what we used to make the layout. However we need to essentially
// Recreate some of the info (Sampler in slot X with the actual sampler) so letting Dawn confirm
// there isn't a mismatch may be easiest.
// Also storing the wgpu ObjectBase takes care of ownership challenges in theory
wgpu::BindGroupLayout mLayout;
std::vector<wgpu::BindGroupEntry> entries;
wgpu::BindGroup mBindGroup;
};
class WGPUTexture : public HwTexture {
public:
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,
wgpu::Device device) noexcept;
// constructors for creating texture views WGPUTexture(WGPUTexture* src, uint8_t baseLevel, uint8_t levelCount) noexcept;
WGPUTexture(WGPUTexture const* src, uint8_t baseLevel, uint8_t levelCount) noexcept;
const wgpu::Texture& getTexture() const { return texture; }
const wgpu::TextureView& getTexView() const { return texView; }
// Public to allow checking for support of a texture format
static wgpu::TextureFormat fToWGPUTextureFormat(const filament::backend::TextureFormat& fUsage);
private:
wgpu::TextureView makeTextureView(const uint8_t& baseLevel, const uint8_t& levelCount);
// CreateTextureR has info for a texture and sampler. Texture Views are needed for binding,
// along with a sampler Current plan: Inherit the sampler and Texture to always exist (It is a
// ref counted pointer) when making views. View is optional
wgpu::Texture texture = nullptr; wgpu::Texture texture = nullptr;
wgpu::TextureView texView = nullptr;
wgpu::TextureUsage fToWGPUTextureUsage(const filament::backend::TextureUsage& fUsage);
}; };
struct WGPURenderPrimitive : public HwRenderPrimitive { struct WGPURenderPrimitive : public HwRenderPrimitive {
WGPURenderPrimitive(); WGPURenderPrimitive() {}
void setBuffers(WGPUVertexBufferInfo const* const vbi, void setBuffers(WGPUVertexBufferInfo const* const vbi,
WGPUVertexBuffer* vertexBuffer, WGPUIndexBuffer* indexBuffer); WGPUVertexBuffer* vertexBuffer, WGPUIndexBuffer* indexBuffer);
@@ -133,7 +177,7 @@ struct WGPURenderTarget : public HwRenderTarget {
Attachment(WGPUTexture* gpuTexture, uint8_t level = 0, uint16_t layer = 0) Attachment(WGPUTexture* gpuTexture, uint8_t level = 0, uint16_t layer = 0)
: level(level), : level(level),
layer(layer), layer(layer),
texture(gpuTexture->texture), texture(gpuTexture->getTexture()),
mWGPUTexture(gpuTexture) {} mWGPUTexture(gpuTexture) {}
uint8_t level = 0; uint8_t level = 0;
@@ -0,0 +1,254 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "WebGPUPipelineCreation.h"
#include "WebGPUHandles.h"
#include <backend/DriverEnums.h>
#include <backend/TargetBufferInfo.h>
#include <utils/Panic.h>
#include <utils/debug.h>
#include <webgpu/webgpu_cpp.h>
#include <array>
#include <cstdint>
#include <sstream>
namespace filament::backend {
namespace {
constexpr wgpu::PrimitiveTopology toWebGPU(PrimitiveType primitiveType) {
switch (primitiveType) {
case PrimitiveType::POINTS:
return wgpu::PrimitiveTopology::PointList;
case PrimitiveType::LINES:
return wgpu::PrimitiveTopology::LineList;
case PrimitiveType::LINE_STRIP:
return wgpu::PrimitiveTopology::LineStrip;
case PrimitiveType::TRIANGLES:
return wgpu::PrimitiveTopology::TriangleList;
case PrimitiveType::TRIANGLE_STRIP:
return wgpu::PrimitiveTopology::TriangleStrip;
}
}
constexpr wgpu::CullMode toWebGPU(CullingMode cullMode) {
switch (cullMode) {
case CullingMode::NONE:
return wgpu::CullMode::None;
case CullingMode::FRONT:
return wgpu::CullMode::Front;
case CullingMode::BACK:
return wgpu::CullMode::Back;
case CullingMode::FRONT_AND_BACK:
// no WegGPU equivalent of front and back
FILAMENT_CHECK_POSTCONDITION(false)
<< "WebGPU does not support CullingMode::FRONT_AND_BACK";
return wgpu::CullMode::Undefined;
}
}
constexpr wgpu::CompareFunction toWebGPU(SamplerCompareFunc compareFunction) {
switch (compareFunction) {
case SamplerCompareFunc::LE:
return wgpu::CompareFunction::LessEqual;
case SamplerCompareFunc::GE:
return wgpu::CompareFunction::GreaterEqual;
case SamplerCompareFunc::L:
return wgpu::CompareFunction::Less;
case SamplerCompareFunc::G:
return wgpu::CompareFunction::Greater;
case SamplerCompareFunc::E:
return wgpu::CompareFunction::Equal;
case SamplerCompareFunc::NE:
return wgpu::CompareFunction::NotEqual;
case SamplerCompareFunc::A:
return wgpu::CompareFunction::Always;
case SamplerCompareFunc::N:
return wgpu::CompareFunction::Never;
}
}
constexpr wgpu::StencilOperation toWebGPU(StencilOperation stencilOp) {
switch (stencilOp) {
case StencilOperation::KEEP:
return wgpu::StencilOperation::Keep;
case StencilOperation::ZERO:
return wgpu::StencilOperation::Zero;
case StencilOperation::REPLACE:
return wgpu::StencilOperation::Replace;
case StencilOperation::INCR:
return wgpu::StencilOperation::IncrementClamp;
case StencilOperation::INCR_WRAP:
return wgpu::StencilOperation::IncrementWrap;
case StencilOperation::DECR:
return wgpu::StencilOperation::DecrementClamp;
case StencilOperation::DECR_WRAP:
return wgpu::StencilOperation::DecrementWrap;
case StencilOperation::INVERT:
return wgpu::StencilOperation::Invert;
}
}
constexpr wgpu::BlendOperation toWebGPU(BlendEquation blendOp) {
switch (blendOp) {
case BlendEquation::ADD:
return wgpu::BlendOperation::Add;
case BlendEquation::SUBTRACT:
return wgpu::BlendOperation::Subtract;
case BlendEquation::REVERSE_SUBTRACT:
return wgpu::BlendOperation::ReverseSubtract;
case BlendEquation::MIN:
return wgpu::BlendOperation::Min;
case BlendEquation::MAX:
return wgpu::BlendOperation::Max;
}
}
constexpr wgpu::BlendFactor toWebGPU(BlendFunction blendFunction) {
switch (blendFunction) {
case BlendFunction::ZERO:
return wgpu::BlendFactor::Zero;
case BlendFunction::ONE:
return wgpu::BlendFactor::One;
case BlendFunction::SRC_COLOR:
return wgpu::BlendFactor::Src;
case BlendFunction::ONE_MINUS_SRC_COLOR:
return wgpu::BlendFactor::OneMinusSrc;
case BlendFunction::DST_COLOR:
return wgpu::BlendFactor::Dst;
case BlendFunction::ONE_MINUS_DST_COLOR:
return wgpu::BlendFactor::OneMinusDst;
case BlendFunction::SRC_ALPHA:
return wgpu::BlendFactor::SrcAlpha;
case BlendFunction::ONE_MINUS_SRC_ALPHA:
return wgpu::BlendFactor::OneMinusSrcAlpha;
case BlendFunction::DST_ALPHA:
return wgpu::BlendFactor::DstAlpha;
case BlendFunction::ONE_MINUS_DST_ALPHA:
return wgpu::BlendFactor::OneMinusDstAlpha;
case BlendFunction::SRC_ALPHA_SATURATE:
return wgpu::BlendFactor::SrcAlphaSaturated;
}
}
}// namespace
wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device,
WGPUProgram const& program, WGPUVertexBufferInfo const& vertexBufferInfo,
wgpu::PipelineLayout const& layout, RasterState const& rasterState,
StencilState const& stencilState, PolygonOffset const& polygonOffset,
PrimitiveType primitiveType, wgpu::TextureFormat colorFormat,
wgpu::TextureFormat depthFormat) {
assert_invariant(program.vertexShaderModule);
const wgpu::DepthStencilState depthStencilState {
.format = depthFormat,
.depthWriteEnabled = rasterState.depthWrite,
.depthCompare = toWebGPU(rasterState.depthFunc),
.stencilFront = {
.compare = toWebGPU(stencilState.front.stencilFunc),
.failOp = toWebGPU(stencilState.front.stencilOpStencilFail),
.depthFailOp = toWebGPU(stencilState.front.stencilOpDepthFail),
.passOp = toWebGPU(stencilState.front.stencilOpDepthStencilPass),
},
.stencilBack = {
.compare = toWebGPU(stencilState.back.stencilFunc),
.failOp = toWebGPU(stencilState.back.stencilOpStencilFail),
.depthFailOp = toWebGPU(stencilState.back.stencilOpDepthFail),
.passOp = toWebGPU(stencilState.back.stencilOpDepthStencilPass),
},
.stencilReadMask = 0,
.stencilWriteMask = stencilState.stencilWrite ? 0xFFFFFFFF : 0,
.depthBias = static_cast<int32_t>(polygonOffset.constant),
.depthBiasSlopeScale = polygonOffset.slope,
.depthBiasClamp = 0.0f
};
std::stringstream pipelineLabelStream;
pipelineLabelStream << program.name.c_str() << " pipeline";
const auto pipelineLabel = pipelineLabelStream.str();
wgpu::RenderPipelineDescriptor pipelineDescriptor{
.label = wgpu::StringView(pipelineLabel),
.layout = layout,
.vertex = {
.module = program.vertexShaderModule,
.entryPoint = "main",
.constantCount = program.constants.size(),
.constants = program.constants.data(),
.bufferCount = vertexBufferInfo.getVertexBufferLayoutSize(),
.buffers = vertexBufferInfo.getVertexBufferLayout()
},
.primitive = {
.topology = toWebGPU(primitiveType),
// TODO should we assume some constant format here or is there a way to get
// this from PipelineState somehow or elsewhere?
// Perhaps, cache/assert format from index buffers as they are requested?
.stripIndexFormat = wgpu::IndexFormat::Undefined,
.frontFace = rasterState.inverseFrontFaces ? wgpu::FrontFace::CW : wgpu::FrontFace::CCW,
.cullMode = toWebGPU(rasterState.culling),
// TODO no depth clamp in WebGPU supported directly. unclippedDepth is close, so we are
// starting there
.unclippedDepth = !rasterState.depthClamp &&
device.HasFeature(wgpu::FeatureName::DepthClipControl)
},
.depthStencil = &depthStencilState,
.multisample = {
.count = 1, // TODO need to get this from the render target
.mask = 0xFFFFFFFF,
.alphaToCoverageEnabled = rasterState.alphaToCoverage
},
.fragment = nullptr // will add below if fragment module is included
};
wgpu::FragmentState fragmentState = {};
std::array<wgpu::ColorTargetState, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> colorTargets {};
const wgpu::BlendState blendState {
.color = {
.operation = toWebGPU(rasterState.blendEquationRGB),
.srcFactor = toWebGPU(rasterState.blendFunctionSrcRGB),
.dstFactor = toWebGPU(rasterState.blendFunctionDstRGB)
},
.alpha = {
.operation = toWebGPU(rasterState.blendEquationAlpha),
.srcFactor = toWebGPU(rasterState.blendFunctionSrcAlpha),
.dstFactor = toWebGPU(rasterState.blendFunctionDstAlpha)
}
};
if (program.fragmentShaderModule != nullptr) {
fragmentState.module = program.fragmentShaderModule;
fragmentState.entryPoint = "main";
fragmentState.constantCount = program.constants.size(),
fragmentState.constants = program.constants.data(),
fragmentState.targetCount = 1; // TODO need to get this from the render target
assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) {
auto& colorTarget = colorTargets[targetIndex];
colorTarget.format = colorFormat;
colorTarget.blend = rasterState.hasBlending() ? &blendState : nullptr;
colorTarget.writeMask =
rasterState.colorWrite ? wgpu::ColorWriteMask::All : wgpu::ColorWriteMask::None;
}
pipelineDescriptor.fragment = &fragmentState;
}
const wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor);
FILAMENT_CHECK_POSTCONDITION(pipeline)
<< "Failed to create render pipeline for " << pipelineDescriptor.label;
return pipeline;
}
}// namespace filament::backend
@@ -0,0 +1,46 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_WEBGPUPIPELINECREATION_H
#define TNT_FILAMENT_BACKEND_WEBGPUPIPELINECREATION_H
#include <cstdint>
namespace wgpu {
class Device;
class PipelineLayout;
class RenderPipeline;
enum class TextureFormat : uint32_t;
}// namespace wgpu
namespace filament::backend {
struct PolygonOffset;
enum class PrimitiveType : uint8_t;
struct RasterState;
struct StencilState;
class WGPUVertexBufferInfo;
class WGPUProgram;
[[nodiscard]] wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const&,
WGPUProgram const&, WGPUVertexBufferInfo const&, wgpu::PipelineLayout const&,
RasterState const&, StencilState const&, PolygonOffset const&, PrimitiveType,
wgpu::TextureFormat colorFormat, wgpu::TextureFormat depthFormat);
}// namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_WEBGPUPIPELINECREATION_H
+34 -10
View File
@@ -73,7 +73,8 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
#endif #endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) #if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config) { void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config,
wgpu::TextureFormat depthFormat) {
std::stringstream formatStream{}; std::stringstream formatStream{};
formatStream << config.format; formatStream << config.format;
std::stringstream usageStream{}; std::stringstream usageStream{};
@@ -82,6 +83,8 @@ void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config) {
alphaModeStream << config.alphaMode; alphaModeStream << config.alphaMode;
std::stringstream presentModeStream{}; std::stringstream presentModeStream{};
presentModeStream << config.presentMode; presentModeStream << config.presentMode;
std::stringstream depthFormatStream;
depthFormatStream << depthFormat;
FWGPU_LOGI << "WebGPU surface configuration:" << utils::io::endl; FWGPU_LOGI << "WebGPU surface configuration:" << utils::io::endl;
FWGPU_LOGI << " surface format: " << formatStream.str() << utils::io::endl; FWGPU_LOGI << " surface format: " << formatStream.str() << utils::io::endl;
FWGPU_LOGI << " surface usage: " << usageStream.str() << utils::io::endl; FWGPU_LOGI << " surface usage: " << usageStream.str() << utils::io::endl;
@@ -98,10 +101,11 @@ void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config) {
FWGPU_LOGI << " surface width: " << config.width << utils::io::endl; FWGPU_LOGI << " surface width: " << config.width << utils::io::endl;
FWGPU_LOGI << " surface height: " << config.height << utils::io::endl; FWGPU_LOGI << " surface height: " << config.height << utils::io::endl;
FWGPU_LOGI << " surface present mode: " << presentModeStream.str() << utils::io::endl; FWGPU_LOGI << " surface present mode: " << presentModeStream.str() << utils::io::endl;
FWGPU_LOGI << "WebGPU selected depth format: " << depthFormatStream.str() << utils::io::endl;
} }
#endif #endif
wgpu::TextureFormat selectColorFormat(size_t availableFormatsCount, constexpr wgpu::TextureFormat selectColorFormat(size_t availableFormatsCount,
wgpu::TextureFormat const* availableFormats, bool useSRGBColorSpace) { wgpu::TextureFormat const* availableFormats, bool useSRGBColorSpace) {
const std::array expectedColorFormats = const std::array expectedColorFormats =
useSRGBColorSpace ? useSRGBColorSpace ?
@@ -119,7 +123,21 @@ wgpu::TextureFormat selectColorFormat(size_t availableFormatsCount,
return *firstFoundColorFormat; return *firstFoundColorFormat;
} }
wgpu::PresentMode selectPresentMode(size_t availablePresentModesCount, constexpr wgpu::TextureFormat selectDepthFormat(bool depth32FloatStencil8Enabled,
bool needStencil) {
if (needStencil) {
if (depth32FloatStencil8Enabled) {
return wgpu::TextureFormat::Depth32FloatStencil8;
} else {
return wgpu::TextureFormat::Depth24PlusStencil8;
}
} else {
// other options: Depth16Unorm or Depth24Plus
return wgpu::TextureFormat::Depth32Float;
}
}
constexpr wgpu::PresentMode selectPresentMode(size_t availablePresentModesCount,
wgpu::PresentMode const* availablePresentModes) { wgpu::PresentMode const* availablePresentModes) {
// Verify that our chosen present mode is supported. In practice all devices support the FIFO // Verify that our chosen present mode is supported. In practice all devices support the FIFO
// mode, but we check for it anyway for completeness. (and to avoid validation warnings) // mode, but we check for it anyway for completeness. (and to avoid validation warnings)
@@ -133,7 +151,7 @@ wgpu::PresentMode selectPresentMode(size_t availablePresentModesCount,
return desiredPresentMode; return desiredPresentMode;
} }
wgpu::CompositeAlphaMode selectAlphaMode(size_t availableAlphaModesCount, constexpr wgpu::CompositeAlphaMode selectAlphaMode(size_t availableAlphaModesCount,
wgpu::CompositeAlphaMode const* availableAlphaModes) { wgpu::CompositeAlphaMode const* availableAlphaModes) {
bool autoAvailable = false; bool autoAvailable = false;
bool inheritAvailable = false; bool inheritAvailable = false;
@@ -209,7 +227,7 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
namespace filament::backend { namespace filament::backend {
WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize, WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize,
wgpu::Adapter& adapter, wgpu::Device& device, uint64_t flags) wgpu::Adapter const& adapter, wgpu::Device const& device, uint64_t flags)
: mSurface(surface) { : mSurface(surface) {
wgpu::SurfaceCapabilities capabilities = {}; wgpu::SurfaceCapabilities capabilities = {};
if (!mSurface.GetCapabilities(adapter, &capabilities)) { if (!mSurface.GetCapabilities(adapter, &capabilities)) {
@@ -220,7 +238,13 @@ WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const&
#endif #endif
} }
const bool useSRGBColorSpace = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0; const bool useSRGBColorSpace = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0;
const bool needStencil = (flags & SWAP_CHAIN_HAS_STENCIL_BUFFER) != 0;
initConfig(mConfig, device, capabilities, surfaceSize, useSRGBColorSpace); initConfig(mConfig, device, capabilities, surfaceSize, useSRGBColorSpace);
mDepthFormat = selectDepthFormat(device.HasFeature(wgpu::FeatureName::Depth32FloatStencil8),
needStencil);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printSurfaceConfiguration(mConfig, mDepthFormat);
#endif
mSurface.Configure(&mConfig); mSurface.Configure(&mConfig);
} }
@@ -235,12 +259,12 @@ void WebGPUSwapChain::setExtent(wgpu::Extent2D const& currentSurfaceSize) {
if (mConfig.width != currentSurfaceSize.width || mConfig.height != currentSurfaceSize.height) { if (mConfig.width != currentSurfaceSize.width || mConfig.height != currentSurfaceSize.height) {
mConfig.width = currentSurfaceSize.width; mConfig.width = currentSurfaceSize.width;
mConfig.height = currentSurfaceSize.height; mConfig.height = currentSurfaceSize.height;
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printSurfaceConfiguration(mConfig);
#endif
FWGPU_LOGD << "Resizing to width " << mConfig.width << " height " << mConfig.height FWGPU_LOGD << "Resizing to width " << mConfig.width << " height " << mConfig.height
<< utils::io::endl; << utils::io::endl;
// TODO we may need to ensure no surface texture is flight when we do this. some #if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printSurfaceConfiguration(mConfig, mDepthFormat);
#endif
// TODO we may need to ensure no surface texture is in flight when we do this. some
// synchronization may be necessary // synchronization may be necessary
mSurface.Configure(&mConfig); mSurface.Configure(&mConfig);
} }
@@ -257,7 +281,7 @@ wgpu::TextureView WebGPUSwapChain::getCurrentSurfaceTextureView(
// Create a view for this surface texture // Create a view for this surface texture
// TODO: review these initiliazations as webgpu pipeline gets mature // TODO: review these initiliazations as webgpu pipeline gets mature
wgpu::TextureViewDescriptor textureViewDescriptor = { wgpu::TextureViewDescriptor textureViewDescriptor = {
.label = "texture_view", .label = "surface_texture_view",
.format = surfaceTexture.texture.GetFormat(), .format = surfaceTexture.texture.GetFormat(),
.dimension = wgpu::TextureViewDimension::e2D, .dimension = wgpu::TextureViewDimension::e2D,
.baseMipLevel = 0, .baseMipLevel = 0,
@@ -29,10 +29,14 @@ namespace filament::backend {
class WebGPUSwapChain final : public Platform::SwapChain, HwSwapChain { class WebGPUSwapChain final : public Platform::SwapChain, HwSwapChain {
public: public:
WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize, WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize,
wgpu::Adapter& adapter, wgpu::Device& device, uint64_t flags); wgpu::Adapter const& adapter, wgpu::Device const& device, uint64_t flags);
~WebGPUSwapChain(); ~WebGPUSwapChain();
wgpu::TextureView getCurrentSurfaceTextureView(wgpu::Extent2D const&); [[nodiscard]] wgpu::TextureFormat getColorFormat() const { return mConfig.format; }
[[nodiscard]] wgpu::TextureFormat getDepthFormat() const { return mDepthFormat; }
[[nodiscard]] wgpu::TextureView getCurrentSurfaceTextureView(wgpu::Extent2D const&);
void present(); void present();
@@ -41,6 +45,7 @@ private:
wgpu::Surface mSurface = {}; wgpu::Surface mSurface = {};
wgpu::SurfaceConfiguration mConfig = {}; wgpu::SurfaceConfiguration mConfig = {};
wgpu::TextureFormat mDepthFormat = wgpu::TextureFormat::Undefined;
}; };
} // namespace filament::backend } // namespace filament::backend
@@ -98,10 +98,22 @@ wgpu::Adapter WebGPUPlatform::requestAdapter(wgpu::Surface const& surface) {
} }
wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) { wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
// TODO consider passing required features and/or limits // TODO consider passing limits
constexpr std::array desiredFeatures = { wgpu::FeatureName::DepthClipControl,
wgpu::FeatureName::Depth32FloatStencil8, wgpu::FeatureName::CoreFeaturesAndLimits,
wgpu::FeatureName::TransientAttachments };
std::vector<wgpu::FeatureName> requiredFeatures;
requiredFeatures.reserve(desiredFeatures.size());
wgpu::SupportedFeatures supportedFeatures;
adapter.GetFeatures(&supportedFeatures);
std::set_intersection(supportedFeatures.features,
supportedFeatures.features + supportedFeatures.featureCount, desiredFeatures.begin(),
desiredFeatures.end(), std::back_inserter(requiredFeatures));
wgpu::DeviceDescriptor deviceDescriptor{}; wgpu::DeviceDescriptor deviceDescriptor{};
deviceDescriptor.label = "graphics_device"; deviceDescriptor.label = "graphics_device";
deviceDescriptor.defaultQueue.label = "default_queue"; deviceDescriptor.defaultQueue.label = "default_queue";
deviceDescriptor.requiredFeatureCount = requiredFeatures.size();
deviceDescriptor.requiredFeatures = requiredFeatures.data();
deviceDescriptor.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous, deviceDescriptor.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous,
[](wgpu::Device const&, wgpu::DeviceLostReason const& reason, [](wgpu::Device const&, wgpu::DeviceLostReason const& reason,
wgpu::StringView message) { wgpu::StringView message) {
+4 -46
View File
@@ -55,14 +55,17 @@ void BackendTest::init(Backend backend, OperatingSystem operatingSystem, bool is
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;
} }
@@ -156,51 +159,6 @@ void BackendTest::renderTriangle(
api.endRenderPass(); api.endRenderPass();
} }
void BackendTest::readPixelsAndAssertHash(const char* testName, size_t width, size_t height,
Handle<HwRenderTarget> rt, uint32_t expectedHash, bool exportScreenshot) {
void* buffer = calloc(1, width * height * 4);
struct Capture {
uint32_t expectedHash;
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,
1, 0, 0, width, [](void* buffer, size_t size, void* user) {
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));
}
bool BackendTest::matchesEnvironment(Backend backend) { bool BackendTest::matchesEnvironment(Backend backend) {
return sBackend == backend; return sBackend == backend;
} }
+7 -5
View File
@@ -25,6 +25,7 @@
#include "private/backend/DriverApi.h" #include "private/backend/DriverApi.h"
#include "PlatformRunner.h" #include "PlatformRunner.h"
#include "ImageExpectations.h"
namespace test { namespace test {
@@ -65,17 +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(Backend backend);
static bool matchesEnvironment(OperatingSystem operatingSystem); static bool matchesEnvironment(OperatingSystem operatingSystem);
static bool matchesEnvironment(OperatingSystem operatingSystem, Backend backend); static bool matchesEnvironment(OperatingSystem operatingSystem, Backend backend);
private: private:
filament::backend::Driver* driver = nullptr; filament::backend::Driver* driver = nullptr;
@@ -83,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
+99 -32
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,18 @@
#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, bool isSrgb)
: mWidth(width), mHeight(height), mFileName(std::move(fileName)), : mWidth(width),
mExpectedPixelHash(expectedPixelHash) {} mHeight(height),
mIsSrgb(isSrgb),
mExpectedPixelHash(expectedHash),
mFileName(std::move(fileName)) {}
int ScreenshotParams::width() const { int ScreenshotParams::width() const {
return mWidth; return mWidth;
@@ -45,28 +48,36 @@ int ScreenshotParams::height() const {
return mHeight; return mHeight;
} }
bool ScreenshotParams::isSrgb() const {
return mIsSrgb;
}
uint32_t ScreenshotParams::expectedHash() const { 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 +102,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 +131,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 +145,40 @@ 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);
image::LinearImage image(internal->params.width(), internal->params.width(), 4);
image = image::toLinearWithAlpha<uint8_t>(internal->params.width(),
internal->params.height(),
internal->params.width() * 4, (uint8_t*)buffer);
std::string filePath = internal->params.generatedActualFilePath();
std::ofstream pngStream(filePath, std::ios::binary | std::ios::trunc);
image::ImageEncoder::encode(pngStream, image::ImageEncoder::Format::PNG, image, "",
filePath);
internal->bytesFilled = true; internal->bytesFilled = true;
#ifndef FILAMENT_IOS
image::LinearImage image;
if (internal->params.isSrgb()) {
image = image::toLinearWithAlpha<uint8_t>(internal->params.width(),
internal->params.height(),
internal->params.width() * 4, (uint8_t*)buffer);
} else {
// The image data is already linear, so pass in transforms that simply go from uint8_t
// to float. toLinearWithAlpha divides the float values by uint8_t max so there's no
// need to scale it to [0, 1]
image = image::toLinearWithAlpha<uint8_t>(
internal->params.width(), internal->params.height(),
internal->params.width() * 4, (uint8_t*) buffer,
[](uint8_t value) -> float { return value; },
[](filament::math::float4 rgba) -> filament::math::float4 { return rgba; });
}
std::string filePath = internal->params.actualFilePath();
std::ofstream pngStream(filePath, std::ios::binary | std::ios::trunc);
// To avoid going from linear -> sRGB -> linear save the PNG as linear.
image::ImageEncoder::encode(pngStream, image::ImageEncoder::Format::PNG_LINEAR, image, "",
filePath);
#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() {
@@ -161,12 +192,48 @@ RenderTargetDump::~RenderTargetDump() {
} }
} }
uint32_t RenderTargetDump::Internal::hash() const {
return utils::hash::murmur3((uint32_t*)bytes.data(), bytes.size() / 4, 0);
}
uint32_t RenderTargetDump::hash() const { uint32_t RenderTargetDump::hash() const {
return utils::hash::murmur3((uint32_t*)mInternal->bytes.data(), mInternal->bytes.size() / 4, 0); return mInternal->hash();
} }
bool RenderTargetDump::bytesFilled() const { 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 << ".\n"
<< "Did you forget to sync CMake after updating the expected image in the source "
"directory?";
if (mBytes.empty()) {
return 0;
}
return utils::hash::murmur3((uint32_t*)mBytes.data(), mBytes.size() / 4, 0);
}
const std::vector<unsigned char>& LoadedPng::bytes() const {
return mBytes;
}
+27 -7
View File
@@ -40,21 +40,25 @@ do { \
*/ */
class ScreenshotParams { class ScreenshotParams {
public: public:
ScreenshotParams(int width, int height, std::string fileName, uint32_t expectedPixelHash); ScreenshotParams(int width, int height, std::string fileName, uint32_t expectedPixelHash,
bool isSrgb = false);
int width() const; int width() const;
int height() const; int height() const;
bool isSrgb() 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;
int mHeight; int mHeight;
bool mIsSrgb;
uint32_t mExpectedPixelHash; uint32_t mExpectedPixelHash;
std::string mFileName; std::string mFileName;
}; };
@@ -91,6 +95,8 @@ private:
ScreenshotParams params; ScreenshotParams params;
std::atomic<bool> bytesFilled = false; std::atomic<bool> bytesFilled = false;
std::vector<unsigned char> bytes; std::vector<unsigned char> bytes;
uint32_t hash() const;
}; };
// We need a memory location that won't be invalidated to pass to GPU callbacks as they can't // We need a memory location that won't be invalidated to pass to GPU callbacks as they can't
@@ -98,6 +104,19 @@ private:
std::unique_ptr<Internal> mInternal; std::unique_ptr<Internal> mInternal;
}; };
class LoadedPng {
public:
explicit LoadedPng(std::string filePath);
uint32_t hash() const;
const std::vector<unsigned char>& bytes() 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 +149,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
+4 -2
View File
@@ -55,8 +55,10 @@ Shader::Shader(DriverApi& api, Cleanup& cleanup, ShaderConfig config) : mCleanup
prog.descriptorBindings(1, bindingsInfo); prog.descriptorBindings(1, bindingsInfo);
mProgram = cleanup.add(api.createProgram(std::move(prog))); mProgram = cleanup.add(api.createProgram(std::move(prog)));
mDescriptorSetLayout = cleanup.add( if (!kLayouts.empty()) {
api.createDescriptorSetLayout(DescriptorSetLayout{ kLayouts })); mDescriptorSetLayout =
cleanup.add(api.createDescriptorSetLayout(DescriptorSetLayout{ kLayouts }));
}
} }
filament::backend::DescriptorSetHandle Shader::createDescriptorSet(DriverApi& api) const { filament::backend::DescriptorSetHandle Shader::createDescriptorSet(DriverApi& api) const {
+1
View File
@@ -96,6 +96,7 @@ public:
protected: protected:
Cleanup& mCleanup; Cleanup& mCleanup;
filament::backend::ProgramHandle mProgram; filament::backend::ProgramHandle mProgram;
// This will be a null handle if there are no uniforms.
filament::backend::DescriptorSetLayoutHandle mDescriptorSetLayout; filament::backend::DescriptorSetLayoutHandle mDescriptorSetLayout;
}; };

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