Skip to main content

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