goldy 0.2.0

Fondaco Machine GPU runtime for Rust (Vulkan, DX12, Metal)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
# Goldy Shader Library


Slang shader sources for native GPU platforms (Vulkan, DX12, Metal).

## Goldy Idioms


Goldy encourages specific patterns that leverage modern GPU capabilities:

### 1. Vertex-less Fullscreen Rendering


For fullscreen effects (plasma, mandelbrot, etc.), use `vs_fullscreen_triangle()` instead of vertex buffers:

```slang
import goldy_exp;

[shader("vertex")]
FullscreenVarying vs_main(uint vertex_id : SV_VertexID) {
    return vs_fullscreen_triangle(vertex_id);  // No vertex buffer needed!
}
```

```rust
// Rust side - no vertex buffer
pass.draw_fullscreen();  // Draws 3 vertices using SV_VertexID
```

This is more efficient than a fullscreen quad (3 verts vs 6) and eliminates buffer creation overhead.

### 2. Compute + Graphics Buffer Sharing


For particle systems and instancing, let compute shaders update data that graphics shaders read:

```slang
// Compute shader updates instances
[shader("compute")]
void cs_main(uint3 id : SV_DispatchThreadID) {
    Instance inst = INSTANCES[id.x];
    inst.rotation += delta_time;
    INSTANCES[id.x] = inst;
}

// Graphics shader reads instances, generates geometry
[shader("vertex")]
VSOutput vs_main(uint vertex_id : SV_VertexID, uint instance_id : SV_InstanceID) {
    Instance inst = INSTANCES[instance_id];
    float2 pos = quad_position_rotated(vertex_id, inst.position, inst.size, inst.rotation);
    // ...
}
```

```rust
// Rust side
compute_pass.dispatch(workgroups, 1, 1);
render_pass.draw_quads(num_instances);  // 6 vertices per quad
```

### 3. Everything is a Buffer


Goldy's bindless architecture treats all GPU data as indexed buffers:

- **Uniforms**: `ConstantBuffer<T>` arrays with resource bindings for indices
- **Instance data**: `StructuredBuffer<T>` indexed by `SV_InstanceID`
- **Geometry**: Generated from `SV_VertexID` + `quad_position()` helpers

This minimizes CPU-GPU synchronization and enables GPU-driven rendering.

## Goldy Module (Experimental)


> ⚠️ **EXPERIMENTAL**: This library's API is unstable and may change significantly
> as we learn what abstractions work best for shader development.

The `goldy_exp` module provides shared utilities that shaders can import:

```slang
import goldy_exp;

[shader("vertex")]
FullscreenVarying vs_main(uint vertex_id : SV_VertexID) {
    return vs_fullscreen_triangle(vertex_id);
}

[shader("fragment")]
float4 fs_main(FullscreenVarying input) : SV_Target {
    float2 uv = center_uv(input.uv);
    return float4(rainbow(uv.x), 1.0);
}
```

### Module Contents


| File | Contents |
|------|----------|
| `goldy_exp.slang` | Primary module entry point |
| `goldy_exp/math.slang` | Math utilities: `PI`, `TAU`, `hash()`, `hash2()`, `center_uv()`, `scale_uv()`, `to_polar()`, `smootherstep()` |
| `goldy_exp/color.slang` | Color utilities: `rainbow()`, `palette()`, `heat()`, `hsv_to_rgb()`, `luminance()`, `gamma_correct()` |
| `goldy_exp/vertex.slang` | Vertex formats and shaders (see below) |
| `goldy_exp/types.slang` | Common data types: `Particle2D`, `Particle3D`, `FrameUniforms`, `Transform2D` |
| `goldy_exp/primitives.slang` | Procedural geometry: `quad_local()`, `quad_position()`, `quad_position_rotated()`, `billboard_position()`, `fullscreen_position()`, `fullscreen_uv()` |
| `goldy_exp/descriptor_handle.slang` | Cross-platform `DescriptorHandle<T>` support (routes by access pattern) |
| `goldy_exp/access.slang` | Resource type aliases (`Scattered<T>` = `StorageBuffer<T>`, `BufRO<T>`, `Interpolated<T>`, etc.) and SV types (`ThreadId`, `VertexId`, etc.). Declare any struct type directly as a parameter for implicit constant-buffer broadcast. |
| `goldy_exp/bindless_resources.slang` | Metal Tier 2 ParameterBlock for bindless resources |

