From fc2a04fe140060c203b4423033ee95bef3f5898f Mon Sep 17 00:00:00 2001 From: Krishna Ayyalasomayajula Date: Sat, 30 May 2026 21:01:23 -0500 Subject: [PATCH] docs: elevate critical WHY explanations to callout blocks --- docs/01-rainbow-triangle.md | 48 ++++++++++++------------------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/docs/01-rainbow-triangle.md b/docs/01-rainbow-triangle.md index 04d4af0..1f3cc5b 100644 --- a/docs/01-rainbow-triangle.md +++ b/docs/01-rainbow-triangle.md @@ -159,23 +159,17 @@ impl ApplicationHandler<()> for App { } ``` -**Why `spawn_blocking`:** The display server event loop must run to completion -and cannot be interrupted. If we ran `run_app()` on the tokio runtime thread, -no other async tasks could execute. By spawning it on a blocking thread, the -tokio runtime remains free for GPU queries, driver I/O, and future background -tasks. +> **WHY: `spawn_blocking` for winit** +> +> The display server event loop must run to completion and cannot be interrupted. If we ran `run_app()` on the tokio runtime thread, no other async tasks could execute. By spawning it on a blocking thread, the tokio runtime remains free for GPU queries, driver I/O, and future background tasks. -**Why `Handle::block_on`:** wgpu's `request_adapter` and `request_device` query -the driver over async D-Bus/Wayland/Vulkan entrypoints. These futures must be -polled by a runtime executor. `block_on` attaches temporarily to the runtime -thread via its handle, polls the future to completion (~50ms), then returns the -result. +> **WHY: `Handle::block_on` for async GPU init** +> +> wgpu's `request_adapter` and `request_device` query the driver over async D-Bus/Wayland/Vulkan entrypoints. These futures must be polled by a runtime executor. `block_on` attaches temporarily to the runtime thread via its handle, polls the future to completion (~50ms), then returns the result. -**Why `ControlFlow::Poll`:** winit supports `ControlFlow::Poll` (continuous -redraw) and `ControlFlow::Wait` (idle until next event). A graphics application -needs a steady render loop. `Poll` tells winit to keep firing `RedrawRequested` -events. We re-queue ourselves inside the handler via `window.request_redraw()`, -matching the wgpu swapchain presentation rhythm. +> **WHY: `ControlFlow::Poll` for the render loop** +> +> winit supports `ControlFlow::Poll` (continuous redraw) and `ControlFlow::Wait` (idle until next event). A graphics application needs a steady render loop. `Poll` tells winit to keep firing `RedrawRequested` events. We re-queue ourselves inside the handler via `window.request_redraw()`, matching the wgpu swapchain presentation rhythm. **Why `request_redraw()`:** After presenting a frame to the display, we ask winit to schedule the next `RedrawRequested` frame. This creates an explicit @@ -556,12 +550,9 @@ weights. At each vertex, the value is exact. Inside the triangle, it is the weighted blend of all three vertex values. The fragment shader receives a different `vertex_color` for every pixel, without any manual interpolation code. -> **Key insight #2 — THE LOCATIONS MUST MATCH:** `shader_location: 0` in -> Rust's `VertexAttribute` MUST equal `@location(0)` in WGSL's parameter -> annotation. If they differ, the shader reads from the wrong memory offset -> and produces garbage. This is not a type error or a runtime panic — it is -> silent data corruption. The GPU reads whatever bytes live at the mismatched -> offset and interprets them as floats. +> **WHY: `@location` must match between Rust and WGSL** +> +> `shader_location: 0` in Rust's `VertexAttribute` MUST equal `@location(0)` in WGSL's parameter annotation. If they differ, the shader reads from the wrong memory offset and produces garbage. This is not a type error or a runtime panic — it is silent data corruption. The GPU reads whatever bytes live at the mismatched offset and interprets them as floats. **`@vertex fn vs_main(...)`** — `@vertex` declares this function as the vertex shader entry point. The function is invoked once per vertex in the draw call. @@ -674,18 +665,9 @@ struct Vertex { } ``` -- **`#[repr(C)]`** — Forces the Rust compiler to lay out the struct fields in - declaration order with no padding reordering. Without this, Rust is free to - reorder fields for optimal alignment, which would break the byte layout the - shader expects. -- **`bytemuck::Pod`** — "Plain Old Data." Guarantees the struct has no padding - holes, no destructors, and a trivial memory representation. wgpu requires - all vertex types to be Pod so they can be safely transmuted to bytes. -- **`bytemuck::Zeroable`** — Guarantees that initializing the struct's memory - to all-zero bytes produces a valid instance. Required because `Pod` alone - does not guarantee zero is a valid discriminant for enums or optional types. - Combined with Pod, it enables `bytemuck::cast_slice` to convert between - `&[Vertex]` and `&[u8]` without a `unsafe` block. +> **WHY: `#[repr(C)]` + bytemuck for GPU data layout** +> +> `#[repr(C)]` forces the Rust compiler to lay out the struct fields in declaration order with no padding reordering. Without this, Rust is free to reorder fields for optimal alignment, which would break the byte layout the shader expects. `bytemuck::Pod` ("Plain Old Data") guarantees the struct has no padding holes, no destructors, and a trivial memory representation. wgpu requires all vertex types to be Pod so they can be safely transmuted to bytes. `bytemuck::Zeroable` guarantees that initializing the struct's memory to all-zero bytes produces a valid instance. Required because `Pod` alone does not guarantee zero is a valid discriminant for enums or optional types. Combined with Pod, it enables `bytemuck::cast_slice` to convert between `&[Vertex]` and `&[u8]` without an unsafe block. ### Vertex Data