diff --git a/docs/01-rainbow-triangle.md b/docs/01-rainbow-triangle.md index ed759cc..942c322 100644 --- a/docs/01-rainbow-triangle.md +++ b/docs/01-rainbow-triangle.md @@ -630,7 +630,7 @@ let shader_module = device.create_shader_module( New concept: **GPU memory isolation.** The GPU cannot read Rust heap or stack memory directly. Vertex data must be laid out as a flat byte array and uploaded -into a dedicated GPU [[buffer slice]](concepts/GLOSSARY.md#buffer-slice). The +into a dedicated GPU [buffer slice](concepts/GLOSSARY.md#buffer-slice). The pipeline configuration then describes how to interpret those bytes: how many bytes per vertex, what format each attribute has, and where in the vertex strides the attribute begins. diff --git a/docs/concepts/GLOSSARY.md b/docs/concepts/GLOSSARY.md index a569f4c..2eedd0b 100644 --- a/docs/concepts/GLOSSARY.md +++ b/docs/concepts/GLOSSARY.md @@ -16,55 +16,55 @@ A view into GPU buffer memory defined by an offset and a length. `buffer.slice(. ## Clip space -The [[homogeneous coordinates]](#homogeneous-coordinates) coordinate space that the [[vertex shader]](#vertex-shader) outputs into (`vec4`). The GPU clips geometry against the clip-space boundaries before performing perspective division (dividing x, y, z by w) to produce [[ndc]](#ndc). For perspective projection, clip space is a pyramid. For orthographic projection, it is a box. Geometry outside these boundaries is discarded by hardware. +The [homogeneous coordinates](#homogeneous-coordinates) coordinate space that the [vertex shader](#vertex-shader) outputs into (`vec4`). The GPU clips geometry against the clip-space boundaries before performing perspective division (dividing x, y, z by w) to produce [ndc](#ndc). For perspective projection, clip space is a pyramid. For orthographic projection, it is a box. Geometry outside these boundaries is discarded by hardware. ## Command buffer -A recorded sequence of GPU commands — buffer copies, render passes, compute dispatches — analogous to a bash script listing operations to execute. You create a command buffer, encode operations into it via a `CommandEncoder`, then submit it to the [[queue]](#queue). The GPU executes the recorded sequence asynchronously. One submission is one unit of GPU work. +A recorded sequence of GPU commands — buffer copies, render passes, compute dispatches — analogous to a bash script listing operations to execute. You create a command buffer, encode operations into it via a `CommandEncoder`, then submit it to the [queue](#queue). The GPU executes the recorded sequence asynchronously. One submission is one unit of GPU work. ## Device -The logical connection to the GPU. Created from an [[adapter]](#adapter), the device owns all GPU resources: buffers, textures, [[pipeline]](#pipeline) objects, shader modules, and bind groups. It is analogous to a file descriptor — the handle through which you allocate and manage GPU memory. All resource creation and destruction flows through the device. +The logical connection to the GPU. Created from an [adapter](#adapter), the device owns all GPU resources: buffers, textures, [pipeline](#pipeline) objects, shader modules, and bind groups. It is analogous to a file descriptor — the handle through which you allocate and manage GPU memory. All resource creation and destruction flows through the device. ## Device poll -`device.poll(PollType::Wait)` — a synchronous call that tells wgpu to drive all in-flight GPU work toward completion. This includes shader compilation, memory allocation on the GPU side, fence signaling, and surface frame acquisition. Without polling, wgpu's internal work queues stall. The [[polltype]](#polltype) `Wait` variant blocks the CPU thread until pending GPU tasks are done. +`device.poll(PollType::Wait)` — a synchronous call that tells wgpu to drive all in-flight GPU work toward completion. This includes shader compilation, memory allocation on the GPU side, fence signaling, and surface frame acquisition. Without polling, wgpu's internal work queues stall. The [polltype](#polltype) `Wait` variant blocks the CPU thread until pending GPU tasks are done. ## Fragment -A potential pixel produced by the [[rasterizer]](#rasterizer). One fragment is generated per screen pixel that a [[primitive]](#primitive) covers. A fragment carries interpolated [[vertex]](#vertex) shader outputs, a depth value, and a color. The fragment may be later discarded by depth testing, stencil testing, or alpha testing during the [[output merge]](#output-merge) ([Stage 5](graphics-pipeline.md#stage-5-output-merge)) stage. Not every fragment becomes a visible pixel. +A potential pixel produced by the [rasterizer](#rasterizer). One fragment is generated per screen pixel that a [primitive](#primitive) covers. A fragment carries interpolated [vertex](#vertex) shader outputs, a depth value, and a color. The fragment may be later discarded by depth testing, stencil testing, or alpha testing during the [output merge](#output-merge) ([Stage 5](graphics-pipeline.md#stage-5-output-merge)) stage. Not every fragment becomes a visible pixel. ## Fragment shader -GPU program running once per [[fragment]](#fragment). It receives pre-interpolated vertex shader outputs from the rasterizer and computes the final RGBA color for that fragment. This is where texture sampling, lighting calculations, and pixel-level effects happen. For the rainbow triangle, the fragment shader passes the interpolated vertex color through unchanged. +GPU program running once per [fragment](#fragment). It receives pre-interpolated vertex shader outputs from the rasterizer and computes the final RGBA color for that fragment. This is where texture sampling, lighting calculations, and pixel-level effects happen. For the rainbow triangle, the fragment shader passes the interpolated vertex color through unchanged. ## Framebuffer -The color buffer that appears on screen. During [[swapchain]](#swapchain) double-buffering, the framebuffer being drawn to is the back buffer. Once the render pass completes and you submit the buffer, it becomes the front buffer and is displayed. The framebuffer is a [[texture view]](#texture-view) tied to a surface frame. +The color buffer that appears on screen. During [swapchain](#swapchain) double-buffering, the framebuffer being drawn to is the back buffer. Once the render pass completes and you submit the buffer, it becomes the front buffer and is displayed. The framebuffer is a [texture view](#texture-view) tied to a surface frame. ## Homogeneous coordinates -A four-component representation (x, y, z, w) that enables perspective projection via the divide-by-w step. When w=1, the coordinates represent a point in 3D space. When w=0, they represent a direction vector. Perspective division (x/w, y/w, z/w) transforms clip-space coordinates into [[ndc]](#ndc). With w=1.0, division is the identity transform. +A four-component representation (x, y, z, w) that enables perspective projection via the divide-by-w step. When w=1, the coordinates represent a point in 3D space. When w=0, they represent a direction vector. Perspective division (x/w, y/w, z/w) transforms clip-space coordinates into [ndc](#ndc). With w=1.0, division is the identity transform. ## Interpolation -The rasterizer's automatic blending of vertex shader outputs across the surface of a triangle. For every `@location(n)` value output by the vertex shader, the [[rasterizer]](#rasterizer) computes a linear blend using [[barycentric coordinates]](#barycentric-coordinates): `value = w0*v0 + w1*v1 + w2*v2`. This is a free, hardware-accelerated feature. No shader code is required to perform interpolation. +The rasterizer's automatic blending of vertex shader outputs across the surface of a triangle. For every `@location(n)` value output by the vertex shader, the [rasterizer](#rasterizer) computes a linear blend using [barycentric coordinates](#barycentric-coordinates): `value = w0*v0 + w1*v1 + w2*v2`. This is a free, hardware-accelerated feature. No shader code is required to perform interpolation. ## Instance -The root wgpu object representing the connection to the system's graphics drivers. Created via `Instance::default()`, the instance discovers available [[adapter]](#adapter)s and manages [[surface]](#surface) creation. It is the first object created in the wgpu initialization chain. (Note: `Instance::default()` calls `Instance::new(Default::default())` internally; both forms produce an equivalent instance.) +The root wgpu object representing the connection to the system's graphics drivers. Created via `Instance::default()`, the instance discovers available [adapter](#adapter)s and manages [surface](#surface) creation. It is the first object created in the wgpu initialization chain. (Note: `Instance::default()` calls `Instance::new(Default::default())` internally; both forms produce an equivalent instance.) ## Loadop -Controls what happens to the [[framebuffer]](#framebuffer) at the start of a render pass. `LoadOp::Clear(color)` fills the entire framebuffer with a solid color — this produces your scene background. `LoadOp::Load` keeps whatever pixels are already in the framebuffer — used for multi-pass rendering where the second pass draws on top of the first. +Controls what happens to the [framebuffer](#framebuffer) at the start of a render pass. `LoadOp::Clear(color)` fills the entire framebuffer with a solid color — this produces your scene background. `LoadOp::Load` keeps whatever pixels are already in the framebuffer — used for multi-pass rendering where the second pass draws on top of the first. ## Output merge -The final GPU pipeline stage. It applies per-fragment tests (depth, stencil, alpha) and blending operations before writing pixels to the [[framebuffer]](#framebuffer). The blend state (configured in the [[pipeline]](#pipeline)) determines whether new colors replace, add, or multiply with existing framebuffer colors. For the rainbow triangle, blending is REPLACE — new pixels overwrite old ones. +The final GPU pipeline stage. It applies per-fragment tests (depth, stencil, alpha) and blending operations before writing pixels to the [framebuffer](#framebuffer). The blend state (configured in the [pipeline](#pipeline)) determines whether new colors replace, add, or multiply with existing framebuffer colors. For the rainbow triangle, blending is REPLACE — new pixels overwrite old ones. ## Pipeline (render) -A compiled GPU configuration bundling: [[vertex shader]](#vertex-shader) + [[fragment shader]](#fragment-shader) + [[topology]](#topology) + blend state + depth/stencil state + vertex buffer layout. Created once via `device.create_render_pipeline()` and reused for every frame. Changing any of these parameters requires creating a new pipeline. Pipeline creation is expensive; do not create one per frame. +A compiled GPU configuration bundling: [vertex shader](#vertex-shader) + [fragment shader](#fragment-shader) + [topology](#topology) + blend state + depth/stencil state + vertex buffer layout. Created once via `device.create_render_pipeline()` and reused for every frame. Changing any of these parameters requires creating a new pipeline. Pipeline creation is expensive; do not create one per frame. ## Polltype @@ -76,11 +76,11 @@ How the display compositor handles frame buffer presentation: `PresentMode::Mail ## 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). ## Operations -Paired `LoadOp` + `StoreOp` controlling [[framebuffer]](#framebuffer) behavior at [[render pass]](#render-pass) boundaries. `LoadOp` defines the pre-draw state (clear or load). `StoreOp` defines the post-draw state (store or discard). Together they form `Operations { load, store }` passed to `RenderPassColorAttachment`. +Paired `LoadOp` + `StoreOp` controlling [framebuffer](#framebuffer) behavior at [render pass](#render-pass) boundaries. `LoadOp` defines the pre-draw state (clear or load). `StoreOp` defines the post-draw state (store or discard). Together they form `Operations { load, store }` passed to `RenderPassColorAttachment`. ## Primitive @@ -88,19 +88,19 @@ A geometric shape the GPU can render: point list, line list, line strip, triangl ## Queue -The submission channel to the GPU. You push [[command buffer]](#command-buffer)s into the queue via `queue.submit()`. The queue executes them asynchronously on the GPU. The queue also handles buffer uploads via `queue.write_buffer()` — these are synchronous copy operations that block until the data lands in GPU memory. +The submission channel to the GPU. You push [command buffer](#command-buffer)s into the queue via `queue.submit()`. The queue executes them asynchronously on the GPU. The queue also handles buffer uploads via `queue.write_buffer()` — these are synchronous copy operations that block until the data lands in GPU memory. ## Rasterizer -Hardware stage that converts [[primitive]](#primitive) geometry into [[fragment]](#fragment)s. For each triangle, determines which screen pixels it covers, generates one fragment per covered pixel, and computes interpolated vertex attributes using [[barycentric coordinates]](#barycentric-coordinates). The rasterizer is a fixed-function unit: no user code runs here. You configure its behavior (culling, fill mode, scissor test) via the pipeline descriptor. +Hardware stage that converts [primitive](#primitive) geometry into [fragment](#fragment)s. For each triangle, determines which screen pixels it covers, generates one fragment per covered pixel, and computes interpolated vertex attributes using [barycentric coordinates](#barycentric-coordinates). The rasterizer is a fixed-function unit: no user code runs here. You configure its behavior (culling, fill mode, scissor test) via the pipeline descriptor. ## Render pass -A scoped section of a [[command buffer]](#command-buffer) that groups draw operations sharing the same target [[framebuffer]](#framebuffer) attachments. Entered via `command_encoder.begin_render_pass()` and ended by dropping the `RenderPass` variable. Between begin and end, you set the pipeline, bind vertex buffers, and issue draw calls. Everything drawn in one render pass targets the same framebuffer with the same [[operations]](#operations). +A scoped section of a [command buffer](#command-buffer) that groups draw operations sharing the same target [framebuffer](#framebuffer) attachments. Entered via `command_encoder.begin_render_pass()` and ended by dropping the `RenderPass` variable. Between begin and end, you set the pipeline, bind vertex buffers, and issue draw calls. Everything drawn in one render pass targets the same framebuffer with the same [operations](#operations). ## Shader -GPU program written in [[wgsl]](#wgsl). No heap allocation, no recursion, no I/O. The only output channel is the return value. A shader module may contain multiple entry points (`@vertex`, `@fragment`, `@compute`). The GPU runs thousands of shader invocations in parallel, each operating on different data but executing the identical program. +GPU program written in [wgsl](#wgsl). No heap allocation, no recursion, no I/O. The only output channel is the return value. A shader module may contain multiple entry points (`@vertex`, `@fragment`, `@compute`). The GPU runs thousands of shader invocations in parallel, each operating on different data but executing the identical program. ## Shader location @@ -108,11 +108,11 @@ A numeric binding label (`@location(n)`) used to tie Rust vertex buffer attribut ## Storeop -Controls what happens to the [[framebuffer]](#framebuffer) at the end of a render pass. `StoreOp::Store` keeps the written pixels — this is what you want for visible frames. `StoreOp::Discard` discards the framebuffer contents — used for offscreen renders where you do not need the result on screen, saving a memory barrier. +Controls what happens to the [framebuffer](#framebuffer) at the end of a render pass. `StoreOp::Store` keeps the written pixels — this is what you want for visible frames. `StoreOp::Discard` discards the framebuffer contents — used for offscreen renders where you do not need the result on screen, saving a memory barrier. ## Surface -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 @@ -120,39 +120,39 @@ The `SurfaceConfiguration` struct that allocates swapchain framebuffers: format, ## 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. ## Texture view -A handle referencing a region of [[texture]](#texture) memory for use inside a [[render pass]](#render-pass) or bind group. Created via `texture.create_view()`, texture views define the mip level range, aspect, and dimensionality (2D, cube, array) of the binding. Surface framebuffers are accessed as texture views inside render passes. +A handle referencing a region of [texture](#texture) memory for use inside a [render pass](#render-pass) or bind group. Created via `texture.create_view()`, texture views define the mip level range, aspect, and dimensionality (2D, cube, array) of the binding. Surface framebuffers are accessed as texture views inside render passes. ## Texture -GPU memory region storing color data. Used for both render targets (framebuffers) and samplers (loaded images). In wgpu, a texture is created from the [[device]](#device) with a defined size, format, and usage flags. You never read texture memory directly from the CPU — you access it through [[texture view]](#texture-view) bindings in shaders. +GPU memory region storing color data. Used for both render targets (framebuffers) and samplers (loaded images). In wgpu, a texture is created from the [device](#device) with a defined size, format, and usage flags. You never read texture memory directly from the CPU — you access it through [texture view](#texture-view) bindings in shaders. ## Topology -The rule for grouping vertices into [[primitive]](#primitive) shapes. `TriangleList` means every 3 consecutive vertices form one independent triangle. `TriangleStrip` means each new vertex combined with the previous two forms a triangle. `PointList` renders individual points. `LineList` renders pairs of connected vertices. Topology is set once on the [[pipeline]](#pipeline) descriptor. +The rule for grouping vertices into [primitive](#primitive) shapes. `TriangleList` means every 3 consecutive vertices form one independent triangle. `TriangleStrip` means each new vertex combined with the previous two forms a triangle. `PointList` renders individual points. `LineList` renders pairs of connected vertices. Topology is set once on the [pipeline](#pipeline) descriptor. ## Vertex -A data point containing one or more attributes: position, color, UV coordinates, normals, tangents. All attributes for one vertex are stored contiguously in a [[vertex buffer]](#vertex-buffer). The stride (total bytes per vertex) is determined by the sum of all attribute sizes. In the rainbow triangle, each vertex has three `f32` position components and three `f32` color components: 24 bytes per vertex. +A data point containing one or more attributes: position, color, UV coordinates, normals, tangents. All attributes for one vertex are stored contiguously in a [vertex buffer](#vertex-buffer). The stride (total bytes per vertex) is determined by the sum of all attribute sizes. In the rainbow triangle, each vertex has three `f32` position components and three `f32` color components: 24 bytes per vertex. ## Vertex buffer -GPU [[buffer slice]](#buffer-slice) containing [[vertex]](#vertex) attribute data in a tightly packed layout. Created via `device.create_buffer()` and populated via `queue.write_buffer()`. The pipeline's vertex state describes how to interpret the buffer: stride, attribute count, and per-attribute format + [[shader location]](#shader-location) mapping. +GPU [buffer slice](#buffer-slice) containing [vertex](#vertex) attribute data in a tightly packed layout. Created via `device.create_buffer()` and populated via `queue.write_buffer()`. The pipeline's vertex state describes how to interpret the buffer: stride, attribute count, and per-attribute format + [shader location](#shader-location) mapping. ## Vertex shader -GPU program running once per [[vertex]](#vertex). It reads vertex attributes from the [[vertex buffer]](#vertex-buffer), transforms the position into [[clip space]](#clip-space), and outputs any per-vertex data the downstream pipeline stages need. The mandatory output is `@builtin(position) vec4`. Optional outputs use `@location(n)` annotations and flow into the rasterizer for interpolation. +GPU program running once per [vertex](#vertex). It reads vertex attributes from the [vertex buffer](#vertex-buffer), transforms the position into [clip space](#clip-space), and outputs any per-vertex data the downstream pipeline stages need. The mandatory output is `@builtin(position) vec4`. Optional outputs use `@location(n)` annotations and flow into the rasterizer for interpolation. ## Viewport transform -Automatic GPU step mapping [[ndc]](#ndc) coordinates (-1..+1) to [[window]](#window) pixel coordinates. Configured via `SurfaceConfiguration` `width` and `height` fields. The GPU performs: `screen_x = (ndc_x + 1) / 2 * width; screen_y = (ndc_y + 1) / 2 * height`. This step happens after perspective division, between NDC and the rasterizer. You never write this math in shader code. +Automatic GPU step mapping [ndc](#ndc) coordinates (-1..+1) to [window](#window) pixel coordinates. Configured via `SurfaceConfiguration` `width` and `height` fields. The GPU performs: `screen_x = (ndc_x + 1) / 2 * width; screen_y = (ndc_y + 1) / 2 * height`. This step happens after perspective division, between NDC and the rasterizer. You never write this math in shader code. ## Window -The operating system window created by the windowing library. In wgpu, the window is passed to `instance.create_surface()` to bind the GPU to a display target. The window dimensions dictate the [[viewport transform]](#viewport-transform) and thus the size of the rendered image. Resizing the window requires creating a new `SurfaceConfiguration` with updated dimensions. +The operating system window created by the windowing library. In wgpu, the window is passed to `instance.create_surface()` to bind the GPU to a display target. The window dimensions dictate the [viewport transform](#viewport-transform) and thus the size of the rendered image. Resizing the window requires creating a new `SurfaceConfiguration` with updated dimensions. ## WGSL diff --git a/docs/concepts/coordinate-systems.md b/docs/concepts/coordinate-systems.md index be3d91c..5b1f037 100644 --- a/docs/concepts/coordinate-systems.md +++ b/docs/concepts/coordinate-systems.md @@ -4,7 +4,7 @@ Your window is a grid of pixels: 800×600 in our configuration. The 3D scene you want to render spans from -∞ to +∞ in every direction. The GPU cannot reason in window pixels because every window has a different size. It cannot reason in world space because that is application-defined. The GPU needs a standard intermediate coordinate space. -That space is [[ndc]](GLOSSARY.md#ndc), Normalized Device Coordinates. +That space is [ndc](GLOSSARY.md#ndc), Normalized Device Coordinates. ## NDC Definition @@ -48,7 +48,7 @@ In a real application, vertices live in arbitrary world units and you apply a se ## Homogeneous Coordinates -The GPU vertex shader outputs a `vec4`, not a `vec3`. The fourth component `w` is the [[homogeneous coordinates]](GLOSSARY.md#homogeneous-coordinates) value that enables the clip space → NDC conversion. +The GPU vertex shader outputs a `vec4`, not a `vec3`. The fourth component `w` is the [homogeneous coordinates](GLOSSARY.md#homogeneous-coordinates) value that enables the clip space → NDC conversion. When the vertex shader outputs `vec4(x, y, z, w)`, the GPU performs a step called **perspective division**: it divides every component by `w`. The result is `(x/w, y/w, z/w)` — this is what lands in NDC. @@ -66,7 +66,7 @@ Our triangle uses `w = 1.0` because we have no camera and no perspective — jus ## Clip Space -Before NDC, there is [[clip space]](GLOSSARY.md#clip-space). This is the coordinate space the vertex shader outputs into. Clip space is a pyramid (for perspective projection) or a box (for orthographic projection) that the GPU clips against. Geometry outside the clip-space boundaries is discarded by hardware before perspective division. Our triangle is entirely inside the clip space pyramid, so nothing is clipped. +Before NDC, there is [clip space](GLOSSARY.md#clip-space). This is the coordinate space the vertex shader outputs into. Clip space is a pyramid (for perspective projection) or a box (for orthographic projection) that the GPU clips against. Geometry outside the clip-space boundaries is discarded by hardware before perspective division. Our triangle is entirely inside the clip space pyramid, so nothing is clipped. ## Viewport Transform (Automatic) @@ -77,7 +77,7 @@ screen_x = (ndc_x + 1.0) / 2.0 * window_width screen_y = (ndc_y + 1.0) / 2.0 * window_height ``` -This step is automatic. You never write it in code. It is configured by the [[viewport transform]](GLOSSARY.md#viewport-transform) fields in your `SurfaceConfiguration`, specifically the `width` and `height` values. When the surface configuration says 800×600, the GPU maps NDC `[-1, +1]` onto `[0, 800]` and `[0, 600]`. +This step is automatic. You never write it in code. It is configured by the [viewport transform](GLOSSARY.md#viewport-transform) fields in your `SurfaceConfiguration`, specifically the `width` and `height` values. When the surface configuration says 800×600, the GPU maps NDC `[-1, +1]` onto `[0, 800]` and `[0, 600]`. You do write code to update the viewport transform — but only when the window size changes. At that point, you create a new `SurfaceConfiguration` with the new dimensions and configure the surface. The GPU then uses the updated mapping on subsequent frames. @@ -87,7 +87,7 @@ For our triangle, every vertex follows this path: 1. **Vertex data:** Stored as `vec3` in the vertex buffer. Values are already in NDC. 2. **Vertex shader:** Wraps in `vec4(f32)` by appending `w = 1.0`. This is clip space (which, for identity `w`, equals NDC). -3. **Perspective division:** GPU divides by `w = 1.0` → identity. Vertex is now in [[ndc]](GLOSSARY.md#ndc). +3. **Perspective division:** GPU divides by `w = 1.0` → identity. Vertex is now in [ndc](GLOSSARY.md#ndc). 4. **Viewport transform (automatic):** GPU scales NDC to window pixel coordinates. The triangle appears on screen. In a real 3D application, this journey includes model, view, and projection matrices before clip space. For the rainbow triangle, the journey is three steps through identity transforms. The hardware pipeline stages are the same regardless. diff --git a/docs/concepts/graphics-pipeline.md b/docs/concepts/graphics-pipeline.md index fe015fc..6b4e535 100644 --- a/docs/concepts/graphics-pipeline.md +++ b/docs/concepts/graphics-pipeline.md @@ -27,11 +27,11 @@ Each stage is a pipeline filter. Data flows through; nothing flows backward. Thi ### Stage 1: Vertex Shader -[[vertex shader]](GLOSSARY.md#vertex-shader) — a GPU program running once per input [[vertex]](GLOSSARY.md#vertex). +[vertex shader](GLOSSARY.md#vertex-shader) — a GPU program running once per input [vertex](GLOSSARY.md#vertex). -Input: vertex attributes read from the [[vertex buffer]](GLOSSARY.md#vertex-buffer). In our case: position and color. +Input: vertex attributes read from the [vertex buffer](GLOSSARY.md#vertex-buffer). In our case: position and color. -Output: mandatory clip-space position (`vec4`) plus any per-vertex data the [[fragment shader]](GLOSSARY.md#fragment-shader) needs downstream: color, UV coordinates, normals, etc. +Output: mandatory clip-space position (`vec4`) plus any per-vertex data the [fragment shader](GLOSSARY.md#fragment-shader) needs downstream: color, UV coordinates, normals, etc. The vertex shader is the only place you transform geometry. In complex scenes this means multiplying by model-view-projection matrices. For our triangle, the vertices are already in the GPU's native coordinate space, so the vertex shader passes the position through unchanged. @@ -39,7 +39,7 @@ The vertex shader is the only place you transform geometry. In complex scenes th Hardware only. No user code runs here. -The GPU takes vertices in the order you submitted them and groups them into [[primitive]](GLOSSARY.md#primitive) shapes. With [[topology]](GLOSSARY.md#topology) set to `TriangleList`, every group of 3 consecutive vertices becomes one triangle. Vertex 0, 1, 2 → triangle A. Vertex 3, 4, 5 → triangle B. +The GPU takes vertices in the order you submitted them and groups them into [primitive](GLOSSARY.md#primitive) shapes. With [topology](GLOSSARY.md#topology) set to `TriangleList`, every group of 3 consecutive vertices becomes one triangle. Vertex 0, 1, 2 → triangle A. Vertex 3, 4, 5 → triangle B. ### Stage 3: Rasterizer @@ -53,17 +53,17 @@ Before rasterization proper, the GPU performs several fixed-function vertex post These stages are automatic and happen in hardware. You do not write code for them. -Then the [[rasterizer]](GLOSSARY.md#rasterizer) takes over — the hardware stage that converts triangles into fragments. +Then the [rasterizer](GLOSSARY.md#rasterizer) takes over — the hardware stage that converts triangles into fragments. -For each submitted triangle, the rasterizer determines which screen pixels the triangle covers. For each covered pixel, it generates one [[fragment]](GLOSSARY.md#fragment) — a "potential pixel" carrying interpolated data. +For each submitted triangle, the rasterizer determines which screen pixels the triangle covers. For each covered pixel, it generates one [fragment](GLOSSARY.md#fragment) — a "potential pixel" carrying interpolated data. -The critical function here is [[interpolation]](GLOSSARY.md#interpolation). The rasterizer computes [[barycentric coordinates]](GLOSSARY.md#barycentric-coordinates) — three weights (w0, w1, w2) that sum to 1 — describing where inside the triangle the pixel falls. Then for every value the vertex shader output, the rasterizer computes: `value = w0 * value0 + w1 * value1 + w2 * value2`. +The critical function here is [interpolation](GLOSSARY.md#interpolation). The rasterizer computes [barycentric coordinates](GLOSSARY.md#barycentric-coordinates) — three weights (w0, w1, w2) that sum to 1 — describing where inside the triangle the pixel falls. Then for every value the vertex shader output, the rasterizer computes: `value = w0 * value0 + w1 * value1 + w2 * value2`. -This is the step that makes colors blend across the triangle. It is free, automatic, hardware-accelerated [[interpolation]](GLOSSARY.md#interpolation). You do not write the code. The GPU computes it because it is how the rendering pipeline architecture works. +This is the step that makes colors blend across the triangle. It is free, automatic, hardware-accelerated [interpolation](GLOSSARY.md#interpolation). You do not write the code. The GPU computes it because it is how the rendering pipeline architecture works. ### Stage 4: Fragment Shader -[[fragment shader]](GLOSSARY.md#fragment-shader) — a GPU program running once per [[fragment]](GLOSSARY.md#fragment). +[fragment shader](GLOSSARY.md#fragment-shader) — a GPU program running once per [fragment](GLOSSARY.md#fragment). Input: the pre-interpolated values from the vertex shader, delivered by the rasterizer. The fragment shader receives one invocation per covered screen pixel. If a triangle covers 2000 pixels, the fragment shader runs 2000 times. @@ -71,7 +71,7 @@ Output: the final RGBA color for that pixel. The fragment shader computes lighti ### Stage 5: Output Merge -The final hardware stage before the color hits the [[framebuffer]](GLOSSARY.md#framebuffer). +The final hardware stage before the color hits the [framebuffer](GLOSSARY.md#framebuffer). Per-fragment operations: @@ -79,7 +79,7 @@ Per-fragment operations: - **Stencil test:** Mask drawing to specific screen regions via a stencil buffer. We disable this. - **Blend:** Combine the new fragment color with the existing framebuffer color. We use REPLACE — the fragment color overwrites whatever was there. -After the output merge, the final color is written to the framebuffer. When you [[load op]](GLOSSARY.md#loadop) is `Clear`, the framebuffer is filled with your background color before the render pass begins. [[Storeop]](GLOSSARY.md#storeop) determines whether you keep or discard the results after the render pass. +After the output merge, the final color is written to the framebuffer. When you [load op](GLOSSARY.md#loadop) is `Clear`, the framebuffer is filled with your background color before the render pass begins. [Storeop](GLOSSARY.md#storeop) determines whether you keep or discard the results after the render pass. ## Why This Matters For The Rainbow Triangle @@ -97,6 +97,6 @@ The rainbow gradient is not programmed. There is no loop, no formula, no color b ## The Pipeline Object In wgpu -In wgpu, you compile all of this into a [[pipeline]](GLOSSARY.md#pipeline): a single opaque render pipeline object encoding your shaders, topology, blend state, vertex layout, and output format. It is created once during initialization and reused every frame. Creating a pipeline up-front saves per-frame compilation and state configuration. The [[device]](GLOSSARY.md#device) owns the pipeline, and you use the [[queue]](GLOSSARY.md#queue) to submit draw calls that reference it. +In wgpu, you compile all of this into a [pipeline](GLOSSARY.md#pipeline): a single opaque render pipeline object encoding your shaders, topology, blend state, vertex layout, and output format. It is created once during initialization and reused every frame. Creating a pipeline up-front saves per-frame compilation and state configuration. The [device](GLOSSARY.md#device) owns the pipeline, and you use the [queue](GLOSSARY.md#queue) to submit draw calls that reference it. -The [[adapter]](GLOSSARY.md#adapter) is the physical GPU or software renderer you select. There may be multiple on a single system — a dedicated NVIDIA card plus integrated Intel graphics. You pick one adapter, create a device from it, and all resources flow from that device. +The [adapter](GLOSSARY.md#adapter) is the physical GPU or software renderer you select. There may be multiple on a single system — a dedicated NVIDIA card plus integrated Intel graphics. You pick one adapter, create a device from it, and all resources flow from that device. diff --git a/docs/concepts/shader-basics.md b/docs/concepts/shader-basics.md index ad3efeb..0a49dee 100644 --- a/docs/concepts/shader-basics.md +++ b/docs/concepts/shader-basics.md @@ -4,7 +4,7 @@ A shader is a GPU program. It is a piece of code that runs on the GPU instead of the CPU. Unlike a CPU program, you do not call a shader function once. You configure it, bind data to it, and then the GPU runs thousands of copies simultaneously on different data elements. One shader invocation per vertex. One shader invocation per pixel. -Shaders are written in WGSL — [[wgsl]](GLOSSARY.md#wgsl), the WebGPU Shading Language. WGSL is compiled down to the platform's native intermediate representation: SPIR-V for Vulkan, MSL for Metal, DXIL for DirectX. You write one shader; wgpu handles the translation. +Shaders are written in WGSL — [wgsl](GLOSSARY.md#wgsl), the WebGPU Shading Language. WGSL is compiled down to the platform's native intermediate representation: SPIR-V for Vulkan, MSL for Metal, DXIL for DirectX. You write one shader; wgpu handles the translation. ## WGSL Constraints @@ -14,7 +14,7 @@ WGSL is designed for parallel execution on hardware with severe restrictions: - **No recursion.** The GPU has a fixed, tiny stack. Recursive calls are banned. - **No I/O.** No `print`, no `println`, no file access, no `socket`. A shader communicates only through its return values and writes to bound buffers/textures. - **Static types.** `f32`, `i32`, `u32` for scalars. `vec2`, `vec3`, `vec4` for vectors. `mat2x2` through `mat4x4` for matrices. Every expression has a known type at compile time. There is no `any` and no `dyn`. -- **No arbitrary memory access.** You read from structured inputs (vertex attributes, uniform buffers, textures) and write to defined outputs. Memory is laid out contiguously in [[buffer slice]](GLOSSARY.md#buffer-slice) regions. +- **No arbitrary memory access.** You read from structured inputs (vertex attributes, uniform buffers, textures) and write to defined outputs. Memory is laid out contiguously in [buffer slice](GLOSSARY.md#buffer-slice) regions. These are not bugs. They are the GPU architecture. Every shader invocation runs in an identical sandbox. That identity is what enables 1000x throughput. @@ -24,19 +24,19 @@ A shader module contains one or more entry point functions. Each entry point is ### `@vertex` — Vertex Shader Entry Point -Runs once per input [[vertex]](GLOSSARY.md#vertex). The GPU calls this function for every vertex in your draw call. +Runs once per input [vertex](GLOSSARY.md#vertex). The GPU calls this function for every vertex in your draw call. -**Mandatory output:** `@builtin(position) vec4` — the [[clip space]](GLOSSARY.md#clip-space) position that the GPU uses for [[primitive]](GLOSSARY.md#primitive) assembly and rasterization. Without this output, the pipeline fails. +**Mandatory output:** `@builtin(position) vec4` — the [clip space](GLOSSARY.md#clip-space) position that the GPU uses for [primitive](GLOSSARY.md#primitive) assembly and rasterization. Without this output, the pipeline fails. **Optional outputs:** Any number of `@location(n)` values that flow to the fragment shader. Color, UV coordinates, normals — everything downstream needs is passed through the vertex shader output. ### `@fragment` — Fragment Shader Entry Point -Runs once per [[fragment]](GLOSSARY.md#fragment) produced by the rasterizer. For a triangle covering 500 pixels on screen, the fragment shader runs 500 times. +Runs once per [fragment](GLOSSARY.md#fragment) produced by the rasterizer. For a triangle covering 500 pixels on screen, the fragment shader runs 500 times. **Input:** Interpolated values from the vertex shader. If the vertex shader output `@location(0) color: vec3`, the fragment shader receives that same `@location(0)` with hardware-interpolated values. -**Output:** `@location(0) vec4` — the final RGBA color written to the [[framebuffer]](GLOSSARY.md#framebuffer). +**Output:** `@location(0) vec4` — the final RGBA color written to the [framebuffer](GLOSSARY.md#framebuffer). ## The Location Contract @@ -44,7 +44,7 @@ Runs once per [[fragment]](GLOSSARY.md#fragment) produced by the rasterizer. For > **LOCATION BINDING IS THE CRITICAL LINK BETWEEN RUST AND WGSL** > -> Every value flowing between Rust buffers and WGSL shader functions is tied together by a numeric [[shader location]](GLOSSARY.md#shader-location) label. The number on the Rust side must match the number on the WGSL side. +> Every value flowing between Rust buffers and WGSL shader functions is tied together by a numeric [shader location](GLOSSARY.md#shader-location) label. The number on the Rust side must match the number on the WGSL side. > > Rust: `VertexAttribute { shader_location: 0, ... }` > @@ -56,7 +56,7 @@ Runs once per [[fragment]](GLOSSARY.md#fragment) produced by the rasterizer. For ## Interpolation Mechanism -Between the vertex shader and the fragment shader, the [[rasterizer]](GLOSSARY.md#rasterizer) performs a computation that most graphics tutorials treat as magic. It is not magic. It is [[interpolation]](GLOSSARY.md#interpolation). +Between the vertex shader and the fragment shader, the [rasterizer](GLOSSARY.md#rasterizer) performs a computation that most graphics tutorials treat as magic. It is not magic. It is [interpolation](GLOSSARY.md#interpolation). For every `@location(n)` value the vertex shader outputs, the rasterizer computes a triangle-wide linear blend: @@ -64,20 +64,20 @@ For every `@location(n)` value the vertex shader outputs, the rasterizer compute fragment_value = w0 * vertex0_value + w1 * vertex1_value + w2 * vertex2_value ``` -where `w0 + w1 + w2 = 1.0` and the weights are [[barycentric coordinates]](GLOSSARY.md#barycentric-coordinates) computed from the fragment's position inside the triangle. +where `w0 + w1 + w2 = 1.0` and the weights are [barycentric coordinates](GLOSSARY.md#barycentric-coordinates) computed from the fragment's position inside the triangle. This interpolation is free. It is a dedicated hardware unit inside every GPU. You do not write the code. You do not pay an algorithmic cost. The rasterizer hardware computes barycentric weights and blends every vertex shader output automatically. The fragment shader receives pre-blended values and does not need to know how they were computed. ## How Shaders Work Together -A complete rendering shader is a two-stage program compiled into a single WGSL module. The **vertex shader** runs once per vertex in your draw call, transforming raw buffer data into GPU-ready outputs. The **fragment shader** runs once per pixel produced by the rasterizer, converting interpolated vertex data into the final color written to the [[framebuffer]](GLOSSARY.md#framebuffer). Both stages execute in parallel across thousands of invocations — the vertex shader processes all vertices simultaneously, then the fragment shader processes all fragments simultaneously. +A complete rendering shader is a two-stage program compiled into a single WGSL module. The **vertex shader** runs once per vertex in your draw call, transforming raw buffer data into GPU-ready outputs. The **fragment shader** runs once per pixel produced by the rasterizer, converting interpolated vertex data into the final color written to the [framebuffer](GLOSSARY.md#framebuffer). Both stages execute in parallel across thousands of invocations — the vertex shader processes all vertices simultaneously, then the fragment shader processes all fragments simultaneously. Data flows between the vertex and fragment stages through a shared struct. The struct's fields are tagged with WGSL attributes that tell the GPU how to route each value: - **`@location(n)`** marks values that bind to Rust vertex buffer attributes or flow between shader stages. The number `n` is a binding index: on the Rust side it appears as `shader_location: n` in a `VertexAttribute`, and in WGSL it appears as `@location(n)` on a parameter or struct field. If the numbers differ, the GPU reads from the wrong buffer offset and produces silent garbage. Between the vertex and fragment stages, `@location` values are automatically interpolated by the rasterizer using barycentric weights — the fragment shader receives a smooth blend without writing any interpolation code. -- **`@builtin(position)`** is a reserved slot the vertex shader must output. It delivers the vertex's [[clip space]](GLOSSARY.md#clip-space) position as `vec4`, which the rasterizer uses for perspective division, viewport transform, and primitive assembly. The fragment shader receives its own independent `@builtin(position)` from the fragment pipeline stage — providing framebuffer pixel coordinates — not the vertex shader's output. The two builtins share a name but are completely separate values from different stages. +- **`@builtin(position)`** is a reserved slot the vertex shader must output. It delivers the vertex's [clip space](GLOSSARY.md#clip-space) position as `vec4`, which the rasterizer uses for perspective division, viewport transform, and primitive assembly. The fragment shader receives its own independent `@builtin(position)` from the fragment pipeline stage — providing framebuffer pixel coordinates — not the vertex shader's output. The two builtins share a name but are completely separate values from different stages. -The vertex shader produces a struct containing a `@builtin(position)` output plus any number of `@location` interpolants. The rasterizer takes these outputs, assembles [[primitives]](GLOSSARY.md#primitive), and for every pixel inside the triangle computes [[barycentric coordinates]](GLOSSARY.md#barycentric-coordinates) and blends all `@location` fields. The fragment shader receives the fully interpolated struct and outputs a `vec4` color at `@location(0)`, which maps to the [[pipeline]](GLOSSARY.md#pipeline)'s color attachment target. +The vertex shader produces a struct containing a `@builtin(position)` output plus any number of `@location` interpolants. The rasterizer takes these outputs, assembles [primitives](GLOSSARY.md#primitive), and for every pixel inside the triangle computes [barycentric coordinates](GLOSSARY.md#barycentric-coordinates) and blends all `@location` fields. The fragment shader receives the fully interpolated struct and outputs a `vec4` color at `@location(0)`, which maps to the [pipeline](GLOSSARY.md#pipeline)'s color attachment target. For a complete line-by-line walkthrough of our rainbow triangle shader, see [Section 4](01-rainbow-triangle.md#s4-writing-the-shaders). @@ -89,6 +89,6 @@ In wgpu, the shader source code lives as a Rust string, embedded at compile time const SHADER_SOURCE: &str = include_str!("shader.wgsl"); ``` -`include_str!` reads the WGSL file during Rust compilation and inlines it as a `&'static str`. There is no runtime file I/O. The shader text is part of the binary. When you create the shader module via `device.create_shader_module()`, wgpu compiles the string to the platform's GPU intermediate format (SPIR-V, MSL, or DXIL). The compilation happens asynchronously on the [[device]](GLOSSARY.md#device) — you drive it to completion with a [[device poll]](GLOSSARY.md#device-poll). +`include_str!` reads the WGSL file during Rust compilation and inlines it as a `&'static str`. There is no runtime file I/O. The shader text is part of the binary. When you create the shader module via `device.create_shader_module()`, wgpu compiles the string to the platform's GPU intermediate format (SPIR-V, MSL, or DXIL). The compilation happens asynchronously on the [device](GLOSSARY.md#device) — you drive it to completion with a [device poll](GLOSSARY.md#device-poll). This is intentional: GPU drivers are slow to initialize file paths. Embedding the source at compile time is idiomatic wgpu and eliminates a class of runtime errors.