### Vertex Formats


**Fullscreen Triangle** (vertex-less, recommended):
```slang
FullscreenVarying          // Output: float4 position, float2 uv
vs_fullscreen_triangle()   // Generates fullscreen triangle from SV_VertexID (no buffer needed!)
```

**Fullscreen Quad** (position + UV, legacy):
```slang
FullscreenVertex   // Input: float2 position, float2 uv
FullscreenVarying  // Output: float4 position, float2 uv
vs_fullscreen()    // Standard vertex shader (requires vertex buffer)
```

**Colored Vertices** (position + color):
```slang
ColoredVertex      // Input: float2 position, float4 color
ColoredVarying     // Output: float4 position, float4 color
vs_colored()       // Standard vertex shader
fs_colored()       // Pass-through fragment shader
```

**Fullscreen with Time** (position + UV + time, legacy):
```slang
FullscreenTimeVertex   // Input: float2 position, float2 uv, float time
FullscreenTimeVarying  // Output: float4 position, float2 uv, float time
vs_fullscreen_time()   // Standard vertex shader
```

### Procedural Geometry (from goldy_exp.primitives)


For instanced rendering, use these helpers instead of vertex buffers:

```slang
quad_local(vertex_id)                                  // Get quad vertex [-1,1]
quad_uv(vertex_id)                                     // Get quad UV [0,1]
quad_position(vertex_id, center, size)                 // Quad at position
quad_position_rotated(vertex_id, center, size, rot)    // Rotated quad
billboard_position(vertex_id, center, size, cam_right, cam_up)  // 3D billboard
fullscreen_position(vertex_id)                         // Fullscreen clip-space
fullscreen_uv(vertex_id)                               // Fullscreen UV
```

### Common Types (from goldy_exp.types)


Shader-side layouts for shared CPU/GPU structs. Define matching Rust types in the
example that uses them (see `examples/instance2d.rs` for instancing).

```slang
Particle2D     // float2 position, float2 velocity
Particle3D     // float3 position, float3 velocity, float age, float lifetime
FrameUniforms  // float time, float delta_time, uint frame, uint _pad
Transform2D    // float2 position, float rotation, float2 scale, float _pad
```

## Usage


**Native (Rust):**

The `goldy_exp` library is automatically registered when you create a `Device`:

```rust
use goldy::{DeviceDescriptor, Instance, RequestAdapterOptions, ShaderModule, shaders};

let instance = Instance::new()?;
let device = instance
    .request_adapter(&RequestAdapterOptions::default())?
    .request_device(&DeviceDescriptor::default())?;

// The goldy_exp library is pre-registered - just use import goldy_exp;
let shader = ShaderModule::from_slang(&device, shaders::PLASMA)?;
```

**Custom Libraries:**

```rust
use goldy::ShaderLibrary;

// Register your own library
device.register_library(ShaderLibrary::from_source("myutils", r#"
    module myutils;
    public float3 effect() { return float3(1, 0, 0); }
"#))?;

// Now your shaders can use: import myutils;
```


## Shader Files


| File | Description | Uses Module |
|------|-------------|-------------|
| `plasma.slang` | Classic demoscene plasma |`import goldy_exp` |
| `mandelbrot.slang` | Fractal explorer with zoom |`import goldy_exp` |
| `gradient.slang` | Animated color gradient |`import goldy_exp` |
| `tunnel.slang` | Demoscene tunnel effect |`import goldy_exp` |
| `checkerboard.slang` | Animated checker pattern |`import goldy_exp` |
| `metaballs.slang` | Blending distance fields |`import goldy_exp` |
| `spinning_cube.slang` | Wireframe 3D cube |`import goldy_exp` |
| `particles.slang` | Rain/snow particle rendering |`import goldy_exp` |
| `starfield_*.slang` | 3D starfield (compute + render) |`import goldy_exp` |
| `game_of_life*.slang` | Conway's Game of Life (compute + render) |`import goldy_exp` |
| `instancing_*.slang` | Instanced quad rendering (compute + render) |`import goldy_exp` |
| `bouncing_lines_*.slang` | Bouncing lines demo (compute + render) |`import goldy_exp` |
| `vertex_color_2d.slang` | Basic 2D position + color ||
| `digital_clock.slang` | 7-segment display shader ||
| `triangle.slang` | Procedural triangle from vertex ID ||

