Skip to content

GPU: Experimental WebGPU SDLGPU Backend#16020

Draft
TheeStickmahn wants to merge 58 commits into
libsdl-org:mainfrom
TheeStickmahn:main
Draft

GPU: Experimental WebGPU SDLGPU Backend#16020
TheeStickmahn wants to merge 58 commits into
libsdl-org:mainfrom
TheeStickmahn:main

Conversation

@TheeStickmahn

@TheeStickmahn TheeStickmahn commented Jul 18, 2026

Copy link
Copy Markdown
  • I confirm that I am the author of this code and release it to the SDL project under the Zlib license. This contribution does not contain code from other sources, including code generated by a Large Language Model ("AI").

Description

This is my SDLGPU WebGPU backend. I've been working on it for about 1 and a half months, and it's reached a level of "finishedness" where I need feedback on how it works.
As of right now it can run 27 out of the 34 SDLGPU examples*, and I've got it running on Windows, Linux, and the Web.
(*most of them need to be modified slightly since WebGPU sucks)

It currently supports most of the SDLGPU standard, however I'm making it a draft PR for a few reasons.

  • 1: It's not finished.
  • 2: Some parts of the backend are shall we say..... oddly designed.
  • 3: I've seen ancient Egyptian hieroglyphics more legible than this code.
  • 4: This codebase is filled with some of the most deranged & demented code I've ever written.
    • 4.5: WebGPU drove me insane, and some of that insanity spilled over into the code comments. They're not very professional.

