render: Take in dirty region in update_texture, only upload those pixels
This commit is contained in:
parent
137593b6a6
commit
6e859891af
|
@ -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,20 +850,27 @@ 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 color = self.get_pixel32_raw(x, y).to_un_multiplied_alpha();
|
||||
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);
|
||||
let color = color_transform * swf::Color::from(color);
|
||||
|
||||
self.set_pixel32_raw(
|
||||
x,
|
||||
y,
|
||||
Color::from(color).to_premultiplied_alpha(self.transparency()),
|
||||
)
|
||||
self.set_pixel32_raw(
|
||||
x,
|
||||
y,
|
||||
Color::from(color).to_premultiplied_alpha(self.transparency()),
|
||||
)
|
||||
}
|
||||
}
|
||||
self.set_cpu_dirty(PixelRegion::encompassing_pixels(
|
||||
(x_min, y_min),
|
||||
(x_max - 1, y_max - 1),
|
||||
));
|
||||
}
|
||||
self.set_cpu_dirty(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)]
|
||||
|
|
|
@ -468,13 +468,17 @@ 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))
|
||||
.map_err(Error::JavascriptError)?;
|
||||
data.update_pixels(Bitmap::new(
|
||||
data.bitmap.width(),
|
||||
data.bitmap.height(),
|
||||
BitmapFormat::Rgba,
|
||||
rgba,
|
||||
))
|
||||
.map_err(Error::JavascriptError)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue