render: Take in dirty region in update_texture, only upload those pixels

This commit is contained in:
Nathan Adams 2023-03-22 16:46:52 +01:00
parent 137593b6a6
commit 6e859891af
8 changed files with 150 additions and 70 deletions

View File

@ -234,7 +234,7 @@ enum DirtyState {
#[default]
Clean,
// The CPU pixels have been modified, and need to be synced to the GPU via `update_dirty_texture`
CpuModified,
CpuModified(PixelRegion),
// The GPU pixels have been modified, and need to be synced to the CPU via `BitmapDataWrapper::sync`
GpuModified(Box<dyn SyncHandle>, PixelRegion),
}
@ -338,7 +338,7 @@ mod wrapper {
write.dirty_state = DirtyState::Clean;
Some(rect)
}
DirtyState::CpuModified => {
DirtyState::CpuModified(_) => {
write.update_dirty_texture(context.renderer);
None
}
@ -451,7 +451,7 @@ impl<'gc> BitmapData<'gc> {
Color(fill_color).to_premultiplied_alpha(self.transparency());
width as usize * height as usize
];
self.set_cpu_dirty(true);
self.set_cpu_dirty(PixelRegion::for_whole_size(width, height));
}
pub fn check_valid(
@ -506,14 +506,12 @@ impl<'gc> BitmapData<'gc> {
self.transparency
}
pub fn set_cpu_dirty(&mut self, dirty: bool) {
let new_state = if dirty {
DirtyState::CpuModified
} else {
DirtyState::Clean
};
match self.dirty_state {
DirtyState::CpuModified | DirtyState::Clean => self.dirty_state = new_state,
pub fn set_cpu_dirty(&mut self, region: PixelRegion) {
debug_assert!(region.max_x <= self.width);
debug_assert!(region.max_y <= self.height);
match &mut self.dirty_state {
DirtyState::CpuModified(old_region) => old_region.union(region),
DirtyState::Clean => self.dirty_state = DirtyState::CpuModified(region),
DirtyState::GpuModified(_, _) => {
panic!("Attempted to modify CPU dirty state while GPU sync is in progress!")
}
@ -529,7 +527,7 @@ impl<'gc> BitmapData<'gc> {
self.height = height;
self.transparency = transparency;
self.pixels = pixels;
self.set_cpu_dirty(true);
self.set_cpu_dirty(PixelRegion::for_whole_size(width, height));
}
pub fn pixels_rgba(&self) -> Vec<u8> {
@ -633,7 +631,7 @@ impl<'gc> BitmapData<'gc> {
} else {
self.set_pixel32_raw(x, y, color.with_alpha(0xFF));
}
self.set_cpu_dirty(true);
self.set_cpu_dirty(PixelRegion::for_whole_size(x, y));
}
}
@ -650,7 +648,7 @@ impl<'gc> BitmapData<'gc> {
pub fn set_pixel32(&mut self, x: u32, y: u32, color: Color) {
if x < self.width && y < self.height {
self.set_pixel32_raw(x, y, color.to_premultiplied_alpha(self.transparency()));
self.set_cpu_dirty(true);
self.set_cpu_dirty(PixelRegion::for_pixel(x, y));
}
}
@ -664,7 +662,7 @@ impl<'gc> BitmapData<'gc> {
self.set_pixel32_raw(x, y, color);
}
}
self.set_cpu_dirty(true);
self.set_cpu_dirty(PixelRegion::encompassing_pixels((x0, y0), (x1 - 1, y1 - 1)));
}
pub fn flood_fill(&mut self, x: u32, y: u32, replace_color: Color) {
@ -674,6 +672,7 @@ impl<'gc> BitmapData<'gc> {
let expected_color = self.get_pixel32_raw(x, y);
let mut pending = vec![(x, y)];
let mut dirty_region = PixelRegion::for_pixel(x, y);
while !pending.is_empty() {
if let Some((x, y)) = pending.pop() {
@ -692,10 +691,11 @@ impl<'gc> BitmapData<'gc> {
pending.push((x, y + 1));
}
self.set_pixel32_raw(x, y, replace_color);
dirty_region.encompass(x, y);
}
}
}
self.set_cpu_dirty(true);
self.set_cpu_dirty(dirty_region);
}
pub fn noise(
@ -756,7 +756,7 @@ impl<'gc> BitmapData<'gc> {
self.set_pixel32_raw(x, y, pixel_color);
}
}
self.set_cpu_dirty(true);
self.set_cpu_dirty(PixelRegion::for_whole_size(self.width, self.height));
}
pub fn copy_channel(
@ -818,8 +818,18 @@ impl<'gc> BitmapData<'gc> {
}
}
}
self.set_cpu_dirty(true);
let mut dirty_region = PixelRegion::encompassing_pixels(
(
(src_min_x.saturating_add(min_x)),
(src_min_y.saturating_add(min_y)),
),
(
(src_max_x.saturating_add(min_x)),
(src_max_y.saturating_add(min_y)),
),
);
dirty_region.clamp(self.width, self.height);
self.set_cpu_dirty(dirty_region);
}
pub fn color_transform(
@ -840,8 +850,11 @@ impl<'gc> BitmapData<'gc> {
|| color_transform.b_add != 0
|| color_transform.a_add != 0
{
for x in x_min..x_max.min(self.width()) {
for y in y_min..y_max.min(self.height()) {
let x_max = x_max.min(self.width());
let y_max = y_max.min(self.height());
if x_max > 0 && y_max > 0 {
for x in x_min..x_max {
for y in y_min..y_max {
let color = self.get_pixel32_raw(x, y).to_un_multiplied_alpha();
let color = color_transform * swf::Color::from(color);
@ -853,7 +866,11 @@ impl<'gc> BitmapData<'gc> {
)
}
}
self.set_cpu_dirty(true);
self.set_cpu_dirty(PixelRegion::encompassing_pixels(
(x_min, y_min),
(x_max - 1, y_max - 1),
));
}
}
}
@ -986,7 +1003,12 @@ impl<'gc> BitmapData<'gc> {
self.set_pixel32_raw(dest_x as u32, dest_y as u32, dest_color);
}
}
self.set_cpu_dirty(true);
let mut dirty_region = PixelRegion::encompassing_pixels_i32(
((dest_min_x), (dest_min_y)),
((dest_min_x + src_width), (dest_min_y + src_height)),
);
dirty_region.clamp(self.width, self.height);
self.set_cpu_dirty(dirty_region);
}
pub fn merge(
@ -1045,7 +1067,13 @@ impl<'gc> BitmapData<'gc> {
);
}
}
self.set_cpu_dirty(true);
let mut dirty_region = PixelRegion::encompassing_pixels_i32(
((dest_min_x), (dest_min_y)),
((dest_min_x + src_width), (dest_min_y + src_height)),
);
dirty_region.clamp(self.width, self.height);
self.set_cpu_dirty(dirty_region);
}
// Unlike `copy_channel` and `copy_pixels`, this function seems to
@ -1092,7 +1120,12 @@ impl<'gc> BitmapData<'gc> {
self.set_pixel32_raw(dest_x as u32, dest_y as u32, mix_color);
}
}
self.set_cpu_dirty(true);
let mut dirty_region = PixelRegion::encompassing_pixels_i32(
((dest_min_x), (dest_min_y)),
((dest_min_x + src_width), (dest_min_y + src_height)),
);
dirty_region.clamp(self.width, self.height);
self.set_cpu_dirty(dirty_region);
}
#[allow(clippy::too_many_arguments)]
@ -1199,7 +1232,7 @@ impl<'gc> BitmapData<'gc> {
self.set_pixel32_raw(x, y, Color::argb(color[3], color[0], color[1], color[2]));
}
}
self.set_cpu_dirty(true);
self.set_cpu_dirty(PixelRegion::for_whole_size(self.width, self.height));
}
pub fn scroll(&mut self, x: i32, y: i32) {
@ -1243,7 +1276,7 @@ impl<'gc> BitmapData<'gc> {
src_y += dy;
}
self.set_cpu_dirty(true);
self.set_cpu_dirty(PixelRegion::for_whole_size(self.width, self.height));
}
/// This implements the threshold operation generically over the test operation performed for each pixel
@ -1270,6 +1303,7 @@ impl<'gc> BitmapData<'gc> {
// The number of modified pixels
// This doesn't seem to include pixels changed due to copy_source
let mut modified_count = 0;
let mut dirty_area: Option<PixelRegion> = None;
// Check each pixel
for src_y in src_min_y..(src_min_y + src_height) {
@ -1302,9 +1336,16 @@ impl<'gc> BitmapData<'gc> {
self.set_pixel32_raw(dest_x as u32, dest_y as u32, new_color);
}
}
if let Some(dirty_area) = &mut dirty_area {
dirty_area.encompass(dest_x as u32, dest_y as u32);
} else {
dirty_area = Some(PixelRegion::for_pixel(dest_x as u32, dest_y as u32));
}
}
self.set_cpu_dirty(true);
}
if let Some(dirty_area) = dirty_area {
self.set_cpu_dirty(dirty_area);
}
modified_count
}
@ -1314,16 +1355,11 @@ impl<'gc> BitmapData<'gc> {
pub fn update_dirty_texture(&mut self, renderer: &mut dyn RenderBackend) {
let handle = self.bitmap_handle(renderer).unwrap();
match &self.dirty_state {
DirtyState::CpuModified => {
if let Err(e) = renderer.update_texture(
&handle,
self.width(),
self.height(),
self.pixels_rgba(),
) {
DirtyState::CpuModified(region) => {
if let Err(e) = renderer.update_texture(&handle, self.pixels_rgba(), *region) {
tracing::error!("Failed to update dirty bitmap {:?}: {:?}", handle, e);
}
self.set_cpu_dirty(false);
self.dirty_state = DirtyState::Clean;
}
DirtyState::Clean | DirtyState::GpuModified(_, _) => {}
}
@ -1417,7 +1453,7 @@ impl<'gc> BitmapData<'gc> {
PixelRegion::for_whole_size(self.width, self.height),
)
}
DirtyState::CpuModified | DirtyState::GpuModified(_, _) => panic!(
DirtyState::CpuModified(_) | DirtyState::GpuModified(_, _) => panic!(
"Called BitmapData.render while already dirty: {:?}",
self.dirty_state
),
@ -1608,7 +1644,7 @@ impl<'gc> BitmapData<'gc> {
DirtyState::Clean => {
self.dirty_state = DirtyState::GpuModified(sync_handle, dirty_region)
}
DirtyState::CpuModified | DirtyState::GpuModified(_, _) => panic!(
DirtyState::CpuModified(_) | DirtyState::GpuModified(_, _) => panic!(
"Called BitmapData.render while already dirty: {:?}",
self.dirty_state
),
@ -1672,7 +1708,7 @@ fn copy_pixels_to_bitmapdata(
write.set_pixel32_raw(x, y, nc);
}
}
write.set_cpu_dirty(true);
write.set_cpu_dirty(area);
}
#[derive(Copy, Clone, Debug)]

View File

@ -468,12 +468,16 @@ impl RenderBackend for WebCanvasRenderBackend {
fn update_texture(
&mut self,
handle: &BitmapHandle,
width: u32,
height: u32,
rgba: Vec<u8>,
_region: PixelRegion,
) -> Result<(), Error> {
let data = as_bitmap_data(handle);
data.update_pixels(Bitmap::new(width, height, BitmapFormat::Rgba, rgba))
data.update_pixels(Bitmap::new(
data.bitmap.width(),
data.bitmap.height(),
BitmapFormat::Rgba,
rgba,
))
.map_err(Error::JavascriptError)?;
Ok(())
}

View File

@ -59,9 +59,8 @@ pub trait RenderBackend: Downcast {
fn update_texture(
&mut self,
bitmap: &BitmapHandle,
width: u32,
height: u32,
rgba: Vec<u8>,
region: PixelRegion,
) -> Result<(), Error>;
fn create_context3d(&mut self) -> Result<Box<dyn Context3D>, Error>;

View File

@ -76,9 +76,8 @@ impl RenderBackend for NullRenderer {
fn update_texture(
&mut self,
_bitmap: &BitmapHandle,
_width: u32,
_height: u32,
_rgba: Vec<u8>,
_region: PixelRegion,
) -> Result<(), Error> {
Ok(())
}

View File

@ -189,6 +189,29 @@ impl PixelRegion {
}
}
pub fn encompassing_pixels_i32(a: (i32, i32), b: (i32, i32)) -> Self {
Self::encompassing_pixels(
(a.0.max(0) as u32, a.1.max(0) as u32),
(b.0.max(0) as u32, b.1.max(0) as u32),
)
}
pub fn encompassing_pixels(a: (u32, u32), b: (u32, u32)) -> Self {
// Figure out what our two ranges are
let (min, max) = ((a.0.min(b.0), a.1.min(b.1)), (a.0.max(b.0), a.1.max(b.1)));
// Increase max by one pixel as we've calculated the *encompassed* max
let max = (max.0.saturating_add(1), max.1.saturating_add(1));
// Make sure we're never going below 0
Self {
min_x: min.0.max(0),
min_y: min.1.max(0),
max_x: max.0.max(0),
max_y: max.1.max(0),
}
}
pub fn for_whole_size(width: u32, height: u32) -> Self {
Self {
min_x: 0,
@ -198,6 +221,15 @@ impl PixelRegion {
}
}
pub fn for_pixel(x: u32, y: u32) -> Self {
Self {
min_x: x,
min_y: y,
max_x: x + 1,
max_y: y + 1,
}
}
pub fn clamp(&mut self, width: u32, height: u32) {
self.min_x = self.min_x.min(width);
self.min_y = self.min_y.min(height);
@ -212,6 +244,13 @@ impl PixelRegion {
self.max_y = self.max_y.max(other.max_y);
}
pub fn encompass(&mut self, x: u32, y: u32) {
self.min_x = self.min_x.min(x);
self.min_y = self.min_y.min(y);
self.max_x = self.max_x.max(x + 1);
self.max_y = self.max_y.max(y + 1);
}
pub fn width(&self) -> u32 {
self.max_x - self.min_x
}

View File

@ -1034,11 +1034,11 @@ impl RenderBackend for WebGlRenderBackend {
fn update_texture(
&mut self,
handle: &BitmapHandle,
width: u32,
height: u32,
rgba: Vec<u8>,
_region: PixelRegion,
) -> Result<(), BitmapError> {
let texture = &as_registry_data(handle).texture;
let data = as_registry_data(handle);
let texture = &data.texture;
self.gl.bind_texture(Gl::TEXTURE_2D, Some(texture));
@ -1047,8 +1047,8 @@ impl RenderBackend for WebGlRenderBackend {
Gl::TEXTURE_2D,
0,
Gl::RGBA as i32,
width as i32,
height as i32,
data.bitmap.width() as i32,
data.bitmap.height() as i32,
0,
Gl::RGBA,
Gl::UNSIGNED_BYTE,

View File

@ -546,15 +546,14 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
fn update_texture(
&mut self,
handle: &BitmapHandle,
width: u32,
height: u32,
rgba: Vec<u8>,
region: PixelRegion,
) -> Result<(), BitmapError> {
let texture = as_texture(handle);
let extent = wgpu::Extent3d {
width,
height,
width: region.width(),
height: region.height(),
depth_or_array_layers: 1,
};
@ -562,13 +561,18 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
wgpu::ImageCopyTexture {
texture: &texture.texture,
mip_level: 0,
origin: Default::default(),
origin: wgpu::Origin3d {
x: region.min_x,
y: region.min_y,
z: 0,
},
aspect: wgpu::TextureAspect::All,
},
&rgba,
&rgba[(region.min_y * texture.width * 4) as usize
..(region.max_y * texture.width * 4) as usize],
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: NonZeroU32::new(4 * extent.width),
offset: (region.min_x * 4) as wgpu::BufferAddress,
bytes_per_row: NonZeroU32::new(4 * texture.width),
rows_per_image: None,
},
extent,

View File

@ -1,7 +1,7 @@
use crate::decoder::VideoDecoder;
use generational_arena::Arena;
use ruffle_render::backend::RenderBackend;
use ruffle_render::bitmap::{Bitmap, BitmapFormat, BitmapHandle, BitmapInfo};
use ruffle_render::bitmap::{Bitmap, BitmapFormat, BitmapHandle, BitmapInfo, PixelRegion};
use ruffle_video::backend::VideoBackend;
use ruffle_video::error::Error;
use ruffle_video::frame::{EncodedFrame, FrameDependency};
@ -81,9 +81,8 @@ impl VideoBackend for SoftwareVideoBackend {
let handle = if let Some(bitmap) = stream.bitmap.clone() {
renderer.update_texture(
&bitmap,
frame.width.into(),
frame.height.into(),
frame.rgba,
PixelRegion::for_whole_size(frame.width.into(), frame.height.into()),
)?;
bitmap
} else {