Skip to content

Commit 2e6ac18

Browse files
jc3265AndyHovingh
authored andcommitted
webgpu: support blit
1 parent 4b4dd3b commit 2e6ac18

10 files changed

+1215
-27
lines changed

filament/backend/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,10 @@ if (FILAMENT_SUPPORTS_WEBGPU)
258258
include/backend/platforms/WebGPUPlatform.h
259259
src/webgpu/platform/WebGPUPlatform.cpp
260260
src/webgpu/SpdMipmapGenerator/SpdMipmapGenerator.cpp
261+
src/webgpu/utils/StringPlaceholderTemplateProcessor.cpp
262+
src/webgpu/utils/StringPlaceholderTemplateProcessor.h
263+
src/webgpu/WebGPUBlitter.cpp
264+
src/webgpu/WebGPUBlitter.h
261265
src/webgpu/WebGPUBufferBase.cpp
262266
src/webgpu/WebGPUBufferBase.h
263267
src/webgpu/WebGPUBufferObject.cpp
@@ -291,6 +295,7 @@ if (FILAMENT_SUPPORTS_WEBGPU)
291295
src/webgpu/WebGPUSwapChain.h
292296
src/webgpu/WebGPUTexture.cpp
293297
src/webgpu/WebGPUTexture.h
298+
src/webgpu/WebGPUTextureHelpers.h
294299
src/webgpu/WebGPUVertexBuffer.cpp
295300
src/webgpu/WebGPUVertexBuffer.h
296301
src/webgpu/WebGPUVertexBufferInfo.cpp

filament/backend/src/webgpu/WebGPUBlitter.cpp

Lines changed: 671 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright (C) 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef TNT_FILAMENT_BACKEND_WEBGPUBLITTER_H
18+
#define TNT_FILAMENT_BACKEND_WEBGPUBLITTER_H
19+
20+
#include <backend/DriverEnums.h>
21+
22+
#include <tsl/robin_map.h>
23+
#include <webgpu/webgpu_cpp.h>
24+
25+
#include <cstdint>
26+
27+
namespace filament::backend {
28+
29+
class WebGPUBlitter final {
30+
public:
31+
struct BlitArgs final {
32+
struct Attachment final {
33+
wgpu::Texture const& texture;
34+
wgpu::Origin2D origin{};
35+
wgpu::Extent2D extent{};
36+
uint32_t mipLevel{ 0 };
37+
uint32_t layerOrDepth{ 0 };
38+
};
39+
40+
Attachment source;
41+
Attachment destination;
42+
SamplerMagFilter filter;
43+
};
44+
45+
explicit WebGPUBlitter(wgpu::Device const& device);
46+
47+
void blit(wgpu::Queue const&, wgpu::CommandEncoder const&, BlitArgs const&);
48+
49+
private:
50+
void createSampler(SamplerMagFilter);
51+
52+
[[nodiscard]] wgpu::RenderPipeline const& getOrCreateRenderPipeline(SamplerMagFilter,
53+
wgpu::TextureViewDimension sourceDimension, uint32_t sourceSampleCount,
54+
wgpu::TextureFormat destinationTextureFormat);
55+
56+
[[nodiscard]] wgpu::RenderPipeline createRenderPipeline(SamplerMagFilter,
57+
wgpu::TextureViewDimension sourceDimension, uint32_t sourceSampleCount,
58+
wgpu::TextureFormat destinationTextureFormat);
59+
60+
[[nodiscard]] static size_t hashRenderPipelineKey(SamplerMagFilter,
61+
wgpu::TextureViewDimension sourceDimension, uint32_t sourceSampleCount);
62+
63+
[[nodiscard]] wgpu::PipelineLayout const& getOrCreatePipelineLayout(SamplerMagFilter,
64+
wgpu::TextureViewDimension, bool multisampledSource);
65+
66+
[[nodiscard]] wgpu::PipelineLayout createPipelineLayout(SamplerMagFilter,
67+
wgpu::TextureViewDimension sourceDimension, bool multisampledSource);
68+
69+
[[nodiscard]] static size_t hashPipelineLayoutKey(SamplerMagFilter,
70+
wgpu::TextureViewDimension sourceDimension, bool multisampledSource);
71+
72+
[[nodiscard]] wgpu::BindGroupLayout const& getOrCreateTextureBindGroupLayout(SamplerMagFilter,
73+
wgpu::TextureViewDimension sourceDimension, bool multisampledSource);
74+
75+
[[nodiscard]] wgpu::BindGroupLayout createTextureBindGroupLayout(SamplerMagFilter,
76+
wgpu::TextureViewDimension sourceDimension, bool multisampledSource);
77+
78+
[[nodiscard]] static size_t hashTextureBindGroupLayoutKey(SamplerMagFilter,
79+
wgpu::TextureViewDimension sourceDimension, bool multisampledSource);
80+
81+
[[nodiscard]] wgpu::ShaderModule const& getOrCreateShaderModule(
82+
wgpu::TextureViewDimension sourceDimension, bool multisampledSource);
83+
84+
[[nodiscard]] wgpu::ShaderModule createShaderModule(wgpu::TextureViewDimension sourceDimension,
85+
bool multisampledSource);
86+
87+
[[nodiscard]] static size_t hashShaderModuleKey(wgpu::TextureViewDimension sourceDimension,
88+
bool multisampledSource);
89+
90+
wgpu::Device mDevice;
91+
wgpu::Sampler mNearestSampler{ nullptr };
92+
wgpu::Sampler mLinearSampler{ nullptr };
93+
tsl::robin_map<size_t, wgpu::RenderPipeline> mRenderPipelines{};
94+
tsl::robin_map<size_t, wgpu::PipelineLayout> mPipelineLayouts{};
95+
tsl::robin_map<size_t, wgpu::BindGroupLayout> mTextureBindGroupLayouts{};
96+
tsl::robin_map<size_t, wgpu::ShaderModule> mShaderModules{};
97+
};
98+
99+
} // namespace filament::backend
100+
101+
#endif // TNT_FILAMENT_BACKEND_WEBGPUBLITTER_H

