shdrlib 0.1.0

A three-tiered Vulkan shader compilation and rendering framework built in pure Rust
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
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
# Migration Guide


**Moving Between Tiers - Progressive Disclosure in Action**

This guide shows you how to move between shdrlib's three tiers, mix tiers in the same project, and progressively adopt more or less control as your needs change.

---

## Table of Contents


- [Overview]#overview
- [EZ → EX Migration]#ez--ex-migration
- [EX → CORE Migration]#ex--core-migration
- [CORE → EX Migration]#core--ex-migration
- [Mixing Tiers]#mixing-tiers
- [When to Drop Down]#when-to-drop-down
- [When to Move Up]#when-to-move-up
- [Common Patterns]#common-patterns

---

## Overview


### Progressive Disclosure


shdrlib's three-tier design enables **progressive disclosure** - start simple, add complexity only when needed:

```
EZ (Simple) ←→ EX (Balanced) ←→ CORE (Control)
```

**Key principle:** All tiers interoperate seamlessly.

### Migration Paths


| From | To | Why |
|------|-----|-----|
| EZ → EX | Need explicit control | Production app outgrows EZ |
| EX → CORE | Need custom patterns | Building engine/framework |
| CORE → EX | Want safety | Reducing boilerplate |
| Mixed | Any combo | Use right tool for each job |

---

## EZ → EX Migration


### Why Migrate?


Your prototype is becoming a production application and you need:
- Explicit configuration control
- Type-safe resource management
- Custom descriptor layouts
- Fine-grained synchronization

### Step-by-Step Migration


#### Before (EZ Tier)


```rust
use shdrlib::ez::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // One-liner setup
    let mut renderer = EzRenderer::new()?;
    
    // One-liner pipeline
    let pipeline = renderer.quick_pipeline(VERT_SHADER, FRAG_SHADER)?;
    
    // Simple render loop
    renderer.render_frame(|frame| {
        frame.bind_pipeline(pipeline)?;
        frame.set_viewport(0.0, 0.0, 800.0, 600.0);
        frame.set_scissor(0, 0, 800, 600);
        frame.draw(3, 1, 0, 0);
        Ok(())
    })?;
    
    Ok(())
}
```

#### After (EX Tier)


```rust
use shdrlib::{
    core::ShaderStage,
    ex::{RuntimeManager, RuntimeConfig, ShaderManager, PipelineBuilder},
};
use ash::vk;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Explicit initialization
    let mut runtime = RuntimeManager::new(RuntimeConfig {
        app_name: "My App".to_string(),
        enable_validation: cfg!(debug_assertions),
        in_flight_frames: 2,
    })?;
    
    let mut shaders = ShaderManager::new(runtime.device())?;
    
    // Explicit shader compilation
    let vert_id = shaders.add_shader(
        VERT_SHADER,
        ShaderStage::Vertex,
        "main_vert"
    )?;
    
    let frag_id = shaders.add_shader(
        FRAG_SHADER,
        ShaderStage::Fragment,
        "main_frag"
    )?;
    
    // Explicit pipeline configuration
    let pipeline_id = shaders.build_pipeline(
        PipelineBuilder::new()
            .vertex_shader(shaders.get_shader(vert_id)?.handle(), "main")
            .fragment_shader(shaders.get_shader(frag_id)?.handle(), "main")
            .color_attachment_formats(vec![vk::Format::R8G8B8A8_UNORM])
            .viewport(0.0, 0.0, 800.0, 600.0)
            .scissor(0, 0, 800, 600),
        "main_pipeline"
    )?;
    
    // Explicit frame management
    let frame = runtime.begin_frame()?;
    let pipeline = shaders.get_pipeline(pipeline_id)?;
    
    // Record commands
    let cmd = frame.command_buffer;
    unsafe {
        let device = runtime.device().handle();
        device.cmd_bind_pipeline(
            cmd,
            vk::PipelineBindPoint::GRAPHICS,
            pipeline.handle()
        );
        device.cmd_draw(cmd, 3, 1, 0, 0);
    }
    
    runtime.end_frame(&Default::default())?;
    
    Ok(())
}
```

### What Changed?


| Aspect | EZ | EX |
|--------|----|----|
| **Setup** | 1 line | 5-10 lines |
| **Shader compilation** | Implicit | Explicit with IDs |
| **Pipeline creation** | 1 function call | Builder pattern |
| **Frame management** | Closure-based | Manual begin/end |
| **Resource management** | Automatic | Type-safe IDs |
| **Configuration** | Defaults | Explicit |

### Benefits Gained


- **Explicit control** over all settings
-**Type-safe IDs** prevent resource confusion
-**Better error handling** with detailed errors
-**Shader hot-reloading** via ShaderManager
-**Custom pipeline configs** via PipelineBuilder
-**Production patterns** ready for real apps

### What Stays the Same


- **Performance** - Still zero-cost
-**Safety** - Still memory safe
-**Shaders** - Same GLSL code
-**Access to CORE** - Can still drop down

---

## EX → CORE Migration


### Why Migrate?


