diff --git a/docs/DRAFT.md b/docs/DRAFT.md new file mode 100644 index 0000000..5bf5a67 --- /dev/null +++ b/docs/DRAFT.md @@ -0,0 +1,72 @@ +# DRAFT — Frame Loop + +## The "Frame Loop" (preparing the draw) + +To display something, you must follow an immutable cycle called the **Frame Lifetime**. In your Renderer (or a dedicated method of Context), you will need to: + +1. **Acquire a surface texture** — ask the surface for the Back Buffer texture (where you'll draw). +2. **Create a texture view** — WGPU doesn't draw directly on the texture, but on a "view" into that texture. +3. **Create a command encoder** — this is the notebook where you write your orders ("clear screen", "draw triangle", "finalize"). +4. **Submit commands** — send this notebook to the GPU via the Queue. + +### Why this is the logical next step + +Because without this, your `configure` call does nothing. You've reserved space on the GPU; now you need to learn how to "grasp" it to work on it. + +Here is the skeleton of this cycle that you should implement: + +```rust +// In your Renderer (or Context) +pub fn render(&self) -> Result<(), WsgError> { + // 1. Acquire the texture to draw on + let frame = self.surface.get_current_texture() + .map_err(|_| WsgError::SurfaceIncompatible)?; + + // 2. Create the view (the "channel" to the texture) + let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default()); + + // 3. Create the command encoder + let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + // --- This is where we'll draw later --- + { + let _render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::BLUE), // Blue background for testing + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + } + + // 4. Submit and present + self.queue.submit(std::iter::once(encoder.finish())); + frame.present(); + + Ok(()) +} +``` + +### Why this separation is vital + +You'll notice that the `{ let _render_pass ... }` block is delimited by braces. This is very important in Rust: `render_pass` must be dropped before calling `encoder.finish()`. If you forget this, your program will crash because you'd be submitting orders while the "notebook" is still being written. + + +## Persistent vs. Per-Frame Resources + +| Element | Lifetime | Why? | +|---------|----------|------| +| `SurfaceConfiguration` | Persistent | Only changes on resize | +| `RenderPipeline` | Persistent | Very expensive to create (shader compilation) | +| Buffers (Vertex/Index) | Persistent | Geometry data doesn't change every frame | +| `CommandEncoder` | Frame | Temporary "notebook" for frame commands | +| `TextureView` | Frame | View into the active Swapchain texture | diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 6a85e45..e66d648 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] wsg-lib = { path = "../lib" } pollster = "0.4.0" -winit = "=0.29" # Pinned — matches lib/ version exactly +winit = "0.29" # Pinned — matches lib/ version exactly [[bin]] name = "demo" # Un nom de binaire qui ne risque pas de conflits diff --git a/lib/context.rs b/lib/context.rs index feb2372..3673de2 100644 --- a/lib/context.rs +++ b/lib/context.rs @@ -59,4 +59,40 @@ impl Context { queue, }) } + + /// Configures the surface with a render format and alpha mode for rendering. + /// Inputs: adapter (GPU capabilities), width/height (surface resolution). + /// Returns Ok(()) on success or SurfaceIncompatible if no SRGB format + alpha mode exist. + /// Typically called by the renderer when window size changes. + pub fn configure(&self, adapter: &wgpu::Adapter, width: u32, height: u32) -> Result<(), WsgError> { + let caps = self.surface.get_capabilities(adapter); + // Prefer SRGB format for color accuracy; fall back to first available if none (technical point documented above). + let format = caps + .formats + .iter() + .copied() + .find(|f| f.is_srgb()) + .or(caps.formats.first().copied()) + .ok_or(WsgError::SurfaceIncompatible)?; + + let alpha_mode = caps + .alpha_modes + .first() + .copied() + .ok_or(WsgError::SurfaceIncompatible)?; + + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format, + width, + height, + present_mode: wgpu::PresentMode::Fifo, // V-Sync activated + alpha_mode, // first supported alpha mode + view_formats: vec![], + color_space: wgpu::SurfaceColorSpace::Srgb, + desired_maximum_frame_latency: 2, + }; + self.surface.configure(&self.device, &config); + Ok(()) + } } diff --git a/lib/error.rs b/lib/error.rs index 4b3a486..a2535f7 100644 --- a/lib/error.rs +++ b/lib/error.rs @@ -45,4 +45,9 @@ pub enum WsgError { /// Caller: `Context::new()` after `Instance::create_surface()`. #[error("Failed to create rendering surface")] SurfaceCreation(wgpu::CreateSurfaceError), + + /// The surface is incompatible — no SRGB texture format and alpha mode are available on the device. + /// Caller: `Context::configure()` when selecting a render format/alpha mode from surface capabilities. + #[error("No compatible render format or alpha mode found for the surface")] + SurfaceIncompatible, }