Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5e64564a1 | ||
|
|
6b2804985b | ||
|
|
320ee183c6 | ||
|
|
8145a5dd25 | ||
|
|
ba0793ac18 | ||
|
|
2c154be0b3 |
+168
-180
@@ -9,6 +9,36 @@ on:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build-desktop-mac:
|
||||
name: build-mac
|
||||
runs-on: macos-14-xlarge
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/mac-prereq
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/mac && 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
|
||||
run: |
|
||||
out/cmake-release/filament/test/test_material_parser
|
||||
|
||||
build-windows:
|
||||
name: build-windows
|
||||
runs-on: windows-2022-32core
|
||||
@@ -18,194 +48,152 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: Run build script
|
||||
run: |
|
||||
build\windows\build-github.bat continuous
|
||||
build\windows\build-github.bat presubmit
|
||||
shell: cmd
|
||||
|
||||
# build-desktop-mac:
|
||||
# name: build-mac
|
||||
# runs-on: macos-14-xlarge
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - uses: ./.github/actions/mac-prereq
|
||||
# - name: Run build script
|
||||
# run: |
|
||||
# cd build/mac && printf "y" | ./build.sh presubmit
|
||||
# - name: Test material parser
|
||||
# run: |
|
||||
# out/cmake-release/filament/test/test_material_parser
|
||||
build-android:
|
||||
name: build-android
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- name: Run build script
|
||||
# Only build 1 64 bit target during presubmit to cut down build times during presubmit
|
||||
# Continuous builds will build everything
|
||||
run: |
|
||||
cd build/android && printf "y" | ./build.sh presubmit arm64-v8a
|
||||
|
||||
# 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
|
||||
# run: |
|
||||
# out/cmake-release/filament/test/test_material_parser
|
||||
build-ios:
|
||||
name: build-iOS
|
||||
runs-on: macos-14-xlarge
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/mac-prereq
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/ios && printf "y" | ./build.sh presubmit
|
||||
- name: Build iOS samples
|
||||
run: |
|
||||
cd build/ios && ./build-samples.sh presubmit
|
||||
|
||||
# build-windows:
|
||||
# name: build-windows
|
||||
# runs-on: windows-2022-32core
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - name: Run build script
|
||||
# run: |
|
||||
# build\windows\build-github.bat presubmit
|
||||
# shell: cmd
|
||||
build-web:
|
||||
name: build-web
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- uses: ./.github/actions/web-prereq
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/web && printf "y" | ./build.sh presubmit
|
||||
|
||||
# build-android:
|
||||
# name: build-android
|
||||
# runs-on: 'ubuntu-24.04-16core'
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - uses: ./.github/actions/linux-prereq
|
||||
# - uses: actions/setup-java@v3
|
||||
# with:
|
||||
# distribution: 'temurin'
|
||||
# java-version: '17'
|
||||
# - name: Run build script
|
||||
# # Only build 1 64 bit target during presubmit to cut down build times during presubmit
|
||||
# # Continuous builds will build everything
|
||||
# run: |
|
||||
# cd build/android && printf "y" | ./build.sh presubmit arm64-v8a
|
||||
validate-docs:
|
||||
name: validate-docs
|
||||
runs-on: 'ubuntu-24.04-4core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- id: get_commit_msg
|
||||
uses: ./.github/actions/get-commit-msg
|
||||
- name: Check for manual edits to /docs
|
||||
run: |
|
||||
COMMIT_ID=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | head -n 1 | sed "s/commit //g")
|
||||
bash docs_src/build/presubmit_check.sh ${COMMIT_ID}
|
||||
|
||||
# build-ios:
|
||||
# name: build-iOS
|
||||
# runs-on: macos-14-xlarge
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - uses: ./.github/actions/mac-prereq
|
||||
# - name: Run build script
|
||||
# run: |
|
||||
# cd build/ios && printf "y" | ./build.sh presubmit
|
||||
# - name: Build iOS samples
|
||||
# run: |
|
||||
# cd build/ios && ./build-samples.sh presubmit
|
||||
test-renderdiff:
|
||||
name: test-renderdiff
|
||||
runs-on: macos-14-xlarge
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- id: get_commit_msg
|
||||
uses: ./.github/actions/get-commit-msg
|
||||
- uses: ./.github/actions/mac-prereq
|
||||
- uses: ./.github/actions/get-mesa
|
||||
- name: Prerequisites
|
||||
run: |
|
||||
pip install tifffile numpy
|
||||
shell: bash
|
||||
- name: Render and compare
|
||||
id: render_compare
|
||||
run: |
|
||||
TEST_DIR=test/renderdiff
|
||||
source ${TEST_DIR}/src/preamble.sh
|
||||
start_
|
||||
GOLDEN_BRANCH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | python3 ${TEST_DIR}/src/commit_msg.py)
|
||||
bash ${TEST_DIR}/generate.sh && \
|
||||
python3 ${TEST_DIR}/src/golden_manager.py \
|
||||
--branch=${GOLDEN_BRANCH} \
|
||||
--output=${GOLDEN_OUTPUT_DIR}
|
||||
|
||||
# build-web:
|
||||
# name: build-web
|
||||
# runs-on: 'ubuntu-24.04-16core'
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - uses: ./.github/actions/linux-prereq
|
||||
# - uses: ./.github/actions/web-prereq
|
||||
# - name: Run build script
|
||||
# run: |
|
||||
# cd build/web && printf "y" | ./build.sh presubmit
|
||||
# Note that we need to upload the output even if comparison fails, so we undo `set -ex`
|
||||
end_
|
||||
|
||||
# validate-docs:
|
||||
# name: validate-docs
|
||||
# runs-on: 'ubuntu-24.04-4core'
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - id: get_commit_msg
|
||||
# uses: ./.github/actions/get-commit-msg
|
||||
# - name: Check for manual edits to /docs
|
||||
# run: |
|
||||
# COMMIT_ID=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | head -n 1 | sed "s/commit //g")
|
||||
# bash docs_src/build/presubmit_check.sh ${COMMIT_ID}
|
||||
python3 ${TEST_DIR}/src/compare.py \
|
||||
--src=${GOLDEN_OUTPUT_DIR} \
|
||||
--dest=${RENDER_OUTPUT_DIR} \
|
||||
--out=${DIFF_OUTPUT_DIR} 2>&1 | tee compare_output.txt
|
||||
|
||||
# test-renderdiff:
|
||||
# name: test-renderdiff
|
||||
# runs-on: macos-14-xlarge
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - id: get_commit_msg
|
||||
# uses: ./.github/actions/get-commit-msg
|
||||
# - uses: ./.github/actions/mac-prereq
|
||||
# - uses: ./.github/actions/get-mesa
|
||||
# - name: Prerequisites
|
||||
# run: |
|
||||
# pip install tifffile numpy
|
||||
# shell: bash
|
||||
# - name: Render and compare
|
||||
# id: render_compare
|
||||
# run: |
|
||||
# TEST_DIR=test/renderdiff
|
||||
# source ${TEST_DIR}/src/preamble.sh
|
||||
# start_
|
||||
# GOLDEN_BRANCH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | python3 ${TEST_DIR}/src/commit_msg.py)
|
||||
# bash ${TEST_DIR}/generate.sh && \
|
||||
# python3 ${TEST_DIR}/src/golden_manager.py \
|
||||
# --branch=${GOLDEN_BRANCH} \
|
||||
# --output=${GOLDEN_OUTPUT_DIR}
|
||||
if grep "Failed" compare_output.txt > /dev/null; then
|
||||
DELIMITER="EOF_FILE_CONTENT_$(date +%s)" # Using timestamp to make it more unique
|
||||
echo "err<<$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||
cat compare_output.txt >> "$GITHUB_OUTPUT"
|
||||
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: presubmit-renderdiff-result
|
||||
path: ./out/renderdiff
|
||||
- name: Compare result
|
||||
run: |
|
||||
ERROR_STR="${{ steps.render_compare.outputs.err }}"
|
||||
if [ -n "${ERROR_STR}" ]; then
|
||||
echo "${ERROR_STR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# # Note that we need to upload the output even if comparison fails, so we undo `set -ex`
|
||||
# end_
|
||||
validate-wgsl-webgpu:
|
||||
name: validate-wgsl-webgpu
|
||||
runs-on: 'ubuntu-24.04-8core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- name: Run build script
|
||||
run: ./build.sh -W debug test_filamat filament
|
||||
- name: Run test
|
||||
run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
|
||||
|
||||
# python3 ${TEST_DIR}/src/compare.py \
|
||||
# --src=${GOLDEN_OUTPUT_DIR} \
|
||||
# --dest=${RENDER_OUTPUT_DIR} \
|
||||
# --out=${DIFF_OUTPUT_DIR} 2>&1 | tee compare_output.txt
|
||||
|
||||
# if grep "Failed" compare_output.txt > /dev/null; then
|
||||
# DELIMITER="EOF_FILE_CONTENT_$(date +%s)" # Using timestamp to make it more unique
|
||||
# echo "err<<$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||
# cat compare_output.txt >> "$GITHUB_OUTPUT"
|
||||
# echo "$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||
# fi
|
||||
# - uses: actions/upload-artifact@v4
|
||||
# with:
|
||||
# name: presubmit-renderdiff-result
|
||||
# path: ./out/renderdiff
|
||||
# - name: Compare result
|
||||
# run: |
|
||||
# ERROR_STR="${{ steps.render_compare.outputs.err }}"
|
||||
# if [ -n "${ERROR_STR}" ]; then
|
||||
# echo "${ERROR_STR}"
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
# validate-wgsl-webgpu:
|
||||
# name: validate-wgsl-webgpu
|
||||
# runs-on: 'ubuntu-24.04-8core'
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - uses: ./.github/actions/linux-prereq
|
||||
# - name: Run build script
|
||||
# run: ./build.sh -W debug test_filamat filament
|
||||
# - name: Run test
|
||||
# run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
|
||||
|
||||
# test-code-correctness:
|
||||
# name: test-code-correctness
|
||||
# runs-on: 'macos-14-xlarge'
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - uses: ./.github/actions/mac-prereq
|
||||
# - name: Install clang-tidy and deps
|
||||
# run: |
|
||||
# pip install pyyaml
|
||||
# brew install llvm@${GITHUB_LLVM_VERSION}
|
||||
# sudo ln -s "$(brew --prefix llvm)@${GITHUB_LLVM_VERSION}/bin/clang-tidy" "/usr/local/bin/clang-tidy"
|
||||
# - name: Run build script
|
||||
# # We need to build before clang-tidy can run analysis
|
||||
# run: |
|
||||
# # This will build for all three desktop backends on mac
|
||||
# ./build.sh -p desktop debug gltf_viewer
|
||||
# - name: Run test
|
||||
# run: bash test/code-correctness/test.sh
|
||||
test-code-correctness:
|
||||
name: test-code-correctness
|
||||
runs-on: 'macos-14-xlarge'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/mac-prereq
|
||||
- name: Install clang-tidy and deps
|
||||
run: |
|
||||
pip install pyyaml
|
||||
brew install llvm@${GITHUB_LLVM_VERSION}
|
||||
sudo ln -s "$(brew --prefix llvm)@${GITHUB_LLVM_VERSION}/bin/clang-tidy" "/usr/local/bin/clang-tidy"
|
||||
- name: Run build script
|
||||
# We need to build before clang-tidy can run analysis
|
||||
run: |
|
||||
# This will build for all three desktop backends on mac
|
||||
./build.sh -p desktop debug gltf_viewer
|
||||
- name: Run test
|
||||
run: bash test/code-correctness/test.sh
|
||||
|
||||
@@ -135,9 +135,7 @@ set build_flags=-j %NUMBER_OF_PROCESSORS%
|
||||
:: cmake --build . --target sample-resources --config %config% %build_flags% || exit /b
|
||||
:: cmake --build . --target suzanne-resources --config %config% %build_flags% || exit /b
|
||||
|
||||
cmake --build . --target sample-resources --config %config% %build_flags% -- /m || exit /b
|
||||
|
||||
:: cmake --build . %INSTALL% --config %config% %build_flags% -- /m || exit /b
|
||||
cmake --build . %INSTALL% --config %config% %build_flags% -- /m || exit /b
|
||||
@echo off
|
||||
|
||||
echo Disk info after building variant: %variant%
|
||||
|
||||
@@ -58,10 +58,10 @@ namespace {
|
||||
areCopyCompatible(sourceFormat, destinationFormat) &&
|
||||
(sourceExtent.width == destinationExtent.width) &&
|
||||
(sourceExtent.height == destinationExtent.height) &&
|
||||
((sourceOrigin.x & sourceBlockSize.width) == 0) &&
|
||||
((sourceOrigin.y & sourceBlockSize.height) == 0) &&
|
||||
((destinationOrigin.x & destinationBlockSize.width) == 0) &&
|
||||
((destinationOrigin.y & destinationBlockSize.height) == 0);
|
||||
((sourceOrigin.x % sourceBlockSize.width) == 0) &&
|
||||
((sourceOrigin.y % sourceBlockSize.height) == 0) &&
|
||||
((destinationOrigin.x % destinationBlockSize.width) == 0) &&
|
||||
((destinationOrigin.y % destinationBlockSize.height) == 0);
|
||||
}
|
||||
|
||||
struct BlitFragmentShaderArgs final {
|
||||
@@ -206,9 +206,20 @@ constexpr std::string_view FRAGMENT_SHADER_SNIPPET_2D_INPUT_TEMPLATE{ R"(
|
||||
|
||||
} // namespace
|
||||
|
||||
// It can perform a direct memory copy if the formats are compatible and no scaling or format
|
||||
// conversion is needed. Otherwise, it uses a render pass with a custom shader to perform
|
||||
// the blit. This allows for scaling, format conversion, and resolving multisampled textures.
|
||||
// The blitter caches pipelines, layouts, and shaders to avoid recompilation.
|
||||
WebGPUBlitter::WebGPUBlitter(wgpu::Device const& device)
|
||||
: mDevice{ device } {}
|
||||
|
||||
// Performs a blit operation from a source to a destination texture.
|
||||
// This function first checks if a direct texture-to-texture copy can be performed.
|
||||
// A direct copy is possible if the source and destination textures have the same sample count,
|
||||
// compatible formats, and the copy extents match. If a direct copy is not possible, this
|
||||
// function falls back to a render pass-based blit. The render pass uses a shader to read from
|
||||
// the source texture and write to the destination texture, allowing for format conversion,
|
||||
// scaling, and MSAA resolves.
|
||||
void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& commandEncoder,
|
||||
BlitArgs const& args) {
|
||||
// current assumptions/simplifications made in this implementation:
|
||||
@@ -267,22 +278,20 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
|
||||
args.destination.texture.GetUsage() & wgpu::TextureUsage::CopyDst)
|
||||
<< "destination texture usage doesn't have wgpu::TextureUsage::CopyDst";
|
||||
|
||||
copy(commandEncoder, args);
|
||||
copyByteByByte(commandEncoder, args);
|
||||
return;
|
||||
}
|
||||
// need to do a render pass blit...
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(args.destination.texture.GetSampleCount() == 1)
|
||||
<< "Blitting does not currently support writing to multisampled output textures.";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(args.source.texture.GetUsage() & wgpu::TextureUsage::TextureBinding)
|
||||
<< "source texture usage doesn't have wgpu::TextureUsage::TextureBinding";
|
||||
<< "Source texture usage doesn't have wgpu::TextureUsage::TextureBinding";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
args.destination.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
|
||||
<< "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
|
||||
<< "Destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
|
||||
|
||||
// create input/output texture views...
|
||||
const wgpu::TextureViewDimension sourceDimension{ args.source.texture.GetDimension() ==
|
||||
wgpu::TextureDimension::e3D
|
||||
? wgpu::TextureViewDimension::e3D
|
||||
@@ -327,7 +336,6 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
|
||||
<< destinationViewDescriptor.baseArrayLayer << " and mip level "
|
||||
<< destinationViewDescriptor.baseMipLevel << " for render pass blit?";
|
||||
|
||||
// create uniform buffer (for shader args)...
|
||||
const BlitFragmentShaderArgs blitFragmentShaderArgs{
|
||||
.depthPlane = args.source.layerOrDepth,
|
||||
.scale = { static_cast<float>(args.source.extent.width) / args.destination.extent.width,
|
||||
@@ -438,7 +446,7 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
|
||||
const wgpu::RenderPassEncoder renderPassEncoder{ commandEncoder.BeginRenderPass(
|
||||
&renderPassDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(renderPassEncoder)
|
||||
<< "Failed to create render pass encoder for blit?";
|
||||
<< "Failed to create render pass encoder for blit.";
|
||||
renderPassEncoder.SetPipeline(getOrCreateRenderPipeline(args.filter, sourceDimension,
|
||||
args.source.texture.GetSampleCount(), depthSource,
|
||||
args.destination.texture.GetFormat()));
|
||||
@@ -448,7 +456,7 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
|
||||
renderPassEncoder.End();
|
||||
}
|
||||
|
||||
void WebGPUBlitter::copy(wgpu::CommandEncoder const& commandEncoder, BlitArgs const& args) {
|
||||
void WebGPUBlitter::copyByteByByte(wgpu::CommandEncoder const& commandEncoder, BlitArgs const& args) {
|
||||
const wgpu::TexelCopyTextureInfo sourceCopyInfo{
|
||||
.texture = args.source.texture,
|
||||
.mipLevel = args.source.mipLevel,
|
||||
@@ -505,7 +513,7 @@ void WebGPUBlitter::createSampler(const SamplerMagFilter filter) {
|
||||
.maxAnisotropy = 1, // should not matter, just being consistently defined
|
||||
};
|
||||
const wgpu::Sampler sampler{ mDevice.CreateSampler(&descriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create blit sampler?";
|
||||
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create blit sampler.";
|
||||
switch (filter) {
|
||||
case SamplerMagFilter::NEAREST:
|
||||
mNearestSampler = sampler;
|
||||
@@ -516,6 +524,8 @@ void WebGPUBlitter::createSampler(const SamplerMagFilter filter) {
|
||||
}
|
||||
}
|
||||
|
||||
// Caches and returns a render pipeline for a given blit configuration.
|
||||
// If a pipeline for the given configuration does not exist, it creates and caches one.
|
||||
wgpu::RenderPipeline const& WebGPUBlitter::getOrCreateRenderPipeline(
|
||||
const SamplerMagFilter filterType, const wgpu::TextureViewDimension sourceDimension,
|
||||
const uint32_t sourceSampleCount, const bool depthSource,
|
||||
@@ -529,6 +539,9 @@ wgpu::RenderPipeline const& WebGPUBlitter::getOrCreateRenderPipeline(
|
||||
return mRenderPipelines[key];
|
||||
}
|
||||
|
||||
// Creates a render pipeline for a blit operation.
|
||||
// The pipeline is configured with the appropriate vertex and fragment shaders,
|
||||
// pipeline layout, and render target state based on the blit parameters.
|
||||
wgpu::RenderPipeline WebGPUBlitter::createRenderPipeline(const SamplerMagFilter filterType,
|
||||
const wgpu::TextureViewDimension sourceDimension, const uint32_t sourceSampleCount,
|
||||
const bool depthSource, const wgpu::TextureFormat destinationTextureFormat) {
|
||||
@@ -580,7 +593,7 @@ wgpu::RenderPipeline WebGPUBlitter::createRenderPipeline(const SamplerMagFilter
|
||||
.fragment = &fragmentState,
|
||||
};
|
||||
const wgpu::RenderPipeline pipeline{ mDevice.CreateRenderPipeline(&pipelineDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline) << "Failed to create pipeline for render pass blit?";
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline) << "Failed to create pipeline for render pass blit.";
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
@@ -595,6 +608,8 @@ size_t WebGPUBlitter::hashRenderPipelineKey(const SamplerMagFilter filterType,
|
||||
return seed;
|
||||
}
|
||||
|
||||
// Caches and returns a pipeline layout for a given blit configuration.
|
||||
// If a layout for the given configuration does not exist, it creates and caches one.
|
||||
wgpu::PipelineLayout const& WebGPUBlitter::getOrCreatePipelineLayout(
|
||||
const SamplerMagFilter filterType, const wgpu::TextureViewDimension sourceDimension,
|
||||
const bool multisampledSource, const bool depthSource) {
|
||||
@@ -607,6 +622,8 @@ wgpu::PipelineLayout const& WebGPUBlitter::getOrCreatePipelineLayout(
|
||||
return mPipelineLayouts[key];
|
||||
}
|
||||
|
||||
// Creates a pipeline layout for a blit operation.
|
||||
// The layout defines the bind group layouts used by the blit pipeline.
|
||||
wgpu::PipelineLayout WebGPUBlitter::createPipelineLayout(const SamplerMagFilter filterType,
|
||||
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
|
||||
const bool depthSource) {
|
||||
@@ -619,7 +636,7 @@ wgpu::PipelineLayout WebGPUBlitter::createPipelineLayout(const SamplerMagFilter
|
||||
const wgpu::PipelineLayout pipelineLayout{ mDevice.CreatePipelineLayout(
|
||||
&pipelineLayoutDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipelineLayout)
|
||||
<< "Failed to create pipeline layout for render pass blit?";
|
||||
<< "Failed to create pipeline layout for render pass blit.";
|
||||
return pipelineLayout;
|
||||
}
|
||||
|
||||
@@ -633,6 +650,8 @@ size_t WebGPUBlitter::hashPipelineLayoutKey(const SamplerMagFilter filterType,
|
||||
return seed;
|
||||
}
|
||||
|
||||
// Caches and returns a bind group layout for a given blit configuration.
|
||||
// If a layout for the given configuration does not exist, it creates and caches one.
|
||||
wgpu::BindGroupLayout const& WebGPUBlitter::getOrCreateTextureBindGroupLayout(
|
||||
const SamplerMagFilter filterType, const wgpu::TextureViewDimension sourceDimension,
|
||||
const bool multisampledSource, const bool depthSource) {
|
||||
@@ -645,6 +664,9 @@ wgpu::BindGroupLayout const& WebGPUBlitter::getOrCreateTextureBindGroupLayout(
|
||||
return mTextureBindGroupLayouts[key];
|
||||
}
|
||||
|
||||
// Creates a bind group layout for the blit operation's texture resources.
|
||||
// This layout specifies the bindings for the source texture, a uniform buffer with blit parameters,
|
||||
// and optionally a sampler.
|
||||
wgpu::BindGroupLayout WebGPUBlitter::createTextureBindGroupLayout(const SamplerMagFilter filterType,
|
||||
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
|
||||
const bool depthSource) {
|
||||
@@ -693,7 +715,7 @@ wgpu::BindGroupLayout WebGPUBlitter::createTextureBindGroupLayout(const SamplerM
|
||||
const wgpu::BindGroupLayout textureBindGroupLayout{ mDevice.CreateBindGroupLayout(
|
||||
&textureBindGroupLayoutDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(textureBindGroupLayout)
|
||||
<< "Failed to create texture bind group layout for render pass blit?";
|
||||
<< "Failed to create texture bind group layout for render pass blit.";
|
||||
return textureBindGroupLayout;
|
||||
}
|
||||
|
||||
@@ -707,6 +729,8 @@ size_t WebGPUBlitter::hashTextureBindGroupLayoutKey(const SamplerMagFilter filte
|
||||
return seed;
|
||||
}
|
||||
|
||||
// Caches and returns a shader module for a given blit configuration.
|
||||
// If a shader module for the given configuration does not exist, it creates and caches one.
|
||||
wgpu::ShaderModule const& WebGPUBlitter::getOrCreateShaderModule(
|
||||
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
|
||||
const bool depthSource, const bool depthDestination) {
|
||||
@@ -719,6 +743,9 @@ wgpu::ShaderModule const& WebGPUBlitter::getOrCreateShaderModule(
|
||||
return mShaderModules[key];
|
||||
}
|
||||
|
||||
// Creates a shader module containing the vertex and fragment shaders for the blit operation.
|
||||
// The shader source is generated from a template, with placeholders filled in based on the
|
||||
// blit configuration (e.g., texture type, sample count).
|
||||
wgpu::ShaderModule WebGPUBlitter::createShaderModule(
|
||||
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
|
||||
const bool depthSource, const bool depthDestination) {
|
||||
@@ -730,7 +757,6 @@ wgpu::ShaderModule WebGPUBlitter::createShaderModule(
|
||||
textureType = "texture_depth_2d";
|
||||
}
|
||||
} else {
|
||||
// non-depth
|
||||
if (multisampledSource) {
|
||||
textureType = "texture_multisampled_2d";
|
||||
} else {
|
||||
@@ -785,7 +811,7 @@ wgpu::ShaderModule WebGPUBlitter::createShaderModule(
|
||||
};
|
||||
const wgpu::ShaderModule shaderModule{ mDevice.CreateShaderModule(&shaderModuleDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(shaderModule)
|
||||
<< "Failed to create shader module for render pass blit?";
|
||||
<< "Failed to create shader module for render pass blit.";
|
||||
return shaderModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,17 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A blit essentially writes pixels to a region of one texture given a region of another texture.
|
||||
* Such a process can be a byte-by-byte copy when possible, while other scenarios involve various
|
||||
* transformations, such as pixel format conversion, image scaling, filtering/sampling
|
||||
* (such as down sampling), or even multi-sample resolves. Some blits can be faster than others,
|
||||
* depending on what is needed; for example, a byte-by-byte copy is generally faster than other
|
||||
* operations.
|
||||
*
|
||||
* This class is responsible for performing blits throughout the lifecycle of its owner/caller,
|
||||
* e.g. the WebGPU backend/driver, where it can leverage state to optimize such blit calls over time.
|
||||
*/
|
||||
class WebGPUBlitter final {
|
||||
public:
|
||||
struct BlitArgs final {
|
||||
@@ -58,10 +69,15 @@ private:
|
||||
/**
|
||||
* ONLY should be called if canDoDirectCopy(...) is true (see it in WebGPUBlitter.cpp)
|
||||
*/
|
||||
void copy(wgpu::CommandEncoder const&, BlitArgs const&);
|
||||
void copyByteByByte(wgpu::CommandEncoder const&, BlitArgs const&);
|
||||
|
||||
void createSampler(SamplerMagFilter);
|
||||
|
||||
// The following methods are used to create and cache WebGPU objects.
|
||||
// The pattern is to have a `getOrCreate...` method that looks up the object in a cache,
|
||||
// and if it's not found, it calls a `create...` method to create it and then stores it in the
|
||||
// cache. A `hash...` method is used to generate a key for the cache.
|
||||
|
||||
[[nodiscard]] wgpu::RenderPipeline const& getOrCreateRenderPipeline(SamplerMagFilter,
|
||||
wgpu::TextureViewDimension sourceDimension, uint32_t sourceSampleCount,
|
||||
bool depthSource, wgpu::TextureFormat destinationTextureFormat);
|
||||
|
||||
@@ -33,6 +33,9 @@ namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
// Creates a wgpu::Buffer, ensuring its size is a multiple of FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS.
|
||||
// WebGPU's WriteBuffer requires the write size to be a multiple of 4. By ensuring the buffer
|
||||
// size is also a multiple of 4, we simplify the update logic.
|
||||
[[nodiscard]] wgpu::Buffer createBuffer(wgpu::Device const& device, const wgpu::BufferUsage usage,
|
||||
uint32_t size, const char* const label) {
|
||||
// Write size must be divisible by WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4).
|
||||
@@ -55,6 +58,11 @@ WebGPUBufferBase::WebGPUBufferBase(wgpu::Device const& device, const wgpu::Buffe
|
||||
const uint32_t size, char const* const label)
|
||||
: mBuffer{ createBuffer(device, usage, size, label) } {}
|
||||
|
||||
// Updates the GPU buffer with data from a BufferDescriptor.
|
||||
// WebGPU requires that the size of the data written to a buffer is a multiple of 4.
|
||||
// This function handles cases where the buffer descriptor's size is not a multiple of 4
|
||||
// by writing the bulk of the data first, and then copying the remaining bytes into a
|
||||
// padded temporary chunk which is then written to the buffer.
|
||||
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
||||
const uint32_t byteOffset, wgpu::Queue const& queue) {
|
||||
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
|
||||
|
||||
@@ -28,6 +28,10 @@ namespace filament::backend {
|
||||
|
||||
class BufferDescriptor;
|
||||
|
||||
/**
|
||||
* A base class for WebGPU buffer objects, providing common functionality for creating and
|
||||
* updating GPU buffers.
|
||||
*/
|
||||
class WebGPUBufferBase /* intended to be extended */ {
|
||||
public:
|
||||
/**
|
||||
@@ -46,7 +50,8 @@ protected:
|
||||
|
||||
private:
|
||||
const wgpu::Buffer mBuffer;
|
||||
// FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4) bytes to hold any extra chunk we need.
|
||||
// WebGPU requires that the source buffer of a writeBuffer call has a size that is a multiple
|
||||
// of 4. This member is used to pad the data if the source size is not a multiple of 4.
|
||||
std::array<uint8_t, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS> mRemainderChunk{};
|
||||
};
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
// The usage flags are determined by the binding type, and always include CopyDst to allow for
|
||||
// updating the buffer.
|
||||
WebGPUBufferObject::WebGPUBufferObject(wgpu::Device const& device,
|
||||
const BufferObjectBinding bindingType, const uint32_t byteCount)
|
||||
: HwBufferObject{ byteCount },
|
||||
|
||||
@@ -31,6 +31,12 @@ namespace filament::backend {
|
||||
|
||||
enum class BufferObjectBinding : uint8_t;
|
||||
|
||||
/**
|
||||
* A WebGPU-specific implementation of BufferObject.
|
||||
* A BufferObject is a GPU buffer that can be used for a variety of purposes, such as storing
|
||||
* uniform data, vertex attributes, or pixel data. The specific usage is determined by the
|
||||
* BufferObjectBinding.
|
||||
*/
|
||||
class WebGPUBufferObject final : public HwBufferObject, public WebGPUBufferBase {
|
||||
public:
|
||||
WebGPUBufferObject(wgpu::Device const&, BufferObjectBinding, uint32_t byteCount);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// WebGPU requires that the source buffer of a writeBuffer call has a size that is a multiple of 4.
|
||||
constexpr size_t FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS = 4;
|
||||
|
||||
// FWGPU is short for Filament WebGPU
|
||||
|
||||
@@ -42,6 +42,9 @@ constexpr uint8_t INVALID_INDEX = MAX_DESCRIPTOR_COUNT + 1;
|
||||
|
||||
} // namespace
|
||||
|
||||
// The constructor initializes the descriptor set based on a given layout.
|
||||
// It pre-allocates space for the bind group entries and sets up a mapping from binding index
|
||||
// to the internal entry array index. This prepares the object to be populated with resources.
|
||||
WebGPUDescriptorSet::WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout,
|
||||
std::vector<WebGPUDescriptorSetLayout::BindGroupEntryInfo> const& bindGroupEntries)
|
||||
: mLayout{ layout },
|
||||
@@ -52,11 +55,10 @@ WebGPUDescriptorSet::WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout,
|
||||
for (size_t i = 0; i < bindGroupEntries.size(); ++i) {
|
||||
mEntries[i].binding = bindGroupEntries[i].binding;
|
||||
}
|
||||
// Establish the size of entries based on the layout. This should be reliable and efficient.
|
||||
|
||||
// Create a mapping from binding index to entry index for efficient lookup.
|
||||
assert_invariant(INVALID_INDEX > mEntryIndexByBinding.size());
|
||||
for (size_t i = 0; i < mEntryIndexByBinding.size(); i++) {
|
||||
mEntryIndexByBinding[i] = INVALID_INDEX;
|
||||
}
|
||||
std::fill(mEntryIndexByBinding.begin(), mEntryIndexByBinding.end(), INVALID_INDEX);
|
||||
for (size_t index = 0; index < mEntries.size(); index++) {
|
||||
wgpu::BindGroupEntry const& entry = mEntries[index];
|
||||
assert_invariant(entry.binding < mEntryIndexByBinding.size());
|
||||
@@ -66,19 +68,20 @@ WebGPUDescriptorSet::WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout,
|
||||
|
||||
void WebGPUDescriptorSet::addEntry(const unsigned int index, wgpu::BindGroupEntry&& entry) {
|
||||
if (mBindGroup) {
|
||||
// We will keep getting hits from future updates, but shouldn't do anything
|
||||
// Filament guarantees this won't change after things have locked.
|
||||
// The bind group has already been created, so we can't add new entries.
|
||||
// Filament guarantees that the descriptor set will not be modified after it has been locked.
|
||||
return;
|
||||
}
|
||||
// TODO: Putting some level of trust that Filament is not going to reuse indexes or go past the
|
||||
// layout index for efficiency. Add guards if wrong.
|
||||
FILAMENT_CHECK_POSTCONDITION(index < mEntryIndexByBinding.size())
|
||||
<< "impossible/invalid index for a descriptor/binding (our of range or >= "
|
||||
<< "impossible/invalid index for a descriptor/binding (out of range or >= "
|
||||
"MAX_DESCRIPTOR_COUNT) "
|
||||
<< index;
|
||||
uint8_t entryIndex = mEntryIndexByBinding[index];
|
||||
FILAMENT_CHECK_POSTCONDITION(entryIndex != INVALID_INDEX && entryIndex < mEntries.size())
|
||||
<< "Invalid binding " << index;
|
||||
<< "Invalid binding index: " << index;
|
||||
|
||||
entry.binding = index;
|
||||
mEntries[entryIndex] = std::move(entry);
|
||||
}
|
||||
@@ -94,8 +97,9 @@ wgpu::BindGroup WebGPUDescriptorSet::lockAndReturn(wgpu::Device const& device) {
|
||||
.entries = mEntries.data()
|
||||
};
|
||||
mBindGroup = device.CreateBindGroup(&descriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(mBindGroup) << "Failed to create bind group?";
|
||||
// once we have created the bind group itself we should no longer need any other state
|
||||
FILAMENT_CHECK_POSTCONDITION(mBindGroup) << "Failed to create bind group.";
|
||||
|
||||
// Once the bind group is created, we can release the resources used to create it.
|
||||
#if FWGPU_ENABLED(FWGPU_DEBUG_BIND_GROUPS)
|
||||
FWGPU_LOGD << "WebGPUDescriptorSet (lockAndReturn):";
|
||||
FWGPU_LOGD << " wgpu::BindGroupLayout handle: " << mLayout.Get();
|
||||
|
||||
@@ -32,23 +32,46 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A WebGPU-specific implementation of DescriptorSet.
|
||||
* This class manages a collection of resource bindings (e.g., textures, buffers) that are used by
|
||||
* a shader. It corresponds to a `wgpu::BindGroup` in WebGPU. The actual `wgpu::BindGroup` is
|
||||
* created lazily when `lockAndReturn` is called, at which point the descriptor set becomes
|
||||
* immutable.
|
||||
*/
|
||||
class WebGPUDescriptorSet final : public HwDescriptorSet {
|
||||
public:
|
||||
WebGPUDescriptorSet(wgpu::BindGroupLayout const&,
|
||||
std::vector<WebGPUDescriptorSetLayout::BindGroupEntryInfo> const&);
|
||||
~WebGPUDescriptorSet();
|
||||
|
||||
/**
|
||||
* Adds or updates a resource binding in the descriptor set.
|
||||
* This method is called to populate the descriptor set with buffers, textures, and samplers.
|
||||
* It is only valid to call this before the descriptor set is locked (i.e., before
|
||||
* `lockAndReturn` is called).
|
||||
*/
|
||||
void addEntry(unsigned int index, wgpu::BindGroupEntry&& entry);
|
||||
|
||||
/**
|
||||
* Finalizes the descriptor set by creating the wgpu::BindGroup. After this call, the
|
||||
* descriptor set is considered "locked" and cannot be modified further.
|
||||
*/
|
||||
[[nodiscard]] wgpu::BindGroup lockAndReturn(wgpu::Device const&);
|
||||
|
||||
/**
|
||||
* @return true if the descriptor set has been locked (i.e., the wgpu::BindGroup has been
|
||||
* created).
|
||||
*/
|
||||
[[nodiscard]] bool getIsLocked() const { return mBindGroup != nullptr; }
|
||||
|
||||
[[nodiscard]] size_t getEntitiesWithDynamicOffsetsCount() const {
|
||||
return mEntriesWithDynamicOffsetsCount;
|
||||
}
|
||||
|
||||
// May be nullptr. Use lockAndReturn to create the bind group when appropriate
|
||||
/**
|
||||
* May be nullptr. Use lockAndReturn to create the bind group when appropriate
|
||||
*/
|
||||
[[nodiscard]] wgpu::BindGroup const& getBindGroup() const { return mBindGroup; }
|
||||
|
||||
#if FWGPU_ENABLED(FWGPU_DEBUG_BIND_GROUPS)
|
||||
@@ -60,6 +83,7 @@ private:
|
||||
std::array<uint8_t, MAX_DESCRIPTOR_COUNT> mEntryIndexByBinding{};
|
||||
std::vector<wgpu::BindGroupEntry> mEntries;
|
||||
const size_t mEntriesWithDynamicOffsetsCount;
|
||||
// This is created lazily when lockAndReturn is called.
|
||||
wgpu::BindGroup mBindGroup = nullptr;
|
||||
};
|
||||
|
||||
|
||||
@@ -40,7 +40,9 @@ namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
// Convert Filament Shader Stage Flags bitmask to webgpu equivalent
|
||||
/**
|
||||
* Converts Filament shader stage flags to the corresponding WebGPU shader stage bitmask.
|
||||
*/
|
||||
[[nodiscard]] wgpu::ShaderStage filamentStageToWGPUStage(const ShaderStageFlags fFlags) {
|
||||
wgpu::ShaderStage retStages = wgpu::ShaderStage::None;
|
||||
if (any(ShaderStageFlags::VERTEX & fFlags)) {
|
||||
@@ -70,10 +72,6 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
|
||||
baseLabel = temp->c_str();
|
||||
}
|
||||
|
||||
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
|
||||
// debugging. For now, hack an incrementing value.
|
||||
static int layoutNum = 0;
|
||||
|
||||
const unsigned int samplerCount =
|
||||
std::count_if(layout.bindings.begin(), layout.bindings.end(), [](auto& fEntry) {
|
||||
return DescriptorSetLayoutBinding::isSampler(fEntry.type);
|
||||
@@ -87,6 +85,11 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
|
||||
auto& wEntry = wEntries.emplace_back();
|
||||
auto& entryInfo = mBindGroupEntries.emplace_back();
|
||||
wEntry.visibility = filamentStageToWGPUStage(fEntry.stageFlags);
|
||||
|
||||
// In WebGPU, textures and samplers are separate bindings.
|
||||
// We map the Filament binding index to two WebGPU binding indices:
|
||||
// - texture: binding * 2
|
||||
// - sampler: binding * 2 + 1
|
||||
wEntry.binding = fEntry.binding * 2;
|
||||
entryInfo.binding = wEntry.binding;
|
||||
|
||||
@@ -178,11 +181,13 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
|
||||
} else if (isCubeArrayTypeDescriptor(fEntry.type)) {
|
||||
wEntry.texture.viewDimension = wgpu::TextureViewDimension::CubeArray;
|
||||
}
|
||||
// fEntry.count is unused currently
|
||||
}
|
||||
std::string label = "layout_" + baseLabel + std::to_string(++layoutNum) ;
|
||||
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
|
||||
// debugging. For now, hack an incrementing value.
|
||||
static int layoutNum = 0;
|
||||
std::string label = "layout_" + baseLabel + "_" + std::to_string(++layoutNum) ;
|
||||
const wgpu::BindGroupLayoutDescriptor layoutDescriptor{
|
||||
.label{label.c_str()}, // Use .c_str() if label needs to be const char*
|
||||
.label{label.c_str()},
|
||||
.entryCount = wEntries.size(),
|
||||
.entries = wEntries.data()
|
||||
};
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A WebGPU implementation of the HwDescriptorSetLayout.
|
||||
* This class defines the layout of a descriptor set, which specifies the types and arrangement
|
||||
* of resources that can be bound to a pipeline.
|
||||
*/
|
||||
class WebGPUDescriptorSetLayout final : public HwDescriptorSetLayout {
|
||||
public:
|
||||
struct BindGroupEntryInfo final {
|
||||
|
||||
@@ -71,7 +71,7 @@ void setDefaultTargetFlags(WebGPURenderTarget& defaultRenderTarget,
|
||||
const wgpu::TextureFormat depthFormat{ swapChain.getDepthFormat() };
|
||||
TargetBufferFlags newTargetFlags{ filament::backend::TargetBufferFlags::NONE };
|
||||
|
||||
// Assuming Color always present in default render target.
|
||||
// Color is always assumed to be present in the default render target.
|
||||
newTargetFlags |= filament::backend::TargetBufferFlags::COLOR;
|
||||
if (depthFormat != wgpu::TextureFormat::Undefined) {
|
||||
if (hasDepth(depthFormat)) {
|
||||
@@ -86,6 +86,12 @@ void setDefaultTargetFlags(WebGPURenderTarget& defaultRenderTarget,
|
||||
|
||||
} // namespace
|
||||
|
||||
// The WebGPUDriver is the main entry point for the WebGPU backend. It is responsible for
|
||||
// creating and managing all WebGPU resources, and for submitting commands to the GPU.
|
||||
// It implements the Driver interface, which is the abstraction layer used by Filament's
|
||||
// renderer. The driver uses a handle-based system to manage resources, and it operates
|
||||
// asynchronously, with `create...S` methods for synchronous handle allocation and
|
||||
// `create...R` methods for resource creation on the backend thread.
|
||||
Driver* WebGPUDriver::create(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
constexpr size_t defaultSize = FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
|
||||
Platform::DriverConfig validConfig {driverConfig};
|
||||
@@ -160,6 +166,7 @@ void WebGPUDriver::endFrame(const uint32_t /* frameId */) {
|
||||
mPipelineLayoutCache.onFrameEnd();
|
||||
mPipelineCache.onFrameEnd();
|
||||
|
||||
// Clear the currently bound descriptor sets at the end of the frame.
|
||||
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
|
||||
mCurrentDescriptorSets[i] = {};
|
||||
}
|
||||
@@ -172,7 +179,7 @@ void WebGPUDriver::flush(int /* dummy */) {
|
||||
if (mCommandEncoder == nullptr) {
|
||||
return;
|
||||
}
|
||||
// submit the command buffer thus far...
|
||||
// Submit the commands recorded so far.
|
||||
assert_invariant(mRenderPassEncoder == nullptr);
|
||||
wgpu::CommandBufferDescriptor commandBufferDescriptor{
|
||||
.label = "frame_command_after_flush",
|
||||
@@ -189,12 +196,15 @@ void WebGPUDriver::flush(int /* dummy */) {
|
||||
assert_invariant(mCommandEncoder);
|
||||
}
|
||||
|
||||
// Submits the currently recorded commands and waits for them to complete on the GPU.
|
||||
// This is a synchronous operation and should be used sparingly.
|
||||
void WebGPUDriver::finish(int /* dummy */) {
|
||||
if (mCommandEncoder == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
flush();
|
||||
// Wait for all previously submitted work to finish.
|
||||
std::mutex syncPoint;
|
||||
std::condition_variable syncCondition;
|
||||
bool done = false;
|
||||
@@ -264,10 +274,8 @@ void WebGPUDriver::destroyRenderTarget(Handle<HwRenderTarget> renderTargetHandle
|
||||
if (renderTarget == mCurrentRenderTarget) {
|
||||
mCurrentRenderTarget = nullptr;
|
||||
}
|
||||
// WGPURenderTarget destructor is trivial.
|
||||
// The HwTexture handles stored within WGPURenderTarget (via MRT, TargetBufferInfo)
|
||||
// are not owned by WGPURenderTarget, so they are not destroyed here.
|
||||
// They are destroyed via WebGPUDriver::destroyTexture.
|
||||
// The HwTexture handles stored within WGPURenderTarget are not owned by it,
|
||||
// so they are not destroyed here. They are destroyed via WebGPUDriver::destroyTexture.
|
||||
destructHandle<WebGPURenderTarget>(renderTargetHandle);
|
||||
}
|
||||
}
|
||||
@@ -312,6 +320,14 @@ void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> descriptorSetHan
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Synchronous handle creation methods (S-methods)
|
||||
//
|
||||
// These methods are called from the frontend thread to allocate a handle for a backend resource.
|
||||
// They are lightweight and do not perform any GPU operations. The actual resource creation
|
||||
// happens in the corresponding R-method on the backend thread.
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
Handle<HwSwapChain> WebGPUDriver::createSwapChainS() noexcept {
|
||||
return allocHandle<WebGPUSwapChain>();
|
||||
}
|
||||
@@ -396,6 +412,13 @@ Handle<HwTexture> WebGPUDriver::createTextureExternalImagePlaneS() noexcept {
|
||||
return allocHandle<WebGPUTexture>();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Asynchronous resource creation methods (R-methods)
|
||||
//
|
||||
// These methods are called on the backend thread to create the actual GPU resources
|
||||
// associated with a handle that was previously allocated by an S-method.
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
|
||||
mNativeWindow = nativeWindow;
|
||||
assert_invariant(!mSwapChain);
|
||||
@@ -544,7 +567,7 @@ void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> renderTargetHandle
|
||||
}
|
||||
|
||||
void WebGPUDriver::createFenceR(Handle<HwFence> fenceHandle, const int /* dummy */) {
|
||||
// the handle was already constructed in createFenceS
|
||||
// The handle is constructed synchronously in createFenceS.
|
||||
const auto fence = handleCast<WebGPUFence>(fenceHandle);
|
||||
assert_invariant(mQueue);
|
||||
fence->addMarkerToQueueState(mQueue);
|
||||
@@ -614,8 +637,6 @@ FenceStatus WebGPUDriver::getFenceStatus(Handle<HwFence> fenceHandle) {
|
||||
return fence->getStatus();
|
||||
}
|
||||
|
||||
// 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.
|
||||
bool WebGPUDriver::isTextureFormatSupported(const TextureFormat format) {
|
||||
return toWGPUTextureFormat(format) != wgpu::TextureFormat::Undefined;
|
||||
}
|
||||
@@ -796,6 +817,10 @@ void WebGPUDriver::setVertexBufferObject(Handle<HwVertexBuffer> vertexBufferHand
|
||||
vertexBuffer->getBuffers()[index] = bufferObject->getBuffer();
|
||||
}
|
||||
|
||||
// Updates a 3D texture region with pixel data from a buffer.
|
||||
// This function handles various pixel formats and performs a blit operation if the source
|
||||
// format needs to be converted to the destination format. For compressed textures, it ensures
|
||||
// that the offsets are aligned to the block dimensions.
|
||||
void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t level,
|
||||
const uint32_t xoffset, const uint32_t yoffset, const uint32_t zoffset,
|
||||
const uint32_t width, const uint32_t height, const uint32_t depth,
|
||||
@@ -812,15 +837,15 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
|
||||
const auto texture{ handleCast<WebGPUTexture>(textureHandle) };
|
||||
FILAMENT_CHECK_PRECONDITION((texture->getTexture().GetWidth() + xoffset) >= width)
|
||||
<< "Blitting requires the destination region to have enough width ("
|
||||
<< texture->getTexture().GetWidth() << " to accommodate the requested " << width
|
||||
<< texture->getTexture().GetWidth() << ") to accommodate the requested " << width
|
||||
<< " width to write/blit (accounting for xoffset, which is " << xoffset << ").";
|
||||
FILAMENT_CHECK_PRECONDITION((texture->getTexture().GetHeight() + yoffset) >= height)
|
||||
<< "Blitting requires the destination region to have enough height ("
|
||||
<< texture->getTexture().GetHeight() << " to accommodate the requested " << height
|
||||
<< texture->getTexture().GetHeight() << ") to accommodate the requested " << height
|
||||
<< " height to write/blit (accounting for yoffset, which is " << yoffset << ").";
|
||||
FILAMENT_CHECK_PRECONDITION((texture->getTexture().GetDepthOrArrayLayers() + zoffset) >= depth)
|
||||
<< "Blitting requires the destination region to have enough depth/arrayLayers ("
|
||||
<< texture->getTexture().GetDepthOrArrayLayers() << " to accommodate the requested "
|
||||
<< texture->getTexture().GetDepthOrArrayLayers() << ") to accommodate the requested "
|
||||
<< depth << " depth to write/blit (accounting for zoffset, which is " << zoffset << ").";
|
||||
|
||||
// TODO: Writing to a depth texture is illegal and errors. I'm not sure why Filament is trying
|
||||
@@ -833,10 +858,8 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
|
||||
inputData->type) };
|
||||
const wgpu::TextureFormat outputLinearFormat{ toLinearFormat(
|
||||
texture->getTexture().GetFormat()) };
|
||||
const bool conversionNecessary{
|
||||
inputPixelFormat != outputLinearFormat && inputData->type != PixelDataType::COMPRESSED
|
||||
}; // compressed formats should never need conversion
|
||||
const bool doBlit{ conversionNecessary };
|
||||
|
||||
const bool doBlit{ conversionNecessary(inputPixelFormat, outputLinearFormat, inputData->type) };
|
||||
#if FWGPU_ENABLED(FWGPU_DEBUG_UPDATE_IMAGE)
|
||||
if (texture->width > 1000 && texture->height > 500) {
|
||||
FWGPU_LOGD << "Update3DImage(..., level=" << level << ", xoffset=" << xoffset
|
||||
@@ -958,7 +981,7 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
|
||||
}
|
||||
stagingTexture.Destroy();
|
||||
} else {
|
||||
// not doing blit (copy byte-by-byte)...
|
||||
// Direct copy without a blit.
|
||||
const auto copyInfo { wgpu::TexelCopyTextureInfo{
|
||||
.texture = texture->getTexture(),
|
||||
.mipLevel = level,
|
||||
@@ -1070,10 +1093,14 @@ void WebGPUDriver::compilePrograms(CompilerPriorityQueue priority,
|
||||
}
|
||||
}
|
||||
|
||||
// Begins a render pass. This sets up the render pass descriptor with the appropriate
|
||||
// color and depth/stencil attachments, based on whether the target is the default
|
||||
// swap chain render target or a custom one. It also handles MSAA by setting up
|
||||
// resolve targets if necessary.
|
||||
void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
|
||||
RenderPassParams const& params) {
|
||||
if (!mCommandEncoder) {
|
||||
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = { .label = "frame_command_encoder" };
|
||||
wgpu::CommandEncoderDescriptor commandEncoderDescriptor{ .label = "frame_command_encoder" };
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
||||
}
|
||||
assert_invariant(mCommandEncoder);
|
||||
@@ -1321,19 +1348,24 @@ void WebGPUDriver::stopCapture(int) {
|
||||
//todo
|
||||
}
|
||||
|
||||
// Reads a block of pixels from a render target into a buffer.
|
||||
// This function is asynchronous. It copies the pixel data to a staging buffer on the GPU,
|
||||
// and then maps the buffer for reading on the CPU. The provided callback is invoked when the
|
||||
// data is ready. This function also handles the 256-byte row alignment requirement for
|
||||
// buffer-to-texture copies in WebGPU.
|
||||
void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, const uint32_t x,
|
||||
const uint32_t y, const uint32_t width, const uint32_t height,
|
||||
PixelBufferDescriptor&& pixelBufferDescriptor) {
|
||||
auto srcTarget = handleCast<WebGPURenderTarget>(sourceRenderTargetHandle);
|
||||
const auto srcTarget{ handleCast<WebGPURenderTarget>(sourceRenderTargetHandle) };
|
||||
assert_invariant(srcTarget);
|
||||
|
||||
wgpu::Texture srcTexture = nullptr;
|
||||
wgpu::Texture srcTexture{ nullptr };
|
||||
if (srcTarget->isDefaultRenderTarget()) {
|
||||
assert_invariant(mSwapChain);
|
||||
srcTexture = mSwapChain->getCurrentTexture();
|
||||
} else {
|
||||
// Handle custom render targets. Read from the first color attachment.
|
||||
const auto& colorAttachmentInfos = srcTarget->getColorAttachmentInfos();
|
||||
const auto& colorAttachmentInfos{ srcTarget->getColorAttachmentInfos() };
|
||||
// TODO we are currently assuming the first attachment is the desired texture.
|
||||
if (colorAttachmentInfos[0].handle) {
|
||||
auto texture = handleCast<WebGPUTexture>(colorAttachmentInfos[0].handle);
|
||||
@@ -1348,97 +1380,145 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
|
||||
scheduleDestroy(std::move(pixelBufferDescriptor));
|
||||
return;
|
||||
}
|
||||
const uint32_t srcWidth = srcTexture.GetWidth();
|
||||
const uint32_t srcHeight = srcTexture.GetHeight();
|
||||
const uint32_t srcWidth {srcTexture.GetWidth()};
|
||||
const uint32_t srcHeight{srcTexture.GetHeight()};
|
||||
|
||||
// Clamp read region to texture bounds
|
||||
if (x >= srcWidth || y >= srcHeight) {
|
||||
if (UTILS_UNLIKELY(x >= srcWidth || y >= srcHeight)) {
|
||||
scheduleDestroy(std::move(pixelBufferDescriptor));
|
||||
return;
|
||||
}
|
||||
auto actualWidth = std::min(width, srcWidth - x);
|
||||
auto actualHeight = std::min(height, srcHeight - y);
|
||||
auto actualWidth{ std::min(width, srcWidth - x) };
|
||||
auto actualHeight{ std::min(height, srcHeight - y)};
|
||||
if (UTILS_UNLIKELY(actualWidth == 0 || actualHeight == 0)) {
|
||||
scheduleDestroy(std::move(pixelBufferDescriptor));
|
||||
return;
|
||||
}
|
||||
|
||||
// Once we're ready to read the pixels, we need to flush all the previous work of this frame.
|
||||
// This ensures that the readPixels will be ordered after all the draws.
|
||||
flush();
|
||||
if (!mCommandEncoder) {
|
||||
const wgpu::CommandEncoderDescriptor commandEncoderDescriptor{
|
||||
.label = "read_pixels_command",
|
||||
};
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(mCommandEncoder)
|
||||
<< "Failed to create command encoder for readPixels?";
|
||||
}
|
||||
|
||||
const size_t dstBytesPerPixel = PixelBufferDescriptor::computePixelSize(
|
||||
pixelBufferDescriptor.format, pixelBufferDescriptor.type);
|
||||
const size_t srcBytesPerPixel = getWGPUTextureFormatPixelSize(srcTexture.GetFormat());
|
||||
const wgpu::TextureFormat srcFormat{srcTexture.GetFormat()};
|
||||
const wgpu::TextureFormat dstFormat{toWebGPUFormat(pixelBufferDescriptor.format,
|
||||
pixelBufferDescriptor.type)};
|
||||
wgpu::Texture textureToReadFrom{srcTexture};
|
||||
wgpu::Texture stagingTexture{ nullptr };
|
||||
uint32_t readX{x};
|
||||
uint32_t readY{y};
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(dstBytesPerPixel == srcBytesPerPixel && dstBytesPerPixel > 0)
|
||||
<< "Source texture pixel size (" << srcBytesPerPixel
|
||||
<< ") does not match destination pixel buffer pixel size (" << dstBytesPerPixel
|
||||
<< "), or the format is not supported for readPixels.";
|
||||
// If the source format is different from the destination (e.g. BGRA vs RGBA),
|
||||
// we need to perform a conversion using an intermediate blit.
|
||||
if (conversionNecessary(srcFormat, dstFormat, pixelBufferDescriptor.type)) {
|
||||
const wgpu::TextureDescriptor stagingDescriptor{
|
||||
.label = "readpixels_staging_texture",
|
||||
.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment,
|
||||
.dimension = wgpu::TextureDimension::e2D, // are we sure we can assume this? thus far we have
|
||||
.size = {
|
||||
.width = actualWidth,
|
||||
.height = actualHeight,
|
||||
.depthOrArrayLayers = 1,
|
||||
},
|
||||
.format = dstFormat,
|
||||
.mipLevelCount = 1,
|
||||
.sampleCount = srcTexture.GetSampleCount(),
|
||||
};
|
||||
stagingTexture = mDevice.CreateTexture(&stagingDescriptor);
|
||||
assert_invariant(stagingTexture);
|
||||
textureToReadFrom = stagingTexture;
|
||||
const WebGPUBlitter::BlitArgs blitArgs{
|
||||
.source = {
|
||||
.texture = srcTexture,
|
||||
.origin = {.x = x, .y = y,},
|
||||
.extent = {
|
||||
.width = actualWidth,
|
||||
.height = actualHeight,
|
||||
},
|
||||
},
|
||||
.destination = {
|
||||
.texture = stagingTexture,
|
||||
.origin = {.x = 0, .y = 0},
|
||||
.extent = {
|
||||
.width = actualWidth,
|
||||
.height = actualHeight,
|
||||
},
|
||||
},
|
||||
.filter = SamplerMagFilter::NEAREST, // Use NEAREST for a 1:1 copy
|
||||
};
|
||||
mBlitter.blit(mQueue, mCommandEncoder, blitArgs);
|
||||
|
||||
// Create a staging buffer to copy the texture to. WebGPU requires 256 byte alignment
|
||||
const size_t bytesPerPixel = dstBytesPerPixel;
|
||||
const size_t unpaddedBytesPerRow = actualWidth * bytesPerPixel;
|
||||
const size_t alignment = 256;
|
||||
const size_t paddedBytesPerRow = (unpaddedBytesPerRow + alignment - 1) & ~(alignment - 1);
|
||||
// The subsequent read will be from the top-left of the new intermediate texture.
|
||||
readX = 0;
|
||||
readY = 0;
|
||||
}
|
||||
|
||||
size_t bufferSize = paddedBytesPerRow * height;
|
||||
wgpu::BufferDescriptor bufferDesc;
|
||||
bufferDesc.size = bufferSize;
|
||||
bufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
|
||||
wgpu::Buffer stagingBuffer = mDevice.CreateBuffer(&bufferDesc);
|
||||
// Create a staging buffer to copy the texture to. WebGPU requires 256 byte alignment for buffer-to-texture copies.
|
||||
const size_t bytesPerPixel{ PixelBufferDescriptor::computePixelSize(
|
||||
pixelBufferDescriptor.format, pixelBufferDescriptor.type) };
|
||||
const size_t unpaddedBytesPerRow{ actualWidth * bytesPerPixel };
|
||||
const size_t alignment{ 256 };
|
||||
const size_t paddedBytesPerRow = { (unpaddedBytesPerRow + alignment - 1) & ~(alignment - 1) };
|
||||
|
||||
const size_t bufferSize{ paddedBytesPerRow * actualHeight };
|
||||
const wgpu::BufferDescriptor bufferDesc{
|
||||
.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead,
|
||||
.size = bufferSize,
|
||||
};
|
||||
|
||||
const wgpu::Buffer stagingBuffer{ mDevice.CreateBuffer(&bufferDesc) };
|
||||
assert_invariant(stagingBuffer);
|
||||
|
||||
wgpu::CommandEncoder encoder = mDevice.CreateCommandEncoder();
|
||||
assert_invariant(encoder);
|
||||
// WebGPU's texture coordinates for copies are top-left, but Filament's y-coordinate is
|
||||
// bottom-left. We must flip the y-coordinate relative to the texture we are reading from.
|
||||
const uint32_t textureHeight{ textureToReadFrom.GetHeight() };
|
||||
const uint32_t flippedY{ textureHeight - readY - actualHeight };
|
||||
|
||||
// WebGPU flips the y axis like Metal does
|
||||
const uint32_t flippedY = srcHeight - y - height;
|
||||
wgpu::TexelCopyTextureInfo source{
|
||||
.texture = srcTexture,
|
||||
.mipLevel = 0,
|
||||
.origin = { .x = x, .y = flippedY, .z = 0 },
|
||||
const wgpu::TexelCopyTextureInfo source{
|
||||
.texture = textureToReadFrom, // Read from the original or intermediate texture
|
||||
.mipLevel = 0,
|
||||
.origin = {.x = readX, .y = flippedY, .z = 0,},
|
||||
};
|
||||
wgpu::TexelCopyBufferInfo destination {
|
||||
.layout = {
|
||||
.offset = 0,
|
||||
.bytesPerRow = static_cast<uint32_t>(paddedBytesPerRow),
|
||||
.rowsPerImage = actualHeight,
|
||||
},
|
||||
.buffer = stagingBuffer
|
||||
const wgpu::TexelCopyBufferInfo destination{
|
||||
.layout = {
|
||||
.offset = 0,
|
||||
.bytesPerRow = static_cast<uint32_t>(paddedBytesPerRow),
|
||||
.rowsPerImage = actualHeight,
|
||||
},
|
||||
.buffer = stagingBuffer
|
||||
};
|
||||
wgpu::Extent3D copySize{ .width = actualWidth,
|
||||
const wgpu::Extent3D copySize{
|
||||
.width = actualWidth,
|
||||
.height = actualHeight,
|
||||
.depthOrArrayLayers = 1 };
|
||||
// TODO consider removing encoder finish + submit once we have test coverage and can
|
||||
// safely verify doing so will not result in strange errors. We don't think this
|
||||
// submit is necessary, as the encoder should get finished and the command
|
||||
// submitted in commit(), finish(), and/or flush() in this frame
|
||||
// or the next anyway. And, the read/copy bits will be encoded before other things.
|
||||
// Thus, not submitting here _seems_ ok. Nonetheless, doing the submit here likely
|
||||
// represents unnecessary inefficiency and higher bandwidth
|
||||
// between the CPU and GPU, but we are worried about functional correctness at this
|
||||
// stage.
|
||||
encoder.CopyTextureToBuffer(&source, &destination, ©Size);
|
||||
wgpu::CommandBuffer commandBuffer = encoder.Finish();
|
||||
mQueue.Submit(1, &commandBuffer);
|
||||
.depthOrArrayLayers = 1,
|
||||
};
|
||||
mCommandEncoder.CopyTextureToBuffer(&source, &destination, ©Size);
|
||||
mCommandBuffer = mCommandEncoder.Finish();
|
||||
assert_invariant(mCommandBuffer);
|
||||
mCommandEncoder = nullptr;
|
||||
mQueue.Submit(1, &mCommandBuffer);
|
||||
mCommandBuffer = nullptr;
|
||||
|
||||
// Map the buffer to read the data
|
||||
struct UserData {
|
||||
struct UserData final {
|
||||
PixelBufferDescriptor pixelBufferDescriptor;
|
||||
wgpu::Buffer buffer;
|
||||
size_t unpaddedBytesPerRow;
|
||||
size_t paddedBytesPerRow;
|
||||
uint32_t height;
|
||||
WebGPUDriver* driver;
|
||||
WebGPUDriver *driver;
|
||||
};
|
||||
auto userData = std::make_unique<UserData>(UserData{
|
||||
.pixelBufferDescriptor = std::move(pixelBufferDescriptor),
|
||||
.buffer = std::move(stagingBuffer),
|
||||
.unpaddedBytesPerRow = unpaddedBytesPerRow,
|
||||
.paddedBytesPerRow = paddedBytesPerRow,
|
||||
.height = height,
|
||||
.driver = this,
|
||||
.pixelBufferDescriptor = std::move(pixelBufferDescriptor),
|
||||
.buffer = std::move(stagingBuffer),
|
||||
.unpaddedBytesPerRow = unpaddedBytesPerRow,
|
||||
.paddedBytesPerRow = paddedBytesPerRow,
|
||||
.height = actualHeight,
|
||||
.driver = this,
|
||||
});
|
||||
|
||||
userData->buffer.MapAsync(
|
||||
@@ -1446,12 +1526,18 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
|
||||
[](wgpu::MapAsyncStatus status, const char* message, UserData* userdata) {
|
||||
std::unique_ptr<UserData> data(static_cast<UserData*>(userdata));
|
||||
if (UTILS_LIKELY(status == wgpu::MapAsyncStatus::Success)) {
|
||||
const char* src = static_cast<const char*>(
|
||||
data->buffer.GetConstMappedRange(0, data->buffer.GetSize()));
|
||||
char* dst = static_cast<char*>(data->pixelBufferDescriptor.buffer);
|
||||
for (uint32_t i = 0; i < data->height; ++i) {
|
||||
memcpy(dst + i * data->unpaddedBytesPerRow,
|
||||
src + i * data->paddedBytesPerRow, data->unpaddedBytesPerRow);
|
||||
const char *src {static_cast<const char *>(
|
||||
data->buffer.GetConstMappedRange(0, data->buffer.GetSize()))};
|
||||
char* dst{ static_cast<char*>(data->pixelBufferDescriptor.buffer) };
|
||||
|
||||
// If padding was added for alignment, we need to copy row by row.
|
||||
if (data->paddedBytesPerRow == data->unpaddedBytesPerRow) {
|
||||
memcpy(dst, src, data->unpaddedBytesPerRow * data->height);
|
||||
} else {
|
||||
for (uint32_t i{ 0 }; i < data->height; ++i) {
|
||||
memcpy(dst + i * data->unpaddedBytesPerRow,
|
||||
src + i * data->paddedBytesPerRow, data->unpaddedBytesPerRow);
|
||||
}
|
||||
}
|
||||
data->buffer.Unmap();
|
||||
} else {
|
||||
@@ -1478,8 +1564,12 @@ void WebGPUDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
void WebGPUDriver::resolve(Handle<HwTexture> destinationTextureHandle, const uint8_t sourceLevel,
|
||||
const uint8_t sourceLayer, Handle<HwTexture> sourceTextureHandle,
|
||||
const uint8_t destinationLevel, const uint8_t destinationLayer) {
|
||||
FILAMENT_CHECK_PRECONDITION(mCommandEncoder)
|
||||
<< "Resolve assumes there is a valid command encoder to piggyback on.";
|
||||
if (!mCommandEncoder) {
|
||||
const wgpu::CommandEncoderDescriptor encoderDescriptor{
|
||||
.label = "command_created_with_resolve",
|
||||
};
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&encoderDescriptor);
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION(mRenderPassEncoder == nullptr)
|
||||
<< "Resolve cannot be called during an existing render pass";
|
||||
const auto sourceTexture{ handleCast<WebGPUTexture>(sourceTextureHandle) };
|
||||
@@ -1605,13 +1695,10 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
};
|
||||
wgpu::TextureFormat depthStencilFormat{ wgpu::TextureFormat::Undefined };
|
||||
if (renderTarget->isDefaultRenderTarget()) {
|
||||
// default render target color(s) (one)...
|
||||
colorFormatCount = 1;
|
||||
colorFormats[0] = mSwapChain->getColorFormat();
|
||||
// default render target depth/stencil...
|
||||
depthStencilFormat = mSwapChain->getDepthFormat();
|
||||
} else {
|
||||
// custom render target color(s)...
|
||||
MRT const& mrtColorAttachments{ mCurrentRenderTarget->getColorAttachmentInfos() };
|
||||
for (size_t i{ 0 }; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; ++i) {
|
||||
if (mrtColorAttachments[i].handle) {
|
||||
@@ -1677,7 +1764,7 @@ void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> renderPrimitive
|
||||
|
||||
void WebGPUDriver::draw2(const uint32_t indexOffset, const uint32_t indexCount,
|
||||
const uint32_t instanceCount) {
|
||||
// We defer actually binding until we actually draw
|
||||
// Bind groups are deferred until the actual draw call.
|
||||
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
|
||||
auto& binding = mCurrentDescriptorSets[i];
|
||||
if (binding.bindGroup) {
|
||||
@@ -1747,10 +1834,8 @@ void WebGPUDriver::updateDescriptorSetTexture(Handle<HwDescriptorSet> descriptor
|
||||
auto texture = handleCast<WebGPUTexture>(textureHandle);
|
||||
|
||||
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
|
||||
// Dawn will cache duplicate samplers, so we don't strictly need to maintain a cache here.
|
||||
const 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->getDefaultTextureView() };
|
||||
@@ -1784,7 +1869,7 @@ void WebGPUDriver::bindDescriptorSet(Handle<HwDescriptorSet> descriptorSetHandle
|
||||
const backend::descriptor_set_t setIndex, backend::DescriptorSetOffsetArray&& offsets) {
|
||||
assert_invariant(setIndex < MAX_DESCRIPTOR_SET_COUNT);
|
||||
|
||||
// An empty handle signifies we need to release this bind point
|
||||
// An empty handle unbinds the descriptor set.
|
||||
if (!descriptorSetHandle) {
|
||||
mCurrentDescriptorSets[setIndex] = {};
|
||||
return;
|
||||
@@ -1914,4 +1999,4 @@ wgpu::AddressMode WebGPUDriver::fWrapModeToWAddressMode(const SamplerWrapMode& f
|
||||
return wgpu::AddressMode::Undefined;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -47,8 +47,12 @@
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPUSwapChain;
|
||||
|
||||
/**
|
||||
* WebGPU backend (driver) implementation
|
||||
* Implements the private backend driver API for WebGPU specifically (that API is essentially
|
||||
* expressed in private/backend/DriverAPI.inc)
|
||||
*
|
||||
* It manages all the WebGPU resources necessary to accomplish this.
|
||||
*/
|
||||
class WebGPUDriver final : public DriverBase {
|
||||
public:
|
||||
@@ -64,8 +68,8 @@ private:
|
||||
[[nodiscard]] wgpu::Sampler makeSampler(SamplerParams const& params);
|
||||
[[nodiscard]] static wgpu::AddressMode fWrapModeToWAddressMode(const filament::backend::SamplerWrapMode& fUsage);
|
||||
|
||||
// the platform (e.g. OS) specific aspects of the WebGPU backend are strictly only
|
||||
// handled in the WebGPUPlatform
|
||||
// The platform (e.g. OS) specific aspects of the WebGPU backend are strictly only
|
||||
// handled in the WebGPUPlatform.
|
||||
WebGPUPlatform& mPlatform;
|
||||
wgpu::Adapter mAdapter = nullptr;
|
||||
wgpu::Device mDevice = nullptr;
|
||||
|
||||
@@ -28,12 +28,11 @@ namespace filament::backend {
|
||||
FenceStatus WebGPUFence::getStatus() { return mStatus.load(); }
|
||||
|
||||
void WebGPUFence::addMarkerToQueueState(wgpu::Queue const& queue) {
|
||||
// The lambda function is called when the work is done. It updates the fence status based on the
|
||||
// result of the work.
|
||||
queue.OnSubmittedWorkDone(
|
||||
wgpu::CallbackMode::AllowSpontaneous,
|
||||
[this](const wgpu::QueueWorkDoneStatus status, wgpu::StringView message) {
|
||||
// Note: The 'message' parameter is required by the function signature
|
||||
// but can be ignored if not needed.
|
||||
|
||||
switch (status) {
|
||||
case wgpu::QueueWorkDoneStatus::Success:
|
||||
mStatus.store(FenceStatus::CONDITION_SATISFIED);
|
||||
|
||||
@@ -28,6 +28,11 @@ class Queue;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A WebGPU-specific implementation of the HwFence.
|
||||
* This class is used to synchronize the CPU and GPU. It allows the CPU to know when the GPU has
|
||||
* finished a set of commands.
|
||||
*/
|
||||
class WebGPUFence final : public HwFence {
|
||||
public:
|
||||
[[nodiscard]] FenceStatus getStatus();
|
||||
@@ -35,6 +40,9 @@ public:
|
||||
void addMarkerToQueueState(wgpu::Queue const&);
|
||||
|
||||
private:
|
||||
// The current status of the fence.
|
||||
// This is atomic because it can be updated by a WebGPU callback thread and read from the main
|
||||
// thread.
|
||||
std::atomic<FenceStatus> mStatus{ FenceStatus::TIMEOUT_EXPIRED };
|
||||
};
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@ enum class IndexFormat : uint32_t;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A WebGPU implementation of the HwIndexBuffer.
|
||||
* This class represents a GPU buffer that stores indices for indexed drawing.
|
||||
*/
|
||||
class WebGPUIndexBuffer final : public HwIndexBuffer, public WebGPUBufferBase {
|
||||
public:
|
||||
WebGPUIndexBuffer(wgpu::Device const&, uint8_t elementSize, uint32_t indexCount);
|
||||
|
||||
@@ -48,8 +48,8 @@ void resolveColorTextures(wgpu::CommandEncoder const& commandEncoder,
|
||||
const wgpu::RenderPassEncoder renderPassEncoder{ commandEncoder.BeginRenderPass(
|
||||
&renderPassDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(renderPassEncoder)
|
||||
<< "Failed to create wgpu::RenderPassEncoder for WebGPUDriver::resolve";
|
||||
renderPassEncoder.End(); // only the implicit resolve is happening in the pass
|
||||
<< "Failed to create wgpu::RenderPassEncoder for MSAA resolve.";
|
||||
renderPassEncoder.End(); // The resolve is implicit in the pass.
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -59,30 +59,29 @@ void WebGPUMsaaTextureResolver::resolve(ResolveRequest const& request) {
|
||||
ResolveRequest::TextureInfo const& destination{ request.destination };
|
||||
FILAMENT_CHECK_PRECONDITION(destination.texture.GetWidth() == source.texture.GetWidth() &&
|
||||
destination.texture.GetHeight() == source.texture.GetHeight())
|
||||
<< "invalid resolve: source and destination sizes don't match";
|
||||
<< "Invalid resolve: source and destination sizes don't match.";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
source.texture.GetSampleCount() > 1 && destination.texture.GetSampleCount() == 1)
|
||||
<< "invalid resolve: source.samples=" << source.texture.GetSampleCount()
|
||||
<< "Invalid resolve: source.samples=" << source.texture.GetSampleCount()
|
||||
<< ", destination.samples=" << destination.texture.GetSampleCount();
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(source.texture.GetFormat() == destination.texture.GetFormat())
|
||||
<< "source and destination texture format don't match";
|
||||
<< "Source and destination texture format don't match.";
|
||||
const wgpu::TextureFormat format{ source.texture.GetFormat() };
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!hasDepth(format)) << "can't resolve depth formats";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!hasStencil(format)) << "can't resolve stencil formats";
|
||||
FILAMENT_CHECK_PRECONDITION(!hasDepth(format)) << "Can't resolve depth formats.";
|
||||
FILAMENT_CHECK_PRECONDITION(!hasStencil(format)) << "Can't resolve stencil formats.";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(source.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
|
||||
<< "source texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
|
||||
<< "Source texture usage doesn't have wgpu::TextureUsage::RenderAttachment.";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
destination.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
|
||||
<< "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
|
||||
<< "Destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment.";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(destination.texture.GetUsage() & wgpu::TextureUsage::TextureBinding)
|
||||
<< "destination texture usage doesn't have wgpu::TextureUsage::TextureBinding";
|
||||
<< "Destination texture usage doesn't have wgpu::TextureUsage::TextureBinding.";
|
||||
|
||||
const wgpu::TextureViewDescriptor sourceTextureViewDescriptor{
|
||||
.label = "resolve_source_texture_view",
|
||||
@@ -116,7 +115,7 @@ void WebGPUMsaaTextureResolver::resolve(ResolveRequest const& request) {
|
||||
<< "Failed to create wgpu::TextureView destinationTextureView.";
|
||||
|
||||
if (hasDepth(format)) {
|
||||
PANIC_PRECONDITION("DEPTH RESOLVE NOT IMPLEMENTED YET");
|
||||
PANIC_PRECONDITION("Depth resolve not yet implemented.");
|
||||
} else {
|
||||
resolveColorTextures(request.commandEncoder, sourceTextureView, destinationTextureView);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@ namespace filament::backend {
|
||||
|
||||
class WebGPUTexture;
|
||||
|
||||
/**
|
||||
* A utility class for resolving multisampled textures in WebGPU.
|
||||
* This is necessary because WebGPU does not have an implicit resolve step like other APIs.
|
||||
*/
|
||||
class WebGPUMsaaTextureResolver final {
|
||||
public:
|
||||
struct ResolveRequest final {
|
||||
@@ -47,6 +51,9 @@ public:
|
||||
TextureInfo destination;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves a multisampled texture into a single-sampled texture.
|
||||
*/
|
||||
void resolve(ResolveRequest const&);
|
||||
};
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace {
|
||||
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
|
||||
// WebGPU does not support culling both front and back faces simultaneously.
|
||||
FILAMENT_CHECK_POSTCONDITION(false)
|
||||
<< "WebGPU does not support CullingMode::FRONT_AND_BACK";
|
||||
return wgpu::CullMode::Undefined;
|
||||
@@ -220,7 +220,7 @@ wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
|
||||
const bool requestedDepth{ any(request.targetRenderFlags & TargetBufferFlags::DEPTH) };
|
||||
const bool requestedStencil{ any(request.targetRenderFlags & TargetBufferFlags::STENCIL) };
|
||||
const bool depthOrStencilRequested{ requestedDepth || requestedStencil };
|
||||
// depth/stencil...
|
||||
|
||||
if (depthOrStencilRequested) {
|
||||
FILAMENT_CHECK_PRECONDITION(request.depthStencilFormat != wgpu::TextureFormat::Undefined)
|
||||
<< "Depth or Stencil requested for pipeline, but depthStencilFormat is "
|
||||
@@ -312,6 +312,13 @@ wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
|
||||
},
|
||||
.fragment = nullptr // will add below if fragment module is included
|
||||
};
|
||||
// TODO:
|
||||
if (pipelineDescriptor.primitive.topology == wgpu::PrimitiveTopology::LineStrip ||
|
||||
pipelineDescriptor.primitive.topology == wgpu::PrimitiveTopology::TriangleStrip) {
|
||||
PANIC_POSTCONDITION("stripIndexFormat must be set for strip topologies. "
|
||||
"This needs to be plumbed through from the RenderPrimitive.");
|
||||
}
|
||||
|
||||
wgpu::FragmentState fragmentState = {};
|
||||
const wgpu::BlendState blendState {
|
||||
.color = {
|
||||
|
||||
@@ -34,6 +34,11 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A cache for WebGPU render pipelines.
|
||||
* This class is responsible for creating and caching wgpu::RenderPipeline objects to avoid
|
||||
* expensive pipeline creation at runtime.
|
||||
*/
|
||||
class WebGPUPipelineCache final {
|
||||
public:
|
||||
struct RenderPipelineRequest final {
|
||||
@@ -63,6 +68,9 @@ public:
|
||||
[[nodiscard]] wgpu::RenderPipeline const& getOrCreateRenderPipeline(
|
||||
RenderPipelineRequest const&);
|
||||
|
||||
/**
|
||||
* Should be called at the end of each frame to perform cache maintenance.
|
||||
*/
|
||||
void onFrameEnd();
|
||||
|
||||
private:
|
||||
|
||||
@@ -77,12 +77,13 @@ wgpu::PipelineLayout WebGPUPipelineLayoutCache::createPipelineLayout(
|
||||
};
|
||||
const wgpu::PipelineLayout layout{ mDevice.CreatePipelineLayout(&descriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(layout)
|
||||
<< "Failed to create pipeline layout " << descriptor.label << "?";
|
||||
<< "Failed to create pipeline layout " << descriptor.label << ".";
|
||||
return layout;
|
||||
}
|
||||
|
||||
bool WebGPUPipelineLayoutCache::PipelineLayoutKeyEqual::operator()(PipelineLayoutKey const& key1,
|
||||
PipelineLayoutKey const& key2) const {
|
||||
// Compare the raw bytes of the keys for equality.
|
||||
return 0 == memcmp(reinterpret_cast<void const*>(&key1), reinterpret_cast<void const*>(&key2),
|
||||
sizeof(key1));
|
||||
}
|
||||
@@ -93,10 +94,9 @@ void WebGPUPipelineLayoutCache::removeExpiredPipelineLayouts() {
|
||||
PipelineLayoutCacheEntry const& entry{ iterator.value() };
|
||||
if (mFrameCount > (entry.lastUsedFrameCount +
|
||||
FILAMENT_WEBGPU_PIPELINE_LAYOUT_EXPIRATION_IN_FRAME_COUNT)) {
|
||||
// pipeline layout expired...
|
||||
// The pipeline layout has not been used recently, so we can remove it from the cache.
|
||||
iterator = mPipelineLayouts.erase(iterator);
|
||||
} else {
|
||||
// pipeline layout not yet expired...
|
||||
++iterator;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,11 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A cache for WebGPU pipeline layouts.
|
||||
* This class is responsible for creating and caching wgpu::PipelineLayout objects to avoid
|
||||
* expensive pipeline layout creation at runtime.
|
||||
*/
|
||||
class WebGPUPipelineLayoutCache final {
|
||||
public:
|
||||
struct PipelineLayoutRequest final {
|
||||
@@ -45,9 +50,16 @@ public:
|
||||
WebGPUPipelineLayoutCache& operator=(WebGPUPipelineLayoutCache const&) = delete;
|
||||
WebGPUPipelineLayoutCache& operator=(WebGPUPipelineLayoutCache const&&) = delete;
|
||||
|
||||
/**
|
||||
* Retrieves a pipeline layout from the cache or creates a new one if it doesn't exist.
|
||||
* @return A constant reference to the cached or newly created pipeline layout.
|
||||
*/
|
||||
[[nodiscard]] wgpu::PipelineLayout const& getOrCreatePipelineLayout(
|
||||
PipelineLayoutRequest const&);
|
||||
|
||||
/**
|
||||
* Should be called at the end of each frame to perform cache maintenance.
|
||||
*/
|
||||
void onFrameEnd();
|
||||
|
||||
private:
|
||||
@@ -59,10 +71,10 @@ private:
|
||||
* instances, single bytes for booleans etc.), trivial copying and comparison (byte by byte),
|
||||
* and a word-aligned structure with a size in bytes as a multiple of 4 (for murmer hash).
|
||||
*/
|
||||
struct PipelineLayoutKey final { // size : offset (need multiples of 4 bytes for hashing)
|
||||
WGPUBindGroupLayout bindGroupLayoutHandles[MAX_DESCRIPTOR_SET_COUNT]{ nullptr }; // 32 : 0
|
||||
uint8_t bindGroupLayoutCount{ 0 }; // 1 : 32
|
||||
uint8_t padding[7]{ 0 }; // 7 : 33
|
||||
struct PipelineLayoutKey final {
|
||||
WGPUBindGroupLayout bindGroupLayoutHandles[MAX_DESCRIPTOR_SET_COUNT]{ nullptr }; // 32 :0
|
||||
uint8_t bindGroupLayoutCount{ 0 }; // 1 :32
|
||||
uint8_t padding[7]{ 0 }; // 7 :33
|
||||
};
|
||||
static_assert(sizeof(PipelineLayoutKey) == 40,
|
||||
"PipelineLayoutKey must not have implicit padding.");
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace {
|
||||
std::string_view(sourceData + posAfterId, posEndOfStatement - posAfterId);
|
||||
size_t posOfEqual = statementSegment.find('=');
|
||||
if (posOfEqual == std::string::npos) {
|
||||
// not an assignment statement, so stream to the end of the statement and continue...
|
||||
// Not an assignment, so we don't need to replace it.
|
||||
processedShaderSource << std::string_view(sourceData + pos,
|
||||
posEndOfStatement + 1 - pos);
|
||||
pos = posEndOfStatement + 1;
|
||||
@@ -122,10 +122,7 @@ namespace {
|
||||
}
|
||||
const auto newValueItr = specConstants.find(static_cast<uint32_t>(constantId));
|
||||
if (newValueItr == specConstants.end()) {
|
||||
// not going to override the constant,
|
||||
// as the specConstants parameter doesn't specify it. So, we will keep the default
|
||||
// already in the source text
|
||||
// (stream to the end of the statement)...
|
||||
// The constant is not being overridden, so keep the default value.
|
||||
processedShaderSource << std::string_view(sourceData + pos,
|
||||
posEndOfStatement + 1 - pos);
|
||||
pos = posEndOfStatement + 1;
|
||||
@@ -174,7 +171,7 @@ namespace {
|
||||
utils::FixedCapacityVector<uint8_t> const& sourceBytes =
|
||||
shaderSource[static_cast<size_t>(stage)];
|
||||
if (sourceBytes.empty()) {
|
||||
return nullptr;// nothing to compile/create, the shader was not provided
|
||||
return nullptr; // No shader source to compile.
|
||||
}
|
||||
std::stringstream labelStream;
|
||||
labelStream << programName << " " << filamentShaderStageToString(stage) << " shader";
|
||||
@@ -192,7 +189,8 @@ namespace {
|
||||
};
|
||||
const wgpu::ShaderModule shaderModule = device.CreateShaderModule(&descriptor);
|
||||
const wgpu::Instance instance = device.GetAdapter().GetInstance();
|
||||
// synchronously creates the shader module...
|
||||
|
||||
// Synchronously compile the shader module.
|
||||
const wgpu::WaitStatus waitResult = instance.WaitAny(
|
||||
shaderModule.GetCompilationInfo(wgpu::CallbackMode::WaitAnyOnly,
|
||||
[&descriptor](auto const& status,
|
||||
@@ -282,7 +280,7 @@ WebGPUProgram::WebGPUProgram(wgpu::Device const& device, Program const& program)
|
||||
: HwProgram{ program.getName() } {
|
||||
std::unordered_map<uint32_t, std::variant<int32_t, float, bool>> specConstants;
|
||||
toMap(program.getSpecializationConstants(), specConstants);
|
||||
// TODO consider creating/compiling these shaders in parallel
|
||||
// TODO: Consider creating/compiling these shaders in parallel.
|
||||
vertexShaderModule = createShaderModule(device, program, ShaderStage::VERTEX, specConstants);
|
||||
fragmentShaderModule =
|
||||
createShaderModule(device, program, ShaderStage::FRAGMENT, specConstants);
|
||||
|
||||
@@ -25,6 +25,10 @@ namespace filament::backend {
|
||||
|
||||
class Program;
|
||||
|
||||
/**
|
||||
* A WebGPU implementation of the HwProgram.
|
||||
* This class holds the compiled shader modules for a given program.
|
||||
*/
|
||||
class WebGPUProgram final : public HwProgram {
|
||||
public:
|
||||
WebGPUProgram(wgpu::Device const&, Program const&);
|
||||
|
||||
@@ -27,9 +27,8 @@ namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
// The shaders expect a single-sampled 2D texture
|
||||
// with a format compatible with scalar sample format f32
|
||||
// (this is checked with the getCompatibilityFor function)
|
||||
// The shaders expect a single-sampled 2D texture with a format compatible with a scalar sample format of f32.
|
||||
// This is checked with the getCompatibilityFor function.
|
||||
constexpr uint32_t TEXTURE_BIND_GROUP_INDEX{ 0 };
|
||||
constexpr size_t TEXTURE_BIND_GROUP_ENTRY_SIZE{ 2 }; // sampler and texture
|
||||
constexpr uint32_t SAMPLER_BINDING_INDEX{ 0 };
|
||||
@@ -85,7 +84,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
|
||||
.maxAnisotropy = 1, // should not matter, just being consistently defined
|
||||
};
|
||||
const wgpu::Sampler sampler{ device.CreateSampler(&descriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create previous mip level sampler?";
|
||||
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create previous mip level sampler.";
|
||||
return sampler;
|
||||
}
|
||||
|
||||
@@ -98,7 +97,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
|
||||
};
|
||||
const wgpu::ShaderModule shaderModule{ device.CreateShaderModule(&shaderModuleDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(shaderModule)
|
||||
<< "Failed to create shader module for render pass mipmap generation?";
|
||||
<< "Failed to create shader module for render pass mipmap generation.";
|
||||
return shaderModule;
|
||||
}
|
||||
|
||||
@@ -131,7 +130,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
|
||||
const wgpu::BindGroupLayout textureBindGroupLayout{ device.CreateBindGroupLayout(
|
||||
&textureBindGroupLayoutDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(textureBindGroupLayout)
|
||||
<< "Failed to create texture bind group layout for render pass mipmap generation?";
|
||||
<< "Failed to create texture bind group layout for render pass mipmap generation.";
|
||||
return textureBindGroupLayout;
|
||||
}
|
||||
|
||||
@@ -145,7 +144,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
|
||||
const wgpu::PipelineLayout pipelineLayout{ device.CreatePipelineLayout(
|
||||
&pipelineLayoutDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipelineLayout)
|
||||
<< "Failed to create pipeline layout for render pass mipmap generation?";
|
||||
<< "Failed to create pipeline layout for render pass mipmap generation.";
|
||||
return pipelineLayout;
|
||||
}
|
||||
|
||||
@@ -194,7 +193,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
|
||||
};
|
||||
const wgpu::RenderPipeline pipeline{ device.CreateRenderPipeline(&pipelineDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create pipeline for render pass mipmap generation?";
|
||||
<< "Failed to create pipeline for render pass mipmap generation.";
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
@@ -210,8 +209,7 @@ WebGPURenderPassMipmapGenerator::WebGPURenderPassMipmapGenerator(wgpu::Device co
|
||||
WebGPURenderPassMipmapGenerator::FormatCompatibility
|
||||
WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat format,
|
||||
const wgpu::TextureDimension dimension, const uint32_t sampleCount) {
|
||||
// check that the format is compatible (currently expects that the scalar sample type is
|
||||
// f32)...
|
||||
// Check that the format is compatible (currently expects that the scalar sample type is f32).
|
||||
switch (format) {
|
||||
case wgpu::TextureFormat::Depth16Unorm:
|
||||
case wgpu::TextureFormat::Depth24Plus:
|
||||
@@ -220,20 +218,16 @@ WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat f
|
||||
case wgpu::TextureFormat::Depth32FloatStencil8:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "A depth texture format requires special sampler treatment and does "
|
||||
"not "
|
||||
"generally support linear filtering needed for render pass based "
|
||||
"mipmap "
|
||||
"generation, thus this texture is not supported, as it is a kind of "
|
||||
"depth texture.",
|
||||
.reason = "A depth texture format requires special sampler treatment and does not "
|
||||
"generally support linear filtering needed for render pass based mipmap "
|
||||
"generation.",
|
||||
};
|
||||
case wgpu::TextureFormat::Undefined:
|
||||
case wgpu::TextureFormat::External:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "Undefined or External textures are not supported for render pass "
|
||||
"based "
|
||||
"mipmap generation",
|
||||
.reason = "Undefined or External textures are not supported for render pass based "
|
||||
"mipmap generation.",
|
||||
};
|
||||
default:
|
||||
const ScalarSampleType scalarSampleType{ getScalarSampleTypeFrom(format) };
|
||||
@@ -242,21 +236,19 @@ WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat f
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "The provided texture format requires an unsigned integer "
|
||||
"sampler, which does not natively support linear filtering, "
|
||||
"which is needed for render pass based mipmap generation.",
|
||||
"sampler, which does not natively support linear filtering.",
|
||||
};
|
||||
case ScalarSampleType::I32:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "The provided texture format requires a signed integer "
|
||||
"sampler, which does not natively support linear filtering, "
|
||||
"which is needed for render pass based mipmap generation.",
|
||||
"sampler, which does not natively support linear filtering.",
|
||||
};
|
||||
case ScalarSampleType::F32:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// check that the dimensionality is compatible (currently expects 2D textures)...
|
||||
// Check that the dimensionality is compatible (currently expects 2D textures).
|
||||
switch (dimension) {
|
||||
case wgpu::TextureDimension::Undefined:
|
||||
return {
|
||||
@@ -282,7 +274,7 @@ WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat f
|
||||
case wgpu::TextureDimension::e2D:
|
||||
break;
|
||||
}
|
||||
// check that the texture is single-sampled (for now)...
|
||||
// Check that the texture is single-sampled.
|
||||
if (sampleCount > 1) {
|
||||
return {
|
||||
.compatible = false,
|
||||
@@ -427,7 +419,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmaps(wgpu::Queue const& queue,
|
||||
wgpu::Texture const& texture) {
|
||||
const uint32_t mipLevelCount{ texture.GetMipLevelCount() };
|
||||
if (mipLevelCount < 2) {
|
||||
return; // nothing to do
|
||||
return; // Nothing to do.
|
||||
}
|
||||
wgpu::RenderPipeline const& pipeline{ getOrCreatePipelineFor(texture.GetFormat()) };
|
||||
const wgpu::CommandEncoderDescriptor commandEncoderDescriptor{
|
||||
@@ -436,20 +428,19 @@ void WebGPURenderPassMipmapGenerator::generateMipmaps(wgpu::Queue const& queue,
|
||||
const wgpu::CommandEncoder commandEncoder{ mDevice.CreateCommandEncoder(
|
||||
&commandEncoderDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(commandEncoder)
|
||||
<< "Failed to create command encoder for layer for render pass mipmap generation?";
|
||||
<< "Failed to create command encoder for render pass mipmap generation.";
|
||||
const uint32_t layerCount{ texture.GetDepthOrArrayLayers() };
|
||||
for (uint32_t layer = 0; layer < layerCount; layer++) {
|
||||
for (uint32_t mipLevel = 1; mipLevel < mipLevelCount; mipLevel++) {
|
||||
generateMipmap(commandEncoder, texture, pipeline, layer, mipLevel);
|
||||
}
|
||||
}
|
||||
// submit the command buffer...
|
||||
const wgpu::CommandBufferDescriptor commandBufferDescriptor{
|
||||
.label = "mipmap_generation_render_pass_cmd_buffer",
|
||||
};
|
||||
const wgpu::CommandBuffer commandBuffer{ commandEncoder.Finish(&commandBufferDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(commandBuffer)
|
||||
<< "Failed to create command buffer for layer for render pass mipmap generation?";
|
||||
<< "Failed to create command buffer for render pass mipmap generation.";
|
||||
queue.Submit(1, &commandBuffer);
|
||||
}
|
||||
|
||||
@@ -472,7 +463,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
|
||||
.usage = wgpu::TextureUsage::TextureBinding,
|
||||
};
|
||||
const wgpu::TextureView sourceView{ texture.CreateView(&sourceViewDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
FILAMENT_CHECK_POSTCONDITION(sourceView)
|
||||
<< "Failed to create source texture view for layer " << layer << " and mip level "
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
const wgpu::TextureViewDescriptor destinationViewDescriptor{
|
||||
@@ -487,7 +478,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
|
||||
.usage = wgpu::TextureUsage::RenderAttachment,
|
||||
};
|
||||
const wgpu::TextureView destinationView{ texture.CreateView(&destinationViewDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
FILAMENT_CHECK_POSTCONDITION(destinationView)
|
||||
<< "Failed to create destination texture view for layer " << layer << " and mip level "
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
// create the render pass...
|
||||
@@ -508,7 +499,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
|
||||
.entries = textureBindGroupEntries,
|
||||
};
|
||||
const wgpu::BindGroup textureBindGroup{ mDevice.CreateBindGroup(&textureBindGroupDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
FILAMENT_CHECK_POSTCONDITION(textureBindGroup)
|
||||
<< "Failed to create texture bind group for layer " << layer << " and mip level "
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
const wgpu::RenderPassColorAttachment colorAttachment{
|
||||
@@ -534,8 +525,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
renderPassEncoder.SetPipeline(pipeline);
|
||||
renderPassEncoder.SetBindGroup(TEXTURE_BIND_GROUP_INDEX, textureBindGroup);
|
||||
renderPassEncoder.Draw(3); // draw the full-screen triangle
|
||||
// with hard-coded vertices in the shader
|
||||
renderPassEncoder.Draw(3); // Draw a full-screen triangle.
|
||||
renderPassEncoder.End();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A utility class for generating mipmaps for a texture using a series of render passes.
|
||||
*/
|
||||
class WebGPURenderPassMipmapGenerator final {
|
||||
public:
|
||||
struct FormatCompatibility final {
|
||||
@@ -34,6 +37,11 @@ public:
|
||||
std::string_view reason;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a given texture format is compatible with render pass-based mipmap generation.
|
||||
* @return A FormatCompatibility struct indicating whether the format is compatible
|
||||
* and a reason if not.
|
||||
*/
|
||||
[[nodiscard]] static FormatCompatibility getCompatibilityFor(wgpu::TextureFormat,
|
||||
wgpu::TextureDimension, uint32_t sampleCount);
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ namespace filament::backend {
|
||||
class WebGPUVertexBuffer;
|
||||
class WebGPUIndexBuffer;
|
||||
|
||||
/**
|
||||
* A WebGPU implementation of the HwRenderPrimitive.
|
||||
* This struct holds the vertex and index buffers that define a renderable primitive.
|
||||
*/
|
||||
struct WebGPURenderPrimitive : public HwRenderPrimitive {
|
||||
WebGPUVertexBuffer* vertexBuffer = nullptr;
|
||||
WebGPUIndexBuffer* indexBuffer = nullptr;
|
||||
|
||||
@@ -41,9 +41,9 @@ namespace filament::backend {
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Panics if the number of samples in the original attachment textures do not match.
|
||||
* @return The number of samples in the original attachment textures (the number/count in each one).
|
||||
* Returns 0 if there are no attachments.
|
||||
* Creates MSAA sidecar textures for attachments if necessary.
|
||||
* This function also verifies that all attachments have the same sample count.
|
||||
* @return The number of samples per attachment.
|
||||
*/
|
||||
[[nodiscard]] uint8_t createMsaaSidecarTextures(const uint8_t renderTargetSampleCount,
|
||||
const TargetBufferFlags targetFlags, MRT const& colorAttachments,
|
||||
@@ -102,7 +102,7 @@ namespace {
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION(texture->samples == sampleCountPerAttachment)
|
||||
<< target.name << " attachment texture has " << +texture->samples
|
||||
<< " but the other attachment(s) have " << +sampleCountPerAttachment;
|
||||
<< " samples, but the other attachment(s) have " << +sampleCountPerAttachment << ".";
|
||||
if (renderTargetSampleCount > 1 && sampleCountPerAttachment == 1) {
|
||||
texture->createMsaaSidecarTextureIfNotAlreadyCreated(renderTargetSampleCount,
|
||||
device);
|
||||
@@ -136,7 +136,7 @@ WebGPURenderTarget::WebGPURenderTarget(const uint32_t width, const uint32_t heig
|
||||
mColorAttachmentDesc.reserve(MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
|
||||
}
|
||||
|
||||
// Default constructor for the default render target
|
||||
// Constructor for the default render target
|
||||
WebGPURenderTarget::WebGPURenderTarget()
|
||||
: HwRenderTarget{ 0, 0 },
|
||||
mDefaultRenderTarget{ true },
|
||||
@@ -182,19 +182,19 @@ void WebGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor&
|
||||
FILAMENT_CHECK_PRECONDITION(std::all_of(customColorMsaaSidecarTextureViews,
|
||||
customColorMsaaSidecarTextureViews + customColorTextureViewCount,
|
||||
[](wgpu::TextureView const& msaaView) { return msaaView != nullptr; }))
|
||||
<< "A color or depth/stencil attachment texture has a MSAA sidecar but at least "
|
||||
<< "A color or depth/stencil attachment texture has an MSAA sidecar but at least "
|
||||
"one other color attachment texture does not.";
|
||||
FILAMENT_CHECK_PRECONDITION(customDepthStencilMsaaSidecarTextureView != nullptr)
|
||||
<< "The color attachment texture(s) have MSAA sidecar(s) but the depth/stencil "
|
||||
<< "The color attachment texture(s) have MSAA sidecars but the depth/stencil "
|
||||
"texture does not.";
|
||||
} else {
|
||||
FILAMENT_CHECK_PRECONDITION(std::all_of(customColorMsaaSidecarTextureViews,
|
||||
customColorMsaaSidecarTextureViews + customColorTextureViewCount,
|
||||
[](wgpu::TextureView const& msaaView) { return msaaView == nullptr; }))
|
||||
<< "A color or depth/stencil attachment texture does not have a MSAA sidecar but "
|
||||
<< "A color or depth/stencil attachment texture does not have an MSAA sidecar but "
|
||||
"at least one color attachment texture does.";
|
||||
FILAMENT_CHECK_PRECONDITION(customDepthStencilMsaaSidecarTextureView == nullptr)
|
||||
<< "Custom color textures for the render target do not have MSAA sidecar(s) but "
|
||||
<< "Custom color textures for the render target do not have MSAA sidecars but "
|
||||
"the depth/stencil texture does.";
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A WebGPU implementation of the HwRenderTarget.
|
||||
* This class represents a collection of attachments (textures) that can be rendered to.
|
||||
*/
|
||||
class WebGPURenderTarget : public HwRenderTarget {
|
||||
public:
|
||||
using Attachment = TargetBufferInfo; // Using TargetBufferInfo directly for attachments
|
||||
@@ -38,7 +42,8 @@ public:
|
||||
WebGPURenderTarget(uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount,
|
||||
MRT const& colorAttachments, Attachment const& depthAttachment,
|
||||
Attachment const& stencilAttachment, TargetBufferFlags const& targetFlags,
|
||||
std::function<WebGPUTexture*(const Handle<HwTexture>)> const&, wgpu::Device const&);
|
||||
std::function<WebGPUTexture*(const Handle<HwTexture>)> const&,
|
||||
wgpu::Device const&);
|
||||
|
||||
// Default constructor for the default render target
|
||||
WebGPURenderTarget();
|
||||
@@ -80,7 +85,7 @@ private:
|
||||
|
||||
MRT mColorAttachments{};
|
||||
// TODO WebGPU only supports a DepthStencil attachment, should this be just
|
||||
// mDepthStencilAttachment?
|
||||
// mDepthStencilAttachment?
|
||||
Attachment mDepthAttachment{};
|
||||
Attachment mStencilAttachment{};
|
||||
|
||||
|
||||
@@ -187,7 +187,8 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
|
||||
wgpu::SurfaceCapabilities const& capabilities, wgpu::Extent2D const& extent,
|
||||
bool useSRGBColorSpace) {
|
||||
config.device = device;
|
||||
config.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
|
||||
config.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc |
|
||||
wgpu::TextureUsage::TextureBinding;
|
||||
config.width = extent.width;
|
||||
config.height = extent.height;
|
||||
config.format = selectColorFormat(capabilities.formatCount, capabilities.formats, useSRGBColorSpace);
|
||||
@@ -269,8 +270,6 @@ WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const&
|
||||
const bool useSRGBColorSpace = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0;
|
||||
|
||||
initConfig(mConfig, device, capabilities, extent, useSRGBColorSpace);
|
||||
mDepthFormat = selectDepthFormat(device.HasFeature(wgpu::FeatureName::Depth32FloatStencil8),
|
||||
mNeedStencil);
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
printSurfaceConfiguration(mConfig, mDepthFormat);
|
||||
#endif
|
||||
@@ -297,8 +296,6 @@ WebGPUSwapChain::WebGPUSwapChain(wgpu::Extent2D const& extent,
|
||||
mConfig.presentMode = wgpu::PresentMode::Fifo;
|
||||
mConfig.alphaMode = wgpu::CompositeAlphaMode::Auto;
|
||||
|
||||
mDepthFormat = selectDepthFormat(device.HasFeature(wgpu::FeatureName::Depth32FloatStencil8),
|
||||
mNeedStencil);
|
||||
const wgpu::TextureDescriptor textureDescriptor = {
|
||||
.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc,
|
||||
.dimension = wgpu::TextureDimension::e2D,
|
||||
|
||||
@@ -78,9 +78,9 @@ public:
|
||||
|
||||
/**
|
||||
* @param samples The number of samples the underlying texture supports
|
||||
* @param mipLevel The mip level into the underyling texture for which this view will reference
|
||||
* @param mipLevel The mip level into the underlying texture for which this view will reference
|
||||
* (this view will only have one mip level)
|
||||
* @param arrayLayer The layer into the underyling texture for which this view will reference
|
||||
* @param arrayLayer The layer into the underlying texture for which this view will reference
|
||||
* (this view will only have one layer)
|
||||
* @return A texture view for the MSAA sidecar texture
|
||||
*/
|
||||
|
||||
@@ -534,6 +534,22 @@ namespace filament::backend {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Todo: should this take into account sRGB/linear when determining if conversion is necessary?
|
||||
* For instance, if the output format is the same as the input, except the output is sRGB
|
||||
* do we really need to do a conversion? If the answer is no, we should do that check here
|
||||
* and return conversionNecessary false in that case.
|
||||
* However, doing a straight-forward comparison is the safest most conservative thing to do
|
||||
* for functional correctness and NOT doing a conversion in such cases could be considered
|
||||
* an optimization. Thus, consider the optimization when we have better test coverage to
|
||||
* experiment with such a refactor.
|
||||
* @return True if theres a format mismatch
|
||||
*/
|
||||
[[nodiscard]] constexpr bool conversionNecessary(const wgpu::TextureFormat source,
|
||||
const wgpu::TextureFormat destination, const PixelDataType pixelDataType) {
|
||||
return source != destination && pixelDataType != PixelDataType::COMPRESSED;
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_WEBGPUTEXTUREHELPERS_H
|
||||
|
||||
@@ -49,7 +49,7 @@ Backend parseArgumentsForBackend(int argc, char* argv[]) {
|
||||
} else if (arg == "webgpu") {
|
||||
backend = Backend::WEBGPU;
|
||||
} else {
|
||||
std::cerr << "Unrecognized target API. Must be 'opengl'|'vulkan'|'metal'."
|
||||
std::cerr << "Unrecognized target API. Must be 'opengl'|'vulkan'|'metal'|'webgpu'."
|
||||
<< std::endl
|
||||
<< "Defaulting to OpenGL."
|
||||
<< std::endl;
|
||||
|
||||
@@ -145,7 +145,6 @@ static void createFaces(DriverApi& dapi, Handle<HwTexture> texture, int baseWidt
|
||||
|
||||
TEST_F(BlitTest, ColorMagnify) {
|
||||
auto& api = getDriverApi();
|
||||
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||
|
||||
constexpr int kSrcTexWidth = 256;
|
||||
constexpr int kSrcTexHeight = 256;
|
||||
@@ -207,7 +206,6 @@ TEST_F(BlitTest, ColorMagnify) {
|
||||
|
||||
TEST_F(BlitTest, ColorMinify) {
|
||||
auto& api = getDriverApi();
|
||||
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||
|
||||
constexpr int kSrcTexWidth = 1024;
|
||||
constexpr int kSrcTexHeight = 1024;
|
||||
@@ -342,7 +340,6 @@ TEST_F(BlitTest, ColorResolve) {
|
||||
|
||||
TEST_F(BlitTest, Blit2DTextureArray) {
|
||||
auto& api = getDriverApi();
|
||||
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||
|
||||
api.startCapture(0);
|
||||
mCleanup.addPostCall([&]() { api.stopCapture(0); });
|
||||
@@ -412,7 +409,6 @@ TEST_F(BlitTest, Blit2DTextureArray) {
|
||||
|
||||
TEST_F(BlitTest, BlitRegion) {
|
||||
auto& api = getDriverApi();
|
||||
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||
|
||||
constexpr int kSrcTexWidth = 1024;
|
||||
constexpr int kSrcTexHeight = 1024;
|
||||
@@ -488,7 +484,6 @@ TEST_F(BlitTest, BlitRegion) {
|
||||
TEST_F(BlitTest, BlitRegionToSwapChain) {
|
||||
FAIL_IF(Backend::VULKAN, "Crashes due to not finding color attachment, see b/417481493");
|
||||
auto& api = getDriverApi();
|
||||
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||
|
||||
constexpr int kSrcTexWidth = 1024;
|
||||
constexpr int kSrcTexHeight = 1024;
|
||||
@@ -541,13 +536,13 @@ TEST_F(BlitTest, BlitRegionToSwapChain) {
|
||||
dstRect, srcRenderTargets[srcLevel],
|
||||
srcRect, SamplerMagFilter::LINEAR);
|
||||
|
||||
api.commit(swapChain);
|
||||
}
|
||||
api.commit(swapChain);
|
||||
|
||||
// TODO: for some reason, this test has been disabled. It needs to be tested on all
|
||||
// machines.
|
||||
// EXPECT_IMAGE(dstRenderTarget,
|
||||
// ScreenshotParams(kDstTexWidth, kDstTexHeight, "BlitRegionToSwapChain", 0x0));
|
||||
// TODO: for some reason, this test has been disabled. It needs to be tested on all
|
||||
// machines.
|
||||
// EXPECT_IMAGE(dstRenderTarget,
|
||||
// ScreenshotParams(kDstTexWidth, kDstTexHeight, "BlitRegionToSwapChain", 0x0));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -91,6 +91,7 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
|
||||
shader.bindUniform<SimpleMaterialParams>(api, ubuffer, kBindingConfig);
|
||||
|
||||
api.startCapture(0);
|
||||
cleanup.addPostCall([&]() { api.stopCapture(0); });
|
||||
|
||||
// Upload the uniform, but with an offset to accommodate the padding in the shader's
|
||||
// uniform definition.
|
||||
@@ -101,29 +102,42 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
|
||||
});
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
|
||||
// Draw 10 triangles, updating the vertex buffer / index buffer each time.
|
||||
size_t triangleIndex = 0;
|
||||
for (float i = -1.0f; i < 1.0f; i += 0.2f) {
|
||||
const float low = i, high = i + 0.2;
|
||||
const filament::math::float2 v[3]{{ low, low },
|
||||
{ high, low },
|
||||
{ low, high }};
|
||||
triangle.updateVertices(v);
|
||||
// Draw 10 triangles, updating the vertex buffer / index buffer each time.
|
||||
size_t triangleIndex = 0;
|
||||
for (float i = -1.0f; i < 1.0f; i += 0.2f) {
|
||||
const float low = i, high = i + 0.2;
|
||||
const filament::math::float2 v[3]{{ low, low },
|
||||
{ high, low },
|
||||
{ low, high }};
|
||||
triangle.updateVertices(v);
|
||||
|
||||
if (updateIndices) {
|
||||
if (triangleIndex % 2 == 0) {
|
||||
// Upload each index separately, to test offsets.
|
||||
const TrianglePrimitive::index_type i[3]{ 0, 1, 2 };
|
||||
triangle.updateIndices(i + 0, 1, 0);
|
||||
triangle.updateIndices(i + 1, 1, 1);
|
||||
triangle.updateIndices(i + 2, 1, 2);
|
||||
} else {
|
||||
// This effectively hides this triangle.
|
||||
const TrianglePrimitive::index_type i[3]{ 0, 0, 0 };
|
||||
triangle.updateIndices(i);
|
||||
if (updateIndices) {
|
||||
if (triangleIndex % 2 == 0) {
|
||||
// Upload each index separately, to test offsets.
|
||||
const TrianglePrimitive::index_type i[3]{ 0, 1, 2 };
|
||||
triangle.updateIndices(i + 0, 1, 0);
|
||||
triangle.updateIndices(i + 1, 1, 1);
|
||||
triangle.updateIndices(i + 2, 1, 2);
|
||||
} else {
|
||||
// This effectively hides this triangle.
|
||||
const TrianglePrimitive::index_type i[3]{ 0, 0, 0 };
|
||||
triangle.updateIndices(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (triangleIndex > 0) {
|
||||
params.flags.clear = TargetBufferFlags::NONE;
|
||||
params.flags.discardStart = TargetBufferFlags::NONE;
|
||||
}
|
||||
|
||||
api.beginRenderPass(defaultRenderTarget, params);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
triangleIndex++;
|
||||
}
|
||||
|
||||
if (triangleIndex > 0) {
|
||||
@@ -141,15 +155,7 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
|
||||
|
||||
triangleIndex++;
|
||||
}
|
||||
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
api.stopCapture(0);
|
||||
}
|
||||
|
||||
executeCommands();
|
||||
}
|
||||
|
||||
// This test renders two triangles in two separate draw calls. Between the draw calls, a uniform
|
||||
@@ -241,15 +247,13 @@ TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
|
||||
"BufferObjectUpdateWithOffset", 2320747245));
|
||||
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
// This ensures all driver commands have finished before exiting the test.
|
||||
api.finish();
|
||||
|
||||
executeCommands();
|
||||
|
||||
getDriver().purge();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -29,58 +29,61 @@ TEST_F(BackendTest, FrameScheduledCallback) {
|
||||
SKIP_IF(Backend::VULKAN, "Frame callbacks are unsupported in Vulkan, see b/417254479");
|
||||
SKIP_IF(Backend::WEBGPU, "Frame callbacks are unsupported in WebGPU");
|
||||
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
// Create a SwapChain.
|
||||
// In order for the frameScheduledCallback to be called, this must be a real SwapChain (not
|
||||
// headless) so we obtain a drawable.
|
||||
auto swapChain = cleanup.add(createSwapChain());
|
||||
|
||||
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createDefaultRenderTarget());
|
||||
|
||||
int callbackCountA = 0;
|
||||
api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountA](PresentCallable callable) {
|
||||
callable();
|
||||
callbackCountA++;
|
||||
}, 0);
|
||||
|
||||
// Render the first frame.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
api.beginRenderPass(renderTarget, {});
|
||||
api.endRenderPass(0);
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
// Render the next frame. The same callback should be called.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
api.beginRenderPass(renderTarget, {});
|
||||
api.endRenderPass(0);
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
// Now switch out the callback.
|
||||
int callbackCountB = 0;
|
||||
api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountB](PresentCallable callable) {
|
||||
callable();
|
||||
callbackCountB++;
|
||||
}, 0);
|
||||
{
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
cleanup.addPostCall([&]() { executeCommands(); });
|
||||
cleanup.addPostCall([&]() { getDriver().purge(); });
|
||||
|
||||
// Render one final frame.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
api.beginRenderPass(renderTarget, {});
|
||||
api.endRenderPass(0);
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
// Create a SwapChain.
|
||||
// In order for the frameScheduledCallback to be called, this must be a real SwapChain (not
|
||||
// headless) so we obtain a drawable.
|
||||
auto swapChain = cleanup.add(createSwapChain());
|
||||
|
||||
api.finish();
|
||||
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createDefaultRenderTarget());
|
||||
|
||||
executeCommands();
|
||||
getDriver().purge();
|
||||
api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountA](PresentCallable callable) {
|
||||
callable();
|
||||
callbackCountA++;
|
||||
}, 0);
|
||||
|
||||
// Render the first frame.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
api.beginRenderPass(renderTarget, {});
|
||||
api.endRenderPass(0);
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
// Render the next frame. The same callback should be called.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
api.beginRenderPass(renderTarget, {});
|
||||
api.endRenderPass(0);
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
// Now switch out the callback.
|
||||
api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountB](PresentCallable callable) {
|
||||
callable();
|
||||
callbackCountB++;
|
||||
}, 0);
|
||||
|
||||
// Render one final frame.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
api.beginRenderPass(renderTarget, {});
|
||||
api.endRenderPass(0);
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
api.finish();
|
||||
}
|
||||
EXPECT_EQ(callbackCountA, 2);
|
||||
EXPECT_EQ(callbackCountB, 1);
|
||||
}
|
||||
@@ -90,43 +93,47 @@ TEST_F(BackendTest, FrameCompletedCallback) {
|
||||
SKIP_IF(Backend::VULKAN, "Frame callbacks are unsupported in Vulkan, see b/417254479");
|
||||
SKIP_IF(Backend::WEBGPU, "Frame callbacks are unsupported in WebGPU");
|
||||
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
// Create a SwapChain.
|
||||
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
||||
|
||||
int callbackCountA = 0;
|
||||
api.setFrameCompletedCallback(swapChain, nullptr,
|
||||
[&callbackCountA]() { callbackCountA++; });
|
||||
|
||||
// Render the first frame.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
// Render the next frame. The same callback should be called.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
// Now switch out the callback.
|
||||
int callbackCountB = 0;
|
||||
api.setFrameCompletedCallback(swapChain, nullptr,
|
||||
[&callbackCountB]() { callbackCountB++; });
|
||||
{
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
cleanup.addPostCall([&]() { executeCommands(); });
|
||||
cleanup.addPostCall([&]() { getDriver().purge(); });
|
||||
|
||||
// Render one final frame.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
// Create a SwapChain.
|
||||
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
||||
|
||||
api.finish();
|
||||
api.setFrameCompletedCallback(swapChain, nullptr,
|
||||
[&callbackCountA]() { callbackCountA++; });
|
||||
|
||||
executeCommands();
|
||||
getDriver().purge();
|
||||
// Render the first frame.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
// Render the next frame. The same callback should be called.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
// Now switch out the callback.
|
||||
api.setFrameCompletedCallback(swapChain, nullptr,
|
||||
[&callbackCountB]() { callbackCountB++; });
|
||||
|
||||
// Render one final frame.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
api.finish();
|
||||
}
|
||||
|
||||
EXPECT_EQ(callbackCountA, 2);
|
||||
EXPECT_EQ(callbackCountB, 1);
|
||||
|
||||
@@ -158,7 +158,10 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
PixelBufferDescriptor pb(buffer, size, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb);
|
||||
api.update3DImage(texture, 0, 0, 0, 0, kTexWidth, kTexHeight, 1, std::move(pb));
|
||||
|
||||
for (int frame = 0; frame < kNumFrames; frame++) {
|
||||
for (int frameCount = 0; frameCount < kNumFrames; frameCount++) {
|
||||
Cleanup frameCleanup(api);
|
||||
frameCleanup.addPostCall([&]() { executeCommands(); });
|
||||
frameCleanup.addPostCall([&]() { getDriver().purge(); });
|
||||
|
||||
// Prep for rendering.
|
||||
PipelineState state = getColorWritePipelineState();
|
||||
@@ -191,9 +194,9 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
};
|
||||
shader.bindUniform<MaterialParams>(api, ubuffer, uniformBinding);
|
||||
shader.uploadUniform(api, ubuffer, uniformBinding, MaterialParams{
|
||||
.fbWidth = float(params.viewport.width),
|
||||
.fbHeight = float(params.viewport.height),
|
||||
.sourceLevel = float(sourceLevel),
|
||||
.fbWidth = float(params.viewport.width),
|
||||
.fbHeight = float(params.viewport.height),
|
||||
.sourceLevel = float(sourceLevel),
|
||||
});
|
||||
|
||||
api.beginRenderPass(renderTargets[targetLevel], params);
|
||||
@@ -246,7 +249,7 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
//
|
||||
// NOTE: Calling glReadPixels on any miplevel other than the base level
|
||||
// seems to be un-reliable on some GPU's.
|
||||
if (frame == kNumFrames - 1) {
|
||||
if (frameCount == kNumFrames - 1) {
|
||||
EXPECT_IMAGE(renderTargets[0],
|
||||
ScreenshotParams(kTexWidth, kTexHeight, "FeedbackLoops", 4192780705));
|
||||
}
|
||||
@@ -255,8 +258,6 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
api.finish();
|
||||
executeCommands();
|
||||
getDriver().purge();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ layout(location = 0) out vec4 fragColor;
|
||||
|
||||
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
|
||||
// This parameter is ignored for other backends.
|
||||
layout(location = 0, set = 0) uniform {samplerType} test_tex;
|
||||
layout(binding = 0, set = 0) uniform {samplerType} test_tex;
|
||||
|
||||
void main() {
|
||||
vec2 fbsize = vec2({texSize});
|
||||
@@ -66,7 +66,7 @@ std::string fragmentUpdateImage3DTemplate (R"(#version 450 core
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
|
||||
layout(location = 0, set = 0) uniform {samplerType} test_tex;
|
||||
layout(binding = 0, set = 0) uniform {samplerType} test_tex;
|
||||
|
||||
float getLayer(in sampler3D s) { return 2.5f / 4.0f; }
|
||||
float getLayer(in sampler2DArray s) { return 2.0f; }
|
||||
@@ -87,7 +87,7 @@ std::string fragmentUpdateImageMip (R"(#version 450 core
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
|
||||
layout(location = 0, set = 0) uniform sampler2D test_tex;
|
||||
layout(binding = 0, set = 0) uniform sampler2D test_tex;
|
||||
|
||||
void main() {
|
||||
vec2 fbsize = vec2({texSize});
|
||||
@@ -271,15 +271,18 @@ TEST_F(LoadImageTest, UpdateImage2D) {
|
||||
|
||||
// Test format conversion.
|
||||
// TODO: Vulkan crashes with `Texture at colorAttachment[0] has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)'
|
||||
// TODO: WebGPU Crashes with "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment"
|
||||
testCases.emplace_back("RGBA FLOAT to RGBA16F", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F);
|
||||
|
||||
// Test texture formats not all backends support natively.
|
||||
// TODO: Vulkan crashes with "VK_FORMAT_R32G32B32_SFLOAT is not supported"
|
||||
// TODO: WebGPU Crashes with "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment"
|
||||
testCases.emplace_back("RGB FLOAT to RGB32F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F);
|
||||
testCases.emplace_back("RGB FLOAT to RGB16F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB16F);
|
||||
|
||||
// Test packed format uploads.
|
||||
// TODO: Vulkan crashes with "Texture at colorAttachment[0] has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)"
|
||||
// TODO: WebGPU Crashes with "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment"
|
||||
testCases.emplace_back("RGBA UINT_2_10_10_10_REV to RGB10_A2", PixelDataFormat::RGBA, PixelDataType::UINT_2_10_10_10_REV, TextureFormat::RGB10_A2);
|
||||
testCases.emplace_back("RGB UINT_10F_11F_11F_REV to R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::UINT_10F_11F_11F_REV, TextureFormat::R11F_G11F_B10F);
|
||||
testCases.emplace_back("RGB HALF to R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::HALF, TextureFormat::R11F_G11F_B10F);
|
||||
@@ -287,9 +290,12 @@ TEST_F(LoadImageTest, UpdateImage2D) {
|
||||
// Test integer format uploads.
|
||||
// TODO: These cases fail on OpenGL and Vulkan.
|
||||
// TODO: These cases now also fail on Metal, but at some point previously worked.
|
||||
testCases.emplace_back("RGB_INTEGER UBYTE to RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI);
|
||||
testCases.emplace_back("RGB_INTEGER USHORT to RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI);
|
||||
testCases.emplace_back("RGB_INTEGER INT to RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I);
|
||||
// TODO: These cases fail for WebGPU. However, leaving them causes
|
||||
// Tint Reader Error: error: sampled image must have float component type
|
||||
// Beginning SpirV-output dump with ret 0
|
||||
// testCases.emplace_back("RGB_INTEGER UBYTE to RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI);
|
||||
// testCases.emplace_back("RGB_INTEGER USHORT to RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI);
|
||||
// testCases.emplace_back("RGB_INTEGER INT to RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I);
|
||||
|
||||
// Test uploads with buffer padding.
|
||||
// TODO: Vulkan crashes with "Assertion failed: (offset + size <= allocationSize)"
|
||||
@@ -308,15 +314,19 @@ TEST_F(LoadImageTest, UpdateImage2D) {
|
||||
|
||||
api.startCapture();
|
||||
|
||||
Cleanup cleanup(api);
|
||||
cleanup.addPostCall([&]() { api.finish(); });
|
||||
cleanup.addPostCall([&]() { api.stopCapture(); });
|
||||
|
||||
// The test is executed within this block scope to force destructors to run before
|
||||
// executeCommands().
|
||||
for (const auto& t : testCases) {
|
||||
Cleanup cleanup(api);
|
||||
Cleanup caseCleanup(api);
|
||||
|
||||
// Create a platform-specific SwapChain and make it current.
|
||||
auto swapChain = cleanup.add(createSwapChain());
|
||||
auto swapChain = caseCleanup.add(createSwapChain());
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
auto defaultRenderTarget = cleanup.add(api.createDefaultRenderTarget(0));
|
||||
auto defaultRenderTarget = caseCleanup.add(api.createDefaultRenderTarget(0));
|
||||
|
||||
// Create a program.
|
||||
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
|
||||
@@ -380,8 +390,6 @@ TEST_F(LoadImageTest, UpdateImage2D) {
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
}
|
||||
|
||||
api.stopCapture();
|
||||
}
|
||||
|
||||
TEST_F(LoadImageTest, UpdateImageSRGB) {
|
||||
@@ -392,6 +400,7 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
api.startCapture();
|
||||
cleanup.addPostCall([&]() { api.stopCapture(); });
|
||||
|
||||
PixelDataFormat const pixelFormat = PixelDataFormat::RGBA;
|
||||
PixelDataType const pixelType = PixelDataType::UBYTE;
|
||||
@@ -439,37 +448,36 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
|
||||
|
||||
api.update3DImage(texture, 0, 0, 0, 0, kTexSize, kTexSize, 1, std::move(descriptor));
|
||||
|
||||
api.beginFrame(0, 0, 0);
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
|
||||
// Update samplers.
|
||||
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
|
||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
// Update samplers.
|
||||
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
|
||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
|
||||
api.bindDescriptorSet(descriptorSet, 0, {});
|
||||
api.bindDescriptorSet(descriptorSet, 0, {});
|
||||
|
||||
RenderPassParams params = getClearColorRenderPass();
|
||||
params.viewport.width = kTexSize;
|
||||
params.viewport.height = kTexSize;
|
||||
PipelineState state = getColorWritePipelineState();
|
||||
shader.addProgramToPipelineState(state);
|
||||
state.primitiveType = PrimitiveType::TRIANGLES;
|
||||
state.vertexBufferInfo = mTriangle.getVertexBufferInfo();
|
||||
api.beginRenderPass(defaultRenderTarget, params);
|
||||
api.bindPipeline(state);
|
||||
api.bindRenderPrimitive(mTriangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
RenderPassParams params = getClearColorRenderPass();
|
||||
params.viewport.width = kTexSize;
|
||||
params.viewport.height = kTexSize;
|
||||
PipelineState state = getColorWritePipelineState();
|
||||
shader.addProgramToPipelineState(state);
|
||||
state.primitiveType = PrimitiveType::TRIANGLES;
|
||||
state.vertexBufferInfo = mTriangle.getVertexBufferInfo();
|
||||
api.beginRenderPass(defaultRenderTarget, params);
|
||||
api.bindPipeline(state);
|
||||
api.bindRenderPrimitive(mTriangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
EXPECT_IMAGE(defaultRenderTarget,
|
||||
ScreenshotParams(kTexSize, kTexSize, "UpdateImageSRGB", 3300305265));
|
||||
EXPECT_IMAGE(defaultRenderTarget,
|
||||
ScreenshotParams(kTexSize, kTexSize, "UpdateImageSRGB", 3300305265));
|
||||
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
api.stopCapture();
|
||||
api.commit(swapChain);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(LoadImageTest, UpdateImageMipLevel) {
|
||||
@@ -480,6 +488,7 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
api.startCapture();
|
||||
cleanup.addPostCall([&]() { api.stopCapture(); });
|
||||
|
||||
PixelDataFormat pixelFormat = PixelDataFormat::RGBA;
|
||||
PixelDataType pixelType = PixelDataType::HALF;
|
||||
@@ -512,17 +521,7 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
|
||||
PixelBufferDescriptor descriptor = checkerboardPixelBuffer(pixelFormat, pixelType, kTexSize);
|
||||
api.update3DImage(texture, /* level*/ 1, 0, 0, 0, kTexSize, kTexSize, 1, std::move(descriptor));
|
||||
|
||||
api.beginFrame(0, 0, 0);
|
||||
|
||||
// Update samplers.
|
||||
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
|
||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
|
||||
api.bindDescriptorSet(descriptorSet, 0, {});
|
||||
|
||||
RenderFrame outerFrame(api);
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
RenderPassParams params = getClearColorRenderPass();
|
||||
@@ -543,9 +542,6 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
|
||||
ScreenshotParams(kTexSize, kTexSize, "UpdateImageMipLevel", 1875922935));
|
||||
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
api.stopCapture();
|
||||
}
|
||||
|
||||
TEST_F(LoadImageTest, UpdateImage3D) {
|
||||
@@ -558,6 +554,7 @@ TEST_F(LoadImageTest, UpdateImage3D) {
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
api.startCapture();
|
||||
cleanup.addPostCall([&]() { api.stopCapture(); });
|
||||
|
||||
PixelDataFormat pixelFormat = PixelDataFormat::RGBA;
|
||||
PixelDataType pixelType = PixelDataType::FLOAT;
|
||||
@@ -628,8 +625,6 @@ TEST_F(LoadImageTest, UpdateImage3D) {
|
||||
EXPECT_IMAGE(defaultRenderTarget,
|
||||
ScreenshotParams(kTexSize, kTexSize, "UpdateImage3D", 1875922935));
|
||||
}
|
||||
|
||||
api.stopCapture();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -108,28 +108,27 @@ TEST_F(BackendTest, MRT) {
|
||||
RenderPassParams params = getClearColorRenderPass();
|
||||
params.viewport = getFullViewport();
|
||||
|
||||
Cleanup captureCleanup(api);
|
||||
api.startCapture(0);
|
||||
captureCleanup.addPostCall([&]() { api.stopCapture(0); });
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
|
||||
// Draw a triangle.
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
state.primitiveType = PrimitiveType::TRIANGLES;
|
||||
state.vertexBufferInfo = triangle.getVertexBufferInfo();
|
||||
api.bindPipeline(state);
|
||||
api.bindRenderPrimitive(triangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
// Draw a triangle.
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
state.primitiveType = PrimitiveType::TRIANGLES;
|
||||
state.vertexBufferInfo = triangle.getVertexBufferInfo();
|
||||
api.bindPipeline(state);
|
||||
api.bindRenderPrimitive(triangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
api.stopCapture(0);
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
}
|
||||
}
|
||||
|
||||
executeCommands();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -56,8 +56,9 @@ using namespace filament::backend;
|
||||
|
||||
TEST_F(BackendTest, TextureViewLod) {
|
||||
auto& api = getDriverApi();
|
||||
api.startCapture(0);
|
||||
Cleanup cleanup(api);
|
||||
api.startCapture(0);
|
||||
cleanup.addPostCall([&]() { api.stopCapture(0); });
|
||||
|
||||
// The test is executed within this block scope to force destructors to run before
|
||||
// executeCommands().
|
||||
@@ -123,7 +124,7 @@ TEST_F(BackendTest, TextureViewLod) {
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
|
||||
api.beginFrame(0, 0, 0);
|
||||
RenderFrame frame(api);
|
||||
|
||||
// We set the base mip to 1, and the max mip to 3
|
||||
// Level 0: 128x128 (red)
|
||||
@@ -211,15 +212,9 @@ TEST_F(BackendTest, TextureViewLod) {
|
||||
api.endRenderPass();
|
||||
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
api.stopCapture(0);
|
||||
}
|
||||
|
||||
api.finish();
|
||||
|
||||
executeCommands();
|
||||
getDriver().purge();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -59,10 +59,11 @@ using namespace filament::backend;
|
||||
* successfully.
|
||||
*/
|
||||
TEST_F(BackendTest, MissingRequiredAttributes) {
|
||||
DriverApi& api = getDriverApi();
|
||||
|
||||
// The test is executed within this block scope to force destructors to run before
|
||||
// executeCommands().
|
||||
{
|
||||
DriverApi& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
// Create a platform-specific SwapChain and make it current.
|
||||
auto swapChain = cleanup.add(createSwapChain());
|
||||
@@ -86,9 +87,10 @@ TEST_F(BackendTest, MissingRequiredAttributes) {
|
||||
params.viewport = getFullViewport();
|
||||
|
||||
api.startCapture(0);
|
||||
cleanup.addPostCall([&]() { api.stopCapture(0); });
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
RenderFrame frame(api);
|
||||
|
||||
// Render a triangle.
|
||||
api.beginRenderPass(defaultRenderTarget, params);
|
||||
@@ -101,12 +103,7 @@ TEST_F(BackendTest, MissingRequiredAttributes) {
|
||||
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
api.stopCapture(0);
|
||||
}
|
||||
|
||||
executeCommands();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -111,12 +111,13 @@ TEST_F(BackendTest, PushConstants) {
|
||||
|
||||
auto& api = getDriverApi();
|
||||
|
||||
api.startCapture(0);
|
||||
Cleanup cleanup(api);
|
||||
|
||||
// The test is executed within this block scope to force destructors to run before
|
||||
// executeCommands().
|
||||
{
|
||||
Cleanup cleanup(api);
|
||||
api.startCapture(0);
|
||||
cleanup.addPostCall([&]() { api.stopCapture(0); });
|
||||
|
||||
// Create a SwapChain and make it current.
|
||||
auto swapChain = cleanup.add(createSwapChain());
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
@@ -141,7 +142,7 @@ TEST_F(BackendTest, PushConstants) {
|
||||
ps.rasterState.depthWrite = false;
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
RenderFrame frame(api);
|
||||
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
api.bindPipeline(ps);
|
||||
@@ -183,10 +184,7 @@ TEST_F(BackendTest, PushConstants) {
|
||||
"pushConstants", 3575588741));
|
||||
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
}
|
||||
|
||||
api.stopCapture(0);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -288,7 +288,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
params.viewport.height = t.getRenderTargetSize();
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
RenderFrame frame (api);
|
||||
|
||||
// Render a white triangle over blue.
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
@@ -350,11 +350,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
api.endRenderPass();
|
||||
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
}
|
||||
|
||||
// This ensures all driver commands have finished before exiting the test.
|
||||
flushAndWait();
|
||||
}
|
||||
|
||||
TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
|
||||
@@ -363,6 +359,7 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
|
||||
|
||||
DriverApi& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
cleanup.addPostCall([&]() { executeCommands(); });
|
||||
|
||||
// Create a platform-specific SwapChain and make it current.
|
||||
auto swapChain = cleanup.add(
|
||||
@@ -416,28 +413,29 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
|
||||
}
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
|
||||
// Render some content, just so we don't read back uninitialized data.
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
state.primitiveType = PrimitiveType::TRIANGLES;
|
||||
state.vertexBufferInfo = triangle.getVertexBufferInfo();
|
||||
api.bindPipeline(state);
|
||||
api.bindRenderPrimitive(triangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
// Render some content, just so we don't read back uninitialized data.
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
state.primitiveType = PrimitiveType::TRIANGLES;
|
||||
state.vertexBufferInfo = triangle.getVertexBufferInfo();
|
||||
api.bindPipeline(state);
|
||||
api.bindRenderPrimitive(triangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
PixelBufferDescriptor descriptor(buffer, renderTargetSize * renderTargetSize * 4,
|
||||
PixelDataFormat::RGBA, PixelDataType::UBYTE, 1, 0, 0, renderTargetSize,
|
||||
[](void* buffer, size_t size, void* user) {
|
||||
ReadPixelsTest* test = (ReadPixelsTest*)user;
|
||||
test->readPixelsFinished = true;
|
||||
}, this);
|
||||
PixelBufferDescriptor descriptor(buffer, renderTargetSize * renderTargetSize * 4,
|
||||
PixelDataFormat::RGBA, PixelDataType::UBYTE, 1, 0, 0, renderTargetSize,
|
||||
[](void* buffer, size_t size, void* user) {
|
||||
ReadPixelsTest* test = (ReadPixelsTest*) user;
|
||||
test->readPixelsFinished = true;
|
||||
}, this);
|
||||
|
||||
api.readPixels(renderTarget, 0, 0, renderTargetSize, renderTargetSize,
|
||||
std::move(descriptor));
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
api.readPixels(renderTarget, 0, 0, renderTargetSize, renderTargetSize,
|
||||
std::move(descriptor));
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
flushAndWait();
|
||||
getDriver().purge();
|
||||
@@ -448,7 +446,6 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
|
||||
free(buffer);
|
||||
|
||||
api.finish();
|
||||
executeCommands();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -81,31 +81,31 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
|
||||
|
||||
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
|
||||
|
||||
api.startCapture(0);
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
{
|
||||
Cleanup cleanupCapture(api);
|
||||
api.startCapture(0);
|
||||
cleanup.addPostCall([&]() { api.stopCapture(0); });
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
|
||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {});
|
||||
api.bindDescriptorSet(descriptorSet, 0, {});
|
||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {});
|
||||
api.bindDescriptorSet(descriptorSet, 0, {});
|
||||
|
||||
// Render a triangle.
|
||||
api.beginRenderPass(defaultRenderTarget, params);
|
||||
state.primitiveType = PrimitiveType::TRIANGLES;
|
||||
state.vertexBufferInfo = triangle.getVertexBufferInfo();
|
||||
api.bindPipeline(state);
|
||||
api.bindRenderPrimitive(triangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
api.stopCapture(0);
|
||||
// Render a triangle.
|
||||
api.beginRenderPass(defaultRenderTarget, params);
|
||||
state.primitiveType = PrimitiveType::TRIANGLES;
|
||||
state.vertexBufferInfo = triangle.getVertexBufferInfo();
|
||||
api.bindPipeline(state);
|
||||
api.bindRenderPrimitive(triangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
}
|
||||
}
|
||||
api.finish();
|
||||
|
||||
executeCommands();
|
||||
}
|
||||
|
||||
TEST_F(BackendTest, RenderExternalImage) {
|
||||
@@ -180,32 +180,32 @@ TEST_F(BackendTest, RenderExternalImage) {
|
||||
RenderPassParams params = getClearColorRenderPass();
|
||||
params.viewport = getFullViewport();
|
||||
|
||||
api.startCapture(0);
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
{
|
||||
Cleanup cleanupCapture(api);
|
||||
api.startCapture(0);
|
||||
cleanup.addPostCall([&]() { api.stopCapture(0); });
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
|
||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {});
|
||||
api.bindDescriptorSet(descriptorSet, 0, {});
|
||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {});
|
||||
api.bindDescriptorSet(descriptorSet, 0, {});
|
||||
|
||||
// Render a triangle.
|
||||
api.beginRenderPass(defaultRenderTarget, params);
|
||||
state.primitiveType = PrimitiveType::TRIANGLES;
|
||||
state.vertexBufferInfo = triangle.getVertexBufferInfo();
|
||||
api.bindPipeline(state);
|
||||
api.bindRenderPrimitive(triangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
EXPECT_IMAGE(defaultRenderTarget,
|
||||
ScreenshotParams(screenWidth(), screenHeight(), "RenderExternalImage", 1206264951));
|
||||
|
||||
api.stopCapture(0);
|
||||
api.finish();
|
||||
flushAndWait();
|
||||
// Render a triangle.
|
||||
api.beginRenderPass(defaultRenderTarget, params);
|
||||
state.primitiveType = PrimitiveType::TRIANGLES;
|
||||
state.vertexBufferInfo = triangle.getVertexBufferInfo();
|
||||
api.bindPipeline(state);
|
||||
api.bindRenderPrimitive(triangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
}
|
||||
EXPECT_IMAGE(defaultRenderTarget,
|
||||
ScreenshotParams(screenWidth(), screenHeight(), "RenderExternalImage", 1206264951));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -43,8 +43,9 @@ TEST_F(BackendTest, ScissorViewportRegion) {
|
||||
constexpr int kSrcRtWidth = 384;
|
||||
constexpr int kSrcRtHeight = 384;
|
||||
|
||||
api.startCapture(0);
|
||||
Cleanup cleanup(api);
|
||||
api.startCapture(0);
|
||||
cleanup.addPostCall([&]() { api.stopCapture(0); });
|
||||
|
||||
// color texture (mip level 1) 512x512 depth texture (mip level 0) 512x512
|
||||
// +----------------------------------------+ +------------------------------------------+
|
||||
@@ -121,7 +122,7 @@ TEST_F(BackendTest, ScissorViewportRegion) {
|
||||
shader.addProgramToPipelineState(ps);
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
RenderFrame frame(api);
|
||||
|
||||
api.beginRenderPass(srcRenderTarget, params);
|
||||
api.scissor(scissor);
|
||||
@@ -136,9 +137,6 @@ TEST_F(BackendTest, ScissorViewportRegion) {
|
||||
ScreenshotParams(kSrcTexWidth >> 1, kSrcTexHeight >> 1, "scissor", 15842520));
|
||||
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
api.stopCapture(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,12 +144,11 @@ TEST_F(BackendTest, ScissorViewportRegion) {
|
||||
TEST_F(BackendTest, ScissorViewportEdgeCases) {
|
||||
auto& api = getDriverApi();
|
||||
|
||||
api.startCapture(0);
|
||||
Cleanup cleanup(api);
|
||||
|
||||
// The test is executed within this block scope to force destructors to run before
|
||||
// executeCommands().
|
||||
{
|
||||
Cleanup cleanup(api);
|
||||
api.startCapture(0);
|
||||
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
|
||||
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
@@ -201,7 +198,7 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
|
||||
shader.addProgramToPipelineState(ps);
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
RenderFrame frame(api);
|
||||
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
api.scissor(scissor);
|
||||
@@ -228,9 +225,6 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
|
||||
ScreenshotParams(512, 512, "ScissorViewportEdgeCases", 2199186852));
|
||||
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
api.stopCapture(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ public:
|
||||
ps.stencilState.front.stencilOpDepthStencilPass = StencilOperation::INCR;
|
||||
|
||||
api.makeCurrent(mSwapChain, mSwapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
RenderFrame frame(api);
|
||||
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
ps.primitiveType = PrimitiveType::TRIANGLES;
|
||||
@@ -117,7 +117,6 @@ public:
|
||||
api.endRenderPass();
|
||||
|
||||
api.commit(mSwapChain);
|
||||
api.endFrame(0);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
@@ -146,9 +145,6 @@ TEST_F(BasicStencilBufferTest, StencilBuffer) {
|
||||
|
||||
EXPECT_IMAGE(renderTarget,
|
||||
ScreenshotParams(screenWidth(), screenHeight(), "StencilBuffer", 0x3B1AEF0F));
|
||||
|
||||
flushAndWait();
|
||||
getDriver().purge();
|
||||
}
|
||||
|
||||
TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
|
||||
@@ -172,9 +168,6 @@ TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
|
||||
|
||||
EXPECT_IMAGE(renderTarget,
|
||||
ScreenshotParams(screenWidth(), screenHeight(), "DepthAndStencilBuffer", 0x3B1AEF0F));
|
||||
|
||||
flushAndWait();
|
||||
getDriver().purge();
|
||||
}
|
||||
|
||||
TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
|
||||
@@ -204,6 +197,7 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
|
||||
{ depthStencilTextureMSAA }, { depthStencilTextureMSAA }));
|
||||
|
||||
api.startCapture(0);
|
||||
cleanup.addPostCall([&]() { api.stopCapture(0); });
|
||||
|
||||
// We'll be using a triangle as geometry.
|
||||
TrianglePrimitive smallTriangle(api);
|
||||
@@ -233,44 +227,41 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
|
||||
ps.stencilState.front.stencilOpDepthStencilPass = StencilOperation::INCR;
|
||||
|
||||
api.makeCurrent(mSwapChain, mSwapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
|
||||
api.beginRenderPass(renderTarget0, params);
|
||||
ps.primitiveType = PrimitiveType::TRIANGLES;
|
||||
ps.vertexBufferInfo = smallTriangle.getVertexBufferInfo();
|
||||
api.bindPipeline(ps);
|
||||
api.bindRenderPrimitive(smallTriangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
api.beginRenderPass(renderTarget0, params);
|
||||
ps.primitiveType = PrimitiveType::TRIANGLES;
|
||||
ps.vertexBufferInfo = smallTriangle.getVertexBufferInfo();
|
||||
api.bindPipeline(ps);
|
||||
api.bindRenderPrimitive(smallTriangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
// Step 2: Render a larger triangle with the stencil test enabled.
|
||||
params.flags.clear = TargetBufferFlags::COLOR0;
|
||||
params.flags.discardStart = TargetBufferFlags::COLOR0;
|
||||
params.flags.discardEnd = TargetBufferFlags::STENCIL;
|
||||
params.clearColor = math::float4(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
ps.rasterState.colorWrite = true;
|
||||
ps.stencilState.stencilWrite = false;
|
||||
ps.stencilState.front.stencilOpDepthStencilPass = StencilOperation::KEEP;
|
||||
ps.stencilState.front.stencilFunc = StencilState::StencilFunction::E;
|
||||
ps.stencilState.front.ref = 0u;
|
||||
// Step 2: Render a larger triangle with the stencil test enabled.
|
||||
params.flags.clear = TargetBufferFlags::COLOR0;
|
||||
params.flags.discardStart = TargetBufferFlags::COLOR0;
|
||||
params.flags.discardEnd = TargetBufferFlags::STENCIL;
|
||||
params.clearColor = math::float4(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
ps.rasterState.colorWrite = true;
|
||||
ps.stencilState.stencilWrite = false;
|
||||
ps.stencilState.front.stencilOpDepthStencilPass = StencilOperation::KEEP;
|
||||
ps.stencilState.front.stencilFunc = StencilState::StencilFunction::E;
|
||||
ps.stencilState.front.ref = 0u;
|
||||
|
||||
api.beginRenderPass(renderTarget1, params);
|
||||
ps.primitiveType = PrimitiveType::TRIANGLES;
|
||||
ps.vertexBufferInfo = triangle.getVertexBufferInfo();
|
||||
api.bindPipeline(ps);
|
||||
api.bindRenderPrimitive(triangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
api.commit(mSwapChain);
|
||||
api.stopCapture(0);
|
||||
api.endFrame(0);
|
||||
api.beginRenderPass(renderTarget1, params);
|
||||
ps.primitiveType = PrimitiveType::TRIANGLES;
|
||||
ps.vertexBufferInfo = triangle.getVertexBufferInfo();
|
||||
api.bindPipeline(ps);
|
||||
api.bindRenderPrimitive(triangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
api.commit(mSwapChain);
|
||||
api.stopCapture(0);
|
||||
}
|
||||
EXPECT_IMAGE(renderTarget1, ScreenshotParams(screenWidth(), screenHeight(),
|
||||
"StencilBufferAutoResolve", 3353562179));
|
||||
|
||||
flushAndWait();
|
||||
getDriver().purge();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -1165,30 +1165,23 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
|
||||
std::sort(metalEntries.begin(), metalEntries.end(), compare);
|
||||
std::sort(wgslEntries.begin(), wgslEntries.end(), compare);
|
||||
|
||||
|
||||
utils::slog.e <<"----------------------- a" << utils::io::endl;
|
||||
// Generate the dictionaries.
|
||||
for (const auto& s : glslEntries) {
|
||||
textDictionary.addText(s.shader);
|
||||
}
|
||||
utils::slog.e <<"----------------------- b" << utils::io::endl;
|
||||
for (const auto& s : essl1Entries) {
|
||||
textDictionary.addText(s.shader);
|
||||
}
|
||||
utils::slog.e <<"----------------------- c" << utils::io::endl;
|
||||
for (auto& s : spirvEntries) {
|
||||
std::vector const spirv{ std::move(s.data) };
|
||||
s.dictionaryIndex = spirvDictionary.addBlob(spirv);
|
||||
}
|
||||
utils::slog.e <<"----------------------- d" << utils::io::endl;
|
||||
for (const auto& s : metalEntries) {
|
||||
textDictionary.addText(s.shader);
|
||||
}
|
||||
utils::slog.e <<"----------------------- e" << utils::io::endl;
|
||||
for (const auto& s : wgslEntries) {
|
||||
textDictionary.addText(s.shader);
|
||||
}
|
||||
utils::slog.e <<"----------------------- f" << utils::io::endl;
|
||||
|
||||
// Emit dictionary chunk (TextDictionaryReader and DictionaryTextChunk)
|
||||
const auto& dictionaryChunk = container.push<DictionaryTextChunk>(
|
||||
|
||||
@@ -30,34 +30,13 @@
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
namespace filamat {
|
||||
|
||||
namespace {
|
||||
bool isWordChar(char const c) { return std::isalnum(c) || c == '_'; }
|
||||
|
||||
|
||||
// Function to trim specified characters from both ends of a string_view
|
||||
std::string_view trim(std::string_view sv, std::string_view chars_to_trim = " \t\r\n") {
|
||||
// Find the first character that is not in our set of trim characters
|
||||
const auto start = sv.find_first_not_of(chars_to_trim);
|
||||
|
||||
// If no such character exists, the string is empty or all trim characters
|
||||
if (start == std::string_view::npos) {
|
||||
return ""; // Return an empty view
|
||||
bool isWordChar(char const c) {
|
||||
return std::isalnum(c) || c == '_';
|
||||
}
|
||||
|
||||
// Find the last character that is not in our set of trim characters
|
||||
const auto end = sv.find_last_not_of(chars_to_trim);
|
||||
|
||||
// Calculate the length of the new view
|
||||
const auto len = end - start + 1;
|
||||
|
||||
// Return a new view representing the trimmed string
|
||||
return sv.substr(start, len);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
LineDictionary::LineDictionary() = default;
|
||||
@@ -85,15 +64,10 @@ std::vector<LineDictionary::index_t> LineDictionary::getIndices(
|
||||
}
|
||||
|
||||
void LineDictionary::addText(std::string_view const text) noexcept {
|
||||
utils::slog.e <<"begin addText l(" << text.length() << ")=" <<
|
||||
trim(text.substr(0, std::min(15, (int)text.length())));
|
||||
|
||||
size_t cur = 0;
|
||||
size_t const len = text.length();
|
||||
const char* s = text.data();
|
||||
size_t trueCount = 0;
|
||||
while (cur < len) {
|
||||
trueCount++;
|
||||
// Start of the current line
|
||||
size_t const pos = cur;
|
||||
// Find the end of the current line or end of text
|
||||
@@ -105,22 +79,12 @@ void LineDictionary::addText(std::string_view const text) noexcept {
|
||||
cur++;
|
||||
}
|
||||
addLine({ s + pos, cur - pos });
|
||||
if (trueCount > 1000000 && trueCount > 0) {
|
||||
utils::slog.e <<"print addText at=" << trim(text.substr(0, std::min(15, (int)text.length()))) <<
|
||||
" count=" << trueCount << " cur=" << cur << utils::io::endl;
|
||||
trueCount = 0;
|
||||
}
|
||||
}
|
||||
utils::slog.e <<" ----- end addText" << utils::io::endl;
|
||||
}
|
||||
|
||||
void LineDictionary::addLine(std::string_view const line) noexcept {
|
||||
utils::slog.e <<"********** begin addline l(" << line.length() << ")=" <<
|
||||
trim(line.substr(0, std::min(15, (int)line.length())));
|
||||
|
||||
auto const lines = splitString(line);
|
||||
for (std::string_view const& subline : lines) {
|
||||
utils::slog.e <<"addline indices=" << mLineIndices.size() << utils::io::endl;
|
||||
// Never add a line twice.
|
||||
auto pos = mLineIndices.find(subline);
|
||||
if (pos != mLineIndices.end()) {
|
||||
@@ -133,7 +97,6 @@ void LineDictionary::addLine(std::string_view const line) noexcept {
|
||||
.index = index_t(mStrings.size() - 1),
|
||||
.count = 1 });
|
||||
}
|
||||
utils::slog.e <<"*********** end addline" << utils::io::endl;
|
||||
}
|
||||
|
||||
std::string_view LineDictionary::ltrim(std::string_view s) {
|
||||
@@ -147,26 +110,14 @@ std::pair<size_t, size_t> LineDictionary::findPattern(
|
||||
// Patterns are ordered from longest to shortest to ensure correct prefix matching.
|
||||
static constexpr std::string_view kPatterns[] = { "hp_copy_", "mp_copy_", "_" };
|
||||
|
||||
utils::slog.e <<"begin findPattern l(" << line.length() << ")=" <<
|
||||
trim(line.substr(0, std::min(15, (int)line.length())));
|
||||
|
||||
if (line.length() == 2) {
|
||||
utils::slog.e <<"begin findPattern l[0]=" << (int) line[0] << " l[1]=" << (int) line[1] <<
|
||||
|
||||
utils::io::endl;
|
||||
|
||||
}
|
||||
|
||||
size_t trueCount = 1;
|
||||
const size_t line_len = line.length();
|
||||
for (size_t i = offset; i < line_len; ++i, ++trueCount) {
|
||||
for (size_t i = offset; i < line_len; ++i) {
|
||||
// A pattern must be a whole word (or at the start of the string).
|
||||
if (i > 0 && isWordChar(line[i - 1])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& prefix : kPatterns) {
|
||||
trueCount++;
|
||||
if (line.size() - i >= prefix.size() && line.substr(i, prefix.size()) == prefix) {
|
||||
// A known prefix has been matched. Now, check for a sequence of digits.
|
||||
size_t const startOfDigits = i + prefix.size();
|
||||
@@ -190,19 +141,11 @@ std::pair<size_t, size_t> LineDictionary::findPattern(
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (trueCount % 10000 == 0 && trueCount > 0) {
|
||||
utils::slog.e <<"print findPattern at=" << trim(line) <<
|
||||
" count=" << trueCount << utils::io::endl;
|
||||
}
|
||||
}
|
||||
utils::slog.e <<" ----- end findPattern" << utils::io::endl;
|
||||
return { std::string_view::npos, 0 }; // No pattern found
|
||||
}
|
||||
|
||||
std::vector<std::string_view> LineDictionary::splitString(std::string_view const line) {
|
||||
utils::slog.e <<"begin splitString l=" << trim(line.substr(0,
|
||||
std::min(15, (int)line.length())));
|
||||
|
||||
std::vector<std::string_view> result;
|
||||
size_t current_pos = 0;
|
||||
|
||||
@@ -211,9 +154,7 @@ std::vector<std::string_view> LineDictionary::splitString(std::string_view const
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t trueCount = 0;
|
||||
while (current_pos < line.length()) {
|
||||
trueCount++;
|
||||
auto const [match_pos, match_len] = findPattern(line, current_pos);
|
||||
|
||||
if (match_pos == std::string_view::npos) {
|
||||
@@ -232,13 +173,7 @@ std::vector<std::string_view> LineDictionary::splitString(std::string_view const
|
||||
|
||||
// Move cursor past the match.
|
||||
current_pos = match_pos + match_len;
|
||||
|
||||
if (trueCount % 10000 == 0 && trueCount > 0) {
|
||||
utils::slog.e <<"print splitString at=" << trim(line) <<
|
||||
" count=" << trueCount << utils::io::endl;
|
||||
}
|
||||
}
|
||||
utils::slog.e <<"------- end splitString" << utils::io::endl;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ bool MaterialCompiler::run(const matp::Config& config) {
|
||||
return true;
|
||||
}
|
||||
|
||||
JobSystem js(1);
|
||||
JobSystem js;
|
||||
js.adopt();
|
||||
|
||||
// Write builder.build() to output.
|
||||
|
||||
Reference in New Issue
Block a user