Skip to content

WebGPU (also emdawnwebgpu) missing Anisotropic filtering - fix #24362

@Rippletank

Description

@Rippletank

Version of emscripten/emsdk:

emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 4.0.5 (53b38d0)
clang version 21.0.0git (https:/github.com/llvm/llvm-project 553da9634dc4bae215e6c850d2de3186d09f9da5)
Target: wasm32-unknown-emscripten

I was trying to workout why anisotropic filtering seemed to be either very subtle or wasn't working.

Turns out, the the javascript bridging code doesn't carry across this value to the javascript object. Adding a line to the javascript source unit fixed it but its a quick and dirty fix for now. I don't know the inner workings of emscripten so I'll just document what I found in the hopes that someone more knowledgeable can quickly fix it permanently.

In C++, you can create a wgpu::SamplerDescriptor with a .maxAnisotropy field which is a uint16_t.

This code appears to be referencing webgpu_cpp.h line 1519:

    struct SamplerDescriptor {
        ChainedStruct const * nextInChain = nullptr;
        char const * label = nullptr;
        AddressMode addressModeU = AddressMode::ClampToEdge;
        AddressMode addressModeV = AddressMode::ClampToEdge;
        AddressMode addressModeW = AddressMode::ClampToEdge;
        FilterMode magFilter = FilterMode::Nearest;
        FilterMode minFilter = FilterMode::Nearest;
        MipmapFilterMode mipmapFilter = MipmapFilterMode::Nearest;
        float lodMinClamp = 0.0f;
        float lodMaxClamp = 32.0f;
        CompareFunction compare = CompareFunction::Undefined;
        uint16_t maxAnisotropy = 1;
    };

Which matches line 2860 of webgpu.h.

When this descriptor is sent to wgpu::Device .CreateSampler() function it is translated in javascript using the following code in the generated js file:

var _wgpuDeviceCreateSampler = (deviceId, descriptor) => {
 var desc;
  if (descriptor) {
    assert(descriptor);
    assert(SAFE_HEAP_LOAD(((descriptor) >> 2) * 4, 4, 1) === 0);
    desc = {
      "label": undefined,
      "addressModeU": WebGPU.AddressMode[SAFE_HEAP_LOAD((((descriptor) + (8)) >> 2) * 4, 4, 1)],
      "addressModeV": WebGPU.AddressMode[SAFE_HEAP_LOAD((((descriptor) + (12)) >> 2) * 4, 4, 1)],
      "addressModeW": WebGPU.AddressMode[SAFE_HEAP_LOAD((((descriptor) + (16)) >> 2) * 4, 4, 1)],
      "magFilter": WebGPU.FilterMode[SAFE_HEAP_LOAD((((descriptor) + (20)) >> 2) * 4, 4, 1)],
      "minFilter": WebGPU.FilterMode[SAFE_HEAP_LOAD((((descriptor) + (24)) >> 2) * 4, 4, 1)],
      "mipmapFilter": WebGPU.MipmapFilterMode[SAFE_HEAP_LOAD((((descriptor) + (28)) >> 2) * 4, 4, 1)],
      "lodMinClamp": SAFE_HEAP_LOAD_D((((descriptor) + (32)) >> 2) * 4, 4, 0),
      "lodMaxClamp": SAFE_HEAP_LOAD_D((((descriptor) + (36)) >> 2) * 4, 4, 0),
      "compare": WebGPU.CompareFunction[SAFE_HEAP_LOAD((((descriptor) + (40)) >> 2) * 4, 4, 1)],
    };
    var labelPtr = SAFE_HEAP_LOAD((((descriptor) + (4)) >> 2) * 4, 4, 1);
    if (labelPtr) desc["label"] = UTF8ToString(labelPtr);
  }
  var device = WebGPU.mgrDevice.get(deviceId);
  return WebGPU.mgrSampler.create(device.createSampler(desc));
};

The maxAnistropy field is missing. Editing the generated js file, to add maxAnisotropy to the creation code for the javascript object actually make the anisotropy work perfectly:

