From 23a7e3b15144cf9cbd3d04d8b266099c33bc3c34 Mon Sep 17 00:00:00 2001 From: Krishna Ayyalasomayajula Date: Sat, 30 May 2026 20:01:24 -0500 Subject: [PATCH] docs: fix SurfaceStatus API to match wgpu 29 CurrentSurfaceTexture --- docs/01-rainbow-triangle.md | 196 +++++++++++++++++------------------- 1 file changed, 92 insertions(+), 104 deletions(-) diff --git a/docs/01-rainbow-triangle.md b/docs/01-rainbow-triangle.md index 503621a..a294ee3 100644 --- a/docs/01-rainbow-triangle.md +++ b/docs/01-rainbow-triangle.md @@ -921,7 +921,7 @@ wait for GPU completion. ### Acquiring a Back Buffer from the Swapchain ```rust -let status = self.surface.get_current_texture(); +let frame = self.surface.get_current_texture(); ``` `get_current_texture()` is how you acquire a back buffer from the @@ -930,104 +930,95 @@ into for this frame. In a triple-buffered swapchain (`PresentMode::Mailbox`), there are up to two spare back buffers waiting for you. `get_current_texture()` hands you the next available one. -In wgpu 29+, this method returns a `CurrentSurfaceTexture` **enum** — not a -`Result`. The swapchain can be in seven distinct states, and every state is a -valid, non-error condition: +In wgpu 29, this method returns `Result`. On +success you receive a `SurfaceTexture` containing the back-buffer texture. On +failure you receive a `SurfaceError` describing what went wrong: -> **Key insight #5 — 7 swapchain states you must handle:** `Success(buf)` — -> render normally. `Suboptimal(buf)` — render but reconfig is advisable. -> `Timeout` — skip frame (GPU late). `Occluded` — skip frame (window behind -> another). `Outdated` — `self.resize()` to reconfigure. `Lost` — skip frame -> (display server restarted). `Validation` — skip frame (API misuse; check -> logs). +> **Key insight #5 — 4 surface error variants you must handle:** `Ok(texture)` — +> render normally. `SurfaceError::Timeout` — skip frame (GPU late). +> `SurfaceError::Outdated` — surface changed, reconfigure when possible. +> `SurfaceError::Lost` — surface destroyed, cannot recover without re-init. +> `SurfaceError::OutOfMemory` — GPU memory exhausted, fatal. -WHY `match` on 7 variants: `get_current_texture()` does not return a `Result`. -All 7 states are valid and the match must be exhaustive. The Rust compiler -enforces this — you cannot miss a variant. +WHY `match` on the Result: `get_current_texture()` returns a `Result`, so you +use standard Rust error handling. The happy path (`Ok`) extracts the +`SurfaceTexture`, and the error path (`Err`) matches on the specific +`SurfaceError` variant. The Rust compiler enforces exhaustive matching. ### The Complete `render` Implementation ```rust fn render(&mut self) { - let status = self.surface.get_current_texture(); - - match status { - wgpu::SurfaceStatus::Success(surface_texture) - | wgpu::SurfaceStatus::Suboptimal(surface_texture) => { - // Drive GPU work: shader compilation, memory allocation, fence signaling - if let Err(e) = self.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None }) { - log::error!("Device poll failed: {e}"); - return; + let frame = match self.surface.get_current_texture() { + Ok(frame) => frame, + Err(e) => { + match &e { + wgpu::SurfaceError::Timeout => { + log::warn!("Surface error: Timeout — skipping frame"); + } + wgpu::SurfaceError::Outdated => { + log::warn!("Surface error: Outdated — resizing"); + let size = wgpu::dpi::PhysicalSize { + width: self.config.width, + height: self.config.height, + }; + self.resize(size); + } + wgpu::SurfaceError::Lost => { + log::error!("Surface error: Lost — cannot recover without re-creating State"); + } + wgpu::SurfaceError::OutOfMemory => { + log::error!("Surface error: OutOfMemory — GPU memory exhausted"); + } } - - let texture_view = surface_texture.texture.create_view(&Default::default()); - - let mut encoder = self.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { - label: Some("Main Command Encoder"), - }, - ); - - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Main Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &texture_view, - depth_slice: None, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.1, - g: 0.1, - b: 0.1, - a: 1.0, - }), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - multiview_mask: None, - }); - - render_pass.set_pipeline(&self.pipeline); - render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); - render_pass.draw(0..3, 0..1); - } // render_pass drops here — render pass ends automatically - - self.queue.submit(std::iter::once(encoder.finish())); - surface_texture.present(); + return; } + }; - wgpu::SurfaceStatus::Timeout => { - // GPU took too long to finish previous work. Skip this frame. - log::warn!("Surface status: Timeout — skipping frame"); - } - - wgpu::SurfaceStatus::Occluded => { - // Window is fully occluded by another window. Skip rendering. - log::debug!("Surface status: Occluded — skipping frame"); - } - - wgpu::SurfaceStatus::Outdated => { - // Swapchain resolution no longer matches window. Reconfigure. - log::warn!("Surface status: Outdated — resizing"); - if let Some(window) = &self.window { - self.resize(window.inner_size()); - } - } - - wgpu::SurfaceStatus::Lost => { - // Display server restarted or GPU lost. Fatal without re-init. - log::error!("Surface status: Lost — cannot recover without re-creating State"); - } - - wgpu::SurfaceStatus::Validation { source, description } => { - // wgpu validated your descriptor and found it invalid. - log::error!("Surface validation: {source} — {description}"); - } + // Drive GPU work: shader compilation, memory allocation, fence signaling + if let Err(e) = self.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None }) { + log::error!("Device poll failed: {e}"); + return; } + + let texture_view = frame.texture.create_view(&Default::default()); + + let mut encoder = self.device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { + label: Some("Main Command Encoder"), + }, + ); + + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Main Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &texture_view, + depth_slice: None, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.1, + g: 0.1, + b: 0.1, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + multiview_mask: None, + }); + + render_pass.set_pipeline(&self.pipeline); + render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + render_pass.draw(0..3, 0..1); + } // render_pass drops here — render pass ends automatically + + self.queue.submit(std::iter::once(encoder.finish())); + frame.present(); } ``` @@ -1165,22 +1156,19 @@ buffer from "render target" to "front buffer" on the next vsync. ### Why the Match Arms Differ -- **`Success` / `Suboptimal`** — both deliver a `SurfaceTexture` you can render - into. The difference: `Suboptimal` means the current swapchain configuration - is not ideal for the GPU (e.g., format mismatch). You render normally but - should consider reconfiguring the surface during idle time. -- **`Timeout`** — the GPU exceeded the wait threshold for a back buffer. Skip - the frame. The GPU will catch up. -- **`Occluded`** — another fully covers your window. Skip rendering entirely — - the display server will not show your output. Saves GPU work. -- **`Outdated`** — the swapchain was created for a resolution that no longer - matches the window. Reconfigure the surface to match. -- **`Lost`** — the GPU or display server has been reset. Without re-creating - the device and surface, you cannot recover. In a real application, you'd - trigger a full re-initialization. -- **`Validation`** — wgpu rejected the surface configuration due to API misuse. - Check logs for the description. This is a programming error, not a runtime - condition. +- **`Ok(frame)`** — the swapchain delivered a `SurfaceTexture` you can render + into. Extract `frame.texture` to create a view, render, then call + `frame.present()`. +- **`SurfaceError::Timeout`** — the GPU exceeded the wait threshold for a back + buffer. Skip the frame. The GPU will catch up. +- **`SurfaceError::Outdated`** — the swapchain was created for a resolution + that no longer matches the window. Reconfigure the surface to match the + current dimensions. +- **`SurfaceError::Lost`** — the GPU or display server has been reset. Without + re-creating the device and surface, you cannot recover. In a real + application, you'd trigger a full re-initialization. +- **`SurfaceError::OutOfMemory`** — GPU memory is exhausted. This is a fatal + condition requiring process-level recovery. ## S8: Handling Window Resize