Skip to content

Aelstraz/Unity-GPU-Compute

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

54 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GPU Compute (Unity)

GPU Compute provides the ultimate & easiest way to setup, manage & execute GPU compute shaders in Unity. Reduces complexity and boilerplate code while providing powerful features for standard/async execution, global & local buffer/render texture creation, management, reading (standard/async), tracking & disposal, global and local variable management, GPU memory tracking, performance tracking, shader execution control & much more!

Features

  • Simplified Shader Execution - Easy setup and execution of compute shaders (synchronous & asynchronous)
  • Automatic Buffer Management - Create, edit, and read buffers with automatically calculated strides and lengths
  • Flexible Thread Group Sizing - Automatically calculate optimal GPU thread group sizes for 1D, 2D, and 3D workloads
  • GPU Memory Tracking - Track local and global GPU memory usage in real-time with formatted output
  • Performance Monitoring - Measure compute shader execution time
  • Async GPU Readback - Efficiently retrieve data from GPU using AsyncGPUReadbackRequest
  • Render Texture Support - Full support for creating and managing render textures
  • Global Resources - Centralized management of global buffers and textures accessible across multiple compute shader instances
  • Buffer Resizing - Dynamically resize buffers without recreating instances
  • Shader Keywords - Enable/disable local shader keywords programmatically
  • Buffer Debugging - Retrieve and inspect buffer contents for debugging purposes
  • Centralized in a Single Class - All GPU compute operations managed through one easy-to-use class
using (GPUCompute gpuCompute = new GPUCompute(computeShader))
{
    gpuCompute.SetBuffer("myBuffer", ref myBuffer);
    gpuCompute.SetFloat("myFloatVar", 2f);
    Debug.Log("GPU Compute Memory Allocated: " + gpuCompute.GetGPUMemoryUsedFormatted());
    gpuCompute.Execute();
    gpuCompute.GetBufferData("myBuffer", ref myBuffer);
    Debug.Log("Compute Time: " + gpuCompute.GetLastComputeTime());
}

Table of Contents

  1. Installation
  2. Quick Start
  3. Buffers & Textures
  4. Shader Variables
  5. Thread Group Sizes
  6. Execution
  7. Memory & Performance
  8. Async Operations
  9. Global Resources
  10. Buffer Debugging
  11. Advanced Features
  12. Example Usage

Installation

Simply add the entire folder into your project's Assets folder.


Quick Start

Instantiate

Create a new instance of GPUCompute by providing your compute shader:

GPUCompute gpuCompute = new GPUCompute(myComputeShader);

You can also specify a compute queue type for async execution:

GPUCompute gpuCompute = new GPUCompute(myComputeShader, ComputeQueueType.Default);

Dispose

Always dispose of your GPUCompute instances when finished to prevent GPU memory leaks:

gpuCompute.Dispose();              // Disposes both local and global resources
gpuCompute.DisposeLocal();         // Disposes only local buffers/textures
GPUCompute.DisposeGlobal();        // Disposes only global buffers/textures

Buffers & Textures

Creating & Setting Buffers

Create buffers from existing data (stride and length are calculated automatically):

Vector3[] vertices = new Vector3[1000];
gpuCompute.SetBuffer("vertices", ref vertices);

float[] uvs = new float[2000];
gpuCompute.SetBuffer("uvs", ref uvs);

Create empty buffers by specifying the struct type and length:

gpuCompute.CreateEmptyBuffer<int>("myEmptyBuffer", 500);
gpuCompute.CreateEmptyBuffer<Vector3>("positions", 1000, kernelIndex: 0);

Set buffers with multiple kernel indices:

int[] kernelIndices = { 0, 1, 2 };
gpuCompute.SetBuffer("sharedData", ref data, kernelIndices);

Specify buffer type and mode:

gpuCompute.CreateEmptyBuffer<float>(
    "counterBuffer", 
    100, 
    ComputeBufferType.Counter, 
    ComputeBufferMode.Dynamic
);

Reading Buffer Data