desc = {
    "label": undefined,
    "addressModeU": WebGPU.AddressMode[SAFE_HEAP_LOAD((((descriptor) + (8)) >> 2) * 4, 4, 1)],
    "addressModeV": WebGPU.AddressMode[SAFE_HEAP_LOAD((((descriptor) + (12)) >> 2) * 4, 4, 1)],
    "addressModeW": WebGPU.AddressMode[SAFE_HEAP_LOAD((((descriptor) + (16)) >> 2) * 4, 4, 1)],
    "magFilter": WebGPU.FilterMode[SAFE_HEAP_LOAD((((descriptor) + (20)) >> 2) * 4, 4, 1)],
    "minFilter": WebGPU.FilterMode[SAFE_HEAP_LOAD((((descriptor) + (24)) >> 2) * 4, 4, 1)],
    "mipmapFilter": WebGPU.MipmapFilterMode[SAFE_HEAP_LOAD((((descriptor) + (28)) >> 2) * 4, 4, 1)],
    "lodMinClamp": SAFE_HEAP_LOAD_D((((descriptor) + (32)) >> 2) * 4, 4, 0),
    "lodMaxClamp": SAFE_HEAP_LOAD_D((((descriptor) + (36)) >> 2) * 4, 4, 0),
    "compare": WebGPU.CompareFunction[SAFE_HEAP_LOAD((((descriptor) + (40)) >> 2) * 4, 4, 1)],

    "maxAnisotropy": SAFE_HEAP_LOAD((((descriptor) + (44)) >> 2) * 4, 2, 1) //<===== added line=====
};

To make a fix that works across compiles, I edited the emsdk\upstream\emscripten\src\lib\libwebgpu.js and added the extra code at line 1021 but the syntax there is different to generated output js code and I don't understand exactly what should go there to be correct:

desc = {
        "label": undefined,
        "addressModeU": WebGPU.AddressMode[
            {{{ gpu.makeGetU32('descriptor', C_STRUCTS.WGPUSamplerDescriptor.addressModeU) }}}],
        "addressModeV": WebGPU.AddressMode[
            {{{ gpu.makeGetU32('descriptor', C_STRUCTS.WGPUSamplerDescriptor.addressModeV) }}}],
        "addressModeW": WebGPU.AddressMode[
            {{{ gpu.makeGetU32('descriptor', C_STRUCTS.WGPUSamplerDescriptor.addressModeW) }}}],
        "magFilter": WebGPU.FilterMode[
            {{{ gpu.makeGetU32('descriptor', C_STRUCTS.WGPUSamplerDescriptor.magFilter) }}}],
        "minFilter": WebGPU.FilterMode[
            {{{ gpu.makeGetU32('descriptor', C_STRUCTS.WGPUSamplerDescriptor.minFilter) }}}],
        "mipmapFilter": WebGPU.MipmapFilterMode[
            {{{ gpu.makeGetU32('descriptor', C_STRUCTS.WGPUSamplerDescriptor.mipmapFilter) }}}],
        "lodMinClamp": {{{ makeGetValue('descriptor', C_STRUCTS.WGPUSamplerDescriptor.lodMinClamp, 'float') }}},
        "lodMaxClamp": {{{ makeGetValue('descriptor', C_STRUCTS.WGPUSamplerDescriptor.lodMaxClamp, 'float') }}},
        "compare": WebGPU.CompareFunction[
            {{{ gpu.makeGetU32('descriptor', C_STRUCTS.WGPUSamplerDescriptor.compare) }}}],
	  "maxAnisotropy": SAFE_HEAP_LOAD((((descriptor) + (44)) >> 2) * 4, 2, 1) //<== My Hacky added code ==
      };

This works for me but I don't fully understand the implications.

Anyway, this is to put the bug on record along with the apparently simple solution. I understand WebGPU support is moving to emdawnwebgpu, which I haven't tried yet, but looking at their Github repo it has the same issue.

And, again for the record, the difference is not subtle at all. In fact, I find it hard to believe I am the first to notice this so please do redirect me if I am in the wrong place with this.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions