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

Code before:
void WebGPUDriver::setupExternalImage(void* image) {
    //todo
}

TimerQueryResult WebGPUDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
    return TimerQueryResult::ERROR;
}

void WebGPUDriver::setupExternalImage2(Platform::ExternalImageHandleRef image) {
    //todo
}

void WebGPUDriver::setExternalStream(Handle<HwTexture> textureHandle,
        Handle<HwStream> streamHandle) {
    // todo
}

void WebGPUDriver::generateMipmaps(Handle<HwTexture> textureHandle) {
    auto texture = handleCast<WebGPUTexture>(textureHandle);
    assert_invariant(texture);
    wgpu::Texture wgpuTexture = texture->getTexture();
    assert_invariant(wgpuTexture);

    const auto usage = wgpuTexture.GetUsage();
    FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::TextureBinding)
            << "Texture for mipmap generation must have TextureBinding usage.";
    FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::StorageBinding)
            << "Texture for mipmap generation must have StorageBinding usage.";

    const uint32_t totalMipLevels = wgpuTexture.GetMipLevelCount();
    if (totalMipLevels <= 1) {
        return;
    }

    // We will record all passes into a single command encoder.
    wgpu::CommandEncoderDescriptor encoderDesc = {};
    encoderDesc.label = "Mipmap Command Encoder";
    wgpu::CommandEncoder encoder = mDevice.CreateCommandEncoder(&encoderDesc);

    spd::SPDPassConfig spdConfig = {};
    spdConfig.filter = spd::SPDFilter::Average; // Default filter
    spdConfig.targetTexture = wgpuTexture;      // Source and destination are the same
    spdConfig.numMips = totalMipLevels;         // numMips now means total desired levels
    // spdConfig.sourceMipLevel defaults to 0, for a full chain from the base level.
    // spdConfig.halfPrecision can be set based on texture format or global settings if needed.

    mMipMapGenerator.Generate(encoder, wgpuTexture, spdConfig);

    // Finish the encoder and submit all the passes at once.
    wgpu::CommandBufferDescriptor cmdBufferDesc = {};
    cmdBufferDesc.label = "Mipmap Command Buffer";

Review comment (bug):
I am not sure explicitly checking the texture and storage binding support here and _not_ the texture's `supportsMultipleMipLevels()` function is the best approach.

On the one hand, this provides better and more useful error messaging feedback, which is good _(as opposed to just checking `supportsMultipleMipLevels()` and potentially failing without a more concrete reason for why it is not supported)_.

However, these functions can get out of sync; both this `WebGPUDriver::generateMipmaps(...)` and `WebGPUTexture::WebGPUTexture(...)` independently make a determination of whether mipmaps can be generated for the texture. By using the `supportsMultipleMipLevels()` function we keep the code DRY.

Perhaps there is a best of both worlds here, where we reuse the same logic for determining if mipmap generation is supported, and we provide meaningful error messaging at the right place when we try to do said generation when we know we can't?


Code after:

void WebGPUDriver::setupExternalImage(void* image) {
    //todo
}

TimerQueryResult WebGPUDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
    return TimerQueryResult::ERROR;
}

void WebGPUDriver::setupExternalImage2(Platform::ExternalImageHandleRef image) {
    //todo
}

void WebGPUDriver::setExternalStream(Handle<HwTexture> textureHandle,
        Handle<HwStream> streamHandle) {
    // todo
}

void WebGPUDriver::generateMipmaps(Handle<HwTexture> textureHandle) {
    auto texture = handleCast<WebGPUTexture>(textureHandle);
    assert_invariant(texture);
    wgpu::Texture wgpuTexture = texture->getTexture();
    assert_invariant(wgpuTexture);

    const auto usage = wgpuTexture.GetUsage();
    FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::TextureBinding)
            << "Texture for mipmap generation must have TextureBinding usage. "
               "supportsMultipleMipLevels for texture format "
            << static_cast<uint32_t>(wgpuTexture.GetFormat()) << " is "
            << WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(wgpuTexture.GetFormat());
    FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::StorageBinding)
            << "Texture for mipmap generation must have StorageBinding usage. "
               "supportsMultipleMipLevels for texture format "
            << static_cast<uint32_t>(wgpuTexture.GetFormat()) << " is "
            << WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(wgpuTexture.GetFormat());

    const uint32_t totalMipLevels = wgpuTexture.GetMipLevelCount();
    if (totalMipLevels <= 1) {
        return;
    }

    // We will record all passes into a single command encoder.
    wgpu::CommandEncoderDescriptor encoderDesc = {};
    encoderDesc.label = "Mipmap Command Encoder";
    wgpu::CommandEncoder encoder = mDevice.CreateCommandEncoder(&encoderDesc);

    spd::SPDPassConfig spdConfig = { .filter = spd::SPDFilter::Average,
        .targetTexture = wgpuTexture,
        .numMips = totalMipLevels,
        .halfPrecision = false,
        .sourceMipLevel = 0 };