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}