Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 252 additions & 8 deletions core/src/bitmap/bitmap_data.rs

Large diffs are not rendered by default.

116 changes: 95 additions & 21 deletions core/src/bitmap/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1065,14 +1065,93 @@
return;
}

let needs_blend = (source_transparency && !transparency) || merge_alpha;
let different_bitmaps = !source_bitmap.ptr_eq(target);

// Fast path 1: plain replace (no blending) from a GPU-resident source
// onto a different target. Uses a raw texture-to-texture copy so we
// skip the blocking GPU->CPU readback that `copy_on_cpu` would do.
if !needs_blend
&& different_bitmaps
&& !source_bitmap.can_read(source_region)
&& context.renderer.is_offscreen_supported()
{
// `bitmap_handle` flushes each side's pending batch, so our raw
// copy observes the right source pixels and isn't clobbered by a
// later `render_offscreen` into the target.
let source_handle = source_bitmap.bitmap_handle(context.gc(), context.renderer);
let target_handle = target.bitmap_handle(context.gc(), context.renderer);
if let Some(sync_handle) = context.renderer.copy_pixels_to_texture(
source_handle,
source_region,
target_handle,
(dest_region.x_min, dest_region.y_min),
) {
let (target_ref, old_dirty) = target.overwrite_cpu_pixels_from_gpu(context.gc());
let mut write = target_ref.borrow_mut(context.gc());
let mut dirty = dest_region;
if let Some(old) = old_dirty {
dirty.union(old);

Check warning on line 1094 in core/src/bitmap/operations.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (1094)
}
write.set_gpu_dirty(context.gc(), sync_handle, dirty);
return;
}

Check warning on line 1098 in core/src/bitmap/operations.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (1098)
}

// Fast path 2: when the original src_rect covers the full source and
// the target's current pixels don't need to come back to the CPU, we
// can queue a `render_bitmap` into the pending GPU batch instead of
// reading back both sides and blending on the CPU. Using the original
// dest_point (before clamp) lets the GPU naturally clip pixels that
// fall outside the target texture.
let original_covers_full_source = src_min_x <= 0
&& src_min_y <= 0
&& src_width >= source_bitmap.width() as i32
&& src_height >= source_bitmap.height() as i32;
if original_covers_full_source
&& different_bitmaps
&& !target.can_read(dest_region)
&& context.renderer.is_offscreen_supported()

Check warning on line 1114 in core/src/bitmap/operations.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (1114)
{
let source_handle = source_bitmap.bitmap_handle(context.gc(), context.renderer);
let transform = Transform {
matrix: Matrix {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
tx: Twips::from_pixels(dest_min_x as f64),
ty: Twips::from_pixels(dest_min_y as f64),
},
color_transform: ColorTransform::default(),
perspective_projection: None,
};

Check warning on line 1128 in core/src/bitmap/operations.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (1116–1128)

// copyPixels is a plain blit - MSAA on it just wastes a 4x buffer
// and a resolve pass. Use `Low` so contiguous copyPixels runs skip
// MSAA entirely; a later vector `draw()` with higher stage quality
// will flush this batch and start its own MSAA pass.
target.append_render_bitmap(
context.gc(),
context.renderer,
source_handle,
transform,

Check warning on line 1138 in core/src/bitmap/operations.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (1134–1138)
false,
PixelSnapping::Never,
dest_region,
StageQuality::Low,

Check warning on line 1142 in core/src/bitmap/operations.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (1140–1142)
);
return;

Check warning on line 1144 in core/src/bitmap/operations.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (1144)
}

copy_on_cpu(
context.gc(),
context.renderer,
source_bitmap,
target,
source_region,
dest_region,
(source_transparency && !transparency) || merge_alpha,
needs_blend,
);
}

Expand Down Expand Up @@ -1592,8 +1671,6 @@
render_context.commands.pop_mask();
}

let handle = target.bitmap_handle(render_context.gc(), render_context.renderer);

let commands = if blend_mode == BlendMode::Normal {
render_context.commands
} else {
Expand All @@ -1605,28 +1682,25 @@
commands
};

let (target, include_dirty_area) = target.overwrite_cpu_pixels_from_gpu(context.gc());
let mut write = target.borrow_mut(context.gc());
// If we have another dirty area to preserve, expand this to include it
if let Some(old) = include_dirty_area {
dirty_region.union(old);
}

assert!(
cache_draws.is_empty(),
"BitmapData.draw() should not use cacheAsBitmap"
);
let image = context
.renderer
.render_offscreen(handle, commands, quality, dirty_region);

match image {
Some(sync_handle) => {
write.set_gpu_dirty(context.gc(), sync_handle, dirty_region);
Ok(())
}
None => Err(BitmapDataDrawError::Unimplemented),
}

// Defer the draw into the target's pending GPU batch instead of calling
// `render_offscreen` synchronously. Consecutive `BitmapData.draw()` calls
// onto the same target then share one submission at flush time - crucial
// for scenes (e.g. cached backgrounds) that do tens of thousands of
// draws per frame and would otherwise hit the backend's per-frame draw
// cap with fresh command-encoder allocations.
target.append_gpu_commands(
context.gc(),
context.renderer,
commands,
dirty_region,
quality,
);
Ok(())
}