filament/backend/src/webgpu/WebGPUDriver.cpp

Lines changed: 177 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "WebGPUStrings.h"
2929
#include "WebGPUSwapChain.h"
3030
#include "WebGPUTexture.h"
31+
#include "WebGPUTextureHelpers.h"
3132
#include "WebGPUVertexBuffer.h"
3233
#include "WebGPUVertexBufferInfo.h"
3334
#include <backend/platforms/WebGPUPlatform.h>
@@ -79,8 +80,9 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
7980
mPipelineCache{ mDevice },
8081
mRenderPassMipmapGenerator{ mDevice },
8182
mSpdComputePassMipmapGenerator{ mDevice },
83+
mBlitter{ mDevice },
8284
mHandleAllocator{ "Handles", driverConfig.handleArenaSize,
83-
driverConfig.disableHandleUseAfterFreeCheck, driverConfig.disableHeapHandleTags } {
85+
driverConfig.disableHandleUseAfterFreeCheck, driverConfig.disableHeapHandleTags }{
8486
mDevice.GetLimits(&mDeviceLimits);
8587
}
8688

@@ -741,21 +743,65 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
741743
const uint32_t xoffset, const uint32_t yoffset, const uint32_t zoffset,
742744
const uint32_t width, const uint32_t height, const uint32_t depth,
743745
PixelBufferDescriptor&& pixelBufferDescriptor) {
744-
PixelBufferDescriptor* data = &pixelBufferDescriptor;
746+
PixelBufferDescriptor* inputData{ &pixelBufferDescriptor };
745747
PixelBufferDescriptor reshapedData;
746748
if (reshape(pixelBufferDescriptor, reshapedData)) {
747-
data = &reshapedData;
749+
inputData = &reshapedData;
748750
}
749-
auto texture = handleCast<WebGPUTexture>(textureHandle);
751+
const auto texture{ handleCast<WebGPUTexture>(textureHandle) };
752+
FILAMENT_CHECK_PRECONDITION((texture->getTexture().GetWidth() + xoffset) >= width)
753+
<< "Blitting requires the destination region to have enough width ("
754+
<< texture->getTexture().GetWidth() << " to accommodate the requested " << width
755+
<< " width to write/blit (accounting for xoffset, which is " << xoffset << ").";
756+
FILAMENT_CHECK_PRECONDITION((texture->getTexture().GetHeight() + yoffset) >= height)
757+
<< "Blitting requires the destination region to have enough height ("
758+
<< texture->getTexture().GetHeight() << " to accommodate the requested " << height
759+
<< " height to write/blit (accounting for yoffset, which is " << yoffset << ").";
760+
FILAMENT_CHECK_PRECONDITION((texture->getTexture().GetDepthOrArrayLayers() + zoffset) >= depth)
761+
<< "Blitting requires the destination region to have enough depth/arrayLayers ("
762+
<< texture->getTexture().GetDepthOrArrayLayers() << " to accommodate the requested "
763+
<< depth << " depth to write/blit (accounting for zoffset, which is " << zoffset << ").";
750764

751765
// TODO: Writing to a depth texture is illegal and errors. I'm not sure why Filament is trying
752766
// to do so, but early returning is working?
753-
if(texture->getAspect() == wgpu::TextureAspect::DepthOnly){
767+
if (texture->getAspect() == wgpu::TextureAspect::DepthOnly) {
754768
scheduleDestroy(std::move(pixelBufferDescriptor));
755769
return;
756770
}
757-
size_t blockWidth = texture->getBlockWidth();
758-
size_t blockHeight = texture->getBlockHeight();
771+
772+
const wgpu::TextureFormat inputPixelFormat{ toWebGPUFormat(inputData->format,
773+
inputData->type) };
774+
const wgpu::TextureFormat outputLinearFormat{ toWebGPULinearFormat(
775+
texture->getTexture().GetFormat()) };
776+
const bool conversionNecessary{
777+
inputPixelFormat != outputLinearFormat && inputData->type != PixelDataType::COMPRESSED
778+
}; // compressed formats should never need conversion
779+
const bool doBlit{ conversionNecessary };
780+
#if FWGPU_ENABLED(FWGPU_DEBUG_VALIDATION)
781+
if (texture->width > 1000 && texture->height > 500) {
782+
FWGPU_LOGD << "Update3DImage(..., level=" << level << ", xoffset=" << xoffset
783+
<< ", yoffset=" << yoffset << ", zoffset=" << zoffset << ", width=" << width
784+
<< ", height=" << height << ", depth=" << depth << ", ...):";
785+
FWGPU_LOGD << " PixelBufferDescriptor format (input): " << toString(inputData->format);
786+
FWGPU_LOGD << " PixelBufferDescriptor type (input): " << toString(inputData->type);
787+
FWGPU_LOGD << " Pixel WebGPUFormat (input): "
788+
<< webGPUTextureFormatToString(inputPixelFormat);
789+
FWGPU_LOGD << " Texture View format (output): "
790+
<< webGPUTextureFormatToString(texture->getViewFormat());
791+
FWGPU_LOGD << " Texture format (output): "
792+
<< webGPUTextureFormatToString(texture->getTexture().GetFormat());
793+
FWGPU_LOGD << " Linear Texture format (output): "
794+
<< webGPUTextureFormatToString(outputLinearFormat);
795+
FWGPU_LOGD << " Conversion Necessary: " << conversionNecessary;
796+
FWGPU_LOGD << " Do Blit: " << doBlit;
797+
}
798+
#endif
799+
FILAMENT_CHECK_PRECONDITION(inputData->type == PixelDataType::COMPRESSED ||
800+
inputPixelFormat != wgpu::TextureFormat::Undefined)
801+
<< "Failed to determine uncompressed input pixel format for WebGPU. Pixel format "
802+
<< toString(inputData->format) << " type " << toString(inputData->type);
803+
const size_t blockWidth{ texture->getBlockWidth() };
804+
const size_t blockHeight{ texture->getBlockHeight() };
759805
// WebGPU specification requires that for compressed textures, the x and y offsets
760806
// must be a multiple of the compressed texture format's block width and height.
761807
// See: https://www.w3.org/TR/webgpu/#abstract-opdef-validating-gputexelcopytextureinfo
@@ -767,22 +813,91 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
767813
<< "yoffset must be aligned to blockHeight, but offset is " << blockHeight
768814
<< "and offset is " << yoffset;
769815
}
770-
771-
auto copyInfo = wgpu::TexelCopyTextureInfo{
772-
.texture = texture->getTexture(),
773-
.mipLevel = level,
774-
.origin = { .x = xoffset, .y = yoffset, .z = zoffset },
775-
.aspect = texture->getAspect() };
776-
uint32_t bytesPerRow = static_cast<uint32_t>(
777-
PixelBufferDescriptor::computePixelSize(data->format, data->type) * width);
778-
auto extent = wgpu::Extent3D{ .width = width, .height = height, .depthOrArrayLayers = depth };
779-
780-
const uint8_t* dataBuff = static_cast<const uint8_t*>(data->buffer);
781-
size_t dataSize = data->size;
782-
783-
auto layout = wgpu::TexelCopyBufferLayout{ .bytesPerRow = bytesPerRow, .rowsPerImage = height };
784-
785-
mQueue.WriteTexture(&copyInfo, dataBuff, dataSize, &layout, &extent);
816+
const auto extent{
817+
wgpu::Extent3D{ .width = width, .height = height, .depthOrArrayLayers = depth }
818+
};
819+
const uint32_t bytesPerRow{ static_cast<uint32_t>(
820+
PixelBufferDescriptor::computePixelSize(inputData->format, inputData->type) * width) };
821+
const uint8_t* dataBuff{ static_cast<const uint8_t*>(inputData->buffer) };
822+
const size_t dataSize{ inputData->size };
823+
const auto layout{ wgpu::TexelCopyBufferLayout{
824+
.bytesPerRow = bytesPerRow,
825+
.rowsPerImage = height,
826+
} };
827+
if (doBlit) {
828+
const wgpu::TextureDescriptor stagingTextureDescriptor{
829+
.label = "blit_staging_input_texture",
830+
.usage = texture->getTexture().GetUsage(),
831+
.dimension = texture->getTexture().GetDimension(),
832+
.size = extent,
833+
.format = inputPixelFormat,
834+
.mipLevelCount = 1,
835+
.sampleCount = texture->getTexture().GetSampleCount(),
836+
.viewFormatCount = 0,
837+
.viewFormats = nullptr,
838+
};
839+
const wgpu::Texture stagingTexture{ mDevice.CreateTexture(&stagingTextureDescriptor) };
840+
FILAMENT_CHECK_POSTCONDITION(stagingTexture)
841+
<< "Failed to create staging input texture for blit?";
842+
const auto copyInfo{ wgpu::TexelCopyTextureInfo{
843+
.texture = stagingTexture,
844+
.mipLevel = level,
845+
.origin = { .x = 0, .y = 0, .z = 0 },
846+
.aspect = texture->getAspect(),
847+
} };
848+
mQueue.WriteTexture(&copyInfo, dataBuff, dataSize, &layout, &extent);
849+
bool reusedCommandEncoder{ true };
850+
if (!mCommandEncoder) {
851+
reusedCommandEncoder = false;
852+
const wgpu::CommandEncoderDescriptor commandEncoderDescriptor{
853+
.label = "blit_command",
854+
};
855+
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
856+
FILAMENT_CHECK_POSTCONDITION(mCommandEncoder)
857+
<< "Failed to create command encoder for blit?";
858+
}
859+
WebGPUBlitter::BlitArgs blitArgs{
860+
.source = {
861+
.texture = stagingTexture,
862+
.origin = { .x = 0, .y = 0 },
863+
.extent = {.width = width, .height = height},
864+
.mipLevel = 0,
865+
},
866+
.destination = {
867+
.texture = texture->getTexture(),
868+
.origin = {.x=xoffset,.y=yoffset},
869+
.extent = {.width = width, .height = height},
870+
.mipLevel = level,
871+
},
872+
.filter = SamplerMagFilter::NEAREST,
873+
};
874+
for (uint32_t layerIndex{ 0 }; layerIndex < depth; ++layerIndex) {
875+
blitArgs.source.layerOrDepth = layerIndex;
876+
blitArgs.destination.layerOrDepth = layerIndex + zoffset;
877+
mBlitter.blit(mQueue, mCommandEncoder, blitArgs);
878+
}
879+
if (!reusedCommandEncoder) {
880+
const wgpu::CommandBufferDescriptor commandBufferDescriptor{
881+
.label = "blit_command_buffer",
882+
};
883+
const wgpu::CommandBuffer blitCommand{ mCommandEncoder.Finish(
884+
&commandBufferDescriptor) };
885+
FILAMENT_CHECK_POSTCONDITION(blitCommand)
886+
<< "Failed to create command buffer for blit?";
887+
mQueue.Submit(1, &blitCommand);
888+
mCommandEncoder = nullptr;
889+
}
890+
stagingTexture.Destroy();
891+
} else {
892+
// not doing blit (copy byte-by-byte)...
893+
const auto copyInfo { wgpu::TexelCopyTextureInfo{
894+
.texture = texture->getTexture(),
895+
.mipLevel = level,
896+
.origin = { .x = xoffset, .y = yoffset, .z = zoffset, },
897+
.aspect = texture->getAspect(),
898+
}};
899+
mQueue.WriteTexture(&copyInfo, dataBuff, dataSize, &layout, &extent);
900+
}
786901
scheduleDestroy(std::move(pixelBufferDescriptor));
787902
}
788903

