# Quick Start Tutorial
Get your first triangle rendered with shdrlib in under 10 minutes!
## Prerequisites
- ✅ Rust 1.82+ installed
- ✅ Vulkan drivers installed
- ✅ shdrlib added to your project
Not set up yet? See the [Installation Guide](installation.md).
---
## Step 1: Create a New Project
```bash
cargo new my_triangle
cd my_triangle
cargo add shdrlib ash
```
---
## Step 2: Choose Your Starting Tier
### Option A: EZ Tier (Recommended for Beginners)
**Best for:** Learning Vulkan, prototyping, quick results
Add this to `src/main.rs`:
```rust
use shdrlib::ez::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Step 1: Create renderer (replaces 100+ lines of setup)
let mut renderer = EzRenderer::new()?;
// Step 2: Create graphics pipeline
let pipeline = renderer.quick_pipeline(VERTEX_SHADER, FRAGMENT_SHADER)?;
// Step 3: Render a frame
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); // Draw 3 vertices (a triangle)
Ok(())
})?;
println!("✅ Rendered a triangle!");
Ok(())
}
// Vertex shader: positions the triangle vertices
const VERTEX_SHADER: &str = r#"
#version 450
vec2 positions[3] = vec2[](
vec2(0.0, -0.5), // Top vertex
vec2(0.5, 0.5), // Bottom-right
vec2(-0.5, 0.5) // Bottom-left
);
void main() {
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
}
"#;
// Fragment shader: colors the triangle orange
const FRAGMENT_SHADER: &str = r#"
#version 450
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(1.0, 0.5, 0.2, 1.0); // Orange color
}
"#;
```
**Run it:**
```bash
cargo run
```
**What you just did:**
- Created a Vulkan instance, device, and queues
- Compiled GLSL shaders to SPIR-V
- Built a graphics pipeline
- Rendered a triangle to an offscreen image
**Total lines of code: ~30** (vs 400+ with raw Vulkan)
---
### Option B: EX Tier (Production-Ready)
**Best for:** Real applications, when you need more control
```rust
use shdrlib::{
core::ShaderStage,
ex::{RuntimeManager, RuntimeConfig, ShaderManager, PipelineBuilder},
};
use ash::vk;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Step 1: Initialize runtime
let mut runtime = RuntimeManager::new(RuntimeConfig::default())?;
let device = runtime.device();
// Step 2: Compile shaders
let mut shaders = ShaderManager::new(device)?;
let vert_id = shaders.add_shader(VERTEX_SHADER, ShaderStage::Vertex, "vert")?;
let frag_id = shaders.add_shader(FRAGMENT_SHADER, ShaderStage::Fragment, "frag")?;
// Step 3: Build pipeline
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),
"triangle"
)?;
// Step 4: Render frame
let frame = runtime.begin_frame()?;
let pipeline = shaders.get_pipeline(pipeline_id)?;
// Record commands (simplified - see full examples)
// ... command buffer recording ...
runtime.end_frame(&Default::default())?;
println!("✅ Rendered with EX tier!");
Ok(())
}
const VERTEX_SHADER: &str = r#"
#version 450
vec2 positions[3] = vec2[](vec2(0.0, -0.5), vec2(0.5, 0.5), vec2(-0.5, 0.5));
void main() { gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); }
"#;
const FRAGMENT_SHADER: &str = r#"
#version 450
layout(location = 0) out vec4 outColor;
void main() { outColor = vec4(1.0, 0.5, 0.2, 1.0); }
"#;
```
**What's different:**
- Explicit configuration at each step
- Type-safe shader/pipeline IDs
- More control over the render loop
- Better for production code
---
## Step 3: Understanding What Happened
### The Shaders
**Vertex Shader:**
- Runs once per vertex (3 times for a triangle)
- Positions vertices in clip space (-1 to 1)
- Outputs `gl_Position` for each vertex
**Fragment Shader:**
- Runs once per pixel inside the triangle
- Determines the color of each pixel
- Outputs to `outColor` (the framebuffer)
### The Pipeline
A **graphics pipeline** connects:
1. Vertex shader → processes vertices
2. Rasterizer → converts triangles to pixels
3. Fragment shader → colors pixels
shdrlib creates this pipeline from your shaders automatically!
### The Draw Call
```rust
frame.draw(3, 1, 0, 0);
```
This means:
- **3** vertices (a triangle)
- **1** instance (draw once)
- Start at vertex **0**
- Start at instance **0**
---
## Step 4: Add Color Interpolation
Let's make the triangle more interesting with per-vertex colors:
```rust
const VERTEX_SHADER: &str = r#"
#version 450
vec2 positions[3] = vec2[](
vec2(0.0, -0.5),
vec2(0.5, 0.5),
vec2(-0.5, 0.5)
);
vec3 colors[3] = vec3[](
vec3(1.0, 0.0, 0.0), // Red
vec3(0.0, 1.0, 0.0), // Green
vec3(0.0, 0.0, 1.0) // Blue
);
layout(location = 0) out vec3 fragColor;
void main() {
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
fragColor = colors[gl_VertexIndex];
}
"#;
const FRAGMENT_SHADER: &str = r#"
#version 450
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(fragColor, 1.0);
}
"#;
```
**Run again:**
```bash
cargo run
```
Now you have a **gradient triangle**! The GPU interpolates colors between vertices automatically.
---
## Step 5: Add Vertex Buffers
Instead of hardcoding positions in the shader, let's use a vertex buffer:
```rust
use shdrlib::ez::*;
#[repr(C)]
#[derive(Clone, Copy)]
struct Vertex {
pos: [f32; 2],
color: [f32; 3],
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut renderer = EzRenderer::new()?;
// Create vertex data
let vertices = vec![
Vertex { pos: [0.0, -0.5], color: [1.0, 0.0, 0.0] },
Vertex { pos: [0.5, 0.5], color: [0.0, 1.0, 0.0] },
Vertex { pos: [-0.5, 0.5], color: [0.0, 0.0, 1.0] },
];
// Upload to GPU
let buffer = renderer.create_vertex_buffer(&vertices)?;
let pipeline = renderer.quick_pipeline(VERTEX_SHADER, FRAGMENT_SHADER)?;
renderer.render_frame(|frame| {
frame.bind_pipeline(pipeline)?;
frame.bind_vertex_buffer(buffer)?;
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(())
}
// Updated shaders to read from vertex buffer
const VERTEX_SHADER: &str = r#"
#version 450
layout(location = 0) in vec2 inPos;
layout(location = 1) in vec3 inColor;
layout(location = 0) out vec3 fragColor;
void main() {
gl_Position = vec4(inPos, 0.0, 1.0);
fragColor = inColor;
}
"#;
const FRAGMENT_SHADER: &str = r#"
#version 450
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main() { outColor = vec4(fragColor, 1.0); }
"#;
```
Now your triangle data is on the GPU, not hardcoded in shaders!
---
## Next Steps
### 🎨 Run the Examples
```bash
# EZ tier examples
cargo run --bin ez_01_hello_triangle
cargo run --bin ez_02_compute_multiply
cargo run --bin ez_03_buffers_demo
# EX tier examples
cargo run --bin ex_01_triangle_100_lines
cargo run --bin ex_03_textured_quad
# CORE tier examples
cargo run --bin 01_triangle_raw
```
### 📚 Read the Guides
- **[EZ Tier Guide](../guides/ez-tier-guide.md)** - Deep dive into EZ tier
- **[EX Tier Guide](../guides/ex-tier-guide.md)** - Production patterns
- **[Vulkan Basics](vulkan-basics.md)** - Understanding the graphics pipeline
### 🔧 Add Features
- **Textures:** Load and sample images
- **Uniforms:** Pass data from CPU to GPU
- **Compute shaders:** Run parallel computations
- **Multiple objects:** Draw more than one thing
### 🤔 Get Help
- [FAQ](faq.md) - Common questions
- [Troubleshooting](troubleshooting.md) - Fix issues
- [GitHub Discussions](https://github.com/paulburnettjones-wq/shdrlib/discussions)
---
**Congratulations! 🎉** You just rendered with Vulkan using shdrlib!
---
**Last Updated:** October 30, 2025