Retrieve buffer data from GPU to CPU:

Vector3[] resultBuffer = new Vector3[100];
gpuCompute.GetBufferData("vertices", ref resultBuffer);

List<Vector3> resultList = new List<Vector3>(100);
gpuCompute.GetBufferData("vertices", ref resultList);

NativeArray<Vector3> resultNative = new NativeArray<Vector3>(100, Allocator.Persistent);
gpuCompute.GetBufferData("vertices", ref resultNative);

Resizing Buffers

Dynamically resize buffers (data will be lost):

gpuCompute.ResizeBuffer("myBuffer", 2000);

Render Textures

Create render textures for compute shader output:

RenderTextureDescriptor descriptor = new RenderTextureDescriptor(1024, 1024, RenderTextureFormat.ARGBFloat);
gpuCompute.CreateEmptyRenderTexture("outputTexture", descriptor);

// With mip levels and custom settings
gpuCompute.CreateEmptyRenderTexture(
    "outputTexture",
    descriptor,
    kernelIndex: 0,
    mipLevel: 0,
    wrapMode: TextureWrapMode.Clamp,
    filterMode: FilterMode.Point,
    anisoLevel: 1
);

Set render textures:

RenderTexture myTexture = new RenderTexture(1024, 1024, 0, RenderTextureFormat.ARGBFloat);
gpuCompute.SetRenderTexture("myRenderTexture", ref myTexture);

Retrieve render texture data:

Texture2D outputTexture = new Texture2D(1024, 1024, TextureFormat.RGBAFloat, false);
gpuCompute.GetRenderTextureData("outputTexture", ref outputTexture);

Texture Support

Set various texture types:

Texture2D texture2D = Resources.Load<Texture2D>("myTexture");
gpuCompute.SetTexture("inputTexture", ref texture2D);

Texture3D texture3D = Resources.Load<Texture3D>("myVolume");
gpuCompute.SetTexture("volumeTexture", ref texture3D);

Texture2DArray textureArray = Resources.Load<Texture2DArray>("myTextureArray");
gpuCompute.SetTextureArray("textureArrayInput", ref textureArray);

Shader Variables

Scalar Values

gpuCompute.SetInt("count", 100);
gpuCompute.SetFloat("deltaTime", Time.deltaTime);
gpuCompute.SetBool("useOptimization", true);

Vectors

gpuCompute.SetVector("position", new Vector3(1, 2, 3));
gpuCompute.SetVector("colorData", new Vector4(1, 0, 0, 1));
gpuCompute.SetVector("uv", new Vector2(0.5f, 0.5f));
gpuCompute.SetVector("gridSize", new Vector3Int(10, 10, 10));

Arrays

float[] floatArray = new float[100];
gpuCompute.SetFloatArray("floatData", floatArray);

Vector4[] vectorArray = new Vector4[50];
gpuCompute.SetVectorArray("vectorData", vectorArray);

Matrices

Matrix4x4 transformMatrix = Matrix4x4.identity;
gpuCompute.SetMatrix("transform", transformMatrix);

Matrix4x4[] matrixArray = new Matrix4x4[10];
gpuCompute.SetMatrixArray("transforms", matrixArray);

Retrieving Variable Values

int value = gpuCompute.GetInt("myInt");
float value = gpuCompute.GetFloat("myFloat");
Vector4 vector = gpuCompute.GetVector("myVector");
Matrix4x4 matrix = gpuCompute.GetMatrix("myMatrix");

Thread Group Sizes

Manual Thread Group Setting

gpuCompute.SetThreadGroupSize(new Vector3Int(8, 8, 1));
Vector3Int currentSize = gpuCompute.GetThreadGroupSize();

Automatic 1D Thread Group Calculation

For array-based workloads:

int jobLength = vertices.Length;
int threadLength = gpuCompute.SetCalculatedThreadGroupSize(jobLength, kernelIndex: 0);

Automatic 2D Thread Group Calculation

For texture-based workloads:

