# API Reference
Welcome to the **shdrlib API Reference** (version 0.1.4). This document provides a comprehensive, quick-reference guide to the library's three-tier architecture and its public APIs.
> **Note (v0.1.4):** The EX and EZ tiers now provide automatic memory management for buffers, images, and meshes. All critical memory safety issues have been resolved—users no longer need to manually destroy resources, and use-after-free/double-free bugs are eliminated.
## About This Reference
This reference is designed for developers who are already familiar with the basics of shdrlib and need quick access to API signatures, return types, and usage patterns. If you're new to the library, we recommend starting with the [Quick Start Guide](QUICKSTART.md) or exploring the [documentation](docs/README.md).
**What you'll find here:**
- Complete API signatures for all three tiers (CORE, EX, EZ)
- Method descriptions with parameter and return type information
- Common usage patterns and tier comparison examples
- Error types and their use cases
- Quick navigation to tier-specific functionality
**What you won't find here:**
- Comprehensive tutorials (see [docs/getting-started/](docs/getting-started/))
- Architecture discussions (see [docs/architecture/](docs/architecture/))
- Detailed examples (see [demos/](demos/) directory)
- In-depth API documentation (run `cargo doc --open`)
## How to Use This Reference
**By tier:**
- [CORE Tier](#core-tier) - For maximum control and custom frameworks
- [EX Tier](#ex-tier) - For production applications (recommended)
- [EZ Tier](#ez-tier) - For learning and rapid prototyping
**By task:**
- Looking for a specific method? Use your browser's search (Ctrl+F / Cmd+F)
- Need usage examples? See [Common Patterns](#common-patterns)
- Handling errors? Check [Error Types](#error-types)
> **💡 Tip:** For detailed documentation with examples and in-depth explanations, run `cargo doc --open` to generate the full API documentation from source.
---
## CORE Tier
The CORE tier provides thin, ergonomic wrappers around Vulkan with minimal abstraction. These APIs offer maximum control and zero-cost overhead, making them suitable for custom frameworks, engines, or when you need direct access to Vulkan objects.
**Key characteristics:**
- Manual lifetime management required
- Direct access to underlying Vulkan handles
- Explicit resource cleanup
- Suitable for advanced users and framework builders
> ⚠️ **Safety Note:** While CORE uses safe Rust, incorrect usage (like using objects after their dependencies are destroyed) can lead to undefined behavior. The EX tier provides automatic lifetime management if this is a concern.
### Instance & Device
The foundation of any Vulkan application. Instance represents the Vulkan API connection, while Device provides access to GPU functionality.
```rust
use shdrlib::core::{Instance, Device, InstanceCreateInfo, DeviceCreateInfo};
// Create Instance - connects to Vulkan API
let instance = Instance::new(&InstanceCreateInfo {
app_name: "My Application".to_string(),
enable_validation: true,
..Default::default()
})?;
// Query methods
instance.handle() -> &ash::Instance // Get raw Vulkan handle
instance.enumerate_physical_devices() -> Result<Vec<vk::PhysicalDevice>> // List GPUs
instance.get_physical_device_properties(device) -> vk::PhysicalDeviceProperties
instance.get_physical_device_features(device) -> vk::PhysicalDeviceFeatures
// Create Device - logical connection to a GPU
let physical_devices = instance.enumerate_physical_devices()?;
let device = Device::new(&instance, physical_devices[0], &DeviceCreateInfo {
queue_families: vec![/* ... */],
..Default::default()
})?;
// Device methods
device.handle() -> &ash::Device // Get raw Vulkan handle
device.physical_device() -> vk::PhysicalDevice // Get physical device
device.find_memory_type(type_filter, properties) -> Option<u32> // Find suitable memory
device.find_queue_family(flags) -> Option<u32> // Find queue family index
device.wait_idle() -> Result<()> // Wait for all GPU work
```
**Common usage:** Create once at application startup, share across your application via `Arc`.
### Shaders & Pipelines
Compile GLSL shaders to SPIR-V and build graphics or compute pipelines.
```rust
use shdrlib::core::{Shader, ShaderStage, Pipeline, PipelineBuilder};
use ash::vk;
// Shader - compile GLSL to SPIR-V (pure Rust, no external tools!)
let vertex_shader = Shader::from_glsl(
&device,
VERTEX_SOURCE,
ShaderStage::Vertex,
"main" // Entry point
)?;
let fragment_shader = Shader::from_spirv(
&device,
&spirv_bytes,
ShaderStage::Fragment,
"main"
)?;
// Shader methods
vertex_shader.handle() -> vk::ShaderModule // Get Vulkan shader module
vertex_shader.stage() -> ShaderStage // Get shader stage
vertex_shader.reflection() -> &ShaderReflection // Get reflection data
vertex_shader.destroy(&device) // Manual cleanup required
// Pipeline - configurable graphics or compute pipeline
let pipeline = PipelineBuilder::new()
.vertex_shader(vertex_shader.handle(), "main")
.fragment_shader(fragment_shader.handle(), "main")
.color_attachment_formats(vec![vk::Format::R8G8B8A8_UNORM])
.depth_format(vk::Format::D32_SFLOAT)
.cull_mode(vk::CullModeFlags::BACK)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
.polygon_mode(vk::PolygonMode::FILL)
.build_graphics(&device, layout)?;
// Pipeline methods
pipeline.handle() -> vk::Pipeline // Get Vulkan pipeline
pipeline.layout() -> vk::PipelineLayout // Get pipeline layout
pipeline.bind_point() -> vk::PipelineBindPoint // Get bind point
pipeline.destroy(&device) // Manual cleanup required
```
**Common usage:** Load shaders at initialization, build pipelines for each material/effect.
### Buffers & Images
Memory allocation for vertex data, uniforms, textures, and render targets.
```rust
use shdrlib::core::{Buffer, Image};
use ash::vk;
// Buffer - GPU memory for vertex data, uniforms, storage, etc.
let buffer = Buffer::new(
&device,
size_in_bytes,
vk::BufferUsageFlags::VERTEX_BUFFER | vk::BufferUsageFlags::TRANSFER_DST,
vk::MemoryPropertyFlags::DEVICE_LOCAL
)?;
// Buffer methods
buffer.handle() -> vk::Buffer // Get Vulkan buffer
buffer.size() -> vk::DeviceSize // Get buffer size
buffer.map<T>(&device) -> Result<*mut T> // Map for CPU access
buffer.unmap(&device) // Unmap after writing
buffer.copy_from_slice(&device, data: &[T]) -> Result<()> // Write data (if host visible)
buffer.destroy(&device) // Manual cleanup required
// Image - textures, render targets, depth buffers
let image = Image::new(
&device,
vk::Extent3D { width: 1024, height: 1024, depth: 1 },
vk::Format::R8G8B8A8_UNORM,
vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::SAMPLED,
vk::MemoryPropertyFlags::DEVICE_LOCAL
)?;
// Image methods
image.handle() -> vk::Image // Get Vulkan image
image.view() -> vk::ImageView // Get image view
image.format() -> vk::Format // Get image format
image.extent() -> vk::Extent3D // Get dimensions
image.destroy(&device) // Manual cleanup required
```
**Common usage:** Create buffers for geometry and uniforms, images for textures and framebuffers.
### Commands
Command pools allocate command buffers, which record GPU operations for execution.
```rust
use shdrlib::core::{CommandPool, CommandBuffer};
use ash::vk;
// CommandPool - allocates command buffers
let pool = CommandPool::new(
&device,
queue_family_index,
vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER
)?;
// Pool methods
pool.handle() -> vk::CommandPool // Get Vulkan pool
pool.allocate_primary(&device, count) -> Result<Vec<CommandBuffer>>
pool.allocate_secondary(&device, count) -> Result<Vec<CommandBuffer>>
pool.reset(&device, flags) -> Result<()> // Reset all buffers
pool.destroy(&device) // Manual cleanup required
// CommandBuffer - records GPU commands
let mut cmd = pool.allocate_primary(&device, 1)?[0];
// Recording commands
cmd.begin(&device, vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT)?;
// Rendering commands
cmd.begin_rendering(&device, &rendering_info);
cmd.bind_pipeline(&device, vk::PipelineBindPoint::GRAPHICS, pipeline);
cmd.bind_vertex_buffers(&device, 0, &[buffer.handle()], &[0]);
cmd.bind_index_buffer(&device, index_buffer, 0, vk::IndexType::UINT16);
cmd.bind_descriptor_sets(&device, bind_point, layout, first_set, sets, offsets);
cmd.set_viewport(&device, &[viewport]);
cmd.set_scissor(&device, &[scissor]);
cmd.draw(&device, vertex_count, instance_count, first_vertex, first_instance);
cmd.draw_indexed(&device, index_count, instance_count, first_index, vertex_offset, first_instance);
cmd.end_rendering(&device);
// Compute commands
cmd.bind_pipeline(&device, vk::PipelineBindPoint::COMPUTE, compute_pipeline);
cmd.dispatch(&device, group_count_x, group_count_y, group_count_z);
// Transfer commands
cmd.copy_buffer(&device, src_buffer, dst_buffer, &[copy_region]);
cmd.copy_buffer_to_image(&device, buffer, image, layout, &[regions]);
// Synchronization
cmd.pipeline_barrier(&device, src_stage, dst_stage, deps, mem_barriers, buf_barriers, img_barriers);
cmd.end(&device)?;
```
**Common usage:** Allocate one pool per thread, reuse command buffers each frame.
### Synchronization & Queues
Fences, semaphores, and queues manage GPU execution and host-GPU synchronization.
```rust
use shdrlib::core::{Fence, Semaphore, Queue};
use ash::vk;
// Fence - CPU waits for GPU
let fence = Fence::new(&device, true)?; // signaled = true for first frame
// Fence methods
fence.handle() -> vk::Fence // Get Vulkan fence
fence.wait(&device, timeout_ns) -> Result<()> // Wait for GPU
fence.reset(&device) -> Result<()> // Reset to unsignaled
fence.status(&device) -> Result<bool> // Check if signaled
fence.destroy(&device) // Manual cleanup required
// Semaphore - GPU-GPU synchronization
let semaphore = Semaphore::new(&device)?;
// Semaphore methods
semaphore.handle() -> vk::Semaphore // Get Vulkan semaphore
semaphore.destroy(&device) // Manual cleanup required
// Queue - submits work to GPU
let queue = Queue::get(&device, queue_family_index, queue_index);
// Queue methods
queue.handle() -> vk::Queue // Get Vulkan queue
queue.family_index() -> u32 // Get queue family
queue.submit(
&device,
&[cmd_buffer],
&[wait_semaphore],
&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT],
&[signal_semaphore],
fence
) -> Result<()>
queue.present(
&device,
&[swapchain],
&[image_index],
&[wait_semaphore]
) -> Result<bool> // Returns true if suboptimal
queue.wait_idle(&device) -> Result<()> // Wait for queue completion
```
**Common usage:** Use fences for frame synchronization, semaphores for queue dependencies.
### Descriptors
Descriptor sets bind resources (buffers, images) to shader bindings.
```rust
use shdrlib::core::{DescriptorPool, DescriptorSetLayout, DescriptorSet, update_descriptor_sets};
use ash::vk;
// DescriptorSetLayout - defines binding layout
let layout = DescriptorSetLayout::new(&device, &bindings)?;
// DescriptorPool - allocates descriptor sets
let pool = DescriptorPool::new(&device, max_sets, &pool_sizes)?;
// Allocate sets
let sets = pool.allocate(&device, &[layout.handle()])?;
// Update descriptor sets
update_descriptor_sets(&device, &writes, &[])?;
// Layout methods
layout.handle() -> vk::DescriptorSetLayout
layout.destroy(&device)
// Pool methods
pool.handle() -> vk::DescriptorPool
pool.allocate(&device, layouts) -> Result<Vec<DescriptorSet>>
pool.reset(&device) -> Result<()>
pool.destroy(&device)
```
**Common usage:** Create layouts at init, allocate from pools per material, update as needed.
### Surface & Swapchain
Surface represents a window, swapchain manages presentation images.
```rust
use shdrlib::core::{Surface, Swapchain};
// Surface - window surface (platform-specific)
let surface = Surface::new(&instance, window_handle)?;
// Surface methods
surface.handle() -> vk::SurfaceKHR
surface.get_capabilities(&instance, physical_device) -> Result<vk::SurfaceCapabilitiesKHR>
surface.get_formats(&instance, physical_device) -> Result<Vec<vk::SurfaceFormatKHR>>
surface.get_present_modes(&instance, physical_device) -> Result<Vec<vk::PresentModeKHR>>
surface.destroy(&instance)
// Swapchain - presentation images
let swapchain = Swapchain::new(&device, &surface, &create_info)?;
// Swapchain methods
swapchain.handle() -> vk::SwapchainKHR
swapchain.images() -> &[vk::Image]
swapchain.format() -> vk::Format
swapchain.extent() -> vk::Extent2D
swapchain.acquire_next_image(&device, timeout, semaphore, fence) -> Result<(u32, bool)>
swapchain.destroy(&device)
```
**Common usage:** Create at startup, recreate on window resize.
---
## EX Tier
The EX (Explicit) tier provides production-ready abstractions that balance control with convenience. This tier is **recommended for most applications**, offering automatic resource management through Rust's ownership system while maintaining explicit configuration and zero-cost abstractions.
**Key characteristics:**
- Automatic resource cleanup via RAII (Resource Acquisition Is Initialization)
- Type-safe resource handles (no raw integers)
- Explicit configuration with sensible builder patterns
- 4-8x code reduction compared to CORE tier
- Zero runtime overhead
> ✅ **Memory Safety:** All EX tier types guarantee memory safety through Rust's ownership model and automatic cleanup via `Drop`. Use-after-free and double-free are prevented by design. Manual cleanup is never required—resources are released safely when they go out of scope. We continue to audit and improve safety; please report any issues.
### RuntimeManager
The `RuntimeManager` handles Vulkan instance, device, queues, command pools, and frame synchronization. It provides the foundation for EX tier applications.
```rust
use shdrlib::ex::{RuntimeManager, RuntimeConfig, SubmitInfo};
// Configuration (all fields optional with sensible defaults)
let config = RuntimeConfig {
app_name: "My Application".to_string(),
enable_validation: true,
preferred_device_index: None, // Auto-select best GPU
..Default::default()
};
// Create runtime - handles all Vulkan initialization
let mut runtime = RuntimeManager::new(config)?;
// Access managed resources
runtime.device() -> Arc<Device> // Get device (thread-safe)
runtime.queue() -> &Queue // Get graphics queue
runtime.command_pool() -> &CommandPool // Get command pool
runtime.instance() -> &Instance // Get Vulkan instance
// Frame management with automatic synchronization
let frame = runtime.begin_frame()?; // Acquire frame resources
// frame.command_buffer -> CommandBuffer // Pre-allocated command buffer
// frame.frame_index -> usize // Current frame index
// frame.fence -> &Fence // Frame fence
// Submit work to GPU
runtime.end_frame(&SubmitInfo {
wait_semaphores: vec![],
signal_semaphores: vec![],
..Default::default()
})?;
// Utility methods
runtime.wait_idle()?; // Wait for all GPU work
runtime.device_name() -> &str // Get GPU name
```
**Common usage:** Create once at startup, use for entire application lifetime. Handles all synchronization automatically.
### ShaderManager
The `ShaderManager` provides type-safe shader compilation and pipeline management with automatic cleanup.
```rust
use shdrlib::ex::{ShaderManager, PipelineBuilder, ShaderId, PipelineId};
use shdrlib::core::ShaderStage;
use ash::vk;
// Create manager
let mut shaders = ShaderManager::new(runtime.device())?;
// Add shaders - returns type-safe IDs (not integers!)
let vert_id: ShaderId = shaders.add_shader(
VERTEX_GLSL,
ShaderStage::Vertex,
"my_vertex_shader" // Optional debug name
)?;
let frag_id: ShaderId = shaders.add_shader(
FRAGMENT_GLSL,
ShaderStage::Fragment,
"my_fragment_shader"
)?;
let compute_id: ShaderId = shaders.add_shader(
COMPUTE_GLSL,
ShaderStage::Compute,
"my_compute_shader"
)?;
// Build graphics pipeline - returns type-safe ID
let pipeline_id: PipelineId = 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])
.depth_format(vk::Format::D32_SFLOAT)
.cull_mode(vk::CullModeFlags::BACK)
.topology(vk::PrimitiveTopology::TRIANGLE_LIST),
"my_graphics_pipeline"
)?;
// Build compute pipeline
let compute_pipeline_id: PipelineId = shaders.build_compute_pipeline(
shaders.get_shader(compute_id)?.handle(),
"main",
layout,
"my_compute_pipeline"
)?;
// Query methods
shaders.get_shader(vert_id) -> Result<&Shader> // Get shader by ID
shaders.get_pipeline(pipeline_id) -> Result<&Pipeline> // Get pipeline by ID
shaders.shader_count() -> usize // Total shader count
shaders.pipeline_count() -> usize // Total pipeline count
// Pipelines and shaders are automatically cleaned up when ShaderManager drops
```
**Common usage:** Create once, add all shaders at startup, access pipelines via type-safe IDs during rendering.
### PipelineBuilder
The `PipelineBuilder` provides a fluent API for configuring graphics pipelines. All methods are optional with sensible defaults.
```rust
use shdrlib::ex::PipelineBuilder;
use ash::vk;
let pipeline_id = shaders.build_pipeline(
PipelineBuilder::new()
// Shaders (vertex required for graphics)
.vertex_shader(vert_module, "main")
.fragment_shader(frag_module, "main")
.geometry_shader(geom_module, "main")
.tessellation_shaders(tesc_module, tese_module, "main", "main")
// Render targets
.color_attachment_formats(vec![vk::Format::R8G8B8A8_UNORM])
.depth_format(vk::Format::D32_SFLOAT)
.stencil_format(vk::Format::D24_UNORM_S8_UINT)
// Rasterization
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
.polygon_mode(vk::PolygonMode::FILL)
.cull_mode(vk::CullModeFlags::BACK)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
.line_width(1.0)
// Depth/Stencil
.depth_test_enable(true)
.depth_write_enable(true)
.depth_compare_op(vk::CompareOp::LESS)
// Blending
.blend_enable(false)
.blend_src_color(vk::BlendFactor::SRC_ALPHA)
.blend_dst_color(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
// Multisampling
.samples(vk::SampleCountFlags::TYPE_1)
// Vertex input (auto-inferred from shaders by default)
.vertex_bindings(bindings)
.vertex_attributes(attributes),
"pipeline_name"
)?;
```
**Common usage:** Configure only what you need to change from defaults. Most pipelines only need shaders and color formats.
### Resource Wrappers
EX tier provides RAII wrappers for automatic resource cleanup. These are returned by helper functions.
```rust
use shdrlib::ex::{ExBuffer, ExImage};
use ash::vk;
// ExBuffer - buffer with automatic cleanup
// Created by helper functions (see below)
let buffer: ExBuffer = /* ... */;
// ExBuffer methods
buffer.handle() -> vk::Buffer // Get Vulkan handle
buffer.size() -> vk::DeviceSize // Get size in bytes
buffer.usage() -> vk::BufferUsageFlags // Get usage flags
buffer.core_buffer() -> &core::Buffer // Get underlying CORE buffer
buffer.into_core() -> core::Buffer // Transfer ownership to caller
// ExImage - image with automatic cleanup
let image: ExImage = /* ... */;
// ExImage methods
image.handle() -> vk::Image // Get Vulkan handle
image.view() -> vk::ImageView // Get image view
image.extent() -> vk::Extent3D // Get dimensions
image.format() -> vk::Format // Get pixel format
image.usage() -> vk::ImageUsageFlags // Get usage flags
image.core_image() -> &core::Image // Get underlying CORE image
image.into_core() -> core::Image // Transfer ownership to caller
// Cleanup is automatic when these go out of scope!
```
**Common usage:** Let helper functions create these, store in your structures, cleanup is automatic.
### Buffer Helpers
Helper functions for creating common buffer types. All return `ExBuffer` with automatic cleanup.
```rust
use shdrlib::ex::helpers::*;
use std::sync::Arc;
// Vertex buffer - stores vertex data
let vertex_data: &[Vertex] = &[/* ... */];
let vbuf: ExBuffer = create_vertex_buffer(&device, vertex_data)?;
// Index buffer - stores indices (u16 or u32)
let indices: &[u16] = &[0, 1, 2];
let ibuf: ExBuffer = create_index_buffer(&device, indices)?;
// Uniform buffer - for shader uniforms (camera, lights, etc.)
#[repr(C)]
struct CameraData {
view: [[f32; 4]; 4],
projection: [[f32; 4]; 4],
}
let ubuf: ExBuffer = create_uniform_buffer::<CameraData>(&device)?;
// Storage buffer - for large data arrays, compute results
let sbuf: ExBuffer = create_storage_buffer(&device, size_in_bytes)?;
// Staging buffer - for CPU-to-GPU transfers
let staging: ExBuffer = create_staging_buffer(&device, size_in_bytes)?;
// Write data to buffer
write_buffer(&device, vbuf.core_buffer(), &new_vertex_data)?;
// Copy between buffers (requires pool and queue)
copy_buffer(&device, &pool, &queue, staging.core_buffer(), vbuf.core_buffer(), size)?;
```
**Common usage:** Create buffers at initialization or when loading assets. Write/copy data as needed.
### Image Helpers
Helper functions for creating images and textures. All return `ExImage` with automatic cleanup.
```rust
use shdrlib::ex::helpers::*;
use ash::vk;
// Render target - color attachment
let render_target: ExImage = create_render_target(
&device,
width,
height,
vk::Format::R8G8B8A8_UNORM
)?;
// Depth/stencil buffer
let depth: ExImage = create_depth_stencil(
&device,
width,
height,
vk::Format::D32_SFLOAT
)?;
// Texture - for sampling in shaders
let texture: ExImage = create_texture(
&device,
width,
height,
vk::Format::R8G8B8A8_SRGB
)?;
// Storage image - for compute shader output
let storage: ExImage = create_storage_image(
&device,
width,
height,
vk::Format::R8G8B8A8_UNORM
)?;
// Image layout transitions (requires pool and queue)
transition_image_layout(
&device,
&pool,
&queue,
texture.core_image(),
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL
)?;
```
**Common usage:** Create render targets and depth buffers at initialization or on resize. Create textures when loading assets.
### Descriptor Helpers
Fluent builders for descriptor set layouts, pools, and updates.
```rust
use shdrlib::ex::helpers::*;
use ash::vk;
// Descriptor set layout builder
let layout: ExDescriptorSetLayout = DescriptorLayoutBuilder::new()
.uniform_buffer(0, vk::ShaderStageFlags::VERTEX) // Binding 0: UBO in vertex shader
.storage_buffer(1, vk::ShaderStageFlags::COMPUTE) // Binding 1: SSBO in compute
.combined_image_sampler(2, vk::ShaderStageFlags::FRAGMENT) // Binding 2: Texture in fragment
.storage_image(3, vk::ShaderStageFlags::COMPUTE) // Binding 3: Storage image
.build(&device)?;
// Descriptor pool builder
let pool: ExDescriptorPool = DescriptorPoolBuilder::new()
.max_sets(100)
.uniform_buffers(100)
.combined_image_samplers(200)
.build(&device)?;
// Or use preset configurations
let material_pool = DescriptorPoolBuilder::simple_material(50).build(&device)?;
// Descriptor writer - fluent API for updates
DescriptorWriter::new(&device)
.write_buffer(
descriptor_set,
0, // Binding
buffer.handle(),
0, // Offset
vk::WHOLE_SIZE, // Range
vk::DescriptorType::UNIFORM_BUFFER
)
.write_image(
descriptor_set,
2, // Binding
image.view(),
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::DescriptorType::COMBINED_IMAGE_SAMPLER
)
.update(); // Submits all updates at once
```
**Common usage:** Create layouts matching shader bindings, allocate from pools, update before rendering.
---
## EZ Tier
The EZ tier provides the simplest possible API for shader-based rendering with intelligent defaults and automatic resource management. This tier is ideal for learning, prototyping, and shader experiments where you want to focus on visuals rather than Vulkan boilerplate.
**Key characteristics:**
- One-liner initialization with sensible defaults
- Mesh API for simplified geometry management
- Automatic viewport and scissor management
- Progressive disclosure - access lower tiers when needed
- 13-26x code reduction compared to CORE tier
> ✅ **Memory Safety:** EZ tier APIs are designed for maximum safety and simplicity. All resources are managed automatically, leveraging Rust's ownership and `Drop` semantics. Use-after-free and double-free are impossible in safe code. We welcome feedback and remain committed to ongoing safety improvements.
### EzRenderer
The `EzRenderer` is your main entry point to the EZ tier. It wraps `RuntimeManager` and `ShaderManager` with additional convenience methods.
```rust
use shdrlib::ez::{EzRenderer, EzConfig};
// Simple initialization with defaults
let mut renderer = EzRenderer::new()?;
// Or with custom configuration
let config = EzConfig {
app_name: "My Application".to_string(),
enable_validation: true,
preferred_device_index: None,
..Default::default()
};
let mut renderer = EzRenderer::with_config(config)?;
// Pipeline creation - one-liners!
let graphics_pipeline = renderer.quick_pipeline(VERTEX_GLSL, FRAGMENT_GLSL)?;
let compute_pipeline = renderer.quick_compute(COMPUTE_GLSL)?;
// Mesh API - bundled vertex and index buffers
let mesh = renderer.create_mesh(&vertices, Some(&indices))?; // Indexed mesh
let mesh = renderer.create_mesh(&vertices, None)?; // Non-indexed mesh
let quad = renderer.create_fullscreen_quad()?; // Pre-built fullscreen quad
// Buffer creation - returns ExBuffer with automatic cleanup
let vbuf = renderer.create_vertex_buffer(&vertices)?;
let ibuf = renderer.create_index_buffer(&indices)?;
let ubuf = renderer.create_uniform_buffer::<UniformData>()?;
let sbuf = renderer.create_storage_buffer(size_in_bytes)?;
// Image creation - returns ExImage with automatic cleanup
let render_target = renderer.create_render_target(width, height)?;
let depth_buffer = renderer.create_depth_stencil(width, height)?;
let texture = renderer.create_texture(width, height)?;
// Write data to buffers
renderer.write_buffer(&buffer, &data)?;
// Rendering with closure-based API
renderer.render_frame(|frame| {
// One-line mesh drawing (recommended!)
frame.draw_mesh(graphics_pipeline, &quad)?;
// Or manual binding if you prefer
frame.bind_pipeline(graphics_pipeline)?;
frame.draw(3, 1, 0, 0);
Ok(())
})?;
// Wait for GPU completion
renderer.wait_idle()?;
// Access lower tiers for more control
renderer.runtime() -> &RuntimeManager
renderer.runtime_mut() -> &mut RuntimeManager
renderer.shader_manager() -> &ShaderManager
renderer.shader_manager_mut() -> &mut ShaderManager
renderer.device() -> Arc<Device>
```
**Common usage:** Create once, use for entire application. Perfect for shader experiments and prototypes.
### EzFrame
The `EzFrame` is provided in the render callback and offers drawing commands with automatic state management.
```rust
renderer.render_frame(|frame| {
// === Mesh API (Simplified) ===
// One-line drawing - handles everything!
frame.draw_mesh(pipeline_id, &mesh)?;
// Equivalent to: bind_pipeline + bind_vertex_buffers + bind_index_buffer +
// set_viewport + set_scissor + draw_indexed
// === Manual Drawing (If You Prefer) ===
// Pipeline binding
frame.bind_pipeline(graphics_pipeline)?;
frame.bind_compute_pipeline(compute_pipeline)?;
// Buffer binding
frame.bind_vertex_buffers(first_binding, &[buffer.handle()], &[offset]);
frame.bind_index_buffer(buffer.handle(), offset, vk::IndexType::UINT16);
// Descriptor sets
frame.bind_descriptor_sets(
pipeline_id,
first_set,
&[descriptor_set],
&[dynamic_offset]
)?;
// Push constants
frame.push_constants(pipeline_id, stage_flags, offset, &data)?;
// Drawing commands
frame.draw(vertex_count, instance_count, first_vertex, first_instance);
frame.draw_indexed(index_count, instance_count, first_index, vertex_offset, first_instance);
// Compute dispatch
frame.dispatch(group_count_x, group_count_y, group_count_z);
// === Dynamic State ===
// These are automatically set by begin_rendering, but can be overridden
frame.set_viewport(x, y, width, height);
frame.set_scissor(x, y, width, height);
// === Rendering Control ===
// Begin rendering - automatically sets viewport and scissor
frame.begin_rendering(width, height, format);
// ... draw commands ...
frame.end_rendering();
// === Low-Level Access ===
frame.command_buffer() -> vk::CommandBuffer // For advanced users
frame.device() -> &Arc<Device> // Access device
Ok(())
})?;
```
**Common usage:** Use `draw_mesh()` for simple rendering, drop to manual commands for advanced control.
### Mesh
The `Mesh` type bundles vertex and index buffers together for simplified drawing. Created via `EzRenderer`.
```rust
use shdrlib::ez::Mesh;
// Create mesh (via renderer)
let vertices: Vec<f32> = vec![
// x, y, u, v
-1.0, -1.0, 0.0, 0.0,
1.0, -1.0, 1.0, 0.0,
1.0, 1.0, 1.0, 1.0,
-1.0, 1.0, 0.0, 1.0,
];
let indices: Vec<u16> = vec![0, 1, 2, 2, 3, 0];
let mesh = renderer.create_mesh(&vertices, Some(&indices))?;
// Or use pre-built quad
let quad = renderer.create_fullscreen_quad()?;
// Covers entire screen (-1, -1) to (1, 1) with UVs (0, 0) to (1, 1)
// Query methods
mesh.vertex_buffer() -> &Buffer // Get vertex buffer
mesh.index_buffer() -> Option<&Buffer> // Get index buffer (if indexed)
mesh.vertex_count() -> u32 // Number of vertices
mesh.index_count() -> Option<u32> // Number of indices (if indexed)
mesh.is_indexed() -> bool // Check if mesh uses indices
mesh.index_type() -> vk::IndexType // Index type (UINT16/UINT32)
mesh.binding() -> u32 // Vertex buffer binding (usually 0)
// Drawing
renderer.render_frame(|frame| {
frame.draw_mesh(pipeline_id, &mesh)?;
Ok(())
})?;
```
**Common usage:** Create meshes for your geometry, use `draw_mesh()` for one-line rendering. Pre-built quad is perfect for fullscreen effects.
### Buffer Helpers (EZ Tier)
EZ tier buffer helpers are identical to EX tier but accessed through `EzRenderer`:
```rust
// All return ExBuffer with automatic cleanup
let vertex_buffer = renderer.create_vertex_buffer(&vertex_data)?;
let index_buffer = renderer.create_index_buffer(&index_data)?;
let uniform_buffer = renderer.create_uniform_buffer::<T>()?;
let storage_buffer = renderer.create_storage_buffer(size)?;
// Write data
renderer.write_buffer(&buffer, &new_data)?;
```
### Image Helpers (EZ Tier)
EZ tier image helpers are simplified versions of EX tier helpers:
```rust
// All return ExImage with automatic cleanup
// Note: Uses sensible defaults for format
let render_target = renderer.create_render_target(width, height)?;
let depth_buffer = renderer.create_depth_stencil(width, height)?;
let texture = renderer.create_texture(width, height)?;
```
### Progressive Disclosure
EZ tier provides access to lower tiers when you need more control:
```rust
let mut renderer = EzRenderer::new()?;
// Access EX tier
let runtime = renderer.runtime();
let shader_manager = renderer.shader_manager();
// Access CORE tier through EX
let device = runtime.device();
let queue = runtime.queue();
// Even access raw Vulkan handles
let vk_device = device.handle(); // ash::Device
```
**Usage philosophy:** Start with EZ tier conveniences, drop to lower tiers only when necessary. Most shader experiments never need to leave EZ tier.
### Code Comparison: Manual vs. Mesh API
**Manual approach (11 lines):**
```rust
let vbuf = renderer.create_vertex_buffer(&vertices)?;
let ibuf = renderer.create_index_buffer(&indices)?;
renderer.render_frame(|frame| {
frame.bind_pipeline(pipeline)?;
frame.bind_vertex_buffers(0, &[vbuf.handle()], &[0]);
frame.bind_index_buffer(ibuf.handle(), 0, vk::IndexType::UINT16);
frame.set_viewport(0.0, 0.0, 800.0, 600.0);
frame.set_scissor(0, 0, 800, 600);
frame.draw_indexed(6, 1, 0, 0, 0);
Ok(())
})?;
```
**Mesh API (4 lines - 63% reduction):**
```rust
let mesh = renderer.create_mesh(&vertices, Some(&indices))?;
renderer.render_frame(|frame| {
frame.draw_mesh(pipeline, &mesh)?;
Ok(())
})?;
```
---
## Common Patterns
This section demonstrates practical usage patterns across all three tiers, showing how to accomplish common tasks and when to use each approach.
### Pattern 1: Triangle Rendering
**CORE Tier - Maximum Control (~400 lines)**
```rust
use shdrlib::core::*;
// Manual everything - you manage all lifetimes
let instance = Instance::new(&InstanceCreateInfo::default())?;
let devices = instance.enumerate_physical_devices()?;
let device = Device::new(&instance, devices[0], &DeviceCreateInfo::default())?;
let pool = CommandPool::new(&device, queue_family, CommandPoolCreateFlags::empty())?;
let shader = Shader::from_glsl(&device, GLSL, ShaderStage::Vertex, "main")?;
let pipeline = PipelineBuilder::new()
.vertex_shader(shader.handle(), "main")
.color_attachment_formats(vec![Format::R8G8B8A8_UNORM])
.build_graphics(&device, layout)?;
// Recording and submission
let mut cmd = pool.allocate_primary(&device, 1)?[0];
cmd.begin(&device, CommandBufferUsageFlags::ONE_TIME_SUBMIT)?;
cmd.begin_rendering(&device, &rendering_info);
cmd.bind_pipeline(&device, PipelineBindPoint::GRAPHICS, pipeline.handle());
cmd.draw(&device, 3, 1, 0, 0);
cmd.end_rendering(&device);
cmd.end(&device)?;
queue.submit(&device, &[cmd], &[], &[], &[], fence.handle())?;
fence.wait(&device, u64::MAX)?;
// Manual cleanup order matters!
shader.destroy(&device);
pipeline.destroy(&device);
pool.destroy(&device);
// device and instance drop automatically
```
**EX Tier - Production Ready (~100 lines)**
```rust
use shdrlib::ex::*;
// Automatic resource management
let mut runtime = RuntimeManager::new(RuntimeConfig::default())?;
let mut shaders = ShaderManager::new(runtime.device())?;
let vert = shaders.add_shader(VERT_GLSL, ShaderStage::Vertex, "vert")?;
let frag = shaders.add_shader(FRAG_GLSL, ShaderStage::Fragment, "frag")?;
let pipeline = shaders.build_pipeline(
PipelineBuilder::new()
.vertex_shader(shaders.get_shader(vert)?.handle(), "main")
.fragment_shader(shaders.get_shader(frag)?.handle(), "main")
.color_attachment_formats(vec![Format::R8G8B8A8_UNORM]),
"triangle"
)?;
// Frame management
loop {
let frame = runtime.begin_frame()?;
// Record commands using frame.command_buffer
let cmd = &frame.command_buffer;
// ... rendering ...
runtime.end_frame(&SubmitInfo::default())?;
}
// Cleanup is automatic - Drop handles everything
```
**EZ Tier - Learning & Prototyping (~25 lines)**
```rust
use shdrlib::ez::*;
let mut renderer = EzRenderer::new()?;
let pipeline = renderer.quick_pipeline(VERT_GLSL, FRAG_GLSL)?;
renderer.render_frame(|frame| {
frame.bind_pipeline(pipeline)?;
frame.draw(3, 1, 0, 0);
Ok(())
})?;
// Everything is automatic!
```
**EZ Tier with Mesh API - Shader Experiments (~15 lines)**
```rust
use shdrlib::ez::*;
let mut renderer = EzRenderer::new()?;
let quad = renderer.create_fullscreen_quad()?;
let pipeline = renderer.quick_pipeline(VERT_GLSL, FRAG_GLSL)?;
renderer.render_frame(|frame| {
frame.draw_mesh(pipeline, &quad)?;
Ok(())
})?;
```
### Pattern 2: Buffer Management
**Creating and Using Vertex Buffers**
**CORE Tier:**
```rust
// Manual allocation and mapping
let buffer = Buffer::new(
&device,
size,
BufferUsageFlags::VERTEX_BUFFER,
MemoryPropertyFlags::HOST_VISIBLE | MemoryPropertyFlags::HOST_COHERENT
)?;
unsafe {
let ptr = buffer.map::<Vertex>(&device)?;
std::ptr::copy_nonoverlapping(vertices.as_ptr(), ptr, vertices.len());
buffer.unmap(&device);
}
// Manual cleanup required
buffer.destroy(&device);
```
**EX Tier:**
```rust
use shdrlib::ex::helpers::*;
// One-liner with automatic cleanup
let buffer = create_vertex_buffer(&device, &vertices)?;
// buffer (ExBuffer) automatically cleans up on drop
```
**EZ Tier:**
```rust
// Via renderer
let buffer = renderer.create_vertex_buffer(&vertices)?;
// Automatic cleanup
```
### Pattern 3: Compute Shader
**EX Tier Example:**
```rust
use shdrlib::ex::*;
let mut runtime = RuntimeManager::new(RuntimeConfig::default())?;
let mut shaders = ShaderManager::new(runtime.device())?;
// Add compute shader
let compute_id = shaders.add_shader(COMPUTE_GLSL, ShaderStage::Compute, "compute")?;
// Create compute pipeline
let pipeline_id = shaders.build_compute_pipeline(
shaders.get_shader(compute_id)?.handle(),
"main",
layout,
"multiply"
)?;
// Create buffers
let input = helpers::create_storage_buffer(&device, size)?;
let output = helpers::create_storage_buffer(&device, size)?;
// Record and dispatch
let frame = runtime.begin_frame()?;
let cmd = &frame.command_buffer;
cmd.bind_pipeline(&device, PipelineBindPoint::COMPUTE,
shaders.get_pipeline(pipeline_id)?.handle());
cmd.bind_descriptor_sets(&device, PipelineBindPoint::COMPUTE, layout, 0, &[set], &[]);
cmd.dispatch(&device, work_groups_x, work_groups_y, work_groups_z);
runtime.end_frame(&SubmitInfo::default())?;
runtime.wait_idle()?;
```
**EZ Tier Example:**
```rust
use shdrlib::ez::*;
let mut renderer = EzRenderer::new()?;
let pipeline = renderer.quick_compute(COMPUTE_GLSL)?;
let input = renderer.create_storage_buffer(size)?;
let output = renderer.create_storage_buffer(size)?;
renderer.render_frame(|frame| {
frame.bind_compute_pipeline(pipeline)?;
frame.bind_descriptor_sets(pipeline, 0, &[set], &[])?;
frame.dispatch(work_groups_x, work_groups_y, work_groups_z);
Ok(())
})?;
```
### Pattern 4: Error Handling
**Graceful Error Handling:**
```rust
use shdrlib::ez::{EzRenderer, EzError};
fn create_renderer() -> Result<EzRenderer, Box<dyn std::error::Error>> {
let renderer = EzRenderer::new()
.map_err(|e| {
eprintln!("Failed to create renderer: {}", e);
e
})?;
Ok(renderer)
}
fn render_loop(renderer: &mut EzRenderer) -> Result<(), EzError> {
renderer.render_frame(|frame| {
// Early return on error
frame.bind_pipeline(pipeline)?;
frame.draw(3, 1, 0, 0);
Ok(())
})?;
Ok(())
}
```
**Nested Error Context:**
```rust
// EX tier provides rich error context
use shdrlib::ex::{RuntimeError, ShaderManagerError};
match shaders.add_shader(glsl, ShaderStage::Vertex, "my_shader") {
Ok(id) => println!("Shader compiled: {:?}", id),
Err(ShaderManagerError::CompilationFailed { name, message }) => {
eprintln!("Shader '{}' failed to compile:", name);
eprintln!("{}", message);
// Handle gracefully
}
Err(e) => eprintln!("Unexpected error: {}", e),
}
```
### Pattern 5: Tier Interoperability
**Mixing Tiers When Needed:**
```rust
use shdrlib::ez::EzRenderer;
let mut renderer = EzRenderer::new()?;
// Start with EZ convenience
let quad = renderer.create_fullscreen_quad()?;
// Drop to EX for specific control
let runtime = renderer.runtime_mut();
let device = runtime.device();
// Drop to CORE for maximum control
let vk_device = device.handle(); // ash::Device
// Use raw Vulkan for custom operations
unsafe {
vk_device.device_wait_idle()?;
}
// Back to EZ for rendering
renderer.render_frame(|frame| {
frame.draw_mesh(pipeline, &quad)?;
Ok(())
})?;
```
**When to Drop Down:**
- EZ → EX: Need custom pipeline configuration, descriptor management
- EX → CORE: Need direct Vulkan handle access, custom memory management
- Any → Raw Vulkan: Need features not exposed by shdrlib (rare)
### Pattern 6: Progressive Complexity
**Learning Path:**
```rust
// Week 1: Start with EZ tier
let mut renderer = EzRenderer::new()?;
let quad = renderer.create_fullscreen_quad()?;
// Focus on learning shaders
// Week 2: Add custom geometry
let vertices = vec![/* ... */];
let mesh = renderer.create_mesh(&vertices, None)?;
// Learn about vertex data
// Week 3: Add textures and uniforms
let texture = renderer.create_texture(width, height)?;
let uniform = renderer.create_uniform_buffer::<Camera>()?;
// Learn about descriptors
// Week 4: Drop to EX for more control
let runtime = renderer.runtime_mut();
let custom_pipeline = build_advanced_pipeline(runtime)?;
// Graduate to production patterns
```
### Best Practices
**Choose Your Tier:**
- **CORE**: Building engines, need maximum control, custom frameworks
- **EX**: Production applications, games, tools (recommended for most)
- **EZ**: Learning, prototyping, shader experiments, splash screens
**Resource Management:**
- CORE: Manual destroy calls, careful drop order
- EX: Automatic via RAII, stored in managers
- EZ: Automatic, completely transparent
**Error Handling:**
- Always use `?` operator or match for proper error propagation
- Log errors for debugging
- Provide user-friendly messages in release builds
**Performance:**
- All tiers compile to identical machine code
- Zero-cost abstractions - no runtime overhead
- Choose based on development productivity, not performance
---
## Error Types
shdrlib provides structured error types for each tier, making it easy to understand what went wrong and how to handle it. All error types implement `std::error::Error` and can be used with the `?` operator.
### CORE Tier Errors
Each CORE module has its own error type, providing fine-grained error information:
```rust
use shdrlib::core::*;
// Instance errors
pub enum InstanceError {
VulkanInitializationFailed(vk::Result),
NoPhysicalDevices,
ExtensionNotSupported(String),
ValidationLayersNotAvailable,
}
// Device errors
pub enum DeviceError {
NoSuitableDevice,
QueueFamilyNotFound,
DeviceCreationFailed(vk::Result),
OutOfMemory,
}
// Shader errors
pub enum ShaderError {
CompilationFailed(String), // GLSL compilation error
InvalidSpirv,
ModuleCreationFailed(vk::Result),
ReflectionFailed(String),
}
// Pipeline errors
pub enum PipelineError {
InvalidConfiguration(String),
CreationFailed(vk::Result),
LayoutCreationFailed(vk::Result),
MissingShaderStage,
}
// Memory errors
pub enum MemoryError {
AllocationFailed(vk::Result),
NoSuitableMemoryType,
MapFailed(vk::Result),
OutOfMemory,
}
// Command errors
pub enum CommandPoolError {
CreationFailed(vk::Result),
AllocationFailed(vk::Result),
ResetFailed(vk::Result),
}
pub enum CommandBufferError {
BeginFailed(vk::Result),
EndFailed(vk::Result),
RecordingFailed(String),
}
// Queue errors
pub enum QueueError {
SubmitFailed(vk::Result),
PresentFailed(vk::Result),
WaitFailed(vk::Result),
}
// Sync errors
pub enum FenceError {
CreationFailed(vk::Result),
WaitFailed(vk::Result),
ResetFailed(vk::Result),
Timeout,
}
pub enum SemaphoreError {
CreationFailed(vk::Result),
}
// Descriptor errors
pub enum DescriptorError {
LayoutCreationFailed(vk::Result),
PoolCreationFailed(vk::Result),
AllocationFailed(vk::Result),
UpdateFailed(String),
}
// Surface and swapchain errors
pub enum SurfaceError {
CreationFailed(vk::Result),
QueryFailed(vk::Result),
}
pub enum SwapchainError {
CreationFailed(vk::Result),
AcquireImageFailed(vk::Result),
Suboptimal,
OutOfDate,
}
```
**Usage example:**
```rust
match Shader::from_glsl(&device, glsl_source, ShaderStage::Vertex, "main") {
Ok(shader) => {
// Use shader
}
Err(ShaderError::CompilationFailed(msg)) => {
eprintln!("Shader compilation failed:");
eprintln!("{}", msg);
// Handle compilation error
}
Err(e) => {
eprintln!("Unexpected error: {}", e);
}
}
```
### EX Tier Errors
EX tier provides high-level errors that wrap CORE errors with additional context:
```rust
use shdrlib::ex::*;
// Runtime manager errors
pub enum RuntimeError {
InstanceCreationFailed(InstanceError),
DeviceCreationFailed(DeviceError),
CommandPoolCreationFailed(CommandPoolError),
SyncObjectCreationFailed(String),
FrameAcquisitionFailed(String),
SubmitFailed(QueueError),
}
// Shader manager errors
pub enum ShaderManagerError {
CompilationFailed { name: String, message: String },
ShaderNotFound(ShaderId),
PipelineNotFound(PipelineId),
PipelineCreationFailed { name: String, error: PipelineError },
InvalidShaderId(ShaderId),
InvalidPipelineId(PipelineId),
}
// Type-safe IDs prevent many errors at compile time
pub struct ShaderId(usize); // Can't mix with PipelineId
pub struct PipelineId(usize); // Can't mix with ShaderId
```
**Usage example:**
```rust
match shaders.add_shader(glsl, ShaderStage::Vertex, "my_shader") {
Ok(id) => {
println!("Shader compiled successfully: {:?}", id);
}
Err(ShaderManagerError::CompilationFailed { name, message }) => {
eprintln!("Failed to compile shader '{}':", name);
eprintln!("{}", message);
// Show error to user or use fallback shader
}
Err(e) => {
eprintln!("Unexpected error: {}", e);
}
}
// Type safety prevents ID confusion
let shader_id = shaders.add_shader(...)?;
let pipeline_id = shaders.build_pipeline(...)?;
// This won't compile - type error!
// let shader = shaders.get_shader(pipeline_id)?; // ❌ Type error
// This is correct
let shader = shaders.get_shader(shader_id)?; // ✅ Correct type
```
### EZ Tier Errors
EZ tier provides a unified error type that wraps all lower-tier errors:
```rust
use shdrlib::ez::*;
// Unified error type
pub enum EzError {
RuntimeError(RuntimeError),
ShaderManagerError(ShaderManagerError),
BufferCreationFailed(MemoryError),
ImageCreationFailed(MemoryError),
RenderingFailed(String),
InvalidConfiguration(String),
}
// Implements From for all lower-tier errors
impl From<RuntimeError> for EzError { /* ... */ }
impl From<ShaderManagerError> for EzError { /* ... */ }
```
**Usage example:**
```rust
fn setup_renderer() -> Result<EzRenderer, EzError> {
let renderer = EzRenderer::new()?; // Automatically converts errors
Ok(renderer)
}
fn render(renderer: &mut EzRenderer) -> Result<(), EzError> {
renderer.render_frame(|frame| {
frame.bind_pipeline(pipeline)?;
frame.draw(3, 1, 0, 0);
Ok(())
})?;
Ok(())
}
// Main function with simplified error handling
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut renderer = setup_renderer()?;
render(&mut renderer)?;
Ok(())
}
```
### Error Recovery Strategies
**Shader Compilation Errors:**
```rust
// Provide fallback shaders
let shader_id = match shaders.add_shader(user_glsl, ShaderStage::Fragment, "user") {
Ok(id) => id,
Err(ShaderManagerError::CompilationFailed { message, .. }) => {
eprintln!("User shader failed, using fallback: {}", message);
shaders.add_shader(FALLBACK_GLSL, ShaderStage::Fragment, "fallback")?
}
Err(e) => return Err(e),
};
```
**Out of Memory:**
```rust
match create_vertex_buffer(&device, &large_data) {
Ok(buffer) => buffer,
Err(MemoryError::OutOfMemory) => {
// Free unused resources
clear_cache();
// Try again with smaller data
create_vertex_buffer(&device, &reduced_data)?
}
Err(e) => return Err(e),
}
```
**Swapchain Out of Date:**
```rust
loop {
match runtime.begin_frame() {
Ok(frame) => {
// Render...
}
Err(RuntimeError::FrameAcquisitionFailed(_)) => {
// Window was resized, recreate swapchain
recreate_swapchain(&mut runtime)?;
continue;
}
Err(e) => return Err(e),
}
}
```
**Validation Errors:**
```rust
// Enable validation in debug builds only
let config = RuntimeConfig {
enable_validation: cfg!(debug_assertions),
..Default::default()
};
let runtime = RuntimeManager::new(config)?;
```
### Best Practices
**1. Use the `?` operator for propagation:**
```rust
fn load_shader(path: &str) -> Result<ShaderId, ShaderManagerError> {
let source = std::fs::read_to_string(path)?; // Propagates IO error
let id = shaders.add_shader(&source, ShaderStage::Vertex, path)?;
Ok(id)
}
```
**2. Provide context with `map_err`:**
```rust
let buffer = create_vertex_buffer(&device, &vertices)
.map_err(|e| {
eprintln!("Failed to create vertex buffer with {} vertices", vertices.len());
e
})?;
```
**3. Log errors for debugging:**
```rust
if let Err(e) = renderer.render_frame(|frame| { /* ... */ }) {
log::error!("Rendering failed: {}", e);
// Optionally continue or exit
}
```
**4. Graceful degradation:**
```rust
// Try optimal path first
let samples = vk::SampleCountFlags::TYPE_4;
let pipeline = match build_pipeline_with_msaa(samples) {
Ok(p) => p,
Err(_) => {
log::warn!("MSAA 4x not supported, falling back to 1x");
build_pipeline_with_msaa(vk::SampleCountFlags::TYPE_1)?
}
};
```
---
## Additional Resources
This API reference is one part of shdrlib's documentation ecosystem. Here are additional resources to help you get the most out of the library:
### Documentation
- **[Quick Start Guide](QUICKSTART.md)** - Get up and running in 5 minutes with your first triangle
- **[User Guide](docs/README.md)** - Comprehensive documentation covering all aspects of the library
- **[Architecture Overview](docs/architecture/overview.md)** - Understanding the three-tier design
- **[Choosing a Tier](docs/getting-started/choosing-a-tier.md)** - Guidance on which tier to use
- **[Migration Guide](docs/guides/migration-guide.md)** - Moving between tiers as your needs evolve
- **[Troubleshooting](docs/getting-started/troubleshooting.md)** - Common issues and solutions
- **[FAQ](docs/getting-started/faq.md)** - Frequently asked questions
### API Documentation
Run `cargo doc --open` to generate complete API documentation with all implementation details, including:
- Full method signatures with generic parameters
- Detailed parameter descriptions
- Return type documentation
- Safety notes and invariants
- Links between related types
- Source code navigation
### Examples
Working code examples are available in the `demos/` directory:
**CORE Tier Examples:**
- `demos/core/01_triangle_raw.rs` - Complete triangle in ~400 lines
- `demos/core/02_compute_shader.rs` - Compute shader example
- `demos/core/05_custom_integration.rs` - Integrating with existing code
**EX Tier Examples:**
- `demos/ex/01_triangle_100_lines.rs` - Triangle in ~100 lines (recommended)
- `demos/ex/02_compute_100_mul.rs` - Compute shader with managers
- `demos/ex/03_textured_quad.rs` - Textured quad rendering
**EZ Tier Examples:**
- `demos/ez/01_hello_triangle.rs` - Classic triangle in ~25 lines
- `demos/ez/02_compute_multiply.rs` - Compute shader basics
- `demos/ez/03_buffers_demo.rs` - Mesh API demonstration
- `demos/ez/04_splash_screen.rs` - Fullscreen shader in ~15 lines (perfect for learning!)
**Running examples:**
```bash
cargo run --bin ez_04_splash_screen
cargo run --bin ex_01_triangle_100_lines
cargo run --bin 01_triangle_raw
```
### Tier-Specific Guides
- **[CORE Tier Guide](docs/guides/core-tier-guide.md)** - Deep dive into low-level usage
- **[EX Tier Guide](docs/guides/ex-tier-guide.md)** - Production patterns and best practices
- **[EZ Tier Guide](docs/guides/ez-tier-guide.md)** - Learning path and quick tips
- **[Mesh API Guide](demos/ez/MESH_API_GUIDE.md)** - Complete mesh API documentation
### Community and Support
- **[GitHub Repository](https://github.com/paulburnettjones-wq/shdrlib)** - Source code, issues, discussions
- **[Contributing Guide](CONTRIBUTING.md)** - How to contribute to the project
- **[Changelog](CHANGELOG.md)** - Version history and release notes
- **[Memory Safety Audit](MEMORY_SAFETY_AUDIT.md)** - Comprehensive safety review
### Quick Reference Cards
**Version:** 0.1.4
**Minimum Rust:** 1.82 (Edition 2021)
**Vulkan Version:** 1.3+ with dynamic rendering
**Core Dependencies:**
- `ash` 0.38 - Vulkan bindings
- `naga` 22 - Pure Rust shader compilation
- `spirv-reflect` 0.2 - Shader reflection
- `thiserror` 1.0 - Error handling
### Useful Constants
```rust
// Timeouts
pub const FENCE_TIMEOUT: u64 = 1_000_000_000; // 1 second in nanoseconds
pub const NO_TIMEOUT: u64 = u64::MAX;
// Vulkan API version
pub const VULKAN_API_VERSION: u32 = vk::API_VERSION_1_3;
```
### Getting Help
1. **Check the documentation** - Start with this reference and the user guide
2. **Run the examples** - See working code in the `demos/` directory
3. **Search existing issues** - Your question may already be answered
4. **Open an issue** - We're here to help if you're stuck
5. **Read `cargo doc`** - Most detailed API information is there
---
## Summary
This API reference has covered:
- **[CORE Tier](#core-tier)** - Manual control, thin Vulkan wrappers, maximum flexibility
- **[EX Tier](#ex-tier)** - Automatic management, type safety, production-ready (recommended)
- **[EZ Tier](#ez-tier)** - One-liners, smart defaults, perfect for learning
- **[Common Patterns](#common-patterns)** - Real-world usage examples across all tiers
- **[Error Types](#error-types)** - Comprehensive error handling guide
**Remember:** All three tiers compile to identical machine code with zero runtime overhead. Choose the tier that matches your needs and development style—you can always drop to lower tiers when you need more control.
Thank you for using shdrlib. We hope this reference serves you well, and we welcome your feedback to make it even better.
---
*Last updated: November 3, 2025 (Version 0.1.4)*