You're building an engine and need:
- Custom memory allocation strategies
- Non-standard pipeline configurations
- Direct Vulkan control
- Integration with existing Vulkan code

### Step-by-Step Migration


#### Before (EX Tier)


```rust
use shdrlib::ex::*;

let mut runtime = RuntimeManager::new(RuntimeConfig::default())?;
let mut shaders = ShaderManager::new(runtime.device())?;

let shader_id = shaders.add_shader(SHADER, ShaderStage::Vertex, "shader")?;
let pipeline_id = shaders.build_pipeline(
    PipelineBuilder::new()
        .vertex_shader(shaders.get_shader(shader_id)?.handle(), "main")
        .color_attachment_formats(vec![vk::Format::R8G8B8A8_UNORM]),
    "pipeline"
)?;
```

#### After (CORE Tier)


```rust
use shdrlib::core::*;
use ash::vk;

// Manual instance creation
let instance = Instance::new(InstanceCreateInfo {
    app_name: "My Engine".to_string(),
    api_version: vk::API_VERSION_1_3,
    enable_validation: false,
    ..Default::default()
})?;

// Manual device creation
let physical_device = /* select physical device */;
let device = Device::new(&instance, physical_device, &DeviceCreateInfo {
    queue_infos: vec![/* queue configs */],
    enabled_features: /* features */,
    ..Default::default()
})?;

// Manual shader compilation
let shader = Shader::from_glsl(&device, SHADER, ShaderStage::Vertex)?;

// Manual pipeline creation (100+ lines of configuration)
let pipeline = Pipeline::new(&device, &PipelineCreateInfo {
    // ... extensive configuration ...
})?;
```

### What Changed?


| Aspect | EX | CORE |
|--------|-----|------|
| **Setup** | RuntimeManager | Manual instance/device |
| **Shader compilation** | ShaderManager | Direct Shader API |
| **Pipeline creation** | PipelineBuilder | Raw Vulkan structs |
| **Resource IDs** | Type-safe | Raw Vulkan handles |
| **Lifetime safety** | Automatic | Manual |
| **Error handling** | Typed errors | Direct Vulkan errors |

### Responsibilities Gained


- ⚠️ **Manual drop order** - You manage lifetimes
- ⚠️ **Queue selection** - You pick queue families
- ⚠️ **Memory allocation** - You allocate/bind memory
- ⚠️ **Synchronization** - You manage fences/semaphores
- ⚠️ **Validation** - You interpret Vulkan errors

### Benefits Gained


- **Complete control** over every Vulkan call
-**Custom allocators** possible
-**No abstractions** - pure Vulkan
-**Direct ash access** - integrate with any Vulkan code
-**Framework building** - build your own EX tier

---

## CORE → EX Migration


### Why Migrate?


You've built a working CORE tier system but want:
- Automatic lifetime management
- Less boilerplate code
- Type-safe resource tracking
- Easier maintenance

### Step-by-Step Migration


#### Before (CORE Tier)


```rust
use shdrlib::core::*;

struct MyRenderer {
    // Manual drop order management
    pipeline: Pipeline,
    shader: Shader,
    device: Device,
    instance: Instance,
}

impl MyRenderer {
    fn new() -> Result<Self, Box<dyn std::error::Error>> {
        let instance = Instance::new(/* ... */)?;
        let device = Device::new(&instance, /* ... */)?;
        let shader = Shader::from_glsl(&device, /* ... */)?;
        let pipeline = Pipeline::new(&device, /* ... */)?;
        
        Ok(Self { pipeline, shader, device, instance })
    }
}
```

#### After (EX Tier)


```rust
use shdrlib::ex::*;

struct MyRenderer {
    // Automatic drop order - no manual management needed!
    runtime: RuntimeManager,
    shaders: ShaderManager,
}

impl MyRenderer {
    fn new() -> Result<Self, Box<dyn std::error::Error>> {
        let runtime = RuntimeManager::new(RuntimeConfig::default())?;
        let mut shaders = ShaderManager::new(runtime.device())?;
        
        // Resources managed by IDs
        let shader_id = shaders.add_shader(/* ... */)?;
        let pipeline_id = shaders.build_pipeline(/* ... */)?;
        
        Ok(Self { runtime, shaders })
    }
}
```

### Benefits Gained


- **Automatic safety** - Rust ownership ensures correct order
-**4x-8x less code** - Managers handle boilerplate
-**Type-safe IDs** - Can't confuse resources
-**Helper utilities** - Common patterns in one line
-**Same performance** - Still zero-cost

---

## Mixing Tiers


### The Power of Progressive Disclosure


**You don't have to pick just one tier!** Use the right tool for each job.

### Pattern 1: EZ for Main Rendering, CORE for Custom Resources


