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

Code before:
            color += textureLoad(sourceTexture, coordinates, sampleIndex);
        }
        color /= f32(numberOfSamples);
        return color;
    }
)" };

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,

Review comment (suggestion):
I prefer not to document function purposes, usages, etc. in the implementation (e.g. the cpp file), but in the header instead (unless it is a private function or something, something not present in a header). I think comments on the implementation side should be more specific to implementation details as they apply. 

Code after:
            color += textureLoad(sourceTexture, coordinates, sampleIndex);
        }
        color /= f32(numberOfSamples);
        return color;
    }
)" };

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