## Compilation Targets


All shaders compile via native slang.dll:
- **SPIR-V** → Vulkan
- **DXIL** → DX12
- **MSL** → Metal

## Preprocessor Defines


When compiling shaders, Goldy passes backend-specific preprocessor defines:

| Define | When Set | Use For |
|--------|----------|---------|
| `__METAL__` | Targeting Metal | Metal-specific code (ParameterBlock for argument buffers) |
| `__SPIRV__` | Targeting Vulkan | Vulkan-specific code (push constants, descriptor arrays) |
| `__DX12__` | Targeting DX12 | DX12-specific code (root constants, ResourceDescriptorHeap) |

### Metal Requirements


Goldy requires **Argument Buffers Tier 2**, supported on:
- Apple Silicon (all models)
- Intel Macs 2017+
- AMD GPUs 2015+

Older Intel hardware (pre-2017) is not supported.

<details>

Metal's shader compiler has strict rules about pointer and type casts. The `as_type<>` intrinsic
(Slang's `reinterpret<>`) is limited:

- `as_type<device T*>(uint64)` — Cannot cast integers to pointers
-`as_type<uint64>(device T*)` — Cannot cast pointers to integers  
-`(device T*)(device U*)` — C-style pointer-to-pointer casts ARE allowed
-`as_type<T>(uint_data)` — Value-to-value reinterpretation is allowed

**The Problem:**

Slang's `DescriptorHandle<T>` and `reinterpret<ConstantBuffer<T>>()` generate intermediate
integer conversions that Metal rejects:

```metal
// What Slang generated (FAILS):
return *as_type<TimeUniforms device*>(as_type<ulong>(...));
```

**The Solution:**

Store raw `uint*` pointers in the ParameterBlock, then:
1. Read the data through the `uint*` pointer
2. Use `reinterpret<T>` on the **data** (not the pointer)

```slang
// In bindless_resources.slang - store raw pointers
struct GoldyBindlessResources {
    uint* storageBuffers[GOLDY_MAX_RESOURCES];  // Device pointers
    uint* uniformBuffers[GOLDY_MAX_RESOURCES];  // Device pointers
    // ... textures, samplers ...
};
ParameterBlock<GoldyBindlessResources> gGoldy;

// In access.slang - read data and reinterpret
public T goldy_broadcast<T>(uint slot) {
    uint* basePtr = gGoldy.uniformBuffers[slot];
    __target_switch {
    case metal:
        return reinterpret<T>(*basePtr);  // Reinterpret DATA, not pointer
    default:
        T* ptr = (T*)basePtr;
        return *ptr;
    }
}
```

This works because:
- Metal's ArgumentEncoder (`setBuffer()`) stores native device pointers in the argument buffer
- Reading through `uint*` is valid Metal — it's just a pointer dereference
- `reinterpret<T>(*basePtr)` generates valid `as_type<T>(uint)` for value types

For `goldy_scattered<T>()` (returning `RWStructuredBuffer<T>` for indexed access):

```slang
public RWStructuredBuffer<T> goldy_scattered<T>(uint slot) {
    uint* basePtr = gGoldy.storageBuffers[slot];
    return reinterpret<RWStructuredBuffer<T>>(basePtr);
}
```

Slang's `reinterpret<>` handles buffer types specially, avoiding the problematic integer casts.

</details>

### Cross-Platform Resource Binding


Goldy's bindless architecture passes resource slot indices as small `uniform uint` resource
bindings. The preferred way to write bindless shaders is the **virtual main** system:

```slang
import goldy_exp;

struct TimeUniforms { float time; };
struct Particle { float2 pos; float2 vel; };

// [goldy_compute] generates the real [shader("compute")] entry point automatically.
// Resource handles and SV types are resolved automatically. Any plain struct type
// is treated as a constant-buffer broadcast — no wrapper needed.
[goldy_compute]
[numthreads(64, 1, 1)]
void cs_main(TimeUniforms params, Scattered<Particle> particles, ThreadId id) {
    Particle p = particles[id.x];
    p.pos += p.vel * params.time;
    particles[id.x] = p;
}
```

Rust binds resources in declaration order (left to right):

```rust
pass.bind_resources_raw(&[
    particles_buf.bindless_index().unwrap(),
    params_buf.bindless_index().unwrap(),
]);
```

Plain scalar params (e.g. `uint offset`) are also resource bindings — no wrapper needed:

```slang
[goldy_compute]
[numthreads(64, 1, 1)]
void cs_main(Scattered<uint> data, uint offset, ThreadId id) {
    data[id.x + offset] += 1;
}
```

**Platform support:**
- **SPIR-V**: `uniform` entry-point params → push constants / resource bindings (std430 layout, offset 0)
- **DX12**: `uniform` entry-point params → root constants at `b0, space0`
- **Metal**: Slang wraps params in an `EntryPointParams` struct at `[[buffer(1)]]`

#### Alternative: Direct `DescriptorHandle<T>`


For more control, you can also use Slang's `DescriptorHandle<T>` directly:

```slang
// Works on SPIRV and DX12 (Metal uses goldy_broadcast instead)
uniform ConstantBuffer<TimeUniforms>.Handle uniforms;
float t = (*uniforms).time;  // Dereference to access
```

The `goldy_exp` module overrides `getDescriptorFromHandle` for SPIRV to route
to Goldy's binding layout. DX12 uses Slang's default `ResourceDescriptorHeap`.

<details>
<summary>Technical Details: Vulkan Descriptor Override</summary>

The `goldy_exp` module includes `goldy_exp/descriptor_handle.slang` which provides a custom
`getDescriptorFromHandle<T>` override for SPIRV. Slang's default bindings don't match Goldy's
Vulkan descriptor layout.

**Goldy's bindings are organized by ACCESS PATTERN:**

| Binding | Access Pattern | What Hardware Does | Slang Types |
|---------|----------------|-------------------|-------------|
| 0 | **Scattered** | Any thread, any address (read/write or read-only) | `RWStructuredBuffer<T>`, `StructuredBuffer<T>` (NonWritable) |
| 1 | **Broadcast** | All threads same address (cache optimized) | `ConstantBuffer<T>` |
| 2 | **Interpolated** | Hardware filtering between neighbors | `Texture2D<T>` with sampler |
| 3 | **Direct Spatial** | 2D/3D indexing, no filtering, read/write | `RWTexture2D<T>` |
| 4 | **Filter Config** | Settings for interpolated access (not data) | `SamplerState` |

The custom `getDescriptorFromHandle<T>` uses Slang's `__DynamicResource<T>` type to declare
arrays at Goldy's specific bindings, then routes based on `T.kind` (Sampler, Texture,
ConstantBuffer, StorageBuffer, etc.) and `T.descriptorAccess` (Read vs ReadWrite).

