Basics
Once you've installed the extension from the home page, load it unsandboxed in your mod of choice. Run the "WebGPU available" block, and if it returns true you should be good to go. If it's not available, you may need to enable a browser flag or your browser may be incompatible. Check your browser's compatibility with webgpu at webgpu.io. This is a fairly new api, so not all browsers support it.
If your browser doesn't support webgpu, you're pretty much out of luck without switching to another browser.
Now that everything is (hopefully) working correctly, we can start making our first shader. The def shader hat is used to create a new shader.
Def shader [Shader name] using bind group layout [Bind group layout] :: hat #4287f5
The def shader hat has 2 arguments. The first, the shader name, is how you run the shader(see the run shader block). This must only use the text input, you can't add blocks. You can name it whatever you want. Then, there's the bind group layout name(doesn't allow inputs), explained later. You can attach code to the hat, which is what makes up your shader. Be careful, as not all blocks are supported. You can see the list of supported blocks here. Let's make a basic shader that takes in a list and returns the list with every item doubled. We can use the Compute shader C block to define what our shader will do when it is run:
Compute shader with workgroup size [[1\]] { ... code goes here :: #166af2 } :: #4287f5
Don't worry about workgroup size quite yet. It's explained here
Let's define an array in our compute shader, named "data". First we need to make the type so we can construct it. We will have an array of 3 floats, which in blocks looks like this:
Create type (array v) of (Base type (f32 v) :: #4287f5), length\(array only!\) [3] :: reporter #4287f5
We can use the Construct type block to create our array:
Construct type (Create type (array v) of (Base type (f32 v) :: #4287f5), length\(array only!\) [3] :: reporter #4287f5) with values (Function arg input [1.0], next (Function arg input [2.0], next (Function arg input [3.0], next () :: reporter #4287f5) :: reporter #4287f5) :: reporter #4287f5) :: reporter #4287f5
Or, if you want to be fancy and decrease block count, you can replace your function arg inputs with your inputs like this:
Construct type (Create type (array v) of (Base type (f32 v) :: #4287f5), length\(array only!\) [3] :: reporter #4287f5) with values [1.0, 2.0, 3.0] :: reporter #4287f5
This is purely a visual change. Now we can declare our variable:
Compute shader with workgroup size [[1\]] { Declare (var v) variable as [data] with value (Construct type (Create type (array v) of (Base type (f32 v) :: #4287f5), length\(array only!\) [3] :: reporter #4287f5) with values [1.0, 2.0, 3.0] :: reporter #4287f5): (auto v) :: #4287f5 } :: #4287f5
This will make a variable called "data" that we can use. The "var" option says that we can change our variable as much as we want. The "auto" option at the end means that we don't need to specify the variable type here, which saves some time for us. To get a value in this array we can use the In array get block:
In array [data] get [index] :: reporter #4287f5
We can perform operations on this value using the variable operations block:
Variable (In array [data] get [0] :: reporter #4287f5) (= v) [15] :: #4287f5
We don't want to have to have a bunch of these blocks, so we can use the for loop:
For [variable name] in [start], [end] { } :: #4287f5
This isn't the best way to do this! Ideally you would dispatch a workgroup for each item, but this is just a demo so you don't need to worry about that.
This will create a new variable with the name specified in variable name, with an initial value of start. It will run the code in the c, then increment variable name. If the variable is more than end, it stops. We can use this to go through our array, since we can read the value of the variable we are incrementing using the get variable block:
Def shader [doublingShader] with bind group layout [] :: hat #4287f5 Compute shader with workgroup size [[1\]] { Declare (var v) variable as [data] with value (Construct type (Create type (array v) of (Base type (f32 v) :: #4287f5), length\(array only!\) [3] :: reporter #4287f5) with values [1.0, 2.0, 3.0] :: reporter #4287f5): (auto v) :: #4287f5 For [i] in [0], [2] { Variable (In array [data] get (get variable [i] :: reporter #4287f5) :: reporter #4287f5) (= v) ((In array [data] get (get variable [i] :: reporter #4287f5) :: reporter #4287f5) * [2.0] :: math) :: #4287f5 } :: #4287f5 } :: #4287f5WGSL is 0 indexed!
Great! Our shader is done, and we can double the numbers in the array. Now we just need to run it. However, first we need to compile our shaders so they can be run. You can do this with the compile shaders block:
Def shader [doublingShader] with bind group layout [] :: hat #4287f5 Compute shader with workgroup size [[1\]] { Declare (var v) variable as [data] with value (Construct type (Create type (array v) of (Base type (f32 v) :: #4287f5), length\(array only!\) [3] :: reporter #4287f5) with values [1.0, 2.0, 3.0] :: reporter #4287f5): (auto v) :: #4287f5 For [i] in [0], [2] { Variable (In array [data] get (get variable [i] :: reporter #4287f5) :: reporter #4287f5) (= v) ((In array [data] get (get variable [i] :: reporter #4287f5) :: reporter #4287f5) * [2.0] :: math) :: #4287f5 } :: #4287f5 } :: #4287f5 when green flag clicked compile shaders :: #4287f5
Ideally you should only compile your shaders when you make changes, or when your project is run for the first time as it can be pretty slow depending on the number and length of your shaders. We are doing it when the flag is clicked here for simplicity.
Now when you press the flag your shader will be compiled! Check the "Error" block and look for any errors or warnings. If you see something along the lines of "invalid input", it means you put a block where a block isn't allowed to go. If you see something like "invalid opcode", it means you used an unsupported block in your shader. If you see any other errors, dm derpygamer2142 on discord. Now we can finally run our shader! This is done using the run shader block:
Run shader [shader name] using bind group [bind group name] dimensions x: [x dim] y: [y dim] z: [z dim] :: #4287f5
Our shader name is whatever our shader is called, and the args is explained later. The dimensions are the number of workgroups on each axis, which is explained here. For now, let's just dispatch 1 workgroup per axis.
Def shader [doublingShader] with bind group layout [] :: hat #4287f5 Compute shader with workgroup size [[1\]] { Declare (var v) variable as [data] with value (Construct type (Create type (array v) of (Base type (f32 v) :: #4287f5), length\(array only!\) [3] :: reporter #4287f5) with values [1.0, 2.0, 3.0] :: reporter #4287f5): (auto v) :: #4287f5 For [i] in [0], [2] { Variable (In array [data] get (get variable [i] :: reporter #4287f5) :: reporter #4287f5) (= v) ((In array [data] get (get variable [i] :: reporter #4287f5) :: reporter #4287f5) * [2.0] :: math) :: #4287f5 } :: #4287f5 } :: #4287f5 when green flag clicked compile shaders :: #4287f5 Run shader [doublingShader] with bind group [] dimensions x: [1] y: [1] z: [1] :: #4287f5
Now you can click the green flag and hopefully have any errors! If you see something mentioning an error parsing wgsl, then something in your shader is wrong. Otherwise you should be good! Now, you may be thinking "this is a great shader, finally I can easily and quickly double my hardcoded list of 3 numbers, but it would be great if I could give the shader an array to double instead of it being hardcoded!". Well you're in luck, because you can do this in the form of
Shader resources
Your shader will be pretty much useless if it can't interact with the project, so you need to define some stuff to be able to provide your shader with data:
Create bind group layout called [bindGroupLayoutName] { Add bind group layout entry with binding [bindingNumber] for type (bindingType v) and descriptor [bindGroupLayoutEntryDescriptor] :: #4287f5 } :: color #4287f5
Bind groups and bind group layouts
To provide our shader with inputs, we need to first describe what inputs the shader is going to have, otherwise the gpu is essentially going to need to guess based on your shader's code, which isn't ideal. The things that describe how your inputs are used are called "bind group layouts". They tell the gpu what kind of resource is going in what binding. Here's what the extension's blocks to do this look like:
- bindGroupLayoutName - This is the name that you will use to reference your bind group layout. All your bind group layout entry blocks go inside this block.
- bindingNumber - The binding number you are describing. A binding number is a slot in the list of possible inputs you can give your shader.
- bindingType - The type of resource you are going to put in this slot. Here's the possible values for this:
- buffer - Essentially just a list, but instead of items it sometimes has individual bytes. A buffer can only have numbers in it.
- bindGroupLayoutEntryDescriptor - Fancy name, fairly simple input. Takes an input of a entry descriptor block(explained below). This input describes how this slot will be used, and the type of descriptor depends on the bindingType.
Create bind group layout block's inputs
Bind group layout entry block's inputs
Entry descriptor blocks
Which of these blocks to use depends on the bindingType input.
Buffer layout entry descriptor with usage type (usageType v) :: #4287f5 reporter
That's a lot, hopefully our usage of these will help explain a little more. We only need one buffer, which we will use for passing data to and from our shader.
Hey! This isn't best practice, usually you would have seperate buffers for passing data to and from the gpu.when flag clicked Create bind group layout called [doublingBGL] { Add bind group layout entry with binding [0] for type (buffer v) and descriptor (Buffer layout entry descriptor with usage type (storage v) :: #4287f5) :: #4287f5 } :: color #4287f5 compile shaders :: #4287f5 run shader [doublingShader] with bind group [] dimensions x: [1] y: [1] z: [1] :: #4287f5
Now we need to alter our shader's code to use inputs. We set the bindGroupLayout input in the hat to the name of our bindGroupLayout, which is doublingBGL here.
Def shader [doublingShader] with bind group layout [doublingBGL] :: hat #4287f5
Now we want to be able to use the first buffer(which will contain the input array) and double the inputs. We can do this with the bind buffer block in our shader:
Bind shader resource #[binding number] to variable [variable] with settings [variable usage settings] type (variable type v) :: #4287f5
The bind number is the number that is provided in the bind buffer block(explained later). The variable is how you can reference the data in your shader. The variable usage settings come from the variable usage block:
Variable usage (usage v) next [] :: reporter #4287f5
There are a lot of options, here's what they do(note: this isn't very well documented, this is what I think they do. Please report issues):
- read - You can read from this variable
- write - You can write to this variable
- read_write - You can read and write. Not sure what the difference is between this and the above two, probably some internal optimization stuff.
- function - You can only access it from the same function that it is declared? Not too sure.
- private - I assume you can only access this in the same scope it is declared, I have no idea.
- workgroup - This one is pretty cool, it's accessible in each workgroup, regardless of local invocation.
- uniform - This is used with the uniform usage type in a bind group layout entry.
- storage - This is used with the storage usage type in a bind group layout entry.
These settings describe how you can access the variable:
These settings describe when you can access the variable:
Note: The order of these usage settings does actually matter! Put the usage of how you will access the variable first(for example: storage), followed by the access settings.
Back to the Bind shader resource block, the last input in this block is the variable type. This can be any type you want, but for our case it will be an array of floats. We want to be able to read and write to it, so we give it that usage, and since it's a storage buffer we give it the storage usage type. Here's what our block looks like:
Bind shader resource #[0] to variable [data] with settings (Variable usage (read_write v) next (Variable usage (storage v) next [] :: reporter #4287f5) :: reporter #4287f5) type (Create type (array v) of (Base type (f32 v) :: #4287f5), length\(array only!\) [] :: reporter #4287f5) :: #4287f5
Now that we have our input buffer, we can get rid of our other array, leading our shader code to look like this:
Def shader [doublingShader] with resources [doublingBGL] :: hat #4287f5 Bind shader resource #[0] to variable [data] with settings (Variable usage (read_write v) next (Variable usage (storage v) next [] :: reporter #4287f5) :: reporter #4287f5) type (Create type (array v) of (Base type (f32 v) :: #4287f5), length\(array only!\) [] :: reporter #4287f5) :: #4287f5 Compute shader with workgroup size [[1\]] { For [i] in [0], [2] { Variable (In array [data] get (get variable [i] :: reporter #4287f5) :: reporter #4287f5) (= v) ((In array [data] get (get variable [i] :: reporter #4287f5) :: reporter #4287f5) * [2.0] :: math) :: #4287f5 } :: #4287f5 } :: #4287f5
(an image will be provided when all the code is done)
Alright, our shader is prepped to recieve some data, now we just need to give it that data. First, we create our data, in this case buffers.
Create buffer called [bufferName] with size\(in bytes\) [size] and usage flags [usageFlags] :: #4287f5
As mentioned before, a buffer is basically just a list except it only allows numbers, and instead of items in the list you can have individual bytes. Here's how you use the above block:
- bufferName - The name you will use to reference the buffer.
- size - The number of bytes in the buffer. If you don't know what a byte is, god help you.
- usageFlags - How the buffer will be used. These are individual flags from the Buffer usage block, that you combine using the "usage () | () " block. Here's some more info on that...
Buffer usage (usage v) :: reporter #4287f5 Usage [a] | [b] :: reporter #4287f5
- COPY_SRC - This can be the source buffer in the copy data block.
- COPY_DST - This can be the destination buffer in the copy data block, and you can write data to it using the write data block.
- MAP_READ - You can read from this buffer using the read buffer block.
- MAP_WRITE - You can write to this buffer when it's been mapped. Currently unimplemented, so dw about it
- QUERY_RESOLVE - No clue, but I haven't needed to use it anywhere and there's nothing implemented that uses it so dw about this
- STORAGE - This buffer can be written and read from by your shader.
- UNIFORM - This buffer will only be read from by your shader.
Buffer usage block - This is an individual flag to describe how a buffer will be used. It's possible values are:
Usage | block - This just lets you string together usage flags. Put a flag or another usage block in the inputs.
We'll need 2 buffers: One for our buffer to read/write from and one get our data from the gpu to our project. Here's what this looks like:
Create buffer called [inputBuffer] with size\(in bytes\) [12] and usage flags (Usage (Usage (Buffer usage (STORAGE v) :: #4287f5) | (Buffer usage (COPY_SRC v) :: #4287f5) :: #4287f5) | (Buffer usage (COPY_DST v) :: #4287f5) :: #4287f5) :: #4287f5 Create buffer called [readBuffer] with size\(in bytes\) [12] and usage flags (Usage (Buffer usage (MAP_READ v) :: #4287f5) | (Buffer usage (COPY_DST v) :: #4287f5) :: #4287f5) :: #4287f5
Our first buffer, inputBuffer, has the flags STORAGE(so our shader can read and write from it), COPY_DST(so we can write the data we want our shader to double to it), and COPY_SRC(so we can copy the buffer to the read buffer after running our shader)
Our second buffer, readBuffer, has the flags COPY_DST(so we can copy the data from inputBuffer to it), and MAP_READ(so we can read from it).
To actually write the data to inputBuffer, we can use the writeData block.
Write [amount] elements of data from arraybuffer [array] to buffer [bufferName] from offset [off1] to offset [off2] :: #4287f5 Create arraybuffer and view called [name] from array [data] of type (dataType v) :: #4287f5
- amount - For a typedArray(currently the only available kind of array), this is in elements. Otherwise in bytes.
- array - The arraybuffer to take the data from.
- bufferName - The buffer to write to.
- off1 - The offset to start reading from, see note in amount.
- off2 - The offset to start writing to, see note in amount.
- name - The name to use when creating the objects.
- data - A stringified array of numbers. You can make this using a json extension.
- dataType - A typedArray type. See the mdn reference.
Write data - Writes data
Create arraybuffer - this adds a new arraybuffer and arraybuffer view from the given array. More information at the block list entry.
Since we're using float32s, and there's 4 bytes in 32 bits, and we are using a buffer length of 12 bytes, our array will have a length of 3. Here's what our writing code looks like:
Create arraybuffer and view called [inputArrayBuffer] from array [\[1, 2, 3\]] of type (Float32Array v) :: #4287f5 Write [3] elements of data from arraybuffer [inputArrayBuffer] to buffer [inputBuffer] from offset [0] to offset [0] :: #4287f5
Adding all of that to our existing code and we have
when flag clicked Create bind group layout called [doublingBGL] { Add bind group layout entry with binding [0] for type (buffer v) and descriptor (Buffer layout entry descriptor with usage type (storage v) :: #4287f5) :: #4287f5 } :: color #4287f5 Create buffer called [inputBuffer] with size\(in bytes\) [12] and usage flags (Usage (Usage (Buffer usage (STORAGE v) :: #4287f5) | (Buffer usage (COPY_SRC v) :: #4287f5) :: #4287f5) | (Buffer usage (COPY_DST v) :: #4287f5) :: #4287f5) :: #4287f5 Create buffer called [readBuffer] with size\(in bytes\) [12] and usage flags (Usage (Buffer usage (MAP_READ v) :: #4287f5) | (Buffer usage (COPY_DST v) :: #4287f5) :: #4287f5) :: #4287f5 Create arraybuffer and view called [inputArrayBuffer] from array [\[1, 2, 3\]] of type (Float32Array v) :: #4287f5 Write [3] elements of data from arraybuffer [inputArrayBuffer] to buffer [inputBuffer] from offset [0] to offset [0] :: #4287f5 compile shaders :: #4287f5 run shader [doublingShader] with bind group [] dimensions x: [1] y: [1] z: [1] :: #4287f5
We're almost done, we just need to pass inputBuffer to our shader and then we can read the data after we've run the shader. We can do this by creating a bind group. A bind group essentially passes data to the bindings you described in your bind group layout. Here's what it looks like:
Create bind group called [bindGroupName] using layout [bindGroupLayoutName] { Add bind group entry with binding [bindingNumber] of type (bindingType v) using resource named [resourceName] :: #4287f5 } :: #4287f5
- bindGroupName - The name that we will use to reference this bind group
- bindGroupLayoutName - The bind group layout to use in this bind group. We'll be using the one we defined earlier, doublingBGL
- bindingNumber - The binding slot to bind the resource to
- bindingType - The type of resource to bind to this slot. In our case, it's a buffer.
- resourceName - The name of the resource to bind to this slot. In our case, we're binding the buffer called inputBuffer
Create bind group block - Similar to the create bind group layout block where we put the entries inside of it, but this actually binds the resources to the given slots.
Bind group entry block - Adds a binding entry to the bind group it's inside of, this binds a resource to the given slot.
Our bind group looks like this:
Create bind group called [doublingBG] using layout [doublingBGL] { Add bind group entry with binding [0] of type (buffer v) using resource named [inputBuffer] :: #4287f5 } :: #4287f5
We can throw that in our code, and update the run shader block to use our new bind group:
when flag clicked Create bind group layout called [doublingBGL] { Add bind group layout entry with binding [0] for type (buffer v) and descriptor (Buffer layout entry descriptor with usage type (storage v) :: #4287f5) :: #4287f5 } :: color #4287f5 Create buffer called [inputBuffer] with size\(in bytes\) [12] and usage flags (Usage (Usage (Buffer usage (STORAGE v) :: #4287f5) | (Buffer usage (COPY_SRC v) :: #4287f5) :: #4287f5) | (Buffer usage (COPY_DST v) :: #4287f5) :: #4287f5) :: #4287f5 Create buffer called [readBuffer] with size\(in bytes\) [12] and usage flags (Usage (Buffer usage (MAP_READ v) :: #4287f5) | (Buffer usage (COPY_DST v) :: #4287f5) :: #4287f5) :: #4287f5 Create arraybuffer and view called [inputArrayBuffer] from array [\[1, 2, 3\]] of type (Float32Array v) :: #4287f5 Write [3] elements of data from arraybuffer [inputArrayBuffer] to buffer [inputBuffer] from offset [0] to offset [0] :: #4287f5 Create bind group called [doublingBG] using layout [doublingBGL] { Add bind group entry with binding [0] of type (buffer v) using resource named [inputBuffer] :: #4287f5 } :: #4287f5 compile shaders :: #4287f5 run shader [doublingShader] with bind group [doublingBG] dimensions x: [1] y: [1] z: [1] :: #4287f5
Great, we are passing data to our shader, but we still can't read this data. To do this we need to copy the input buffer to the read buffer and read the read buffer.
copy [amount] bytes of data from buffer [srcBuffer] from position [readPos] to buffer [dstBuffer] at position [writePos] :: #4287f5 Read buffer [buffer] to arraybuffer [arrayBuffer] :: #4287f5 View arraybuffer (arrayBuffer v) as (arrayType v) called [viewName] :: #4287f5 Get view [view] as array :: reporter #4287f5
- amount - Number of bytes to copy from srcBuffer to dstBuffer
- srcBuffer - Buffer to read from. Must have the COPY_SRC usage flag
- readPos - The position to start reading from
- dstBuffer - Buffer to write to. Must have COPY_DST usage flag
- writePos - The position to start writing to
- buffer - The buffer to read, must have MAP_READ usage flag.
- arrayBuffer - The arraybuffer to write the data to
- arrayBuffer - The arraybuffer to view.
- arrayType - See the mdn reference. The type of typedArray to create.
- viewName - The name of the view to create
Copy data block - Copies data between buffers. What did you expect.
Read buffer block - This copies the data from the buffer to the specifed arrayBuffer.
View arraybuffer block - This creates a view of an arraybuffer of the given type.
Get view block - This reads a given view as its given type, returned as a stringified array.
Our code using this looks like this:
Copy [12] bytes of data from buffer [inputBuffer] from position [0] to buffer [readBuffer] at position [0] :: #4287f5 Read buffer [readBuffer] to arraybuffer [outputArrayBuffer] :: #4287f5 View arraybuffer (outputArrayBuffer v) as (Float32Array v) called [outputView] :: #4287f5 set [my variable v] to (Get view [outputView] as array :: #4287f5)
Now we're done(see small note below), here's the full code:
Def shader [doublingShader] with resources [doublingBGL] :: hat #4287f5 Bind shader resource #[0] to variable [data] with settings (Variable usage (read_write v) next (Variable usage (storage v) next [] :: reporter #4287f5) :: reporter #4287f5) type (Create type (array v) of (Base type (f32 v) :: #4287f5), length\(array only!\) [] :: reporter #4287f5) :: #4287f5 Compute shader with workgroup size [[1\]] { For [i] in [0], [2] { Variable (In array [data] get (get variable [i] :: reporter #4287f5) :: reporter #4287f5) (= v) ((In array [data] get (get variable [i] :: reporter #4287f5) :: reporter #4287f5) * [2.0] :: math) :: #4287f5 } :: #4287f5 } :: #4287f5 when flag clicked Create bind group layout called [doublingBGL] { Add bind group layout entry with binding [0] for type (buffer v) and descriptor (Buffer layout entry descriptor with usage type (storage v) :: #4287f5) :: #4287f5 } :: color #4287f5 Create buffer called [inputBuffer] with size\(in bytes\) [12] and usage flags (Usage (Usage (Buffer usage (STORAGE v) :: #4287f5) | (Buffer usage (COPY_SRC v) :: #4287f5) :: #4287f5) | (Buffer usage (COPY_DST v) :: #4287f5) :: #4287f5) :: #4287f5 Create buffer called [readBuffer] with size\(in bytes\) [12] and usage flags (Usage (Buffer usage (MAP_READ v) :: #4287f5) | (Buffer usage (COPY_DST v) :: #4287f5) :: #4287f5) :: #4287f5 Create arraybuffer and view called [inputArrayBuffer] from array [\[1, 2, 3\]] of type (Float32Array v) :: #4287f5 Write [3] elements of data from arraybuffer [inputArrayBuffer] to buffer [inputBuffer] from offset [0] to offset [0] :: #4287f5 Create bind group called [doublingBG] using layout [doublingBGL] { Add bind group entry with binding [0] of type (buffer v) using resource named [inputBuffer] :: #4287f5 } :: #4287f5 compile shaders :: #4287f5 run shader [doublingShader] with bind group [doublingBG] dimensions x: [1] y: [1] z: [1] :: #4287f5 Copy [12] bytes of data from buffer [inputBuffer] from position [0] to buffer [readBuffer] at position [0] :: #4287f5 Read buffer [readBuffer] to arraybuffer [outputArrayBuffer] :: #4287f5 View arraybuffer (outputArrayBuffer v) as (Float32Array v) called [outputView] :: #4287f5 set [my variable v] to (Get view [outputView] as array :: #4287f5)
One last thing! We can make our shader support any length of inputs with the arrayLength built in function(this only works with shader inputs, not declared arrays unfortunately. There is no way to get the length of those :( ). Since the for loop uses an int and arrayLength returns an unsigned int we need to use the i32 builtin function to cast it:
Def shader [doublingShader] with resources [doublingBGL] :: hat #4287f5 Bind shader resource #[0] to variable [data] with settings (Variable usage (read_write v) next (Variable usage (storage v) next [] :: reporter #4287f5) :: reporter #4287f5) type (Create type (array v) of (Base type (f32 v) :: #4287f5), length\(array only!\) [] :: reporter #4287f5) :: #4287f5 Compute shader with workgroup size [[1\]] { For [i] in [0], (WGSL builtin (i32 v) with args ((WGSL builtin (arrayLength v) with args (Get variable [data] :: reporter #4287f5) :: reporter #4287f5) - [1] :: math) :: reporter #4287f5) { Variable (In array [data] get (get variable [i] :: reporter #4287f5) :: reporter #4287f5) (= v) ((In array [data] get (get variable [i] :: reporter #4287f5) :: reporter #4287f5) * [2.0] :: math) :: #4287f5 } :: #4287f5 } :: #4287f5 when flag clicked Create bind group layout called [doublingBGL] { Add bind group layout entry with binding [0] for type (buffer v) and descriptor (Buffer layout entry descriptor with usage type (storage v) :: #4287f5) :: #4287f5 } :: color #4287f5 Create buffer called [inputBuffer] with size\(in bytes\) [12] and usage flags (Usage (Usage (Buffer usage (STORAGE v) :: #4287f5) | (Buffer usage (COPY_SRC v) :: #4287f5) :: #4287f5) | (Buffer usage (COPY_DST v) :: #4287f5) :: #4287f5) :: #4287f5 Create buffer called [readBuffer] with size\(in bytes\) [12] and usage flags (Usage (Buffer usage (MAP_READ v) :: #4287f5) | (Buffer usage (COPY_DST v) :: #4287f5) :: #4287f5) :: #4287f5 Create arraybuffer and view called [inputArrayBuffer] from array [\[1, 2, 3\]] of type (Float32Array v) :: #4287f5 Write [3] elements of data from arraybuffer [inputArrayBuffer] to buffer [inputBuffer] from offset [0] to offset [0] :: #4287f5 Create bind group called [doublingBG] using layout [doublingBGL] { Add bind group entry with binding [0] of type (buffer v) using resource named [inputBuffer] :: #4287f5 } :: #4287f5 compile shaders :: #4287f5 run shader [doublingShader] with bind group [doublingBG] dimensions x: [1] y: [1] z: [1] :: #4287f5 Copy [12] bytes of data from buffer [inputBuffer] from position [0] to buffer [readBuffer] at position [0] :: #4287f5 Read buffer [readBuffer] to arraybuffer [outputArrayBuffer] :: #4287f5 View arraybuffer (outputArrayBuffer v) as (Float32Array v) called [outputView] :: #4287f5 set [my variable v] to (Get view [outputView] as array :: #4287f5)
And we're done! That was quite a large explanation for so little code. As promised, here's a link to the image, and in case you're having issues here's the sb3. If you have any questions, check the next few pages, otherwise dm derpygamer2142 on discord.