@@ -1307,7 +1422,45 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
13071422
const uint8_t sourceLayer, const math::uint2 destinationOrigin,
13081423
Handle<HwTexture> sourceTextureHandle, const uint8_t destinationLevel,
13091424
const uint8_t destinationLayer, const math::uint2 sourceOrigin, const math::uint2 size) {
1310-
// todo
1425+
// TODO uncomment when texture format is taken into account with sampler settings in the
1426+
// blitter
1427+
// bool reusedCommandEncoder{ true };
1428+
// if (!mCommandEncoder) {
1429+
// reusedCommandEncoder = false;
1430+
// const wgpu::CommandEncoderDescriptor commandEncoderDescriptor{
1431+
// .label = "blit_command",
1432+
// };
1433+
// mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
1434+
// FILAMENT_CHECK_POSTCONDITION(mCommandEncoder)
1435+
// << "Failed to create command encoder for blit?";
1436+
// }
1437+
// const WebGPUBlitter::BlitArgs blitArgs{
1438+
// .source = {
1439+
// .texture = handleCast<WebGPUTexture>(sourceTextureHandle)->getTexture(),
1440+
// .origin = {.x = sourceOrigin.x, .y=sourceOrigin.y},
1441+
// .extent = {.width=size.x, .height =size.y},
1442+
// .mipLevel = sourceLevel,
1443+
// .layerOrDepth = sourceLayer,
1444+
// },
1445+
// .destination = {
1446+
// .texture = handleCast<WebGPUTexture>(destinationTextureHandle)->getTexture(),
1447+
// .origin = {.x = destinationOrigin.x, .y=destinationOrigin.y},
1448+
// .extent = {.width=size.x, .height =size.y},
1449+
// .mipLevel = destinationLevel,
1450+
// .layerOrDepth = destinationLayer,
1451+
// },
1452+
// .filter = SamplerMagFilter::NEAREST,
1453+
// };
1454+
// mBlitter.blit(mQueue, mCommandEncoder, blitArgs);
1455+
// if (!reusedCommandEncoder) {
1456+
// const wgpu::CommandBufferDescriptor commandBufferDescriptor{
1457+
// .label = "blit_command_buffer",
1458+
// };
1459+
// const wgpu::CommandBuffer blitCommand{ mCommandEncoder.Finish(&commandBufferDescriptor) };
1460+
// FILAMENT_CHECK_POSTCONDITION(blitCommand) << "Failed to create command buffer for blit?";
1461+
// mQueue.Submit(1, &blitCommand);
1462+
// mCommandEncoder = nullptr;
1463+
// }
13111464
}
13121465