This is transparent to shader code — just `import goldy_exp` and use `DescriptorHandle<T>`.

</details>

### Tips


1. **Preprocessor defines don't propagate to imported modules** — this is a deliberate design decision in Slang, not a limitation. The module system (`import`, `__include`) isolates preprocessor state to enable true separate compilation and clean module boundaries. Think of `import foo;` as closer to `using namespace foo;` in C++ rather than `#include`.

   **However, session-level macros DO propagate!** Macros passed via `-D` flag or the API's `PreprocessorMacroDesc` are visible to ALL modules, including imports. This is why Goldy's backend defines (`__METAL__`, `__SPIRV__`, `__DX12__`) work — they're passed at the session level.

   | Macro Source | Visible in Imports? |
   |--------------|---------------------|
   | `#define` in source | ❌ No — isolated to that file |
   | `-D` flag / session macros | ✅ Yes — visible everywhere |
   | Traditional `#include` | ✅ Yes — behaves like C/C++ |

   **Solutions for module-level configuration:**
   - Use session-level `-D` flags for feature toggles (Goldy does this automatically)
   - Use functions instead of macros (functions export, macros don't)
   - Don't use guards in modules that are only imported when needed

2. **Resource bindings** are expressed as `uniform` entry-point parameters — no manual
   `[[vk::push_constant]]` declaration is needed:
   ```slang
   // Preferred: Slang maps uniform params to resource bindings on all backends
   void cs_main(uniform uint slot, uint3 id : SV_DispatchThreadID) { ... }
   ```

3. **DX12: Use named indices, not arrays** in cbuffers for buffer indices:
   ```slang
   // WORKS - direct named indices
   cbuffer BufferIndices : register(b0, space0) {
       uint instanceBufferIndex;
       uint paramsBufferIndex;
   };
   #define DATA (*DescriptorHandle<StructuredBuffer<T>>(uint2(instanceBufferIndex, 0)))
   
   // MAY NOT WORK - array access has issues on some DX12 configurations
   cbuffer BufferIndices : register(b0, space0) {
       uint indices[16];
   };
   #define DATA (*DescriptorHandle<StructuredBuffer<T>>(uint2(indices[0], 0)))
   ```

4. **Macros don't export from modules**. Use functions instead:
   ```slang
   // In module - this WON'T be visible to importers:
   #define GET_INDEX(slot) indices[slot]
   
   // Use a function instead - this WILL be visible:
   public uint getIndex(uint slot) { return indices[slot]; }
   ```

4. **Metal uses `ParameterBlock`, not push constants**. Unlike Vulkan/DX12 which use push/root constants to pass buffer indices, Metal uses `ParameterBlock` which Slang compiles to argument buffers. For indexed bindless access, use raw `uint*` pointers (see "Technical Details: Metal Tier 2 Bindless Implementation" above):
   ```slang
   struct GoldyBindlessResources {
       uint* uniformBuffers[MAX_RESOURCES];  // Raw pointers, cast in access functions
       Array<Texture2D<float4>, MAX_RESOURCES> textures;  // Textures work directly
   };
   ParameterBlock<GoldyBindlessResources> gGoldy;
   ```
   
   The Goldy backend uses `ArgumentEncoder.setBuffer()` to write native device pointers to the argument buffer. When using `bind_resources()` in Rust with a Metal ParameterBlock shader, the backend automatically handles this translation.

## Module System


Goldy uses Slang's module system for code sharing. Unlike `#include`, Slang modules provide true separate compilation — modules can be precompiled to `.slang-module` files and imported code behaves consistently regardless of where it's imported from.

```
shaders/
├── goldy_exp.slang           # module goldy_exp;
├── goldy_exp/
│   ├── math.slang            # implementing goldy_exp;
│   ├── color.slang           # implementing goldy_exp;
│   └── vertex.slang          # implementing goldy_exp;
├── plasma.slang              # import goldy_exp;
└── mandelbrot.slang          # import goldy_exp;
```

### How Library Registration Works


1. When a `Device` is created, the `goldy_exp` library is automatically registered
2. The library source files are written to a temp directory
3. The Slang compiler uses this directory to resolve `import` statements
4. When the `Device` is dropped, the temp files are cleaned up

### Creating a Shader That Uses the Library


```rust
// The goldy_exp library is pre-registered - just import it in your shader
let shader = ShaderModule::from_slang(&device, r#"
    import goldy_exp;

    [shader("vertex")]
    FullscreenVarying vs_main(FullscreenVertex input) {
        return vs_fullscreen(input);
    }

    [shader("fragment")]
    float4 fs_main(FullscreenVarying input) : SV_Target {
        return float4(rainbow(input.uv.x), 1.0);
    }
"#)?;
```

### Library Management API


```rust
// Check what libraries are available
for name in device.list_libraries() {
    println!("Library: {}", name);
}

// Query specific library
assert!(device.has_library("goldy_exp"));

// Register your own
device.register_library(ShaderLibrary::from_source("mylib", "..."))?;

// Unregister (not recommended for goldy_exp)
device.unregister_library("mylib");
```