renderling/
context.rs

1//! Rendering context initialization
2//!
3//! This module contains [`Context`] initialization and frame management.
4//! This module provides the setup and management of rendering targets,
5//! frames, and surface configurations.
6use core::fmt::Debug;
7use std::{
8    ops::Deref,
9    sync::{Arc, RwLock},
10};
11
12use glam::{UVec2, UVec3};
13use snafu::prelude::*;
14
15use crate::{
16    stage::Stage,
17    texture::{BufferDimensions, CopiedTextureBuffer, Texture, TextureError},
18    ui::Ui,
19};
20
21pub use craballoc::runtime::WgpuRuntime;
22
23/// Represents the internal structure of a render target, which can either be a surface or a texture.
24pub(crate) enum RenderTargetInner {
25    Surface {
26        surface: wgpu::Surface<'static>,
27        surface_config: wgpu::SurfaceConfiguration,
28    },
29    Texture {
30        texture: Arc<wgpu::Texture>,
31    },
32}
33
34#[repr(transparent)]
35/// Represents a render target that can either be a surface or a texture.
36/// It will be a surface if the context was created with a window or canvas,
37/// and a texture if the context is headless.
38pub struct RenderTarget(pub(crate) RenderTargetInner);
39
40/// Converts a `wgpu::Texture` into a `RenderTarget`.
41impl From<wgpu::Texture> for RenderTarget {
42    fn from(value: wgpu::Texture) -> Self {
43        RenderTarget(RenderTargetInner::Texture {
44            texture: Arc::new(value),
45        })
46    }
47}
48
49impl RenderTarget {
50    /// Resizes the render target to the specified width and height using the provided device.
51    pub fn resize(&mut self, width: u32, height: u32, device: &wgpu::Device) {
52        match &mut self.0 {
53            RenderTargetInner::Surface {
54                surface,
55                surface_config,
56            } => {
57                surface_config.width = width;
58                surface_config.height = height;
59                surface.configure(device, surface_config)
60            }
61            RenderTargetInner::Texture { texture } => {
62                let usage = texture.usage();
63                let format = texture.format();
64                let texture_desc = wgpu::TextureDescriptor {
65                    size: wgpu::Extent3d {
66                        width,
67                        height,
68                        depth_or_array_layers: 1,
69                    },
70                    mip_level_count: 1,
71                    sample_count: 1,
72                    dimension: wgpu::TextureDimension::D2,
73                    format,
74                    usage,
75                    label: Some("RenderTarget texture"),
76                    view_formats: &[],
77                };
78                *texture = Arc::new(device.create_texture(&texture_desc));
79            }
80        }
81    }
82
83    /// Returns the format of the render target.
84    pub fn format(&self) -> wgpu::TextureFormat {
85        match &self.0 {
86            RenderTargetInner::Surface { surface_config, .. } => surface_config.format,
87            RenderTargetInner::Texture { texture } => texture.format(),
88        }
89    }
90
91    /// Checks if the render target is headless (i.e., a texture).
92    pub fn is_headless(&self) -> bool {
93        match &self.0 {
94            RenderTargetInner::Surface { .. } => false,
95            RenderTargetInner::Texture { .. } => true,
96        }
97    }
98
99    /// Returns the underlying target as a texture, if possible.
100    pub fn as_texture(&self) -> Option<&wgpu::Texture> {
101        match &self.0 {
102            RenderTargetInner::Surface { .. } => None,
103            RenderTargetInner::Texture { texture } => Some(texture),
104        }
105    }
106
107    /// Returns the size of the render target as a `UVec2`.
108    pub fn get_size(&self) -> UVec2 {
109        match &self.0 {
110            RenderTargetInner::Surface {
111                surface: _,
112                surface_config,
113            } => UVec2::new(surface_config.width, surface_config.height),
114            RenderTargetInner::Texture { texture } => {
115                let s = texture.size();
116                UVec2::new(s.width, s.height)
117            }
118        }
119    }
120}
121
122#[derive(Debug, Snafu)]
123#[snafu(visibility(pub(crate)))]
124/// Represents errors that can occur within the rendering context.
125pub enum ContextError {
126    #[snafu(display("missing surface texture: {}", source))]
127    Surface { source: wgpu::SurfaceError },
128
129    #[snafu(display("cannot create adaptor: {source}"))]
130    CannotCreateAdaptor { source: wgpu::RequestAdapterError },
131
132    #[snafu(display("cannot request device: {}", source))]
133    CannotRequestDevice { source: wgpu::RequestDeviceError },
134
135    #[snafu(display("surface is incompatible with adapter"))]
136    IncompatibleSurface,
137
138    #[snafu(display("could not create surface: {}", source))]
139    CreateSurface { source: wgpu::CreateSurfaceError },
140}
141
142/// A thin wrapper over [`wgpu::TextureView`] returned by [`Frame::view`].
143pub struct FrameTextureView {
144    pub view: Arc<wgpu::TextureView>,
145    pub format: wgpu::TextureFormat,
146}
147
148impl Deref for FrameTextureView {
149    type Target = wgpu::TextureView;
150
151    fn deref(&self) -> &Self::Target {
152        &self.view
153    }
154}
155
156/// Represents the surface of a frame, which can either be a surface texture or a texture.
157pub(crate) enum FrameSurface {
158    Surface(wgpu::SurfaceTexture),
159    Texture(Arc<wgpu::Texture>),
160}
161
162/// Represents the current frame of a render target.
163///
164/// Returned by [`Context::get_next_frame`].
165pub struct Frame {
166    pub(crate) runtime: WgpuRuntime,
167    pub(crate) surface: FrameSurface,
168}
169
170impl Frame {
171    /// Returns the underlying texture of this target.
172    pub fn texture(&self) -> &wgpu::Texture {
173        match &self.surface {
174            FrameSurface::Surface(s) => &s.texture,
175            FrameSurface::Texture(t) => t,
176        }
177    }
178
179    /// Returns a view of the current frame's texture.
180    pub fn view(&self) -> wgpu::TextureView {
181        let texture = self.texture();
182        let format = texture.format().add_srgb_suffix();
183        texture.create_view(&wgpu::TextureViewDescriptor {
184            label: Some("Frame::view"),
185            format: Some(format),
186            ..Default::default()
187        })
188    }
189
190    /// Copies the current frame to a buffer for further processing.
191    pub fn copy_to_buffer(&self, width: u32, height: u32) -> CopiedTextureBuffer {
192        let dimensions = BufferDimensions::new(4, 1, width as usize, height as usize);
193        // The output buffer lets us retrieve the self as an array
194        let buffer = self.runtime.device.create_buffer(&wgpu::BufferDescriptor {
195            label: Some("RenderTarget::copy_to_buffer"),
196            size: (dimensions.padded_bytes_per_row * dimensions.height) as u64,
197            usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
198            mapped_at_creation: false,
199        });
200        let mut encoder =
201            self.runtime
202                .device
203                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
204                    label: Some("post render screen capture encoder"),
205                });
206        let texture = self.texture();
207        // Copy the data from the surface texture to the buffer
208        encoder.copy_texture_to_buffer(
209            texture.as_image_copy(),
210            wgpu::TexelCopyBufferInfo {
211                buffer: &buffer,
212                layout: wgpu::TexelCopyBufferLayout {
213                    offset: 0,
214                    bytes_per_row: Some(dimensions.padded_bytes_per_row as u32),
215                    rows_per_image: None,
216                },
217            },
218            wgpu::Extent3d {
219                width: dimensions.width as u32,
220                height: dimensions.height as u32,
221                depth_or_array_layers: 1,
222            },
223        );
224
225        self.runtime.queue.submit(std::iter::once(encoder.finish()));
226
227        CopiedTextureBuffer {
228            dimensions,
229            buffer,
230            format: texture.format(),
231        }
232    }
233
234    pub fn get_size(&self) -> UVec2 {
235        let s = self.texture().size();
236        UVec2::new(s.width, s.height)
237    }
238
239    /// Reads the current frame buffer into an image.
240    ///
241    /// This should be called after rendering, before presentation.
242    /// Good for getting headless screen grabs.
243    ///
244    /// The resulting image will be in the color space of the frame.
245    ///
246    /// ## Note
247    /// This operation can take a long time, depending on how big the screen is.
248    pub async fn read_image(&self) -> Result<image::RgbaImage, TextureError> {
249        let size = self.get_size();
250        let buffer = self.copy_to_buffer(size.x, size.y);
251        let is_srgb = self.texture().format().is_srgb();
252        let img = if is_srgb {
253            buffer.into_srgba(&self.runtime.device).await?
254        } else {
255            buffer.into_linear_rgba(&self.runtime.device).await?
256        };
257        Ok(img)
258    }
259
260    /// Reads the frame into an image in a sRGB color space.
261    ///
262    /// This should be called after rendering, before presentation.
263    /// Good for getting headless screen grabs.
264    ///
265    /// ## Note
266    /// This operation can take a long time, depending on how big the screen is.
267    pub async fn read_srgb_image(&self) -> Result<image::RgbaImage, TextureError> {
268        let size = self.get_size();
269        let buffer = self.copy_to_buffer(size.x, size.y);
270        log::trace!("read image has the format: {:?}", buffer.format);
271        buffer.into_srgba(&self.runtime.device).await
272    }
273    /// Reads the frame into an image in a linear color space.
274    ///
275    /// This should be called after rendering, before presentation.
276    /// Good for getting headless screen grabs.
277    ///
278    /// ## Note
279    /// This operation can take a long time, depending on how big the screen is.
280    pub async fn read_linear_image(&self) -> Result<image::RgbaImage, TextureError> {
281        let size = self.get_size();
282        let buffer = self.copy_to_buffer(size.x, size.y);
283        buffer.into_linear_rgba(&self.runtime.device).await
284    }
285
286    /// Presents the surface frame if the frame is a `TargetFrame::Surface`.
287    /// If the frame is a `TargetFrame::Texture`, this is a no-op.
288    pub fn present(self) {
289        match self.surface {
290            FrameSurface::Surface(s) => s.present(),
291            FrameSurface::Texture(_) => {}
292        }
293    }
294}
295
296/// Configurable default values to use when creating new [`Stage`]s.
297#[derive(Debug, Clone, Copy)]
298pub(crate) struct GlobalStageConfig {
299    pub(crate) atlas_size: wgpu::Extent3d,
300    pub(crate) shadow_map_atlas_size: wgpu::Extent3d,
301    pub(crate) use_compute_culling: bool,
302}
303
304/// Contains the adapter, device, queue, [`RenderTarget`] and configuration.
305///
306/// A `Context` is created to initialize rendering to a window, canvas or
307/// texture.
308///
309/// ```
310/// use renderling::context::Context;
311///
312/// let ctx = futures_lite::future::block_on(Context::headless(100, 100));
313/// ```
314pub struct Context {
315    runtime: WgpuRuntime,
316    adapter: Arc<wgpu::Adapter>,
317    render_target: RenderTarget,
318    pub(crate) stage_config: Arc<RwLock<GlobalStageConfig>>,
319}
320
321impl AsRef<WgpuRuntime> for Context {
322    fn as_ref(&self) -> &WgpuRuntime {
323        &self.runtime
324    }
325}
326
327impl Context {
328    /// Creates a new `Context` with the specified target, adapter, device, and queue.
329    pub fn new(
330        target: RenderTarget,
331        adapter: impl Into<Arc<wgpu::Adapter>>,
332        device: impl Into<Arc<wgpu::Device>>,
333        queue: impl Into<Arc<wgpu::Queue>>,
334    ) -> Self {
335        let adapter: Arc<wgpu::Adapter> = adapter.into();
336        let limits = adapter.limits();
337        let w = limits
338            .max_texture_dimension_2d
339            .min(crate::atlas::ATLAS_SUGGESTED_SIZE);
340        let stage_config = Arc::new(RwLock::new(GlobalStageConfig {
341            atlas_size: wgpu::Extent3d {
342                width: w,
343                height: w,
344                depth_or_array_layers: adapter
345                    .limits()
346                    .max_texture_array_layers
347                    .min(crate::atlas::ATLAS_SUGGESTED_LAYERS),
348            },
349            shadow_map_atlas_size: wgpu::Extent3d {
350                width: w,
351                height: w,
352                depth_or_array_layers: 4,
353            },
354            use_compute_culling: false,
355        }));
356        Self {
357            adapter,
358            runtime: WgpuRuntime {
359                device: device.into(),
360                queue: queue.into(),
361            },
362            render_target: target,
363            stage_config,
364        }
365    }
366
367    /// Attempts to create a new headless `Context` with the specified width, height, and backends.
368    pub async fn try_new_headless(
369        width: u32,
370        height: u32,
371        backends: Option<wgpu::Backends>,
372    ) -> Result<Self, ContextError> {
373        log::trace!("creating headless context of size ({width}, {height})");
374        let instance = crate::internal::new_instance(backends);
375        let (adapter, device, queue, target) =
376            crate::internal::new_headless_device_queue_and_target(width, height, &instance).await?;
377        Ok(Self::new(target, adapter, device, queue))
378    }
379
380    /// Attempts to create a new `Context` with a surface, using the specified width, height, backends, and window.
381    pub async fn try_new_with_surface(
382        width: u32,
383        height: u32,
384        backends: Option<wgpu::Backends>,
385        window: impl Into<wgpu::SurfaceTarget<'static>>,
386    ) -> Result<Self, ContextError> {
387        let instance = crate::internal::new_instance(backends);
388        let (adapter, device, queue, target) =
389            crate::internal::new_windowed_adapter_device_queue(width, height, &instance, window)
390                .await?;
391        Ok(Self::new(target, adapter, device, queue))
392    }
393
394    #[cfg(feature = "winit")]
395    /// Create a [`Context`] from an existing [`winit::window::Window`].
396    ///
397    /// ## Panics
398    /// Panics if the context could not be created.
399    pub async fn from_winit_window(
400        backends: Option<wgpu::Backends>,
401        window: Arc<winit::window::Window>,
402    ) -> Self {
403        let inner_size = window.inner_size();
404        Self::try_new_with_surface(inner_size.width, inner_size.height, backends, window)
405            .await
406            .unwrap()
407    }
408
409    /// Creates a new headless renderer.
410    ///
411    /// Immediately proxies to `Context::try_new_headless` and unwraps.
412    ///
413    /// ## Panics
414    /// This function will panic if an adapter cannot be found. For example, this
415    /// would happen on machines without a GPU.
416    pub async fn headless(width: u32, height: u32) -> Self {
417        let result = Self::try_new_headless(width, height, None).await;
418        #[cfg(target_arch = "wasm32")]
419        {
420            use wasm_bindgen::UnwrapThrowExt;
421            result.expect_throw("Could not create context")
422        }
423        #[cfg(not(target_arch = "wasm32"))]
424        {
425            result.expect("Could not create context")
426        }
427    }
428
429    pub fn get_size(&self) -> UVec2 {
430        self.render_target.get_size()
431    }
432
433    /// Sets the size of the render target.
434    pub fn set_size(&mut self, size: UVec2) {
435        self.render_target
436            .resize(size.x, size.y, &self.runtime.device);
437    }
438
439    /// Creates a texture from an image buffer.
440    pub fn create_texture<P>(
441        &self,
442        label: Option<&str>,
443        img: &image::ImageBuffer<P, Vec<u8>>,
444    ) -> Result<Texture, TextureError>
445    where
446        P: image::PixelWithColorType,
447        image::ImageBuffer<P, Vec<u8>>: image::GenericImage + std::ops::Deref<Target = [u8]>,
448    {
449        let name = label.unwrap_or("unknown");
450        Texture::from_image_buffer(
451            self,
452            img,
453            Some(&format!("Renderling::create_texture {}", name)),
454            None,
455            None,
456        )
457    }
458
459    /// Creates a `Texture` from a `wgpu::Texture` and an optional sampler.
460    pub fn texture_from_wgpu_tex(
461        &self,
462        texture: impl Into<Arc<wgpu::Texture>>,
463        sampler: Option<wgpu::SamplerDescriptor>,
464    ) -> Texture {
465        Texture::from_wgpu_tex(self.get_device(), texture, sampler, None)
466    }
467
468    /// Returns a reference to the `WgpuRuntime`.
469    pub fn runtime(&self) -> &WgpuRuntime {
470        &self.runtime
471    }
472
473    /// Returns a reference to the `wgpu::Device`.
474    pub fn get_device(&self) -> &wgpu::Device {
475        &self.runtime.device
476    }
477
478    /// Returns a reference to the `wgpu::Queue`.
479    pub fn get_queue(&self) -> &wgpu::Queue {
480        &self.runtime.queue
481    }
482
483    /// Returns a reference to the `wgpu::Adapter`.
484    pub fn get_adapter(&self) -> &wgpu::Adapter {
485        &self.adapter
486    }
487
488    /// Returns the adapter in an owned wrapper.
489    pub fn get_adapter_owned(&self) -> Arc<wgpu::Adapter> {
490        self.adapter.clone()
491    }
492
493    /// Returns a reference to the `RenderTarget`.
494    pub fn get_render_target(&self) -> &RenderTarget {
495        &self.render_target
496    }
497
498    /// Gets the next frame from the render target.
499    ///
500    /// A surface context (window or canvas) will return the next swapchain
501    /// texture.
502    ///
503    /// A headless context will return the underlying headless texture.
504    ///
505    /// ## Errors
506    /// Errs if the render target is a surface and there was an error getting
507    /// the next swapchain texture. This can happen if the frame has already
508    /// been acquired.
509    pub fn get_next_frame(&self) -> Result<Frame, ContextError> {
510        Ok(Frame {
511            runtime: self.runtime.clone(),
512            surface: match &self.render_target.0 {
513                RenderTargetInner::Surface { surface, .. } => {
514                    let surface_texture = surface.get_current_texture().context(SurfaceSnafu)?;
515                    FrameSurface::Surface(surface_texture)
516                }
517                RenderTargetInner::Texture { texture, .. } => {
518                    FrameSurface::Texture(texture.clone())
519                }
520            },
521        })
522    }
523
524    /// Sets the default texture size for the material atlas.
525    ///
526    /// * Width is `size.x` and must be a power of two.
527    /// * Height is `size.y`, must match `size.x` and must be a power of two.
528    /// * Layers is `size.z` and must be two or greater.
529    pub fn set_default_atlas_texture_size(&self, size: impl Into<UVec3>) -> &Self {
530        let size = size.into();
531        let size = wgpu::Extent3d {
532            width: size.x,
533            height: size.y,
534            depth_or_array_layers: size.z,
535        };
536        crate::atlas::check_size(size);
537        self.stage_config.write().unwrap().atlas_size = size;
538        self
539    }
540
541    /// Sets the default texture size for the material atlas.
542    ///
543    /// * Width is `size.x` and must be a power of two.
544    /// * Height is `size.y`, must match `size.x` and must be a power of two.
545    /// * Layers is `size.z` and must be greater than zero.
546    ///
547    /// ## Panics
548    /// Will panic if the above conditions are not met.
549    pub fn with_default_atlas_texture_size(self, size: impl Into<UVec3>) -> Self {
550        self.set_default_atlas_texture_size(size);
551        self
552    }
553
554    /// Sets the default texture size for the shadow mapping atlas.
555    ///
556    /// * Width is `size.x` and must be a power of two.
557    /// * Height is `size.y`, must match `size.x` and must be a power of two.
558    /// * Layers is `size.z` and must be two or greater.
559    pub fn set_shadow_mapping_atlas_texture_size(&self, size: impl Into<UVec3>) -> &Self {
560        let size = size.into();
561        let size = wgpu::Extent3d {
562            width: size.x,
563            height: size.y,
564            depth_or_array_layers: size.z,
565        };
566        crate::atlas::check_size(size);
567        self.stage_config.write().unwrap().shadow_map_atlas_size = size;
568        self
569    }
570
571    /// Sets the default texture size for the shadow mapping atlas.
572    ///
573    /// * Width is `size.x` and must be a power of two.
574    /// * Height is `size.y`, must match `size.x` and must be a power of two.
575    /// * Layers is `size.z` and must be greater than zero.
576    ///
577    /// ## Panics
578    /// Will panic if the above conditions are not met.
579    pub fn with_shadow_mapping_atlas_texture_size(self, size: impl Into<UVec3>) -> Self {
580        self.set_shadow_mapping_atlas_texture_size(size);
581        self
582    }
583
584    /// Sets the use of direct drawing.
585    ///
586    /// Default is **false**.
587    ///
588    /// If set to **true**, all compute culling, including frustum and occlusion culling,
589    /// will **not** run.
590    pub fn set_use_direct_draw(&self, use_direct_drawing: bool) {
591        self.stage_config.write().unwrap().use_compute_culling = !use_direct_drawing;
592    }
593
594    /// Sets the use of direct drawing.
595    ///
596    /// Default is **false**.
597    ///
598    /// If set to **true**, all compute culling is turned **off**.
599    /// This includes frustum and occlusion culling.
600    pub fn with_use_direct_draw(self, use_direct_drawing: bool) -> Self {
601        self.set_use_direct_draw(use_direct_drawing);
602        self
603    }
604
605    /// Returns whether direct drawing is used.
606    pub fn get_use_direct_draw(&self) -> bool {
607        !self.stage_config.read().unwrap().use_compute_culling
608    }
609
610    /// Creates and returns a new [`Stage`] renderer.
611    pub fn new_stage(&self) -> Stage {
612        Stage::new(self)
613    }
614
615    /// Creates and returns a new [`Ui`] renderer.
616    pub fn new_ui(&self) -> Ui {
617        Ui::new(self)
618    }
619}