int width = 1024;
int height = 1024;
gpuCompute.SetCalculatedThreadGroupSize(width, height, kernelIndex: 0);

Automatic 3D Thread Group Calculation

For volume-based workloads:

int width = 64;
int height = 64;
int depth = 64;
gpuCompute.SetCalculatedThreadGroupSize(width, height, depth, kernelIndex: 0);

Getting Kernel Thread Group Sizes

Retrieve the thread group sizes defined in your compute shader kernel:

Vector3Int kernelGroupSizes = gpuCompute.GetKernelThreadGroupSizes(kernelIndex: 0);
Debug.Log($"Kernel thread group size: {kernelGroupSizes}");

Execution

Synchronous Execution

Execute the compute shader and wait for completion:

gpuCompute.Execute(kernelIndex: 0);

Asynchronous Execution

Execute the compute shader asynchronously (DX12 only):

StartCoroutine(gpuCompute.ExecuteAsync(kernelIndex: 0));

Subscribe to completion events:

gpuCompute.OnExecuteComplete += OnComputeComplete;

private void OnComputeComplete(int kernelIndex)
{
    Debug.Log($"Kernel {kernelIndex} execution completed");
}

Check Execution Status

if (gpuCompute.IsExecuting())
{
    Debug.Log("Compute shader is currently executing");
}

Memory & Performance

Track GPU Memory Usage

Get local GPU memory used by the current instance:

long localMemoryBytes = gpuCompute.GetLocalGPUMemoryUsed();
string localMemoryFormatted = gpuCompute.GetLocalGPUMemoryUsedFormatted();
// Output: "1.25 MB"

Get global GPU memory used across all instances:

long globalMemoryBytes = GPUCompute.GetGlobalGPUMemoryUsed();
string globalMemoryFormatted = GPUCompute.GetGlobalGPUMemoryUsedFormatted();

Track Execution Time

Measure the duration of the last compute shader execution:

TimeSpan lastExecutionTime = gpuCompute.GetLastComputeTime();
Debug.Log($"Last execution took: {lastExecutionTime.TotalMilliseconds} ms");

Format Byte Counts

Convert byte counts to human-readable strings:

string formatted = GPUCompute.ByteCountToFormattedString(1024000);
// Output: "1.02 MB"

Async Operations

Async Buffer Readback

Asynchronously retrieve buffer data using AsyncGPUReadbackRequest for better performance:

StartCoroutine(gpuCompute.GetBufferDataAsync("myBuffer"));

// Subscribe to readback completion
gpuCompute.OnReadbackComplete += OnBufferReadbackComplete;

private void OnBufferReadbackComplete(AsyncGPUReadbackRequest request, string bufferName)
{
    Vector3[] data = new Vector3[100]; 
    GPUCompute.ReadbackRequestToArray(ref request, ref data);
    // Process data...
}

Read buffer data with offset and length:

StartCoroutine(gpuCompute.GetBufferDataAsync("myBuffer", length: 100, startIndex: 50));

Async Render Texture Readback

Asynchronously retrieve render texture data:

StartCoroutine(gpuCompute.GetRenderTextureDataAsync("outputTexture"));

gpuCompute.OnReadbackComplete += OnReadbackComplete;

private void OnReadbackComplete(AsyncGPUReadbackRequest request, string textureName)
{
    Texture2D output = new Texture2D(request.width, request.height, TextureFormat.RGBAFloat, false);
    GPUCompute.ReadbackRequestToTexture2D(ref request, ref output);
}

Asynchronously retrieve global render texture data:

Optional parameters can also be passed through for your own use.

int myDataToPassThrough = 0;
StartCoroutine(gpuCompute.GetGlobalRenderTextureDataAsync("outputTexture"), myDataToPassThrough);

gpuCompute.OnGlobalReadbackComplete += OnGlobalReadbackComplete;

private void OnGlobalReadbackComplete(AsyncGPUReadbackRequest request, string textureName, params object[] parameters)
{
    int myDataToPassThrough = (int)parameters[0];
    Texture2D output = new Texture2D(request.width, request.height, TextureFormat.RGBAFloat, false);
    GPUCompute.ReadbackRequestToTexture2D(ref request, ref output);
}

Retrieve specific regions:

StartCoroutine(gpuCompute.GetRenderTextureDataAsync(
    "outputTexture",
    width: 256,
    height: 256,
    depth: 1,
    mipIndex: 0,
    x: 100,
    y: 100,
    z: 0
));

Readback Data Conversion

Convert async readback requests to different formats:

// To NativeArray
NativeArray<Vector3> nativeData = new NativeArray<Vector3>(100, Allocator.Persistent);
GPUCompute.ReadbackRequestToNativeArray(ref request, ref nativeData);

// To List
List<Vector3> listData = new List<Vector3>(100);
GPUCompute.ReadbackRequestToList(ref request, ref listData);

// To Texture3D
Texture3D volume = new Texture3D(64, 64, 64, TextureFormat.RGBAFloat, false);
GPUCompute.ReadbackRequestToTexture3D(ref request, ref volume);

Global Resources

Global buffers and textures are accessible across all GPUCompute instances and persist for the lifetime of the application.

Global Buffers

Create and set global buffers:

Vector3[] globalVertices = new Vector3[1000];
GPUCompute.SetGlobalBuffer("globalVertices", ref globalVertices);

GPUCompute.CreateEmptyGlobalBuffer<float>("globalData", 5000);

Retrieve global buffer data:

Vector3[] outputBuffer = new Vector3[1000];
GPUCompute.GetGlobalBufferData("globalVertices", ref outputBuffer);

Resize global buffers:

GPUCompute.ResizeGlobalBuffer("globalData", 10000);

Global Textures

Set global textures:

Texture2D globalTexture = Resources.Load<Texture2D>("myGlobalTexture");
GPUCompute.SetGlobalTexture("globalTextureName", ref globalTexture);

Texture3D globalVolume = Resources.Load<Texture3D>("myVolume");
GPUCompute.SetGlobalTexture("globalVolume", ref globalVolume);

Texture2DArray globalArray = Resources.Load<Texture2DArray>("myArray");
GPUCompute.SetGlobalTextureArray("globalTextureArray", ref globalArray);

Create global render textures:

RenderTextureDescriptor descriptor = new RenderTextureDescriptor(2048, 2048, RenderTextureFormat.ARGBFloat);
GPUCompute.CreateEmptyGlobalRenderTexture("globalOutput", descriptor);

Retrieve global texture data:

Texture2D output = new Texture2D(2048, 2048, TextureFormat.RGBAFloat, false);
GPUCompute.GetGlobalRenderTextureData("globalOutput", ref output);

Linking Global Resources

Link global resources to compute shader instances:

gpuCompute.LinkGlobalBuffer("globalBufferName", "globalBufferName", kernelIndex: 0);
gpuCompute.LinkGlobalTexture("globalTextureName", "globalTextureName", kernelIndex: 0);
gpuCompute.LinkGlobalRenderTexture("globalOutputName", "globalOutputName", kernelIndex: 0);

Link to multiple kernels:

int[] kernelIndices = { 0, 1, 2 };
gpuCompute.LinkGlobalBuffer("buffer", "buffer", kernelIndices);

Dispose Global Resources

GPUCompute.DisposeGlobalBuffer("globalBufferName");
GPUCompute.DisposeGlobalRenderTexture("globalTextureName");
GPUCompute.DisposeGlobal(); // Dispose all global resources

Shader Keywords

Enable/Disable Keywords

Enable shader keywords:

gpuCompute.EnableKeyword("MY_KEYWORD");

Disable shader keywords:

gpuCompute.DisableKeyword("MY_KEYWORD");

Set keyword state by boolean:

LocalKeyword keyword = gpuCompute.GetKeywordSpace().FindKeyword("MY_KEYWORD");
gpuCompute.SetKeyword(keyword, true);

Query Keyword State

Check if keyword is enabled:

if (gpuCompute.IsKeywordEnabled("MY_KEYWORD"))
{
    Debug.Log("MY_KEYWORD is enabled");
}

Get all enabled keywords:

LocalKeyword[] enabledKeywords = gpuCompute.GetEnabledKeywords();
foreach (var keyword in enabledKeywords)
{
    Debug.Log($"Enabled: {keyword.name}");
}

Buffer Debugging

Debug buffers to inspect their contents (CPU-side only, for debugging):

string debugInfo = gpuCompute.DebugBuffer<Vector3>("myBuffer");
Debug.Log(debugInfo);
// Output:
// Buffer Name: [myBuffer], Data Type: [System.Numerics.Vector3], 
// Length: [100], Stride: [12], VRAM Usage: [1.17 KB], ...
// Values:
// Index 0: (1.00, 2.00, 3.00)
// Index 1: (4.00, 5.00, 6.00)
// ...

Debug global buffers:

string debugInfo = GPUCompute.DebugGlobalBuffer<float>("globalBuffer");
Debug.Log(debugInfo);

Advanced Features

Kernel Management

Find kernel indices:

int kernelIndex = gpuCompute.FindKernel("CSMain");

Check if kernel exists:

if (gpuCompute.HasKernel("CSMain"))
{
    Debug.Log("CSMain kernel found");
}

Check device support:

if (gpuCompute.IsSupported(kernelIndex: 0))
{
    Debug.Log("Kernel is supported on this device");
}

Compute Queue Configuration

Set compute queue type (for async execution):

gpuCompute.SetComputeQueueType(ComputeQueueType.Default);
ComputeQueueType currentType = gpuCompute.GetComputeQueueType();

Set synchronization stage flags:

gpuCompute.SetSynchronisationStageFlags(SynchronisationStageFlags.ComputeProcessing);
SynchronisationStageFlags flags = gpuCompute.GetSynchronisationStageFlags();

Accessing Compute Shader

ComputeShader shader = gpuCompute.GetComputeShader();

Get Keyword Space

LocalKeywordSpace keywordSpace = gpuCompute.GetKeywordSpace();

Example Usage

Complete example demonstrating common workflow:

using UnityEngine;
using GPUComputeModule;

public class ComputeExample : MonoBehaviour
{
    private GPUCompute gpuCompute;
    private ComputeShader computeShader;
    
    void Start()
    {
        // Initialize
        computeShader = Resources.Load<ComputeShader>("MyComputeShader");
        gpuCompute = new GPUCompute(computeShader);
        
        // Setup data
        Vector3[] vertices = new Vector3[1000];
        for (int i = 0; i < vertices.Length; i++)
            vertices[i] = new Vector3(i, 0, 0);
        
        // Create buffer and set data
        gpuCompute.SetBuffer("vertices", ref vertices);
        
        // Set shader variables
        gpuCompute.SetFloat("time", Time.deltaTime);
        gpuCompute.SetInt("vertexCount", vertices.Length);
        
        // Calculate thread groups for 1D workload
        gpuCompute.SetCalculatedThreadGroupSize(vertices.Length, kernelIndex: 0);
        
        // Execute synchronously
        gpuCompute.Execute();
        
        // Read results
        Vector3[] results = new Vector3[1000];
        gpuCompute.GetBufferData("vertices", ref results);
        
        // Check memory usage
        Debug.Log($"GPU Memory Used: {gpuCompute.GetLocalGPUMemoryUsedFormatted()}");
        Debug.Log($"Execution Time: {gpuCompute.GetLastComputeTime().TotalMilliseconds} ms");
        
        // Cleanup
        gpuCompute.Dispose();
    }
}

Twitter: @aelstraz

About

GPU Compute provides an easy way to setup, manage, execute & dispose GPU compute shaders in Unity. Reduces the amount of code and complexity to execute a compute shader. Create, edit and read buffers easily (buffer strides & lengths are calculated automatically). Automatically calculate optimal GPU thread group sizes for your workload. Plus more!

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages