render: Refactor chunk_blends to use CommandHandler

This refactor simplifies code, gets rid of the large number of
parameters, and allows calling other rendering methods from themselves.
The latter is useful for emulating lines on Dx12.
This commit is contained in:
Kamil Jarosz 2024-06-23 18:45:21 +02:00
parent 23f01e0dd9
commit 23931ed863
2 changed files with 292 additions and 236 deletions

View File

@ -132,7 +132,7 @@ impl Surface {
let mut num_masks = 0;
let mut mask_state = MaskState::NoMask;
let chunks = chunk_blends(
commands.commands,
commands,
descriptors,
staging_belt,
dynamic_transforms,

View File

@ -9,13 +9,14 @@ use crate::surface::target::CommandTarget;
use crate::surface::Surface;
use crate::{as_texture, Descriptors, MaskState, Pipelines, Transforms};
use ruffle_render::backend::ShapeHandle;
use ruffle_render::bitmap::BitmapHandle;
use ruffle_render::commands::{Command, RenderBlendMode};
use ruffle_render::bitmap::{BitmapHandle, PixelSnapping};
use ruffle_render::commands::{CommandHandler, CommandList, RenderBlendMode};
use ruffle_render::matrix::Matrix;
use ruffle_render::pixel_bender::PixelBenderShaderHandle;
use ruffle_render::quality::StageQuality;
use ruffle_render::transform::Transform;
use swf::{BlendMode, ColorTransform, Fixed8};
use std::mem;
use swf::{BlendMode, Color, ColorTransform};
use super::target::PoolOrArcTexture;
@ -392,7 +393,7 @@ pub enum LayerRef<'a> {
/// Every complex blend will be its own item, but every other draw will be chunked together
#[allow(clippy::too_many_arguments)]
pub fn chunk_blends<'a>(
commands: Vec<Command>,
commands: CommandList,
descriptors: &'a Descriptors,
staging_belt: &'a mut wgpu::util::StagingBelt,
dynamic_transforms: &'a DynamicTransforms,
@ -404,21 +405,106 @@ pub fn chunk_blends<'a>(
nearest_layer: LayerRef,
texture_pool: &mut TexturePool,
) -> Vec<Chunk> {
let mut result = vec![];
let mut current = vec![];
let mut needs_stencil = false;
let mut num_masks = 0;
let mut transforms = BufferBuilder::new_for_uniform(&descriptors.limits);
WgpuCommandHandler::new(
descriptors,
staging_belt,
dynamic_transforms,
draw_encoder,
meshes,
quality,
width,
height,
nearest_layer,
texture_pool,
)
.chunk_blends(commands)
}
transforms.set_buffer_limit(dynamic_transforms.buffer.size());
struct WgpuCommandHandler<'a> {
descriptors: &'a Descriptors,
quality: StageQuality,
width: u32,
height: u32,
nearest_layer: LayerRef<'a>,
meshes: &'a Vec<Mesh>,
staging_belt: &'a mut wgpu::util::StagingBelt,
dynamic_transforms: &'a DynamicTransforms,
draw_encoder: &'a mut wgpu::CommandEncoder,
texture_pool: &'a mut TexturePool,
result: Vec<Chunk>,
current: Vec<DrawCommand>,
transforms: BufferBuilder,
needs_stencil: bool,
num_masks: i32,
}
impl<'a> WgpuCommandHandler<'a> {
#[allow(clippy::too_many_arguments)]
fn new(
descriptors: &'a Descriptors,
staging_belt: &'a mut wgpu::util::StagingBelt,
dynamic_transforms: &'a DynamicTransforms,
draw_encoder: &'a mut wgpu::CommandEncoder,
meshes: &'a Vec<Mesh>,
quality: StageQuality,
width: u32,
height: u32,
nearest_layer: LayerRef<'a>,
texture_pool: &'a mut TexturePool,
) -> Self {
let transforms = Self::new_transforms(descriptors, dynamic_transforms);
Self {
descriptors,
quality,
width,
height,
nearest_layer,
meshes,
staging_belt,
dynamic_transforms,
draw_encoder,
texture_pool,
result: vec![],
current: vec![],
transforms,
needs_stencil: false,
num_masks: 0,
}
}
fn new_transforms(
descriptors: &'a Descriptors,
dynamic_transforms: &'a DynamicTransforms,
) -> BufferBuilder {
let mut transforms = BufferBuilder::new_for_uniform(&descriptors.limits);
transforms.set_buffer_limit(dynamic_transforms.buffer.size());
transforms
}
/// Replaces every blend with a RenderBitmap, with the subcommands rendered out to a temporary texture
/// Every complex blend will be its own item, but every other draw will be chunked together
fn chunk_blends(&mut self, commands: CommandList) -> Vec<Chunk> {
commands.execute(self);
let current = mem::take(&mut self.current);
let mut result = mem::take(&mut self.result);
let needs_stencil = mem::take(&mut self.needs_stencil);
let transforms = mem::replace(
&mut self.transforms,
Self::new_transforms(self.descriptors, self.dynamic_transforms),
);
if !current.is_empty() {
result.push(Chunk::Draw(current, needs_stencil, transforms));
}
result
}
fn add_to_current(
result: &mut Vec<Chunk>,
current: &mut Vec<DrawCommand>,
transforms: &mut BufferBuilder,
dynamic_transforms: &DynamicTransforms,
needs_stencil: bool,
descriptors: &Descriptors,
&mut self,
matrix: Matrix,
color_transform: ColorTransform,
command_builder: impl FnOnce(wgpu::DynamicOffset) -> DrawCommand,
@ -438,248 +524,218 @@ pub fn chunk_blends<'a>(
mult_color: color_transform.mult_rgba_normalized(),
add_color: color_transform.add_rgba_normalized(),
};
if let Ok(transform_range) = transforms.add(&[transform]) {
current.push(command_builder(
if let Ok(transform_range) = self.transforms.add(&[transform]) {
self.current.push(command_builder(
transform_range.start as wgpu::DynamicOffset,
));
} else {
result.push(Chunk::Draw(
std::mem::take(current),
needs_stencil,
std::mem::replace(
transforms,
BufferBuilder::new_for_uniform(&descriptors.limits),
self.result.push(Chunk::Draw(
mem::take(&mut self.current),
self.needs_stencil,
mem::replace(
&mut self.transforms,
BufferBuilder::new_for_uniform(&self.descriptors.limits),
),
));
transforms.set_buffer_limit(dynamic_transforms.buffer.size());
let transform_range = transforms
self.transforms
.set_buffer_limit(self.dynamic_transforms.buffer.size());
let transform_range = self
.transforms
.add(&[transform])
.expect("Buffer must be able to fit a new thing, it was just emptied");
current.push(command_builder(
self.current.push(command_builder(
transform_range.start as wgpu::DynamicOffset,
));
}
}
}
for command in commands {
match command {
Command::Blend(commands, blend_mode) => {
let mut surface = Surface::new(
descriptors,
quality,
width,
height,
wgpu::TextureFormat::Rgba8Unorm,
);
let target_layer = if let RenderBlendMode::Builtin(BlendMode::Layer) = &blend_mode {
LayerRef::Current
} else {
nearest_layer
impl<'a> CommandHandler for WgpuCommandHandler<'a> {
fn blend(&mut self, commands: CommandList, blend_mode: RenderBlendMode) {
let mut surface = Surface::new(
self.descriptors,
self.quality,
self.width,
self.height,
wgpu::TextureFormat::Rgba8Unorm,
);
let target_layer = if let RenderBlendMode::Builtin(BlendMode::Layer) = &blend_mode {
LayerRef::Current
} else {
self.nearest_layer
};
let blend_type = BlendType::from(blend_mode);
let clear_color = blend_type.default_color();
let target = surface.draw_commands(
RenderTargetMode::FreshWithColor(clear_color),
self.descriptors,
self.meshes,
commands,
self.staging_belt,
self.dynamic_transforms,
self.draw_encoder,
target_layer,
self.texture_pool,
);
target.ensure_cleared(self.draw_encoder);
match blend_type {
BlendType::Trivial(blend_mode) => {
let transform = Transform {
matrix: Matrix::scale(target.width() as f32, target.height() as f32),
color_transform: Default::default(),
};
let blend_type = BlendType::from(blend_mode);
let clear_color = blend_type.default_color();
let target = surface.draw_commands(
RenderTargetMode::FreshWithColor(clear_color),
descriptors,
meshes,
commands,
staging_belt,
dynamic_transforms,
draw_encoder,
target_layer,
texture_pool,
);
target.ensure_cleared(draw_encoder);
match blend_type {
BlendType::Trivial(blend_mode) => {
let transform = Transform {
matrix: Matrix::scale(target.width() as f32, target.height() as f32),
color_transform: Default::default(),
};
let texture = target.take_color_texture();
let bind_group =
descriptors
.device
.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &descriptors.bind_layouts.bitmap,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: descriptors
.quad
.texture_transforms
.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(
texture.view(),
),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(
descriptors
.bitmap_samplers
.get_sampler(false, false),
),
},
],
label: None,
});
add_to_current(
&mut result,
&mut current,
&mut transforms,
dynamic_transforms,
needs_stencil,
descriptors,
transform.matrix,
transform.color_transform,
|transform_buffer| DrawCommand::RenderTexture {
_texture: texture,
binds: bind_group,
transform_buffer,
blend_mode,
},
);
}
blend_type => {
if !current.is_empty() {
result.push(Chunk::Draw(
std::mem::take(&mut current),
needs_stencil,
std::mem::replace(
&mut transforms,
BufferBuilder::new_for_uniform(&descriptors.limits),
),
));
}
transforms.set_buffer_limit(dynamic_transforms.buffer.size());
let chunk_blend_mode = match blend_type {
BlendType::Complex(complex) => ChunkBlendMode::Complex(complex),
BlendType::Shader(shader) => ChunkBlendMode::Shader(shader),
_ => unreachable!(),
};
result.push(Chunk::Blend(
target.take_color_texture(),
chunk_blend_mode,
num_masks > 0,
));
needs_stencil = num_masks > 0;
}
}
}
Command::RenderBitmap {
bitmap,
transform,
smoothing,
pixel_snapping,
} => {
let mut matrix = transform.matrix;
{
let texture = as_texture(&bitmap);
pixel_snapping.apply(&mut matrix);
matrix *= Matrix::scale(
texture.texture.width() as f32,
texture.texture.height() as f32,
);
}
add_to_current(
&mut result,
&mut current,
&mut transforms,
dynamic_transforms,
needs_stencil,
descriptors,
matrix,
let texture = target.take_color_texture();
let bind_group =
self.descriptors
.device
.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.descriptors.bind_layouts.bitmap,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self
.descriptors
.quad
.texture_transforms
.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(texture.view()),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(
self.descriptors.bitmap_samplers.get_sampler(false, false),
),
},
],
label: None,
});
self.add_to_current(
transform.matrix,
transform.color_transform,
|transform_buffer| DrawCommand::RenderBitmap {
bitmap,
|transform_buffer| DrawCommand::RenderTexture {
_texture: texture,
binds: bind_group,
transform_buffer,
smoothing,
blend_mode: TrivialBlend::Normal,
render_stage3d: false,
blend_mode,
},
);
}
Command::RenderStage3D { bitmap, transform } => {
let mut matrix = transform.matrix;
{
let texture = as_texture(&bitmap);
matrix *= Matrix::scale(
texture.texture.width() as f32,
texture.texture.height() as f32,
);
blend_type => {
if !self.current.is_empty() {
self.result.push(Chunk::Draw(
mem::take(&mut self.current),
self.needs_stencil,
mem::replace(
&mut self.transforms,
BufferBuilder::new_for_uniform(&self.descriptors.limits),
),
));
}
add_to_current(
&mut result,
&mut current,
&mut transforms,
dynamic_transforms,
needs_stencil,
descriptors,
matrix,
transform.color_transform,
|transform_buffer| DrawCommand::RenderBitmap {
bitmap,
transform_buffer,
smoothing: false,
blend_mode: TrivialBlend::Normal,
render_stage3d: true,
},
);
}
Command::RenderShape { shape, transform } => add_to_current(
&mut result,
&mut current,
&mut transforms,
dynamic_transforms,
needs_stencil,
descriptors,
transform.matrix,
transform.color_transform,
|transform_buffer| DrawCommand::RenderShape {
shape,
transform_buffer,
},
),
Command::DrawRect { color, matrix } => add_to_current(
&mut result,
&mut current,
&mut transforms,
dynamic_transforms,
needs_stencil,
descriptors,
matrix,
ColorTransform::multiply_from(color),
|transform_buffer| DrawCommand::DrawRect { transform_buffer },
),
Command::PushMask => {
needs_stencil = true;
num_masks += 1;
current.push(DrawCommand::PushMask);
}
Command::ActivateMask => {
needs_stencil = true;
current.push(DrawCommand::ActivateMask);
}
Command::DeactivateMask => {
needs_stencil = true;
current.push(DrawCommand::DeactivateMask);
}
Command::PopMask => {
needs_stencil = true;
num_masks -= 1;
current.push(DrawCommand::PopMask);
self.transforms
.set_buffer_limit(self.dynamic_transforms.buffer.size());
let chunk_blend_mode = match blend_type {
BlendType::Complex(complex) => ChunkBlendMode::Complex(complex),
BlendType::Shader(shader) => ChunkBlendMode::Shader(shader),
_ => unreachable!(),
};
self.result.push(Chunk::Blend(
target.take_color_texture(),
chunk_blend_mode,
self.num_masks > 0,
));
self.needs_stencil = self.num_masks > 0;
}
}
}
if !current.is_empty() {
result.push(Chunk::Draw(current, needs_stencil, transforms));
fn render_bitmap(
&mut self,
bitmap: BitmapHandle,
transform: Transform,
smoothing: bool,
pixel_snapping: PixelSnapping,
) {
let mut matrix = transform.matrix;
{
let texture = as_texture(&bitmap);
pixel_snapping.apply(&mut matrix);
matrix *= Matrix::scale(
texture.texture.width() as f32,
texture.texture.height() as f32,
);
}
self.add_to_current(matrix, transform.color_transform, |transform_buffer| {
DrawCommand::RenderBitmap {
bitmap,
transform_buffer,
smoothing,
blend_mode: TrivialBlend::Normal,
render_stage3d: false,
}
});
}
fn render_stage3d(&mut self, bitmap: BitmapHandle, transform: Transform) {
let mut matrix = transform.matrix;
{
let texture = as_texture(&bitmap);
matrix *= Matrix::scale(
texture.texture.width() as f32,
texture.texture.height() as f32,
);
}
self.add_to_current(matrix, transform.color_transform, |transform_buffer| {
DrawCommand::RenderBitmap {
bitmap,
transform_buffer,
smoothing: false,
blend_mode: TrivialBlend::Normal,
render_stage3d: true,
}
});
}
result
fn render_shape(&mut self, shape: ShapeHandle, transform: Transform) {
self.add_to_current(
transform.matrix,
transform.color_transform,
|transform_buffer| DrawCommand::RenderShape {
shape,
transform_buffer,
},
);
}
fn draw_rect(&mut self, color: Color, matrix: Matrix) {
self.add_to_current(
matrix,
ColorTransform::multiply_from(color),
|transform_buffer| DrawCommand::DrawRect { transform_buffer },
);
}
fn push_mask(&mut self) {
self.needs_stencil = true;
self.num_masks += 1;
self.current.push(DrawCommand::PushMask);
}
fn activate_mask(&mut self) {
self.needs_stencil = true;
self.current.push(DrawCommand::ActivateMask);
}
fn deactivate_mask(&mut self) {
self.needs_stencil = true;
self.current.push(DrawCommand::DeactivateMask);
}
fn pop_mask(&mut self) {
self.needs_stencil = true;
self.num_masks -= 1;
self.current.push(DrawCommand::PopMask);
}
}