13131466
void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {

filament/backend/src/webgpu/WebGPUDriver.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#define TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
1919

2020
#include "WebGPURenderTarget.h"
21+
#include "webgpu/WebGPUBlitter.h"
2122
#include "webgpu/WebGPUConstants.h"
2223
#include "webgpu/WebGPUMsaaTextureResolver.h"
2324
#include "webgpu/WebGPUPipelineCache.h"
@@ -46,7 +47,6 @@
4647
namespace filament::backend {
4748

4849
class WebGPUSwapChain;
49-
5050
/**
5151
* WebGPU backend (driver) implementation
5252
*/
@@ -85,6 +85,7 @@ class WebGPUDriver final : public DriverBase {
8585
WebGPURenderPassMipmapGenerator mRenderPassMipmapGenerator;
8686
spd::MipmapGenerator mSpdComputePassMipmapGenerator;
8787
WebGPUMsaaTextureResolver mMsaaTextureResolver{};
88+
WebGPUBlitter mBlitter;
8889

8990
struct DescriptorSetBindingInfo{
9091
wgpu::BindGroup bindGroup;

filament/backend/src/webgpu/WebGPUTexture.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ namespace {
109109
case wgpu::TextureFormat::BGRA8UnormSrgb: return wgpu::TextureFormat::BGRA8Unorm;
110110
case wgpu::TextureFormat::BC1RGBAUnormSrgb: return wgpu::TextureFormat::BC1RGBAUnorm;
111111
case wgpu::TextureFormat::BC2RGBAUnormSrgb: return wgpu::TextureFormat::BC2RGBAUnorm;
112-
case wgpu::TextureFormat::BC3RGBAUnormSrgb: return wgpu::TextureFormat::BC3RGBAUnorm;
112+
case wgpu::TextureFormat::BC3RGBAUnormSrgb: return wgpu::TextureFormat::BC3RGBAUnorm;
113113
case wgpu::TextureFormat::BC7RGBAUnormSrgb: return wgpu::TextureFormat::BC7RGBAUnorm;
114114
case wgpu::TextureFormat::ETC2RGB8UnormSrgb: return wgpu::TextureFormat::ETC2RGB8Unorm;
115115
case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb: return wgpu::TextureFormat::ETC2RGB8A1Unorm;

0 commit comments

Comments
 (0)