pub fn get_vector<'gc>(
Expand Down
4 changes: 2 additions & 2 deletions render/canvas/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use ruffle_render::backend::{
BitmapCacheEntry, Context3D, Context3DProfile, PixelBenderOutput, PixelBenderTarget,
RenderBackend, ShapeHandle, ShapeHandleImpl, ViewportDimensions,
RenderBackend, RenderOffscreenBatches, ShapeHandle, ShapeHandleImpl, ViewportDimensions,
};
use ruffle_render::bitmap::{
Bitmap, BitmapHandle, BitmapHandleImpl, BitmapSource, PixelRegion, PixelSnapping, RgbaBufRead,
Expand Down Expand Up @@ -509,7 +509,7 @@ impl RenderBackend for WebCanvasRenderBackend {
fn render_offscreen(
&mut self,
_handle: BitmapHandle,
_commands: CommandList,
_batches: RenderOffscreenBatches,
_quality: StageQuality,
_bounds: PixelRegion,
) -> Option<Box<dyn SyncHandle>> {
Expand Down
29 changes: 28 additions & 1 deletion render/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
use crate::quality::StageQuality;
use crate::shape_utils::DistilledShape;
use ruffle_wstr::{FromWStr, WStr};
use smallvec::SmallVec;

/// A sequence of command-list sub-batches passed to `render_offscreen`.
/// Inline size of 1 covers the single-batch call path (e.g. `BitmapData.draw()`)
/// without a heap allocation; longer sequences from deferred batching spill to
/// heap identically to `Vec`.
pub type RenderOffscreenBatches = SmallVec<[CommandList; 1]>;
use std::any::Any;
use std::borrow::Cow;
use std::cell::RefCell;
Expand Down Expand Up @@ -36,10 +43,16 @@
bitmap_source: &dyn BitmapSource,
) -> ShapeHandle;

/// Renders the given sequence of command lists onto the texture bound
/// to `handle` and returns a sync handle for the resulting write. Each
/// entry in `batches` is rendered as its own render pass; with MSAA,
/// this means the previous pass's resolve is visible as input to the
/// next. A single long batch stays in one pass, so callers that pack
/// non-overlapping draws into one batch skip any per-draw resolve.
fn render_offscreen(
&mut self,
handle: BitmapHandle,
commands: CommandList,
batches: RenderOffscreenBatches,
quality: StageQuality,
bounds: PixelRegion,
) -> Option<Box<dyn SyncHandle>>;
Expand Down Expand Up @@ -70,6 +83,20 @@
false
}

/// Copies a rectangular region from one GPU texture to another without
/// CPU readback or blending. Returns a `SyncHandle` the caller can use
/// to mark the destination `GpuModified`. Backends without GPU support
/// return `None` and the caller should fall back to the CPU copy path.
fn copy_pixels_to_texture(
&mut self,
_source: BitmapHandle,
_source_region: PixelRegion,
_destination: BitmapHandle,
_dest_point: (u32, u32),
) -> Option<Box<dyn SyncHandle>> {
None
}

Check warning on line 98 in render/src/backend.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (90–98)

fn submit_frame(
&mut self,
clear: swf::Color,
Expand Down
5 changes: 3 additions & 2 deletions render/src/backend/null.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use std::num::NonZeroU32;
use std::sync::Arc;

use crate::backend::{
BitmapCacheEntry, RenderBackend, ShapeHandle, ShapeHandleImpl, ViewportDimensions,
BitmapCacheEntry, RenderBackend, RenderOffscreenBatches, ShapeHandle, ShapeHandleImpl,
ViewportDimensions,
};
use crate::bitmap::{
Bitmap, BitmapHandle, BitmapHandleImpl, BitmapSize, BitmapSource, PixelRegion, RgbaBufRead,
Expand Down Expand Up @@ -66,7 +67,7 @@ impl RenderBackend for NullRenderer {
fn render_offscreen(
&mut self,
_handle: BitmapHandle,
_commands: CommandList,
_batches: RenderOffscreenBatches,
_quality: StageQuality,
_bounds: PixelRegion,
) -> Option<Box<dyn SyncHandle>> {
Expand Down
4 changes: 2 additions & 2 deletions render/webgl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use bytemuck::{Pod, Zeroable};
use ruffle_render::backend::{
BitmapCacheEntry, Context3D, Context3DProfile, PixelBenderOutput, PixelBenderTarget,
RenderBackend, ShapeHandle, ShapeHandleImpl, ViewportDimensions,
RenderBackend, RenderOffscreenBatches, ShapeHandle, ShapeHandleImpl, ViewportDimensions,
};
use ruffle_render::bitmap::{
Bitmap, BitmapFormat, BitmapHandle, BitmapHandleImpl, BitmapSource, PixelRegion, PixelSnapping,
Expand Down Expand Up @@ -995,7 +995,7 @@ impl RenderBackend for WebGlRenderBackend {
fn render_offscreen(
&mut self,
_handle: BitmapHandle,
_commands: CommandList,
_batches: RenderOffscreenBatches,
_quality: StageQuality,
_bounds: PixelRegion,
) -> Option<Box<dyn SyncHandle>> {
Expand Down
Loading
Loading