# Migration Guide
**Moving between shdrlib's three tiers.**
## Quick Reference
| EZ → EX | Need more control | Easy |
| EZ → CORE | Maximum control | Medium |
| EX → CORE | Specific customization | Easy |
| CORE → EX | Want safety | Easy |
| CORE → EZ | Want simplicity | Easy |
| EX → EZ | Simplify prototype | Easy |
---
## EZ → EX
**Why:** Need explicit control, type-safe IDs, custom configurations
### Access EX Components
```rust
// Start with EZ
let mut renderer = EzRenderer::new()?;
// Access EX managers
let runtime = renderer.runtime();
let shaders = renderer.shader_manager();
```
### Full Migration
**Before (EZ):**
```rust
let mut renderer = EzRenderer::new()?;
let pipeline = renderer.quick_pipeline(VERT, FRAG)?;
renderer.render_frame(|frame| { /* ... */ })?;
```
**After (EX):**
```rust
let mut runtime = RuntimeManager::new(RuntimeConfig::default())?;
let mut shaders = ShaderManager::new(runtime.device())?;
let vert_id = shaders.add_shader(VERT, ShaderStage::Vertex, "vert")?;
let frag_id = shaders.add_shader(FRAG, ShaderStage::Fragment, "frag")?;
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![Format::R8G8B8A8_UNORM]),
"pipeline"
)?;
loop {
let frame = runtime.begin_frame()?;
// ... record commands ...
runtime.end_frame(&SubmitInfo::default())?;
}
```
**What changed:**
- Explicit RuntimeManager and ShaderManager
- Type-safe ShaderId and PipelineId
- Manual frame loop
- More control over pipeline config
---
## EX → CORE
**Why:** Need custom resource creation, specific Vulkan features
### Access CORE Objects
```rust
// Start with EX
let mut runtime = RuntimeManager::new(RuntimeConfig::default())?;
let device = runtime.device();
// Use CORE directly
let custom_shader = core::Shader::from_glsl(&device, glsl, stage)?;
let custom_buffer = core::Buffer::new(&device, size, usage, properties)?;
```
### Full Migration (Rare)
**Before (EX):**
```rust
let mut runtime = RuntimeManager::new(RuntimeConfig::default())?;
let device = runtime.device();
```
**After (CORE):**
```rust
let entry = ash::Entry::linked();
let instance = Instance::new(&entry, &InstanceCreateInfo::default())?;
let physical_devices = instance.enumerate_physical_devices()?;
let device = Device::new(&instance, physical_devices[0], &DeviceCreateInfo::default())?;
```
**What changed:**
- Manual instance/device creation
- No automatic resource management
- Must destroy resources manually in correct order
⚠️ **Usually not needed** - Use CORE for specific resources, keep EX for management.
---
## CORE → EX
**Why:** Want safety, tired of manual lifetime management
### Wrap in EX Managers
**Before (CORE):**
```rust
let device = Device::new(...)?;
let shader = Shader::from_glsl(&device, glsl, stage)?;
// ... later ...
shader.destroy(&device);
```
**After (EX):**
```rust
let device = Arc::new(Device::new(...)?);
let mut shaders = ShaderManager::new(device)?;
let shader_id = shaders.add_shader(glsl, stage, "name")?;
// No manual cleanup needed - ShaderManager handles it
```
**Benefits:**
- Automatic cleanup
- Type-safe IDs
- Can't accidentally use destroyed resources
---
## EZ → CORE
**Why:** Learning Vulkan internals, maximum control
**Before (EZ):**
```rust
let mut renderer = EzRenderer::new()?;
// Everything automatic
```
**After (CORE):**
```rust
// 80+ lines of setup
let entry = ash::Entry::linked();
let instance = Instance::new(...)?;
let device = Device::new(...)?;
let pool = CommandPool::new(...)?;
// ... etc
```
**Consider:** Use EX tier instead - gets you 90% of the control with 10% of the complexity.
---
## CORE → EZ
**Why:** Rapid prototyping, teaching
Simple wrapper:
```rust
// Keep CORE for custom resources
let device = /* your CORE device */;
// Use EZ for rendering
let mut renderer = EzRenderer::new()?;
let ez_device = renderer.device();
// Mix as needed
let custom_buffer = core::Buffer::new(&device, ...)?;
let ez_buffer = renderer.create_vertex_buffer(&data)?;
```
---
## EX → EZ
**Why:** Simplify prototype, reduce code
**Before (EX):**
```rust
let mut runtime = RuntimeManager::new(...)?;
let mut shaders = ShaderManager::new(...)?;
// ... 100 lines ...
```
**After (EZ):**
```rust
let mut renderer = EzRenderer::new()?;
let pipeline = renderer.quick_pipeline(VERT, FRAG)?;
// ... 30 lines ...
```
**Tradeoffs:**
- ✅ Way less code
- ✅ Faster prototyping
- ❌ Less control
- ❌ Fixed patterns
---
## Mixing Tiers (Recommended)
**Best practice:** Use highest tier that works, drop down when needed.
### Pattern 1: EZ + CORE
```rust
let mut renderer = EzRenderer::new()?;
let device = renderer.device();
// Custom CORE resource
let custom = core::Buffer::new(&device, size, usage, properties)?;
// Back to EZ
let pipeline = renderer.quick_pipeline(VERT, FRAG)?;
```
### Pattern 2: EX + CORE
```rust
let mut runtime = RuntimeManager::new(RuntimeConfig::default())?;
let device = runtime.device();
// Use CORE for specific need
let custom_shader = core::Shader::from_glsl(&device, glsl, stage)?;
// Back to EX
let mut shaders = ShaderManager::new(device)?;
let pipeline_id = shaders.build_pipeline(...)?;
```
### Pattern 3: Framework in CORE, API in EX
```rust
// Internal: CORE for flexibility
mod internal {
pub fn create_resources() -> core::Device {
/* ... CORE implementation ... */
}
}
// Public API: EX for safety
pub struct MyFramework {
runtime: RuntimeManager,
shaders: ShaderManager,
}
impl MyFramework {
pub fn new() -> Self {
let runtime = RuntimeManager::new(RuntimeConfig::default()).unwrap();
let shaders = ShaderManager::new(runtime.device()).unwrap();
Self { runtime, shaders }
}
}
```
---
## Common Migration Scenarios
### "I need a custom buffer"
Stay in EX, use CORE for that one buffer:
```rust
let device = runtime.device();
let custom_buf = core::Buffer::new(&device, size, usage, properties)?;
```
### "I want type-safe resource IDs"
Move EZ → EX:
```rust
// Before: renderer.quick_pipeline() returns PipelineId but no tracking
// After: shaders.add_shader() returns ShaderId, build_pipeline() returns PipelineId
```
### "Manual cleanup is annoying"
Move CORE → EX:
```rust
// Before: shader.destroy(&device); pipeline.destroy(&device);
// After: ShaderManager drops everything automatically
```
### "I need to learn Vulkan"
Start EZ → Read CORE code:
```rust
// Use EZ to get started
let renderer = EzRenderer::new()?;
// Read CORE source to understand what's happening
// File: src/core/instance.rs, device.rs, etc.
```
---
## API Mapping
### Setup
| `EzRenderer::new()` | `RuntimeManager::new()` | `Instance::new()` + `Device::new()` |
### Shaders
| `quick_pipeline(v,f)` | `add_shader()` + `build_pipeline()` | `Shader::from_glsl()` + `Pipeline::new()` |
### Buffers
| `create_vertex_buffer()` | `create_vertex_buffer()` | `Buffer::new()` |
### Rendering
| `render_frame(\|frame\| ...)` | `begin_frame()` + `end_frame()` | Manual command recording + `queue.submit()` |
---
## Decision Tree
```
Need maximum simplicity?
YES → Use EZ
NO ↓
Building production app?
YES → Use EX
NO ↓
Building rendering engine/framework?
YES → Use CORE
NO ↓
Learning Vulkan?
Start EZ → Read CORE → Use EX for projects
```
---
## Examples
See `demos/` for examples of each tier in action:
- `demos/ez/` - EZ tier examples
- `demos/ex/` - EX tier examples
- `demos/core/` - CORE tier examples
---
## Next Steps
- **EZ Guide:** [EZ Tier Guide](ez-tier-guide.md)
- **EX Guide:** [EX Tier Guide](ex-tier-guide.md)
- **CORE Guide:** [CORE Tier Guide](core-tier-guide.md)
- **FAQ:** [Common Questions](../getting-started/faq.md)