Compare commits

..

10 Commits

Author SHA1 Message Date
Krishna Ayyalasomayajula
f99e92d1f5 boilerplate 2026-05-30 18:05:19 -05:00
Krishna Ayyalasomayajula
b66be87ab9 fix: add missing glossary entry for present-mode 2026-05-30 18:00:58 -05:00
Krishna Ayyalasomayajula
19c112d3bb fix: correct case-sensitive glossary links in TROUBLESHOOTING 2026-05-30 17:59:42 -05:00
Krishna Ayyalasomayajula
9c3758afc8 fix: add missing glossary entry for surface-configuration 2026-05-30 17:59:16 -05:00
Krishna Ayyalasomayajula
27b5c7c7d5 fix: add missing TROUBLESHOOTING issue #3, fix broken glossary links in S9 2026-05-30 17:57:38 -05:00
Krishna Ayyalasomayajula
dc0e2379a8 docs: append S9-S11, add README and TROUBLESHOOTING 2026-05-30 17:56:38 -05:00
Krishna Ayyalasomayajula
7a7191c17d fix: use PollType::Wait and remove pre_present_notify (subtask 2.4 fixes) 2026-05-30 17:55:14 -05:00
Krishna Ayyalasomayajula
4e8c4be649 docs: append sections S7-S8 (render loop, resize) 2026-05-30 17:53:15 -05:00
Krishna Ayyalasomayajula
de38f526b9 docs: append sections S4-S6 (shaders, vertex data, render pipeline) 2026-05-30 17:44:31 -05:00
Krishna Ayyalasomayajula
4d429cf212 docs: write sections S1-S3 (intro, app skeleton, init chain) 2026-05-30 17:42:21 -05:00
8 changed files with 1537 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
.opencode/**

7
Cargo.lock generated Normal file
View File

@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "learn-wgpu"
version = "0.1.0"

6
Cargo.toml Normal file
View File

@@ -0,0 +1,6 @@
[package]
name = "learn-wgpu"
version = "0.1.0"
edition = "2024"
[dependencies]

1350
docs/01-rainbow-triangle.md Normal file

File diff suppressed because it is too large Load Diff

28
docs/README.md Normal file
View File

@@ -0,0 +1,28 @@
# learn-wgpu
An educational book teaching Rust graphics programming with wgpu 29 + winit 0.30.
Built for backend/systems Rust developers who know Rust thoroughly but have zero
graphics programming experience. Every sentence teaches GPU concepts — not Rust syntax.
## Required Reading Order
1. [The Graphics Pipeline](concepts/graphics-pipeline.md) — GPU vs CPU, the 5-stage pipeline
2. [Coordinate Systems](concepts/coordinate-systems.md) — NDC, homogeneous coordinates, viewport
3. [Shader Basics](concepts/shader-basics.md) — WGSL, vertex/fragment shaders, interpolation
4. [Build a Rainbow Triangle](01-rainbow-triangle.md) — The complete step-by-step guide
5. [Troubleshooting](TROUBLESHOOTING.md) — Common errors and fixes (reference as needed)
6. [Glossary](concepts/GLOSSARY.md) — Every term defined (reference as needed)
> **Reading order matters:** Coordinate systems must be understood before shader basics,
> because the shader walkthrough references NDC coordinates.
## Guides
- **01 — Rainbow Triangle** — The minimal complete program: one triangle, smooth color interpolation
- More guides coming soon (textures, lighting, compute)
## Prerequisites
- Rust stable (edition 2024)
- Linux x86_64 with Vulkan drivers (`libvulkan1`)

133
docs/TROUBLESHOOTING.md Normal file
View File

@@ -0,0 +1,133 @@
# Troubleshooting
## 1. `cargo build` fails with "no matching package"
**Symptom:** Build fails with `could not find package` for wgpu or winit.
**Cause:** Version typo in `Cargo.toml`. wgpu and winit have specific major versions.
**Fix:** Verify your dependency versions:
```toml
wgpu = "29"
winit = "0.30"
tokio = { version = "1", features = ["rt", "macros"] }
bytemuck = { version = "1", features = ["derive"] }
log = "0.4"
simple_logger = "5"
```
## 2. Window opens but shows black screen
**Symptom:** Window appears but is entirely black. No triangle visible.
**Cause:** One (or more) of several common rendering misconfigurations:
- Pipeline not set: `pass.set_pipeline()` not called
- Vertex buffer not bound: `pass.set_vertex_buffer()` not called
- Draw call wrong: `draw()` arguments incorrect (must be `draw(0..3, 0..1)` — two ranges)
- Triangle wound clockwise: `FrontFace::Ccw` requires counter-clockwise vertex order
- Format mismatch: `ColorTargetState::format` must match `SurfaceConfiguration::format`
- Present omitted: `surface_texture.present()` must be called to show rendered frame
**Fix:** Check each item above. Verify the pipeline is set, buffer is bound, draw uses two `Range<u32>` arguments, vertices are CCW, formats match, and `present()` is called.
## 3. Washed-out or wrong colors
**Symptom:** Triangle renders but colors look dull or incorrect.
**Cause:** `SurfaceConfiguration::view_formats` missing `add_srgb_suffix()`. Without sRGB gamma correction, colors appear washed out.
**Fix:** Ensure `view_formats` includes the sRGB variant:
```rust
view_formats: vec![format.add_srgb_suffix()],
```
## 4. Panic: "No adapter found"
**Symptom:** Program crashes immediately with `No GPU adapter found`.
**Cause:** No Vulkan driver installed. wgpu on Linux requires Vulkan.
**Fix:**
```bash
sudo apt install libvulkan1 mesa-vulkan-drivers
vulkaninfo # verify installation
```
## 5. Panic: "Surface lost"
**Symptom:** Program crashes with a surface lost error during rendering.
**Cause:** Display server restarted or GPU context was reset. The [Surface](concepts/GLOSSARY.md#Surface) is permanently invalidated.
**Fix:** In the tutorial, this means the window needs to be reopened. In production code, handle the `Lost` variant of `CurrentSurfaceTexture` by recreating the surface via `Instance::create_surface()`.
## 6. Wayland surface crashes or doesn't render
**Symptom:** Program crashes or shows blank window on Wayland.
**Cause:** winit's Wayland backend may have compatibility issues with certain compositors.
**Fix:** Force X11 backend:
```bash
WINIT_UNIX_BACKEND=x11 cargo run
```
## 7. Window won't close
**Symptom:** Clicking the X button or pressing Escape doesn't close the window.
**Cause:** `ApplicationHandler::exiting()` not implemented, or `event_loop_ctl.exit()` not called.
**Fix:** Ensure `exiting()` (note: NOT `closing()`) is implemented:
```rust
fn exiting(&mut self, event_loop_ctl: &ActiveEventLoop) {
event_loop_ctl.exit();
}
```
## 8. CPU at 100%
**Symptom:** One CPU core at 100% usage.
**Cause:** `ControlFlow::Poll` drives `RedrawRequested` events continuously. Every frame renders and queues another redraw. This is the expected behavior for a `ControlFlow::Poll` render loop.
**Fix:** No fix needed — this is expected for a continuous redraw demo. For production, switch to `ControlFlow::Wait` and call `request_redraw()` only when state changes.
## 9. Shader compilation or pipeline creation error
**Symptom:** Program panics during `create_render_pipeline` with a shader validation error.
**Cause:** `@location` numbers in WGSL don't match `shader_location` numbers in Rust's `VertexAttribute`, or `@builtin(position)` is missing from vertex shader output.
**Fix:** Check the log output for validation messages. Verify:
- WGSL `@location(0)` matches Rust `shader_location: 0`
- WGSL `@location(1)` matches Rust `shader_location: 1`
- Vertex shader outputs `@builtin(position) clip_position: vec4<f32>`
## 10. Triangle shows one solid color instead of gradient
**Symptom:** Triangle renders but is a single uniform color instead of smoothly blending.
**Cause:** Fragment shader returns a constant color instead of passing through `input.vertex_color`. The [rasterizer](concepts/GLOSSARY.md#Rasterizer) interpolates vertex colors automatically, but only if the fragment shader uses them.
**Fix:** Ensure fragment shader returns the interpolated vertex color:
```wgsl
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(input.vertex_color, 1.0);
}
```
Not this (which returns a solid color):
```wgsl
return vec4<f32>(1.0, 1.0, 1.0, 1.0); // wrong: solid white
```
## Additional Resources
- [Glossary](concepts/GLOSSARY.md) — Every term defined
- [Graphics Pipeline](concepts/graphics-pipeline.md) — Pipeline stage overview
- [Coordinate Systems](concepts/coordinate-systems.md) — NDC and transformation spaces
- [Shader Basics](concepts/shader-basics.md) — WGSL and shader concepts
- [wgpu documentation](https://docs.rs/wgpu/29.0.0/) — Official wgpu 29 API docs
- [wgpu repository](https://github.com/gfx-rs/wgpu) — Examples and issue tracker

View File

@@ -70,6 +70,10 @@ A compiled GPU configuration bundling: [[vertex shader]](#vertex-shader) + [[fra
The strategy passed to `device.poll()`. `PollType::Wait` blocks the calling thread until all pending GPU work finishes — equivalent to a fence wait. `PollType::Poll` checks for completed work once and returns immediately, regardless of whether work is done. For the rainbow triangle, `Wait` is correct: we need the GPU to finish the frame before requesting the next surface texture. The strategy passed to `device.poll()`. `PollType::Wait` blocks the calling thread until all pending GPU work finishes — equivalent to a fence wait. `PollType::Poll` checks for completed work once and returns immediately, regardless of whether work is done. For the rainbow triangle, `Wait` is correct: we need the GPU to finish the frame before requesting the next surface texture.
## Present Mode
How the display compositor handles frame buffer presentation: `PresentMode::Mailbox` uses triple buffering for tear-free rendering, `PresentMode::Fifo` provides VSYNC-locked double buffering, `PresentMode::Immediate` renders without synchronization (may show tearing).
## Ndc ## Ndc
Normalized Device Coordinates. The GPU's native intermediate coordinate space. X and Y range from -1.0 (left/bottom) to +1.0 (right/top). Z ranges from 0.0 (near clipping plane) to 1.0 (far clipping plane). Geometry is mapped into NDC by the GPU after perspective division. Anything outside this cube is clipped. See [[coordinate-systems.md]](coordinate-systems.md). Normalized Device Coordinates. The GPU's native intermediate coordinate space. X and Y range from -1.0 (left/bottom) to +1.0 (right/top). Z ranges from 0.0 (near clipping plane) to 1.0 (far clipping plane). Geometry is mapped into NDC by the GPU after perspective division. Anything outside this cube is clipped. See [[coordinate-systems.md]](coordinate-systems.md).
@@ -110,6 +114,10 @@ Controls what happens to the [[framebuffer]](#framebuffer) at the end of a rende
wgpu's connection to a window's display buffer. Created via `instance.create_surface(window)`, the surface is like a bound socket — it is tied to a specific window and cannot be unlinked. The surface manages the [[swapchain]](#swapchain) and provides new framebuffers via `surface.get_current_texture()`. If the window is resized, the surface must be reconfigured with a new `SurfaceConfiguration`. wgpu's connection to a window's display buffer. Created via `instance.create_surface(window)`, the surface is like a bound socket — it is tied to a specific window and cannot be unlinked. The surface manages the [[swapchain]](#swapchain) and provides new framebuffers via `surface.get_current_texture()`. If the window is resized, the surface must be reconfigured with a new `SurfaceConfiguration`.
## Surface Configuration
The `SurfaceConfiguration` struct that allocates swapchain framebuffers: format, dimensions, present mode, and view formats. Reconfigured on window resize via `Surface::configure()`.
## Swapchain ## Swapchain
A ring buffer of 2-3 [[framebuffer]](#framebuffer) textures managed by the GPU driver. The display hardware reads from the front buffer. The application renders to the back buffer. When the frame is complete, the buffers swap: the back buffer becomes the front (displayed), and the old front becomes the available back buffer for the next frame. This prevents screen tearing by ensuring the display never reads a frame mid-update. A ring buffer of 2-3 [[framebuffer]](#framebuffer) textures managed by the GPU driver. The display hardware reads from the front buffer. The application renders to the back buffer. When the frame is complete, the buffers swap: the back buffer becomes the front (displayed), and the old front becomes the available back buffer for the next frame. This prevents screen tearing by ensuring the display never reads a frame mid-update.

3
src/main.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}