ladybird icon indicating copy to clipboard operation
ladybird copied to clipboard

Meta+LibWeb/WebGPU: Implement initialization via Dawn

Open ayeteadoe opened this issue 8 months ago • 12 comments

Second attempt (see initial work here) to start implementing WebGPU in Ladybird. This time, instead of implementing our own homegrown WGSL compiler and backend-agnostic Native GPU API layer, I opted for using Google's dawn library, which is a WebGPU native implementation. Just like with our usage of angle and skia, this allows for focusing on the actual LibWeb/WebGPU layer of the work and lowers the maintenance burden.

The first task to do this was to create a vcpkg dawn port. Long term I may try to get the port upstreamed, but for now it's focus is on just building what Ladybird needs for LibWeb/WebGPU.

What I have so far seems like a good initial checkpoint for feedback but in future work, if this is accepted, I plan on resurrecting the remaining LibWeb/WebGPU portions of my initial attempt and getting to the point where we can start to render various 2D and 3D geometries. I won't add any tests to LibWeb until CI has GPU-enabled machines or if we want to use Swiftshader in CI instead (dawn has support for that), but there is a WebGPU Conformance Testsuite that can be used to track and gradually build up support.

Demo

The following web page was used to produce these demos:

webgpu-init.html
<!DOCTYPE html>
<html>

<head>
    <title>Ladybird WebGPU: Initialization</title>
</head>

<body>
<div id="output">
    <h2>WebGPU Instance Information:</h2>
    <div id="instance-info"></div>

    <h2>WebGPU Adapter Information:</h2>
    <div id="adapter-info"></div>

    <h2>WebGPU Device Information:</h2>
    <div id="device-info"></div>
</div>

<script>
    async function initWebGPU() {
        let gpu = null;

        const instanceInfoText = document.getElementById("instance-info");
        try {
            gpu = navigator.gpu;
            if (!gpu) {
                throw Error("WebGPU not supported");
            }

            instanceInfoText.innerHTML += `<p><strong>Preferred Canvas Format:</strong> ${gpu.getPreferredCanvasFormat()}</p>`;
            instanceInfoText.innerHTML += `<p><strong>WGSL Language Features:</strong> ${[...gpu.wgslLanguageFeatures].join(", ")}</p>`;
        } catch (error) {
            instanceInfoText.innerHTML += `<p style="color: red;"><strong>Error:</strong> ${error.message}</p>`;
            return;
        }

        let adapter = null;
        const adapterInfoText = document.getElementById("adapter-info");
        try {
            adapter = await navigator.gpu.requestAdapter({
                powerPreference: "high-performance",
            });
            if (!adapter) {
                throw Error("Unable to retrieve WebGPU adapter");
            }

            const info = adapter.info;
            adapterInfoText.innerHTML += `<p><strong>Vendor:</strong> ${info.vendor}</p>`;
            adapterInfoText.innerHTML += `<p><strong>Architecture:</strong> ${info.architecture}</p>`;
            adapterInfoText.innerHTML += `<p><strong>Device:</strong> ${info.device}</p>`;
            adapterInfoText.innerHTML += `<p><strong>Description:</strong> ${info.description}</p>`;
            adapterInfoText.innerHTML += `<p><strong>Subgroup (Min Size):</strong> ${info.subgroupMinSize}</p>`;
            adapterInfoText.innerHTML += `<p><strong>Subgroup (Max Size):</strong> ${info.subgroupMaxSize}</p>`;
            adapterInfoText.innerHTML += `<p><strong>Supported Features:</strong> ${[...adapter.features].join(", ")}</p>`;

            const limits = adapter.limits;
            const limitsData = [
                ["maxTextureDimension1D", limits.maxTextureDimension1D],
                ["maxTextureDimension2D", limits.maxTextureDimension2D],
                ["maxTextureDimension3D", limits.maxTextureDimension3D],
                ["maxTextureArrayLayers", limits.maxTextureArrayLayers],
                ["maxBindGroups", limits.maxBindGroups],
                ["maxBindGroupsPlusVertexBuffers", limits.maxBindGroupsPlusVertexBuffers],
                ["maxBindingsPerBindGroup", limits.maxBindingsPerBindGroup],
                ["maxDynamicUniformBuffersPerPipelineLayout", limits.maxDynamicUniformBuffersPerPipelineLayout],
                ["maxDynamicStorageBuffersPerPipelineLayout", limits.maxDynamicStorageBuffersPerPipelineLayout],
                ["maxSampledTexturesPerShaderStage", limits.maxSampledTexturesPerShaderStage],
                ["maxSamplersPerShaderStage", limits.maxSamplersPerShaderStage],
                ["maxStorageBuffersPerShaderStage", limits.maxStorageBuffersPerShaderStage],
                ["maxStorageTexturesPerShaderStage", limits.maxStorageTexturesPerShaderStage],
                ["maxUniformBuffersPerShaderStage", limits.maxUniformBuffersPerShaderStage],
                ["maxUniformBufferBindingSize", limits.maxUniformBufferBindingSize],
                ["maxStorageBufferBindingSize", limits.maxStorageBufferBindingSize],
                ["minUniformBufferOffsetAlignment", limits.minUniformBufferOffsetAlignment],
                ["minStorageBufferOffsetAlignment", limits.minStorageBufferOffsetAlignment],
                ["maxVertexBuffers", limits.maxVertexBuffers],
                ["maxBufferSize", limits.maxBufferSize],
                ["maxVertexAttributes", limits.maxVertexAttributes],
                ["maxVertexBufferArrayStride", limits.maxVertexBufferArrayStride],
                ["maxInterStageShaderVariables", limits.maxInterStageShaderVariables],
                ["maxColorAttachments", limits.maxColorAttachments],
                ["maxColorAttachmentBytesPerSample", limits.maxColorAttachmentBytesPerSample],
                ["maxComputeWorkgroupStorageSize", limits.maxComputeWorkgroupStorageSize],
                ["maxComputeInvocationsPerWorkgroup", limits.maxComputeInvocationsPerWorkgroup],
                ["maxComputeWorkgroupSizeX", limits.maxComputeWorkgroupSizeX],
                ["maxComputeWorkgroupSizeY", limits.maxComputeWorkgroupSizeY],
                ["maxComputeWorkgroupSizeZ", limits.maxComputeWorkgroupSizeZ],
                ["maxComputeWorkgroupsPerDimension", limits.maxComputeWorkgroupsPerDimension],
            ];

            adapterInfoText.innerHTML += `<p><strong>Supported Limits:</strong></p>`;
            let limitsTable = "<div style=\"margin-left: 20px;\"><table><tbody>";

            for (const [name, value] of limitsData) {
                limitsTable += `<tr><td>${name}</td><td>${value !== undefined ? value.toLocaleString() : "undefined"}</td></tr>`;
            }

            limitsTable += "</tbody></table></div>";
            adapterInfoText.innerHTML += limitsTable;
        } catch (error) {
            adapterInfoText.innerHTML += `<p style="color: red;"><strong>Error:</strong> ${error.message}</p>`;
            return;
        }

        let device = null;
        const deviceInfoText = document.getElementById("device-info");
        device = await adapter.requestDevice();
        if (!device) {
            throw Error("Unable to retrieve WebGPU device");
        }

        deviceInfoText.innerHTML += `<p><strong>Queue:</strong> ${device.queue}</p>`;
    }

    initWebGPU();
