Search code examples
rustatomicwgpu-rswgsl

Rust WGPU Atomic Texture Operations


TL;DR:

Is it possible to access textures atomically in WGSL? By atomically, I mean like specified in the "Atomic Operations" section of the documentation of OpenGL's GL_TEXTURE_*.

If not, will changing to GLSL work in WGPU?

Background:

Hi, recently I have been experimenting with WGPU and WGSL, specifically trying to create a cellular automata and storing it's data in a texture_storage_2d.

I was having problems with the fact that accessing the texture asynchronously caused race conditions that made cells disappear (if two cells try to advance to the same point at the same time, they will overwrite one another)

I did some research and couldn't find any solution to my problem in the WGSL spec, but I found something similar in OpenGL and GLSL with OpenGL's GL_TEXTURE_* called atomic operations on textures (which exist AFAIK only for u32 or i32 in WGSL).

Is there something like GL_TEXTURE_* in WGSL? Or is there some alternative that I am not aware of? And is changing to GLSL (while staying with WGPU) the only solution? Will it even work?


Solution

  • The Solution to the Problem

    original reddit discussion

    After doing some tests I confirmed two things:

    1. I managed to successfully implement an atomic texture (code below).
    2. When the texture is very large (my tests were on a 2000 X 2000 texture) the race conditions described do not occur. This can probably be explained by bank conflicts but I haven't researched it enough to know for sure.

    Code

    This following snippet is paraphrased from my original code, it is not tested but should work.

    @group(0) @binding(0) var texture: texture_storage_2d<rg32uint, read_write>;
    
    struct Locks {
        locks: array<array<atomic<u32>, 50>, 50>,
    };
    
    @group(0) @binding(1) var<storage, read_write> locks: Locks;
    
    fn lock(location: vec2<u32>) -> bool {
        let lock_ptr = &locks.locks[location.y][location.x];
        let original_lock_value = atomicLoad(lock_ptr);
        if (original_lock_value > 0u) {
            return false;
        }
        return atomicAdd(lock_ptr, 1u) == original_lock_value;
    }
    
    fn unlock(location: vec2<u32>) {
        atomicStore(&locks.locks[location.y][location.x], 0u);
    }
    

    Ideally, I'd use atomicCompareExchangeWeak instead of that somewhat complex logic in lock, but atomicCompareExchangeWeak didn't seem to work on my machine so I created similar logic myself.

    Just to clarify, reading from the texture should be possible at any time but writing to the texture at location should be done only if lock(location) returned true.

    Don't forget to call unlock after every write and between shader calls to reset the locks :)