.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.2.*" />
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 device = instance.RequestAdapter().RequestDevice();
using var ctx = device.CreateContext();
using var retainedPool = new RetainedPool(device);
using var readback = retainedPool.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 memory = new MemoryExchange(ctx);
using var withdraw = memory.BindWithdrawTexture(scheme, readback);
using var submission = scheme.Submit();
using var claim = withdraw.Claim(submission);
byte[] pixels = claim.Consume();
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 device = instance.RequestAdapter().RequestDevice();
using var ctx = device.CreateContext();
using var scheme = new Scheme(ctx);
Key Differences from Rust
| Aspect | Rust | C# |
|---|---|---|
| Instance creation | Instance::new()? | new Instance() |
| Error handling | Result<T, GoldyError> | Exceptions |
| Device lifetime | Arc<Device> | IDisposable / using |
| Retained buffer | retained_pool.acquire_buffer_with_data(&data, access) | retainedPool.AcquireBuffer<T>(data, access) → Parcel |
| Submission | scheme.submit()? | scheme.Submit() → SchemeSubmission |
| Enums | DeviceType::DiscreteGpu | DeviceType.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 WithdrawTransaction BindWithdraw(Scheme scheme, Parcel parcel);
public WithdrawTransaction BindWithdrawTexture(Scheme scheme, Texture texture);
public DepositTransaction BindDepositBuffer(Scheme scheme, Parcel destination, ulong capacity);
}
public sealed class WithdrawTransaction
{
public WithdrawClaim Claim(SchemeSubmission submission);
}
public sealed class WithdrawClaim : IDisposable
{
public byte[] Consume();
public void Discard();
}
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, bind a MemoryExchange withdraw, Submit(), then withdraw.Claim(submission).Consume().
Windowed: record once with SurfaceExchange.BindRenderTarget (or BindDestination for compute-to-surface); each frame call Submit(), then transaction.Claim(submission).Consume().