```rust
// Start with EZ for ease of use
let mut renderer = EzRenderer::new()?;
let pipeline = renderer.quick_pipeline(VERT, FRAG)?;

// Drop to CORE for custom buffer with specific memory properties
let device = renderer.runtime().device();
let custom_buffer = shdrlib::core::Buffer::new(
    device,
    &vk::BufferCreateInfo {
        size: 1024,
        usage: vk::BufferUsageFlags::STORAGE_BUFFER,
        // Custom configuration...
        ..Default::default()
    }
)?;

// Back to EZ for rendering
renderer.render_frame(|frame| {
    frame.bind_pipeline(pipeline)?;
    // Use custom_buffer in rendering...
    Ok(())
})?;
```

### Pattern 2: EX for Main App, CORE for Specific Optimization


```rust
// EX tier for main application
let mut runtime = RuntimeManager::new(RuntimeConfig::default())?;
let mut shaders = ShaderManager::new(runtime.device())?;

// Drop to CORE for custom memory pool
let device = runtime.device();
let memory_pool = create_custom_memory_pool(device)?;

// Use EX helpers with CORE allocations
let buffer = shdrlib::ex::helpers::create_buffer_with_memory(
    device,
    &buffer_info,
    memory_pool  // Custom memory
)?;
```

### Pattern 3: CORE Engine, EZ for Debug Visualization


```rust
// Your engine uses CORE tier
struct MyEngine {
    device: Device,
    // ... other CORE components
}

impl MyEngine {
    fn debug_visualize(&self) {
        // Use EZ tier for quick debug rendering
        let mut debug_renderer = EzRenderer::from_device(self.device.clone())?;
        let pipeline = debug_renderer.quick_pipeline(DEBUG_VERT, DEBUG_FRAG)?;
        
        debug_renderer.render_frame(|frame| {
            // Quick debug visualization
            frame.bind_pipeline(pipeline)?;
            frame.draw(debug_vertex_count, 1, 0, 0);
            Ok(())
        })?;
    }
}
```

---

## When to Drop Down


### From EZ to EX


Drop down when you need:
- ✅ Explicit pipeline configuration
- ✅ Custom descriptor layouts
- ✅ Shader hot-reloading
- ✅ Multi-pass rendering
- ✅ Type-safe resource management

**Signs it's time:**
- You're fighting EZ tier limitations
- Need fine-grained control over resources
- Building production application

### From EX to CORE


Drop down when you need:
- ✅ Custom memory allocation strategies
- ✅ Non-standard pipeline configurations
- ✅ Direct Vulkan handle access
- ✅ Integration with existing Vulkan code
- ✅ Building your own framework

**Signs it's time:**
- You're working around EX tier abstractions
- Need patterns not supported by EX
- Building an engine/framework

---

## When to Move Up


### From CORE to EX


Move up when:
- ✅ Tired of managing lifetimes manually
- ✅ Want to reduce boilerplate
- ✅ Need better error messages
- ✅ Building application, not engine

**Signs it's time:**
- Spending too much time on boilerplate
- Hit by lifetime bugs
- Want team members to understand code easier

### From EX to EZ


Move up when:
- ✅ Prototyping new ideas
- ✅ Teaching graphics concepts
- ✅ Building simple tools
- ✅ Want maximum simplicity

**Signs it's time:**
- Don't need explicit configuration
- Want fastest possible iteration
- Focus on shaders, not setup

---

## Common Patterns


### Pattern: Gradual Migration


Don't migrate all at once - do it incrementally:

```rust
// Week 1: Start with EZ
let renderer = EzRenderer::new()?;

// Week 2: Access EX components when needed
let shader_manager = renderer.shader_manager_mut();
let custom_pipeline = shader_manager.build_pipeline(/* custom config */)?;

// Week 3: Full EX migration when ready
let runtime = RuntimeManager::new(RuntimeConfig::default())?;
let shaders = ShaderManager::new(runtime.device())?;
```

### Pattern: Tier-Specific Modules


Organize code by tier:

```rust
mod core_engine {
    // CORE tier: Your engine foundation
    use shdrlib::core::*;
}

mod ex_renderer {
    // EX tier: Application rendering
    use shdrlib::ex::*;
}

mod ez_debug {
    // EZ tier: Debug visualization
    use shdrlib::ez::*;
}
```

### Pattern: Abstraction Escape Hatches


Always provide access to lower tiers:

```rust
struct MyRenderer {
    ez_renderer: EzRenderer,
}

impl MyRenderer {
    // Normal API uses EZ
    pub fn render(&mut self) { /* EZ API */ }
    
    // Advanced users can access EX
    pub fn shader_manager(&mut self) -> &mut ShaderManager {
        self.ez_renderer.shader_manager_mut()
    }
    
    // Experts can access CORE
    pub fn device(&self) -> &Device {
        self.ez_renderer.runtime().device()
    }
}
```

---

## Summary


**Progressive Disclosure = Flexibility**

- **Start simple** with EZ tier
-**Add control** with EX tier
-**Go deep** with CORE tier
-**Mix freely** - use right tool for each job
-**Migrate gradually** - no need to rewrite everything

**Key Takeaway:** shdrlib tiers are **not mutually exclusive**. Use them together!

**Next Steps:**
- Try mixing tiers in your project
- Read tier-specific guides for details
- Check examples for mixed-tier patterns

---

**Last Updated:** October 30, 2025