From c5c48d6682183d7a5aa90ab7148916c7925c8bbc Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Thu, 25 Jan 2024 15:34:10 -0800 Subject: [PATCH] Optimization: internally track buffer mutations Adds an internal bool that tracks buffer mutations (with high probability). The pixel buffer is only written to the texture view when the flag has been set, and the flag is always cleared immediately before the upload begins. Exchanges a potentially expensive memcpy with a comparatively free branch. See: https://github.com/parasyte/pixels/discussions/387 --- src/builder.rs | 2 ++ src/lib.rs | 44 +++++++++++++++++++++++++++----------------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 2f1018d5..a89280f5 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,6 +1,7 @@ use crate::renderers::{ScalingMatrix, ScalingRenderer}; use crate::{Error, Pixels, PixelsContext, SurfaceSize, SurfaceTexture, TextureError}; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use std::cell::Cell; /// A builder to help create customized pixel buffers. pub struct PixelsBuilder<'req, 'dev, 'win, W: HasRawWindowHandle + HasRawDisplayHandle> { @@ -342,6 +343,7 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle + HasRawDisplayHandle> surface_texture_format, blend_state, pixels, + dirty: Cell::new(false), scaling_matrix_inverse, alpha_mode, }; diff --git a/src/lib.rs b/src/lib.rs index 538ae9e7..a3718944 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,7 @@ pub use crate::builder::{check_texture_size, PixelsBuilder}; pub use crate::renderers::ScalingRenderer; pub use raw_window_handle; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use std::cell::Cell; use thiserror::Error; pub use wgpu; @@ -103,6 +104,7 @@ pub struct Pixels { // Pixel buffer pixels: Vec, + dirty: Cell, // The inverse of the scaling matrix used by the renderer // Used to convert physical coordinates back to pixel coordinates (for the mouse) @@ -333,6 +335,7 @@ impl Pixels { // Resize the pixel buffer self.pixels .resize_with(pixels_buffer_size, Default::default); + self.dirty.set(true); Ok(()) } @@ -514,23 +517,27 @@ impl Pixels { }); // Update the pixel buffer texture view - let bytes_per_row = - (self.context.texture_extent.width as f32 * self.context.texture_format_size) as u32; - self.context.queue.write_texture( - wgpu::ImageCopyTexture { - texture: &self.context.texture, - mip_level: 0, - origin: wgpu::Origin3d { x: 0, y: 0, z: 0 }, - aspect: wgpu::TextureAspect::All, - }, - &self.pixels, - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(bytes_per_row), - rows_per_image: Some(self.context.texture_extent.height), - }, - self.context.texture_extent, - ); + if self.dirty.get() { + self.dirty.set(false); + + let bytes_per_row = (self.context.texture_extent.width as f32 + * self.context.texture_format_size) as u32; + self.context.queue.write_texture( + wgpu::ImageCopyTexture { + texture: &self.context.texture, + mip_level: 0, + origin: wgpu::Origin3d { x: 0, y: 0, z: 0 }, + aspect: wgpu::TextureAspect::All, + }, + &self.pixels, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(bytes_per_row), + rows_per_image: Some(self.context.texture_extent.height), + }, + self.context.texture_extent, + ); + } let view = frame .texture @@ -565,6 +572,9 @@ impl Pixels { /// Get a mutable byte slice for the pixel buffer. The buffer is _not_ cleared for you; it will /// retain the previous frame's contents until you clear it yourself. pub fn frame_mut(&mut self) -> &mut [u8] { + // Optimistically assume the caller will change the buffer when acquiring mutable access. + self.dirty.set(true); + &mut self.pixels }