pixels/
lib.rs

1//! A tiny library providing a GPU-powered pixel buffer.
2//!
3//! [`Pixels`] represents a 2D pixel buffer with an explicit image resolution, making it ideal for
4//! prototyping simple pixel-based games, animations, and emulators. The pixel buffer is rendered
5//! entirely on the GPU, allowing developers to easily incorporate special effects with shaders and
6//! a customizable pipeline.
7//!
8//! The GPU interface is offered by [`wgpu`](https://crates.io/crates/wgpu), and is re-exported for
9//! your convenience. Use a windowing framework or context manager of your choice;
10//! [`winit`](https://crates.io/crates/winit) is a good place to start. Any windowing framework that
11//! uses [`raw-window-handle`](https://crates.io/crates/raw-window-handle) will work.
12//!
13//! # Environment variables
14//!
15//! Pixels will default to selecting the most powerful GPU and most modern graphics API available on
16//! the system, and these choices can be overridden with environment variables. These are the same
17//! vars supported by the [`wgpu` examples](https://github.com/gfx-rs/wgpu/tree/v0.10/wgpu#usage).
18//!
19//! * `WGPU_BACKEND`: Select the backend (aka graphics API).
20//!     * Supported values: `vulkan`, `metal`, `dx11`, `dx12`, `gl`, `webgpu`
21//!     * The default depends on capabilities of the host system, with `vulkan` being preferred on
22//!       Linux and Windows, and `metal` preferred on macOS.
23//! * `WGPU_ADAPTER_NAME`: Select an adapter (aka GPU) with substring matching.
24//!     * E.g. `1080` will match `NVIDIA GeForce 1080ti`
25//! * `WGPU_POWER_PREF`: Select an adapter (aka GPU) that meets the given power profile.
26//!     * Supported values: `low`, `high`
27//!     * The default is `low`. I.e. an integrated GPU will be preferred over a discrete GPU.
28//!
29//! Note that `WGPU_ADAPTER_NAME` and `WGPU_POWER_PREF` are mutually exclusive and that
30//! `WGPU_ADAPTER_NAME` takes precedence.
31
32#![deny(clippy::all)]
33#![forbid(unsafe_code)]
34
35pub use crate::builder::{check_texture_size, PixelsBuilder};
36pub use crate::renderers::ScalingRenderer;
37pub use raw_window_handle;
38use thiserror::Error;
39pub use wgpu;
40
41mod builder;
42mod renderers;
43
44/// A logical texture for a window surface.
45#[derive(Debug)]
46pub struct SurfaceTexture<W: wgpu::WindowHandle> {
47    window: W,
48    size: SurfaceSize,
49}
50
51/// A logical texture size for a window surface.
52#[derive(Debug)]
53struct SurfaceSize {
54    width: u32,
55    height: u32,
56}
57
58/// Provides the internal state for custom shaders.
59///
60/// A reference to this struct is given to the `render_function` closure when using
61/// [`Pixels::render_with`].
62#[derive(Debug)]
63pub struct PixelsContext<'win> {
64    /// The `Device` allows creating GPU resources.
65    pub device: wgpu::Device,
66
67    /// The `Queue` provides access to the GPU command queue.
68    pub queue: wgpu::Queue,
69
70    surface: wgpu::Surface<'win>,
71
72    /// This is the texture that your raw data is copied to by [`Pixels::render`] or
73    /// [`Pixels::render_with`].
74    pub texture: wgpu::Texture,
75
76    /// Provides access to the texture size.
77    pub texture_extent: wgpu::Extent3d,
78    pub texture_format: wgpu::TextureFormat,
79
80    /// Defines the "data rate" for the raw texture data. This is effectively the "bytes per pixel"
81    /// count.
82    ///
83    /// Compressed textures may have less than one byte per pixel.
84    pub texture_format_size: f32,
85
86    /// A default renderer to scale the input texture to the screen size.
87    pub scaling_renderer: ScalingRenderer,
88}
89
90/// Represents a 2D pixel buffer with an explicit image resolution.
91///
92/// See [`PixelsBuilder`] for building a customized pixel buffer.
93#[derive(Debug)]
94pub struct Pixels<'win> {
95    context: PixelsContext<'win>,
96    surface_size: SurfaceSize,
97    present_mode: wgpu::PresentMode,
98    render_texture_format: wgpu::TextureFormat,
99    surface_texture_format: wgpu::TextureFormat,
100    blend_state: wgpu::BlendState,
101    alpha_mode: wgpu::CompositeAlphaMode,
102    adapter: wgpu::Adapter,
103
104    // Pixel buffer
105    pixels: Vec<u8>,
106
107    // The inverse of the scaling matrix used by the renderer
108    // Used to convert physical coordinates back to pixel coordinates (for the mouse)
109    scaling_matrix_inverse: ultraviolet::Mat4,
110}
111
112/// All the ways in which creating a pixel buffer can fail.
113#[derive(Error, Debug)]
114#[non_exhaustive]
115pub enum Error {
116    /// No suitable [`wgpu::Adapter`] found
117    #[error("No suitable `wgpu::Adapter` found.")]
118    AdapterNotFound,
119    /// Equivalent to [`wgpu::RequestDeviceError`]
120    #[error("No wgpu::Device found.")]
121    DeviceNotFound(#[from] wgpu::RequestDeviceError),
122    /// Equivalent to [`wgpu::SurfaceError`]
123    #[error("The GPU failed to acquire a surface frame.")]
124    Surface(#[from] wgpu::SurfaceError),
125    /// Equivalent to [`wgpu::CreateSurfaceError`]
126    #[error("Unable to create a surface.")]
127    CreateSurface(#[from] wgpu::CreateSurfaceError),
128    /// Equivalent to [`TextureError`]
129    #[error("Texture creation failed: {0}")]
130    InvalidTexture(#[from] TextureError),
131    /// User-defined error from custom render function
132    #[error("User-defined error.")]
133    UserDefined(#[from] DynError),
134}
135
136type DynError = Box<dyn std::error::Error + Send + Sync + 'static>;
137
138/// All the ways in which creating a texture can fail.
139#[derive(Error, Debug)]
140#[non_exhaustive]
141pub enum TextureError {
142    /// Unable to create a backing texture; Width is either 0 or greater than GPU limits
143    #[error("Texture width is invalid: {0}")]
144    TextureWidth(u32),
145    /// Unable to create a backing texture; Height is either 0 or greater than GPU limits
146    #[error("Texture height is invalid: {0}")]
147    TextureHeight(u32),
148}
149
150impl<W: wgpu::WindowHandle> SurfaceTexture<W> {
151    /// Create a logical texture for a window surface.
152    ///
153    /// It is recommended (but not required) that the `width` and `height` are equivalent to the
154    /// physical dimensions of the `surface`. E.g. scaled by the HiDPI factor.
155    ///
156    /// # Examples
157    ///
158    /// ```no_run
159    /// use pixels::SurfaceTexture;
160    /// use winit::event_loop::EventLoop;
161    /// use winit::window::Window;
162    ///
163    /// let event_loop = EventLoop::new().unwrap();
164    /// let window = Window::new(&event_loop).unwrap();
165    /// let size = window.inner_size();
166    ///
167    /// let surface_texture = SurfaceTexture::new(size.width, size.height, &window);
168    /// # Ok::<(), pixels::Error>(())
169    /// ```
170    ///
171    /// # Panics
172    ///
173    /// Panics when `width` or `height` are 0.
174    pub fn new(width: u32, height: u32, window: W) -> Self {
175        assert!(width > 0);
176        assert!(height > 0);
177
178        let size = SurfaceSize { width, height };
179
180        Self { window, size }
181    }
182}
183
184impl<'win> Pixels<'win> {
185    /// Create a pixel buffer instance with default options.
186    ///
187    /// Any ratio differences between the pixel buffer texture size and surface texture size will
188    /// result in a border being added around the pixel buffer texture to maintain an integer
189    /// scaling ratio.
190    ///
191    /// For instance, a pixel buffer with `320x240` can be scaled to a surface texture with sizes
192    /// `320x240`, `640x480`, `960x720`, etc. without adding a border because these are exactly
193    /// 1x, 2x, and 3x scales, respectively.
194    ///
195    /// This method blocks the current thread, making it unusable on Web targets. Use
196    /// [`Pixels::new_async`] for a non-blocking alternative.
197    ///
198    /// # Examples
199    ///
200    /// Pass a borrowed window object to receive a `Pixels` object tied to the corresponding
201    /// lifetime:
202    ///
203    /// ```no_run
204    /// # use pixels::{Pixels, SurfaceTexture};
205    /// # let window = pixels_mocks::Window;
206    /// let surface_texture = SurfaceTexture::new(320, 240, &window);
207    /// let mut pixels: Pixels<'_> = Pixels::new(320, 240, surface_texture)?;
208    /// # Ok::<(), pixels::Error>(())
209    /// ```
210    ///
211    /// Pass an owned window object to receive a static `Pixels` object, not tied to any lifetime.
212    /// This includes objects wrapped in smart pointers like `Arc`, `Rc`, or `Box`:
213    ///
214    /// ```no_run
215    /// # use std::sync::Arc;
216    /// # use pixels::{Pixels, SurfaceTexture};
217    /// # let window = pixels_mocks::Window;
218    /// let arc = Arc::new(window);
219    /// let surface_texture = SurfaceTexture::new(320, 240, arc.clone());
220    /// let mut pixels: Pixels<'static> = Pixels::new(320, 240, surface_texture)?;
221    /// # Ok::<(), pixels::Error>(())
222    /// ```
223    ///
224    /// # Errors
225    ///
226    /// Returns an error when a [`wgpu::Adapter`] cannot be found.
227    ///
228    /// # Panics
229    ///
230    /// Panics when `width` or `height` are 0.
231    #[cfg(not(target_arch = "wasm32"))]
232    pub fn new<W: wgpu::WindowHandle + 'win>(
233        width: u32,
234        height: u32,
235        surface_texture: SurfaceTexture<W>,
236    ) -> Result<Self, Error> {
237        PixelsBuilder::new(width, height, surface_texture).build()
238    }
239
240    /// Asynchronously create a pixel buffer instance with default options.
241    ///
242    /// See [`Pixels::new`] for more information.
243    ///
244    /// # Examples
245    ///
246    /// ```no_run
247    /// # async fn test() -> Result<(), pixels::Error> {
248    /// # use pixels::Pixels;
249    /// # let window = pixels_mocks::Window;
250    /// # let surface_texture = pixels::SurfaceTexture::new(320, 240, &window);
251    /// let mut pixels = Pixels::new_async(320, 240, surface_texture).await?;
252    /// # Ok::<(), pixels::Error>(())
253    /// # }
254    /// ```
255    ///
256    /// # Errors
257    ///
258    /// Returns an error when a [`wgpu::Adapter`] cannot be found.
259    ///
260    /// # Panics
261    ///
262    /// Panics when `width` or `height` are 0.
263    pub async fn new_async<W: wgpu::WindowHandle + 'win>(
264        width: u32,
265        height: u32,
266        surface_texture: SurfaceTexture<W>,
267    ) -> Result<Self, Error> {
268        PixelsBuilder::new(width, height, surface_texture)
269            .build_async()
270            .await
271    }
272
273    /// Change the clear color.
274    ///
275    /// Allows customization of the background color and the border drawn for non-integer scale
276    /// values.
277    ///
278    /// ```no_run
279    /// use pixels::wgpu::Color;
280    ///
281    /// # use pixels::Pixels;
282    /// # let window = pixels_mocks::Window;
283    /// # let surface_texture = pixels::SurfaceTexture::new(320, 240, &window);
284    /// let mut pixels = Pixels::new(320, 240, surface_texture)?;
285    ///
286    /// // Set clear color to red.
287    /// pixels.clear_color(Color::RED);
288    /// # Ok::<(), pixels::Error>(())
289    /// ```
290    pub fn clear_color(&mut self, color: wgpu::Color) {
291        self.context.scaling_renderer.clear_color = color;
292    }
293
294    /// Returns a reference of the `wgpu` adapter used by the crate.
295    ///
296    /// The adapter can be used to retrieve runtime information about the host system
297    /// or the WGPU backend.
298    ///
299    /// ```no_run
300    /// # use pixels::Pixels;
301    /// # let window = pixels_mocks::Window;
302    /// # let surface_texture = pixels::SurfaceTexture::new(320, 240, &window);
303    /// let mut pixels = Pixels::new(320, 240, surface_texture)?;
304    /// let adapter = pixels.adapter();
305    /// // Do something with the adapter.
306    /// # Ok::<(), pixels::Error>(())
307    /// ```
308    pub fn adapter(&self) -> &wgpu::Adapter {
309        &self.adapter
310    }
311
312    /// Resize the pixel buffer and zero its contents.
313    ///
314    /// This does not resize the surface upon which the pixel buffer texture is rendered. Use
315    /// [`Pixels::resize_surface`] to change the size of the surface texture.
316    ///
317    /// The pixel buffer will be fit onto the surface texture as best as possible by scaling to the
318    /// nearest integer, e.g. 2x, 3x, 4x, etc. A border will be added around the pixel buffer
319    /// texture for non-integer scaling ratios.
320    ///
321    /// Call this method to change the virtual screen resolution. E.g. when you want your pixel
322    /// buffer to be resized from `640x480` to `800x600`.
323    ///
324    /// # Errors
325    ///
326    /// - [`TextureError::TextureWidth`] when `width` is 0 or greater than GPU texture limits.
327    /// - [`TextureError::TextureHeight`] when `height` is 0 or greater than GPU texture limits.
328    pub fn resize_buffer(&mut self, width: u32, height: u32) -> Result<(), TextureError> {
329        // Recreate the backing texture
330        let (scaling_matrix_inverse, texture_extent, texture, scaling_renderer, pixels_buffer_size) =
331            builder::create_backing_texture(
332                &self.context.device,
333                // Backing texture values
334                width,
335                height,
336                self.context.texture_format,
337                // Render texture values
338                &self.surface_size,
339                self.render_texture_format,
340                self.context.scaling_renderer.clear_color,
341                self.blend_state,
342            )?;
343
344        self.scaling_matrix_inverse = scaling_matrix_inverse;
345        self.context.texture_extent = texture_extent;
346        self.context.texture = texture;
347        self.context.scaling_renderer = scaling_renderer;
348
349        // Resize the pixel buffer
350        self.pixels
351            .resize_with(pixels_buffer_size, Default::default);
352
353        Ok(())
354    }
355
356    /// Resize the surface upon which the pixel buffer texture is rendered.
357    ///
358    /// This does not resize the pixel buffer. Use [`Pixels::resize_buffer`] to change the size of
359    /// the pixel buffer.
360    ///
361    /// The pixel buffer texture will be fit onto the surface texture as best as possible by scaling
362    /// to the nearest integer, e.g. 2x, 3x, 4x, etc. A border will be added around the pixel buffer
363    /// texture for non-integer scaling ratios.
364    ///
365    /// Call this method in response to a resize event from your window manager. The size expected
366    /// is in physical pixel units. Does nothing when `width` or `height` are 0.
367    ///
368    /// # Errors
369    ///
370    /// - [`TextureError::TextureWidth`] when `width` is 0 or greater than GPU texture limits.
371    /// - [`TextureError::TextureHeight`] when `height` is 0 or greater than GPU texture limits.
372    pub fn resize_surface(&mut self, width: u32, height: u32) -> Result<(), TextureError> {
373        check_texture_size(&self.context.device, width, height)?;
374
375        // Update SurfaceTexture dimensions
376        self.surface_size.width = width;
377        self.surface_size.height = height;
378
379        // Update ScalingMatrix for mouse transformation
380        self.scaling_matrix_inverse = renderers::ScalingMatrix::new(
381            (
382                self.context.texture_extent.width as f32,
383                self.context.texture_extent.height as f32,
384            ),
385            (width as f32, height as f32),
386        )
387        .transform
388        .inversed();
389
390        // Reconfigure the surface
391        self.reconfigure_surface();
392
393        // Update state for all render passes
394        self.context
395            .scaling_renderer
396            .resize(&self.context.queue, width, height);
397
398        Ok(())
399    }
400
401    /// Enable or disable Vsync.
402    ///
403    /// Vsync is enabled by default. It cannot be disabled on Web targets.
404    ///
405    /// The `wgpu` present mode will be set to `AutoVsync` when Vsync is enabled, or `AutoNoVsync`
406    /// when Vsync is disabled. To set the present mode to `Mailbox` or another value, use the
407    /// [`Pixels::set_present_mode`] method.
408    pub fn enable_vsync(&mut self, enable_vsync: bool) {
409        self.present_mode = if enable_vsync {
410            wgpu::PresentMode::AutoVsync
411        } else {
412            wgpu::PresentMode::AutoNoVsync
413        };
414        self.reconfigure_surface();
415    }
416
417    /// Get the `wgpu` present mode.
418    ///
419    /// Returns the present mode currently in use by the surface, which can be changed through
420    /// [`Pixels::enable_vsync`] or [`Pixels::set_present_mode`].
421    pub fn present_mode(&self) -> wgpu::PresentMode {
422        self.present_mode
423    }
424
425    /// Set the `wgpu` present mode.
426    ///
427    /// This differs from [`Pixels::enable_vsync`] by allowing the present mode to be set to
428    /// any value.
429    pub fn set_present_mode(&mut self, present_mode: wgpu::PresentMode) {
430        self.present_mode = present_mode;
431        self.reconfigure_surface();
432    }
433
434    /// Draw this pixel buffer to the configured [`SurfaceTexture`].
435    ///
436    /// # Errors
437    ///
438    /// Returns an error when [`wgpu::Surface::get_current_texture`] fails.
439    ///
440    /// # Example
441    ///
442    /// ```no_run
443    /// # use pixels::Pixels;
444    /// # let window = pixels_mocks::Window;
445    /// # let surface_texture = pixels::SurfaceTexture::new(320, 240, &window);
446    /// let mut pixels = Pixels::new(320, 240, surface_texture)?;
447    ///
448    /// // Clear the pixel buffer
449    /// let frame = pixels.frame_mut();
450    /// for pixel in frame.chunks_exact_mut(4) {
451    ///     pixel[0] = 0x00; // R
452    ///     pixel[1] = 0x00; // G
453    ///     pixel[2] = 0x00; // B
454    ///     pixel[3] = 0xff; // A
455    /// }
456    ///
457    /// // Draw it to the `SurfaceTexture`
458    /// pixels.render()?;
459    /// # Ok::<(), pixels::Error>(())
460    /// ```
461    pub fn render(&self) -> Result<(), Error> {
462        self.render_with(|encoder, render_target, context| {
463            context.scaling_renderer.render(encoder, render_target);
464
465            Ok(())
466        })
467    }
468
469    /// Draw this pixel buffer to the configured [`SurfaceTexture`] using a custom user-provided
470    /// render function.
471    ///
472    /// Provides access to a [`wgpu::CommandEncoder`], a [`wgpu::TextureView`] from the surface
473    /// which you can use to render to the screen, and a [`PixelsContext`] with all of the internal
474    /// `wgpu` context.
475    ///
476    /// The render function must return a `Result`. This allows fallible render functions to be
477    /// handled gracefully. The boxed `Error` will be made available in the [`Error::UserDefined`]
478    /// variant returned by `render_with()`.
479    ///
480    /// # Errors
481    ///
482    /// Returns an error when either [`wgpu::Surface::get_current_texture`] or the provided render
483    /// function fails.
484    ///
485    /// # Example
486    ///
487    /// ```no_run
488    /// # use pixels::Pixels;
489    /// # let window = pixels_mocks::Window;
490    /// # let surface_texture = pixels::SurfaceTexture::new(320, 240, &window);
491    /// let mut pixels = Pixels::new(320, 240, surface_texture)?;
492    ///
493    /// // Clear the pixel buffer
494    /// let frame = pixels.frame_mut();
495    /// for pixel in frame.chunks_exact_mut(4) {
496    ///     pixel[0] = 0x00; // R
497    ///     pixel[1] = 0x00; // G
498    ///     pixel[2] = 0x00; // B
499    ///     pixel[3] = 0xff; // A
500    /// }
501    ///
502    /// // Draw it to the `SurfaceTexture`
503    /// pixels.render_with(|encoder, render_target, context| {
504    ///     context.scaling_renderer.render(encoder, render_target);
505    ///     // etc...
506    ///     Ok(())
507    /// })?;
508    /// # Ok::<(), pixels::Error>(())
509    /// ```
510    pub fn render_with<F>(&self, render_function: F) -> Result<(), Error>
511    where
512        F: FnOnce(
513            &mut wgpu::CommandEncoder,
514            &wgpu::TextureView,
515            &PixelsContext,
516        ) -> Result<(), DynError>,
517    {
518        let frame = self.context.surface.get_current_texture().or_else(|_| {
519            // Reconfigure the surface and retry immediately on any error.
520            // See https://github.com/parasyte/pixels/issues/121
521            // See https://github.com/parasyte/pixels/issues/346
522            self.reconfigure_surface();
523            self.context.surface.get_current_texture()
524        })?;
525        let mut encoder =
526            self.context
527                .device
528                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
529                    label: Some("pixels_command_encoder"),
530                });
531
532        // Update the pixel buffer texture view
533        let bytes_per_row =
534            (self.context.texture_extent.width as f32 * self.context.texture_format_size) as u32;
535        self.context.queue.write_texture(
536            wgpu::ImageCopyTexture {
537                texture: &self.context.texture,
538                mip_level: 0,
539                origin: wgpu::Origin3d { x: 0, y: 0, z: 0 },
540                aspect: wgpu::TextureAspect::All,
541            },
542            &self.pixels,
543            wgpu::ImageDataLayout {
544                offset: 0,
545                bytes_per_row: Some(bytes_per_row),
546                rows_per_image: Some(self.context.texture_extent.height),
547            },
548            self.context.texture_extent,
549        );
550
551        let view = frame
552            .texture
553            .create_view(&wgpu::TextureViewDescriptor::default());
554
555        // Call the user's render function.
556        (render_function)(&mut encoder, &view, &self.context)?;
557
558        self.context.queue.submit(Some(encoder.finish()));
559        frame.present();
560        Ok(())
561    }
562
563    /// Reconfigure the surface.
564    ///
565    /// Call this when the surface or presentation mode needs to be changed.
566    pub(crate) fn reconfigure_surface(&self) {
567        self.context.surface.configure(
568            &self.context.device,
569            &wgpu::SurfaceConfiguration {
570                usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
571                format: self.surface_texture_format,
572                width: self.surface_size.width,
573                height: self.surface_size.height,
574                present_mode: self.present_mode,
575                desired_maximum_frame_latency: 2,
576                alpha_mode: self.alpha_mode,
577                view_formats: vec![],
578            },
579        );
580    }
581
582    /// Get a mutable byte slice for the pixel buffer. The buffer is _not_ cleared for you; it will
583    /// retain the previous frame's contents until you clear it yourself.
584    pub fn frame_mut(&mut self) -> &mut [u8] {
585        &mut self.pixels
586    }
587
588    /// Get an immutable byte slice for the pixel buffer.
589    ///
590    /// This may be useful for operations that must sample the buffer, such as blending pixel
591    /// colours directly into it.
592    pub fn frame(&self) -> &[u8] {
593        &self.pixels
594    }
595
596    /// Calculate the pixel location from a physical location on the window,
597    /// dealing with window resizing, scaling, and margins. Takes a physical
598    /// position (x, y) within the window, and returns a pixel position (x, y).
599    ///
600    /// The location must be given in physical units (for example, winit's `PhysicalLocation`)
601    ///
602    /// If the given physical position is outside of the drawing area, this
603    /// function returns an `Err` value with the pixel coordinates outside of
604    /// the screen, using isize instead of usize.
605    ///
606    /// ```no_run
607    /// use winit::dpi::PhysicalPosition;
608    ///
609    /// # use pixels::Pixels;
610    /// # let window = pixels_mocks::Window;
611    /// # let surface_texture = pixels::SurfaceTexture::new(320, 240, &window);
612    /// let mut pixels = Pixels::new(320, 240, surface_texture)?;
613    ///
614    /// // A cursor position in physical units
615    /// let cursor_position: (f32, f32) = PhysicalPosition::new(0.0, 0.0).into();
616    ///
617    /// // Convert it to a pixel location
618    /// let pixel_position: (usize, usize) = pixels.window_pos_to_pixel(cursor_position)
619    ///     // Clamp the output to within the screen
620    ///     .unwrap_or_else(|pos| pixels.clamp_pixel_pos(pos));
621    /// # Ok::<(), pixels::Error>(())
622    /// ```
623    pub fn window_pos_to_pixel(
624        &self,
625        physical_position: (f32, f32),
626    ) -> Result<(usize, usize), (isize, isize)> {
627        let physical_width = self.surface_size.width as f32;
628        let physical_height = self.surface_size.height as f32;
629
630        let pixels_width = self.context.texture_extent.width as f32;
631        let pixels_height = self.context.texture_extent.height as f32;
632
633        let pos = ultraviolet::Vec4::new(
634            (physical_position.0 / physical_width - 0.5) * pixels_width,
635            (physical_position.1 / physical_height - 0.5) * pixels_height,
636            0.0,
637            1.0,
638        );
639
640        let pos = self.scaling_matrix_inverse * pos;
641        let offset_width = pixels_width.min(physical_width) / 2.0;
642        let offset_height = pixels_height.min(physical_height) / 2.0;
643
644        let pixel_x = (pos.x / pos.w + offset_width).floor() as isize;
645        let pixel_y = (pos.y / pos.w + offset_height).floor() as isize;
646
647        if pixel_x < 0
648            || pixel_x >= self.context.texture_extent.width as isize
649            || pixel_y < 0
650            || pixel_y >= self.context.texture_extent.height as isize
651        {
652            Err((pixel_x, pixel_y))
653        } else {
654            Ok((pixel_x as usize, pixel_y as usize))
655        }
656    }
657
658    /// Clamp a pixel position to the pixel buffer texture size.
659    ///
660    /// This can be used to clamp the `Err` value returned by [`Pixels::window_pos_to_pixel`]
661    /// to a position clamped within the drawing area.
662    ///
663    /// ```no_run
664    /// # use pixels::Pixels;
665    /// # let window = pixels_mocks::Window;
666    /// # let surface_texture = pixels::SurfaceTexture::new(320, 240, &window);
667    /// let mut pixels = Pixels::new(320, 240, surface_texture)?;
668    ///
669    /// let pixel_pos = pixels.clamp_pixel_pos((-19, 20));
670    /// assert_eq!(pixel_pos, (0, 20));
671    ///
672    /// let pixel_pos = pixels.clamp_pixel_pos((11, 3000));
673    /// assert_eq!(pixel_pos, (11, 239));
674    /// # Ok::<(), pixels::Error>(())
675    /// ```
676    pub fn clamp_pixel_pos(&self, pos: (isize, isize)) -> (usize, usize) {
677        (
678            pos.0
679                .clamp(0, self.context.texture_extent.width as isize - 1) as usize,
680            pos.1
681                .clamp(0, self.context.texture_extent.height as isize - 1) as usize,
682        )
683    }
684
685    /// Provides access to the internal [`wgpu::Device`].
686    pub fn device(&self) -> &wgpu::Device {
687        &self.context.device
688    }
689
690    /// Provides access to the internal [`wgpu::Queue`].
691    pub fn queue(&self) -> &wgpu::Queue {
692        &self.context.queue
693    }
694
695    /// Provides access to the internal source [`wgpu::Texture`].
696    ///
697    /// This is the pre-scaled texture copied from the pixel buffer.
698    pub fn texture(&self) -> &wgpu::Texture {
699        &self.context.texture
700    }
701
702    /// Provides access to the internal [`PixelsContext`].
703    pub fn context(&self) -> &PixelsContext {
704        &self.context
705    }
706
707    /// Get the surface texture format.
708    ///
709    /// This texture format may be chosen automatically by the surface. See
710    /// [`PixelsBuilder::surface_texture_format`] for more information.
711    pub fn surface_texture_format(&self) -> wgpu::TextureFormat {
712        self.surface_texture_format
713    }
714
715    /// Get the render texture format.
716    ///
717    ///
718    /// See [`PixelsBuilder::render_texture_format`] for more information.
719    pub fn render_texture_format(&self) -> wgpu::TextureFormat {
720        self.render_texture_format
721    }
722}