S&box Wiki

Compute Shaders

What can you do with compute shaders?

You can use compute shaders to do lots of things, but their most common use is to generate textures at blazing fast speeds; here's an example that shows you how you can do that.

Using compute shaders from C

Here's an example of creating a compute shader and populating a texture with it.

// Create a compute shader from a .shader file var computeShader = new ComputeShader( "my_compute_shader" ); // Create a texture for the compute shader to use var texture = Texture.Create( 512, 512 ) .WithUAVBinding() // Needs to have this if we're using it in a compute shader .WithFormat( ImageFormat.RGBA16161616F ) // Use whatever you need .Finish(); // Attach texture to OutputTexture attribute in shader computeShader.Attributes.Set( "OutputTexture", texture ); // Dispatch computeShader.Dispatch( texture.Width, texture.Height, 1 );

HLSL Shader

Compute shaders are HLSL like normal shaders, except everything goes in a CS block and runs a method named MainCs.

Here's a really simple one that'll generate a solid pink texture:

MODES { Default(); } COMMON { #include "common/shared.hlsl" } CS { // Output texture RWTexture2D<float4> g_tOutput< Attribute( "OutputTexture" ); >; [numthreads(8, 8, 1)] void MainCs( uint uGroupIndex : SV_GroupIndex, uint3 vThreadId : SV_DispatchThreadID ) { g_tOutput[vThreadId.xy] = float4( 1, 0, 1, 1 ); } }

Compute Buffers

You can read and write arbitrary structured data using ComputeBuffers too.

Setting data

var computeShader = new ComputeShader( "my_compute_shader" ); struct MyData { public float Value; } // Allocate the GPU buffer using ( var buffer = new ComputeBuffer<MyData>( 2 ) ) { // Upload data to the GPU buffer var data = new MyData[] { new MyData { Value = 1.0f }, new MyData { Value = 2.0f } }; buffer.SetData( data ); // Pass the buffer to a compute shader computeShader.Attributes.Set( "myData", buffer ); // Dispatch the shader computeShader.Dispatch(); }

Getting data

var computeShader = new ComputeShader( "my_compute_shader" ); // Allocate the GPU buffer to receive data using ( var buffer = new ComputeBuffer<float>( 2 ) ) { // Pass the buffer to a compute shader computeShader.Attributes.Set( "myData", buffer ); // Dispatch the shader computeShader.Dispatch(); // Get the data the compute shader has generated from the GPU var data = new float[2]; buffer.GetData( data ); }

Append buffers

Append buffers have a hidden counter that can be accessed with ComputeBuffer.CopyStructureCount, this means the CPU can know how many elements have been pushed to a buffer by the GPU.

ComputeShader ??= new ComputeShader( "marchingcubes_cs" ); var countBuffer = new ComputeBuffer<int>( 1, ComputeBufferType.ByteAddress ); var trianglesBuffer = new ComputeBuffer<Triangle>( 512, ComputeBufferType.Append ); ComputeShader.Attributes.Set( "Triangles", trianglesBuffer ); // This sets the initial UAV counter for an AppendStructuredBuffer ComputeShader.Attributes.Set( "Triangles", 0 ); ComputeShader.Dispatch( 8, 8, 8 ); // Copy how many triangles have been pushed to the buffer into our count buffer trianglesBuffer.CopyStructureCount( countBuffer, 0 ); // Grab the count to the CPU from the GPU buffer var count = new int[1] { 0 }; countBuffer.GetData( count, 0, 1 ); // Grab that number of triangles from the append buffer var tris = new Triangle[count[0]]; trianglesBuffer.GetData( tris, 0, count[0] );