</script>
</body>

</html>

Windows

https://github.com/user-attachments/assets/29bb2de6-1b46-4601-a758-efbbfc4e696b

macOS

https://github.com/user-attachments/assets/aa8291f1-bf87-4306-ab1a-952a3815a4f0

Linux

webgpu-init-linux.webm

ayeteadoe avatar Aug 09 '25 01:08 ayeteadoe

Fwiw it's gonna break Android and FreeBSD builds, but I'm not sure how much that matters

cqundefine avatar Aug 09 '25 10:08 cqundefine

@Olekoop do you think you could try to see if Android can build this dawn port as-is? My assumption is yes given the chromium build infra used to build dawn does support android. Also @cqundefine you could try FreeBSD too, although I'm more confident in the former being relatively trivial to get working than the latter

ayeteadoe avatar Aug 09 '25 11:08 ayeteadoe

@ayeteadoe Yeah, it might take me a while because I don't have access to my usual setup, but I can take a look at what would it take to get this working on FreeBSD

cqundefine avatar Aug 09 '25 11:08 cqundefine

@ayeteadoe I tried getting the build of dawn working on my FreeBSD VM with no luck, the GN build would require way too much patching for me to do right now.

What I also noticed is that dawn has an official CMake build which we should probably make use of in the overlay port instead of the GN one. It would allow to drop a bunch of patches because everything can be configured with arguments and would eliminate the need to create the custom targets as it provides its own. Using the CMake build would also allow me to get it building on FreeBSD with minimal patches.

cqundefine avatar Aug 09 '25 19:08 cqundefine

@cqundefine based on what I've seen for google/chromium based vcpkg ports, most use the gn build. Also this port would eventually be used for the skia dawn feature (not relevant to Ladybird, but is relevant for eventually getting this upstreamed) and skia uses gn.

Maybe @ADKaster has some thoughts, but in my opinion it would be ideal to just leave FreeBSD support out for now until it is patched to be buildable via gn. Maybe I could add some stub infrastructure so platforms without dawn support can still build LibWeb? But not sure of the value for that if its only FreeBSD that needs the extra work.

ayeteadoe avatar Aug 09 '25 19:08 ayeteadoe

One more rebase incoming as there are a few things I missed in the dawn vcpkg port (stray debug comments, incomplete usage/description, no features exposed) and I am cleaning that up now

ayeteadoe avatar Aug 10 '25 16:08 ayeteadoe

@ayeteadoe I haven't compiled your PR yet for Android but I believe that finding Dawn via PkgConfig is a no go when compiling for Android. Usually PkgConfig tries to find host's libraries and that will obviously fail. It would be nice if there was a cmake find file. I woudn't mind breaking Android support for now.

Olekoop avatar Aug 11 '25 20:08 Olekoop

Just wanted to note that I've gotten the dawn port to build using GN on FreeBSD, tho I'm gonna submit the patches after this PR and the vcpkg PR get merged to not complicate stuff.

cqundefine avatar Aug 12 '25 09:08 cqundefine

Going to slightly extend this and implement requesting the GPUDevice as well. That means that this initial PR will cover the full Initialization section of the spec, this will be nice because then a bunch of subsequent work that only depends on the GPUDevice can be done in parallel, split into multiple PRs, as They'd just need to implement their respective IDL methods on GPUDevice and then their own IDL.

The final version of the test/demo page will be 3 sections:

  1. WebGPU Instance Information: Will show the preferred canvas format and the WGSL language features
  2. WebGPU Adapter Information: Will show all fields from GPUAdapterInfo, GPUSupportedLimits, and GPUSupportedFeatures
  3. WebGPU Device Information: Will just show that the GPUQueue was created, all the other attributes should be equivalent to Adapter above (unless there is a difference between limits and features of the physical device (GPUAdapter) vs logical device (GPUDevice), I'll find out soon

ayeteadoe avatar Aug 12 '25 14:08 ayeteadoe

I wanted to note that I have managed to get Dawn compiled and get WebGPU running on Android. I will submit the patches once this PR gets merged. Screenshot_20250815-143741_Ladybird

Olekoop avatar Aug 15 '25 12:08 Olekoop

Your pull request has conflicts that need to be resolved before it can be reviewed and merged. Make sure to rebase your branch on top of the latest master.

github-actions[bot] avatar Aug 19 '25 20:08 github-actions[bot]

Your pull request has conflicts that need to be resolved before it can be reviewed and merged. Make sure to rebase your branch on top of the latest master.

github-actions[bot] avatar Sep 01 '25 19:09 github-actions[bot]

Your pull request has conflicts that need to be resolved before it can be reviewed and merged. Make sure to rebase your branch on top of the latest master.

github-actions[bot] avatar Sep 16 '25 08:09 github-actions[bot]

Your pull request has conflicts that need to be resolved before it can be reviewed and merged. Make sure to rebase your branch on top of the latest master.

github-actions[bot] avatar Sep 18 '25 14:09 github-actions[bot]

@cqundefine @Olekoop See first commit for context into the dawn port changes, but both Android and FreeBSD should be working with the current state of the PR now. If you have time to try it out let me know if there are any issues and I can adjust to get both of them working

ayeteadoe avatar Sep 19 '25 05:09 ayeteadoe

@ayeteadoe I have managed to compile it for Android and it works without needing to modify your code.

Olekoop avatar Sep 21 '25 18:09 Olekoop

The CMake-based dawn port has been upstreamed into vcpkg, so I'll adjust to remove the overlay port given it should be working on all the platforms, but it can always be overlayed again if we run into issues.

ayeteadoe avatar Sep 27 '25 00:09 ayeteadoe

@ayeteadoe I have submitted a vcpkg PR to fix Dawn on FreeBSD, once this gets merged it will most probably break it temporarily but that's fine by me.

cqundefine avatar Sep 28 '25 15:09 cqundefine

Your pull request has conflicts that need to be resolved before it can be reviewed and merged. Make sure to rebase your branch on top of the latest master.

github-actions[bot] avatar Oct 03 '25 11:10 github-actions[bot]

Your pull request has conflicts that need to be resolved before it can be reviewed and merged. Make sure to rebase your branch on top of the latest master.

github-actions[bot] avatar Oct 19 '25 14:10 github-actions[bot]

Going to close this for now as I have some other PRs in the queue I want to focus on first and this one keeps getting merge conflicts. Thanks to all who helped test on Android/BSDs.

I've also filed https://github.com/LadybirdBrowser/ladybird/issues/6514 and will probably wait to re-open this until we get some more explicit discussion involving maintainers on the desired approach/direction to take WebGPU.

ayeteadoe avatar Oct 20 '25 01:10 ayeteadoe