Repository: google/filament
File: filament/backend/src/webgpu/WebGPUBlitter.cpp (C++)

Code before:
constexpr std::string_view FRAGMENT_SHADER_SNIPPET_3D_INPUT_TEMPLATE{ R"(
    @fragment
    fn fragmentShaderMain(@builtin(position) position: vec4<f32>) -> {{FRAGMENT_RETURN_ATTRIBUTE_AND_TYPE}} {
        let coordinates: vec3<f32> = getNormalized3dSourceTextureCoordinates(position.xy, sourceTexture);
        return textureSample(sourceTexture, sourceSampler, coordinates);
    }
)" };

constexpr std::string_view FRAGMENT_SHADER_SNIPPET_2D_INPUT_TEMPLATE{ R"(
    @fragment
    fn fragmentShaderMain(@builtin(position) position: vec4<f32>) -> {{FRAGMENT_RETURN_ATTRIBUTE_AND_TYPE}} {
        let coordinates: vec2<f32> = getNormalized2dSourceTextureCoordinates(position.xy, sourceTexture);
        return textureSample(sourceTexture, sourceSampler, coordinates);
    }
)" };

} // namespace

// The WebGPUBlitter is responsible for copying regions of pixels from one texture to another.
// 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:
    // 1. We can safely convert to and from f32 floats in the WGSL shader for all input/output
    //    formats (where WebGPU is doing the conversions). Note that the Metal blitter
    //    implementation does essentially the same. This may not be ideal for certain
    //    signed or unsigned integer formats and may represent a possible optimization opportunity
    //    in the future (or we may run into issues/bugs without and be forced to add the complexity
    //    working with different formats as needed).
    //    One idea for accomplishing this would be to define the shader as a template, do search
    //    and replace of placeholders at the right places to get a shader with the desired formats,
    //    and cache compiled shaders, where the key includes input/output formats. But, we would
    //    like to defer that complexity until warranted (which is maybe never).
    // 2. We do not need to support multisampled output textures (only multisampled input,
    //    essentially doing a resolve in those instances). Again,
    //    the Metal blitter implementation does not support this either.
    //    One idea for accomplishing this, if needed (which is maybe never), would be to define
    //    a version of each fragment shader with or without the need to generate multisampled color.

Review comment (refactor):
I think we should remove this comment and ensure the body of the function reflects this in a self-describing way.

We could consider separating out this body into renderpassBlit, createTextureBindGroup, createRenderpassEncoder, etc. functions.

Code after:
constexpr std::string_view FRAGMENT_SHADER_SNIPPET_3D_INPUT_TEMPLATE{ R"(
    @fragment
    fn fragmentShaderMain(@builtin(position) position: vec4<f32>) -> {{FRAGMENT_RETURN_ATTRIBUTE_AND_TYPE}} {
        let coordinates: vec3<f32> = getNormalized3dSourceTextureCoordinates(position.xy, sourceTexture);
        return textureSample(sourceTexture, sourceSampler, coordinates);
    }
)" };

constexpr std::string_view FRAGMENT_SHADER_SNIPPET_2D_INPUT_TEMPLATE{ R"(
    @fragment
    fn fragmentShaderMain(@builtin(position) position: vec4<f32>) -> {{FRAGMENT_RETURN_ATTRIBUTE_AND_TYPE}} {
        let coordinates: vec2<f32> = getNormalized2dSourceTextureCoordinates(position.xy, sourceTexture);
        return textureSample(sourceTexture, sourceSampler, coordinates);
    }
)" };

} // 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:
    // 1. We can safely convert to and from f32 floats in the WGSL shader for all input/output
    //    formats (where WebGPU is doing the conversions). Note that the Metal blitter
    //    implementation does essentially the same. This may not be ideal for certain
    //    signed or unsigned integer formats and may represent a possible optimization opportunity
    //    in the future (or we may run into issues/bugs without and be forced to add the complexity
    //    working with different formats as needed).
    //    One idea for accomplishing this would be to define the shader as a template, do search
    //    and replace of placeholders at the right places to get a shader with the desired formats,
    //    and cache compiled shaders, where the key includes input/output formats. But, we would
    //    like to defer that complexity until warranted (which is maybe never).
    // 2. We do not need to support multisampled output textures (only multisampled input,
    //    essentially doing a resolve in those instances). Again,
    //    the Metal blitter implementation does not support this either.
    //    One idea for accomplishing this, if needed (which is maybe never), would be to define
    //    a version of each fragment shader with or without the need to generate multisampled color.
    //    Alternatively, as in the varied input/output format problem (see 1), we could also