This is the first time I've ever contributed to any project that's not my own (my hubris knows no bounds), so please don't murder me when I do something stupid.
I'll be happy to help with any issues that'll inevitably be found.
(Note that I'm Swedish, so if you're in the Americas I'll be at least ~6 hours behind you.)
(Also, read the README-WEBGPU.md file. Please.)

(TLDR: WebGPU has tortured me and I will further inflict this pain upon others by forcing them to review my code.)

Before this can actually be considered for merging into SDL, we'll need to do a lot of things.

  • Fully remake the CMake building shenanigans I made.
    • My CMake stuff is exceptionally unpolished, and barely works. It's inconsistent and incoherent, and was created for a fundamentally different purpose than it is being used for, as it was originally just for a video backend. It was also created with a fundamental misunderstanding of how Dawn & Emdawnwebgpu worked.
  • Make sure it works on all platforms.
    • Linux
    • Web. (see the (albeit outdated) web demo: https://thestickmahn.gitlab.io/ihatenamingthings/)
    • Windows
    • MacOS (Don't have a Mac.) (Also, didn't even add MacOS support in the WebGPU video subsystem)
    • iOS (Again, don't have a Mac.) (Also, no video support)
    • Android (Don't have an Android phone.) (Also, no video support)
  • Implement unimplemented features
    • Buffer cycling.
    • Mipmap generation.
    • Numerous other things I've certainly forgotten.
  • Add runtime error handling & validation. The backend currently has very sparse error handling, and allows you to shoot yourself in the foot with ease.
  • We should decide what WebGPU features we should require.
  • Bug fixes.
    • Downloading from buffers doesn't work. Not sure why.
    • Bunch of memory leaks.
    • The shader parser doesn't support all valid WGSL.
      • This is largely due to the parser's crude nature. It doesn't understand some key words, and features such as binding a resource to a struct are unsupported entirely.
    • Stencils are broken. Don't know why.
    • Issues with bindings and bind groups.
      • Textures with non-filterable texture types are implemented horribly. You have to put //!SDLGPU_COMPAT_F32_UNFILTERABLE somewhere within your shader code to use non-filterable texture types. This is bad, as not only does it force unfilterable sampling for ALL samplers regardless of type, it is also confusing, as this is also needed for sampling depth textures.
    • Not really a bug per se, but we should move to one canonical WebGPU implementation.
      • SDL_WGPU currently supports Dawn, Emdawnwebgpu, and wgpu-native. This has only caused issues. My suggestion is to move to Emdawnwebgpu & Dawn and drop wgpu-native support, as it is the least mature out of the three. I'd suggest keeping Dawn since debugging native code is much easier than debugging in a browser.
  • Remove redundant code.
  • Numerous other things I've certainly forgotten.
  • Not necessarily something that's a must, but we should add SPIRV -> WGSL support to SDL_shadercross.

Anyways, I'm gonna go play CloverPit now. Have fun!
(Or don't; I'm not your dad.)

Existing Issue(s)

Resolves issue 10768.

image

Currently only supports Wayland, and it's quite..... bad

This is a DRAFT! Just a small proof-of-concept.

I'll submit a draft PR to the SDL project to see if I should continue
development.

If this is greenlit, I might making consider a WGPU SDL_GPU backend if
wgpu-native is in a good enough state (maybe Dawn if it isn't?)

No promises though! Don't get angry at me if I don't!

Current known issues:
1: Error handling is sparse.
2: It only supports the shared library version of wgpu-native, and it
does that by just loading it whenever it needs to do something
3: wgpu-native is bad and doesn't implement wgpuGetProcAddress so we're
just using SDL_LoadFunction instead. I'd much prefer using the native
wgpuGetProcAddress but alas....
Removed webgpu.h and instead we're defining the types ourselves

Thank you @HamdyElzanqali for pointing out my stupidity lol
wgpu_native really seems to like statically linking itself
(as shown by it not implementing the functionality for dynamic linking),
so I added an option to statically link it alongside SDL.

How I implemented this almost certainly goes against some convention or
guideline in SDL but I'm shooting blind here. I have no idea what to do.
Added Windows support for WGPU, although it is untested since I don't
have a Windows machine to test it on.
Added support for Google's Dawn WebGPU implementation.
You control which one's used via the option "SDL_WGPU_LIB" where it's
either "wgpu-native" or "dawn".
Congratulations; you can now use WebGPU on the Web.

I'll make an example eventually, but it's midnight in Sweden and I gotta
go to bed.
I'm calling it that simply because it's quicker.
Oh my god, there's SO MUCH STUFF TO DO
Keep on buffering Denji

I'm losing my mind
I am so confused here.

This backend is effectively a port of the Vulkan backend, but just with
WebGPU instead, but that means that I'm often including redundant
functionality or attempt to create things that just don't work in WebGPU

This backend will have to be polished, as right now it's about as smooth
as 1 grit sandpaper.
Since WebGPU shader modules can have multiple types of stages
(one shader can have a vertex, fragment, and compute stage in it), I had
to make a workaround.
I'm actually sorta proud of my solution! How it works is that every
"SDL_GPUShader" is actually a "WebGPUShaderReference", which stores the
entrypoint, what stage it is, and a hash of the shader source code.

That hash is used as the key into a hash table in the renderer, which
contains the "actual" shader.

I'm bad at explaining things, so I'd just read the code. It's only two
functions and I hope I made it easy to read.
YOU WILL GET A 200 LINE FUNCTION AND YOU WILL MERGE IT
Or, I mean; I actually started work on this three days ago.
I just hate WebGPU bind groups so much that I've actively procrastinated
on implementing them.

I will be the first to say this: My implementation of bindings in WebGPU
SUCKS. I will not deny that.

I made this quickly, and badly, since if this is not done; I will be
permanently stuck in a state of procrastination, since I cannot work on
multiple things at once.

Something something neurodivergence, something something ADHD.
Sorry for the lack of commits in the past few days.
Uniforms'll require some extra work since WebGPU expects to receive a
buffer while SDL_GPU only provides raw data on the CPU side
I've got it compiling, now I just gotta fix why it crashes.
Why's nothing showing up on the screen 😭
Shit leaks like a sieve
tbf I do just hate WebGPU in general
I'm honestly just procrastinating bind group texture fuckery

+1 to the "WebGPU sucks" counter
Only leaks now are from not destroying devices
Alright, we're actually leaking 928 bytes each device creation, but I
can't figure out why and honestly I'm fine with leaving that
Next up: I'll be remaking the bind system for the third time.
I'm planning on giving each bindable resource a "tag" which we'll use to
prevent recreating the same bind group each frame.
I hate WebGPU bind groups.
Fuuuuuuuuuuuuuck dude this backend const so much money I'm so fucked
Fuck this API it SUCKS
@slouken

slouken commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

After you take a break, can you review the SDL GPU API feature set and see if there's anything that just can't be done with WebGPU?

@icculus

icculus commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

I have not looked at this code yet but I want to say that I admire that you've stuck with this. It's a challenging project!

@TheeStickmahn

Copy link
Copy Markdown
Author

After you take a break, can you review the SDL GPU API feature set and see if there's anything that just can't be done with WebGPU?

I wrote down the things that instantly came to mind. There are almost certainly things I've forgotten but these are the ones that have traumatized me enough to where I see them in my nightmares.
I'll keep updating it with any new ones I find or happen to remember.

# README-ISSUES-AND-INCOMPATIBILITIES.md

@TheSpydog

TheSpydog commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

I haven't reviewed the PR yet but there are a handful things I wanted to mention real quick!

First, it looks like something went wonky with whitespace and formatting in many of the source files, which makes the diff appear a lot larger than it should. Would you mind fixing that up? The repo root has a clang-format file you can use for this.

And then regarding the incompatibilities you noted...

This results in uniform buffers being immutable during a pass. Currently, this is worked around by having a PushGPUUniformData call instantly upload the data, before the command buffer is even submitted.

Writing to a uniform buffer is just a CPU-timelined memcpy on the other backends, so in theory we could do the same here? Maybe there's some gotcha with WebGPU buffer uploads that I'm missing though...

WebGPU only supports single channel R32Uint/Sint/Float textures for read/write.

This is actually the case on other backends too! These R32 formats are the only ones that are guaranteed to be supported for simultaneous RW operations on D3D12, Vulkan, and Metal. (It looks like the wiki page for SDL_GPUTextureFormat was missing this documentation, which I've now corrected.) So at least this isn't any worse than the other backends, and users are expected to query for texture format capabilities anyway since they vary so widely between devices.

Also, and you may already be aware of this, but it's worth noting that SIMULTANEOUS_READ_WRITE is different from READ | WRITE. At least on desktop APIs, plenty of texture formats support both WRITE and READ, it's just that you can't read from and write to a given texture in the same shader, due to race conditions.

A texture's BPR must be 256-byte aligned (for some reason)

The reason is D3D12. (Though what their reasoning is, I don't know.) We have a workaround for this in the D3D12 driver, so maybe that could be adapted for WebGPU?

WebGPU only supports 1x or 4x multisampling. The backend will assert if debug mode is on and 2x or 8x multisampling is used.

Like texture formats, multisampling capabilities vary significantly across devices, so users are expected to check the supported counts via SDL_GPUTextureSupportsSampleCount. So it's no problem if certain values aren't supported.

@TheeStickmahn

TheeStickmahn commented Jul 19, 2026

Copy link
Copy Markdown
Author

@TheSpydog Thank you for the comment! When it comes to the weird whitespace that's entirely my fault. I've accidentally autoformatted them multiple times. I'll try to fix that ASAP!

Addressing your comments about the incompatibilites:

For uniform buffers:

WebGPU is very pedantic about using staging buffers. So much so that if you want to map a buffer, it cannot have ANY other usage than mapping.

Therefore, it is impossible to do a memcpy to the uniform buffer, as WebGPU hates puppies, rainbows, and the concept of love.

However; I did figure out a workaround, although it is quite disgusting.
We could have it so that for each SDL_PushGPUUniformData, we write to a new uniform buffer, and then we just do a switcharoo while the user's not looking.
Since the write happens before the render pass begins, WebGPU's fine with it, and to the end-user it looks normal.
Only issue is that it'll be INSANELY slow. Like, ridiculously slow since we'd also have to generate an entirely new bind group.

For Read/Write textures:

(Note: I am not 100% sure of this, WGSL has barely any documentation)

WGSL does not differentiate between simultaneous read/write & regular read/write.
When you define a storage texture in WGSL, it can have one access symbol (idk what they're actually called)
That symbol can be read, write, or read_write. WGSL treats all R/W textures as if they'll be simultaneous.

For the BPR alignment (damn you D3D12):

A staging buffer in WebGPU cannot have both MAP_READ and MAP_WRITE. Therefore, in order to have the backend automatically do the padding for you, we'd have to copy the data to a buffer with the MAP_READ flag, and then read that on the CPU side, and then we'd (not always, but very likely since the padding'll add more data) have to create a new write buffer which we'd then have to map and write to.

User's write buffer -> Download buffer -> RAM -> Another write buffer -> The actual texture

I considered doing that, but I decided that the additional overhead wasn't worth it, when you could just make the user pad the data before it gets uploaded.

Although, if it's decided that the overhead isn't a concern, I'll change it so that it'll automatically pad it.

@TheeStickmahn

Copy link
Copy Markdown
Author

Note: Currently, how the uniform buffers are set up is that there are 12 buffers created at device creation, (4 per stage), which are then used for all uniform buffer calls. This was a...... questionable design decision, I won't lie.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants