.NET Bindings

Goldy provides first-class C# bindings via P/Invoke interop over the native Rust FFI layer.

Installation

NuGet Package

dotnet add package Goldy

Or add to your .csproj directly:

<PackageReference Include="Goldy" Version="0.3.*" />

The NuGet package bundles native Goldy + Slang libraries for all supported platforms — no separate native installation is needed.

Building from Source

cargo build --package goldy-ffi --release
dotnet add reference path/to/goldy/dotnet/Goldy/Goldy.csproj

Requirements

  • .NET 8.0 or later
  • Windows x64, Linux x64, or macOS (x64 / arm64)
  • A GPU with Vulkan 1.4+, DX12, or Metal Tier 2+ support (CUDA and WebGPU backends are in progress; Tenstorrent is planned)

Quick Start

Headless Rendering

using Goldy;

using var instance = new Instance();
using var runtime = instance.RequestAdapter().RequestRuntime();
using var ctx = runtime.CreateContext();
using var readback = runtime.AcquireTexture(
    100, 100, TextureFormat.Rgba8Unorm, TextureKind.Direct,
    TextureFlags.CopySrc | TextureFlags.CopyDst);

using var scheme = new Scheme(ctx);
using var rt = scheme.LeaseRenderTarget(100, 100, TextureFormat.Rgba8Unorm);
using (var pass = scheme.RenderPass("clear", rt))
    pass.Clear(Color.CornflowerBlue);

scheme.CopyToTexture(rt, readback);
using var submission = scheme.Submit();
using var pixels = submission.Take(readback);

See Goldy.Examples/TriangleHeadless.cs for a full triangle readback demo.

Windowed Rendering

Record a retained scheme once, submit each frame, consume the present grant:

using var scheme = new Scheme(ctx);
var (sceneRt, present) = RecordScheme(scheme, swapchain, pipeline, vertexParcel, screen, bg);

using var submission = scheme.Submit();
present.Consume(submission);

See Goldy.Examples/TriangleWindow.cs and GameOfLifeWindow.cs.

Shaders (Slang)

Goldy uses Slang as its shader language across all backends:

var source = """
    [shader("vertex")]
    float4 vs_main(float2 pos : POSITION) : SV_Position {
        return float4(pos, 0.0, 1.0);
    }

    [shader("fragment")]
    float4 fs_main() : SV_Target {
        return float4(1.0, 0.5, 0.0, 1.0);
    }
    """;

using var shader = new ShaderModule(device, source);
using var pipeline = new RenderPipeline(device, shader, new RenderPipelineDesc
{
    TargetFormat = TextureFormat.Rgba8Unorm,
    Topology = PrimitiveTopology.TriangleList,
});

Resource Management

All Goldy objects implement IDisposable. Use using declarations or using blocks to ensure GPU resources are released promptly:

using var runtime = instance.RequestAdapter().RequestRuntime();
using var ctx = device.CreateContext();
using var scheme = new Scheme(ctx);

Key Differences from Rust

AspectRustC#
Instance creationInstance::new()?new Instance()
Error handlingResult<T, GoldyError>Exceptions
Runtime lifetimeArc<Runtime>IDisposable / using
Retained bufferruntime.acquire_buffer_with_data(&data, access)runtime.AcquireBuffer<T>(data, access) → Parcel
Submissionscheme.submit()?scheme.Submit() → SchemeSubmission
EnumsDeviceType::DiscreteGpuDeviceType.DiscreteGpu

API Reference

Scheme

public sealed class Scheme : IDisposable
{
    public Scheme(Context ctx);
    public SchemeComputeNodeScope ComputeNode(string label, ComputePipeline pipeline);
    public SchemeRenderTargetLease LeaseRenderTarget(uint width, uint height, TextureFormat format, ...);
    public SchemeRenderPassScope RenderPass(string label, SchemeRenderTargetLease lease);
    public void CopyToTexture(SchemeRenderTargetLease src, Texture dst);
    public void CopyToPresent(SchemeRenderTargetLease src, PresentLease dst);
    public SchemeSubmission Submit();
}

public sealed class MemoryExchange : IDisposable
{
    public MemoryExchange(Context ctx);
    public DepositTransaction BindDeposit(Scheme scheme, DepositTarget target);
}

public sealed class DepositTransaction : IDisposable
{
    public void Write(ReadOnlySpan<byte> data, ulong offset = 0);
    public static DepositTransaction operator <<(DepositTransaction deposit, byte[] data);
}

public sealed class SchemeSubmission : IDisposable
{
    public HostView Take(Parcel parcel);
    public HostView Take(Texture texture);
}

public sealed class HostView : IDisposable
{
    public int Length { get; }
    public ReadOnlySpan<byte> AsSpan();
    public byte[] ToArray();
}

SchemeRenderPassScope / SchemeComputeNodeScope

using (var pass = scheme.RenderPass("main", rt))
{
    pass.WithParcel(vertexParcel, NodeAccess.Read);
    pass.Clear(Color.CornflowerBlue);
    pass.SetPipeline(pipeline);
    pass.SetVertexBuffer(0, vertexParcel);
    pass.Draw(3);
}

using (var node = scheme.ComputeNode("update", computePipeline))
{
    node.WithParcel(stateBuf, NodeAccess.ReadWrite);
    node.Dispatch(wgX, wgY, 1);
}

SurfaceExchange / Transaction / Claim

using var surface = GlfwSurfaceExchange.Create(ctx, window);
var present = surface.BindRenderTarget(scheme, sceneRt);
// each frame:
using var submission = scheme.Submit();
present.Claim(submission).Consume();

Graphics and compute both go through Scheme.

Enums

public enum DeviceType   { DiscreteGpu, IntegratedGpu, Cpu, Other }
public enum BackendType  { Vulkan, Metal, Dx12 }  // CUDA and WebGPU in progress in core Goldy
public enum BufferKind   { Scattered, Broadcast }
public enum NodeAccess   { Read, Write, ReadWrite, Overwrite }

Headless vs windowed submission

Headless: record a scheme, Submit(), then submission.Take(parcel) / Take(texture).

Windowed: record once with SurfaceExchange.BindRenderTarget (or BindDestination for compute-to-surface); each frame call Submit(), then transaction.Claim(submission).Consume().