render: Add BlendMode infrastructure and implement BlendMode.ADD
Each render backend keeps track of a stack of BlenModes, which are pushed and popped by 'core' as we render objects in the displaay tree. For now, I've just implemented BlendMode.ADD, which maps directly onto blend mode supported by each backend. All other blend modes (besides 'NORMAL') will produce a warning when we try to render using them. This may produce a very large amount of log output, but it's simpler than emitting each warning only once, and will help to point developers in the right direction when they get otherwise inexplicable rendering issues (due to a blend mode not being implemented). The wgpu implementation is by far the most complicated, as we need to construct a `RenderPipeline` for each possible `(BlendMode, MaskState)`. I haven't been able to find any documentation about the maximum supported number of (simultaneous) WebGPU render pipelines - if this becomes an issue, we may need to register them on-demand when a particular blend mode is requested.
This commit is contained in:
parent
1e18fc2227
commit
f7205a02a9
|
@ -99,7 +99,7 @@ jobs:
|
|||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: swf_images
|
||||
path: tests/**/*.png.updated
|
||||
path: tests/**/actual.png
|
||||
|
||||
check-required:
|
||||
needs: changes
|
||||
|
|
|
@ -3497,6 +3497,7 @@ dependencies = [
|
|||
"bitstream-io",
|
||||
"byteorder",
|
||||
"encoding_rs",
|
||||
"enum-map",
|
||||
"flate2",
|
||||
"libflate",
|
||||
"log",
|
||||
|
|
|
@ -13,6 +13,7 @@ use crate::display_object::{HitTestOptions, TDisplayObject};
|
|||
use crate::types::{Degrees, Percent};
|
||||
use crate::vminterface::Instantiator;
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
use swf::BlendMode;
|
||||
use swf::Twips;
|
||||
|
||||
/// Implements `flash.display.DisplayObject`'s instance constructor.
|
||||
|
@ -642,6 +643,62 @@ pub fn set_transform<'gc>(
|
|||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements `DisplayObject.blendMode`'s getter.
|
||||
pub fn blend_mode<'gc>(
|
||||
_activation: &mut Activation<'_, 'gc, '_>,
|
||||
_this: Option<Object<'gc>>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements `DisplayObject.blendMode`'s setter.
|
||||
pub fn set_blend_mode<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
if let Some(mut dobj) = this.and_then(|this| this.as_display_object()) {
|
||||
if let Some(Value::String(mode)) = args.get(0).cloned() {
|
||||
let mode = &*mode;
|
||||
let mode = if mode == b"add" {
|
||||
BlendMode::Add
|
||||
} else if mode == b"alpha" {
|
||||
BlendMode::Alpha
|
||||
} else if mode == b"darken" {
|
||||
BlendMode::Darken
|
||||
} else if mode == b"difference" {
|
||||
BlendMode::Difference
|
||||
} else if mode == b"erase" {
|
||||
BlendMode::Erase
|
||||
} else if mode == b"hardlight" {
|
||||
BlendMode::HardLight
|
||||
} else if mode == b"invert" {
|
||||
BlendMode::Invert
|
||||
} else if mode == b"layer" {
|
||||
BlendMode::Layer
|
||||
} else if mode == b"lighten" {
|
||||
BlendMode::Lighten
|
||||
} else if mode == b"multiply" {
|
||||
BlendMode::Multiply
|
||||
} else if mode == b"normal" {
|
||||
BlendMode::Normal
|
||||
} else if mode == b"overlay" {
|
||||
BlendMode::Overlay
|
||||
} else if mode == b"screen" {
|
||||
BlendMode::Screen
|
||||
} else if mode == b"subtract" {
|
||||
BlendMode::Subtract
|
||||
} else {
|
||||
log::error!("Unknown blend mode {:?}", mode);
|
||||
BlendMode::Normal
|
||||
};
|
||||
dobj.set_blend_mode(activation.context.gc_context, mode);
|
||||
}
|
||||
}
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Construct `DisplayObject`'s class.
|
||||
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
|
||||
let class = Class::new(
|
||||
|
@ -669,6 +726,7 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
|
|||
Option<NativeMethodImpl>,
|
||||
)] = &[
|
||||
("alpha", Some(alpha), Some(set_alpha)),
|
||||
("blendMode", Some(blend_mode), Some(set_blend_mode)),
|
||||
("height", Some(height), Some(set_height)),
|
||||
("scaleY", Some(scale_y), Some(set_scale_y)),
|
||||
("width", Some(width), Some(set_width)),
|
||||
|
|
|
@ -466,6 +466,7 @@ pub fn render_base<'gc>(this: DisplayObject<'gc>, context: &mut RenderContext<'_
|
|||
return;
|
||||
}
|
||||
context.transform_stack.push(this.base().transform());
|
||||
context.renderer.push_blend_mode(this.blend_mode());
|
||||
|
||||
let mask = this.masker();
|
||||
let mut mask_transform = ruffle_render::transform::Transform::default();
|
||||
|
@ -491,6 +492,7 @@ pub fn render_base<'gc>(this: DisplayObject<'gc>, context: &mut RenderContext<'_
|
|||
context.renderer.pop_mask();
|
||||
}
|
||||
|
||||
context.renderer.pop_blend_mode();
|
||||
context.transform_stack.pop();
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ use ruffle_render::matrix::Matrix;
|
|||
use ruffle_render::shape_utils::{DistilledShape, DrawCommand, LineScaleMode, LineScales};
|
||||
use ruffle_render::transform::Transform;
|
||||
use ruffle_web_common::{JsError, JsResult};
|
||||
use swf::Color;
|
||||
use swf::{BlendMode, Color};
|
||||
use wasm_bindgen::{Clamped, JsCast};
|
||||
use web_sys::{
|
||||
CanvasGradient, CanvasPattern, CanvasRenderingContext2d, CanvasWindingRule, DomMatrix, Element,
|
||||
|
@ -29,6 +29,7 @@ pub struct WebCanvasRenderBackend {
|
|||
rect: Path2d,
|
||||
mask_state: MaskState,
|
||||
next_bitmap_handle: BitmapHandle,
|
||||
blend_modes: Vec<BlendMode>,
|
||||
}
|
||||
|
||||
/// Canvas-drawable shape data extracted from an SWF file.
|
||||
|
@ -289,6 +290,7 @@ impl WebCanvasRenderBackend {
|
|||
rect,
|
||||
mask_state: MaskState::DrawContent,
|
||||
next_bitmap_handle: BitmapHandle(0),
|
||||
blend_modes: vec![BlendMode::Normal],
|
||||
};
|
||||
Ok(renderer)
|
||||
}
|
||||
|
@ -345,6 +347,20 @@ impl WebCanvasRenderBackend {
|
|||
self.context.set_filter("none");
|
||||
self.context.set_global_alpha(1.0);
|
||||
}
|
||||
|
||||
fn apply_blend_mode(&mut self, blend: BlendMode) {
|
||||
let mode = match blend {
|
||||
BlendMode::Add => "lighter",
|
||||
BlendMode::Normal => "source-over",
|
||||
_ => {
|
||||
log::warn!("Canvas backend does not yet support blend mode {:?}", blend);
|
||||
"source-over"
|
||||
}
|
||||
};
|
||||
self.context
|
||||
.set_global_composite_operation(mode)
|
||||
.expect("Failed to update BlendMode");
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderBackend for WebCanvasRenderBackend {
|
||||
|
@ -673,6 +689,22 @@ impl RenderBackend for WebCanvasRenderBackend {
|
|||
}
|
||||
}
|
||||
|
||||
fn push_blend_mode(&mut self, blend: BlendMode) {
|
||||
if Some(&blend) != self.blend_modes.last() {
|
||||
self.apply_blend_mode(blend);
|
||||
}
|
||||
self.blend_modes.push(blend);
|
||||
}
|
||||
|
||||
fn pop_blend_mode(&mut self) {
|
||||
let old = self.blend_modes.pop();
|
||||
// We should never pop our base 'BlendMode::Normal'
|
||||
let current = *self.blend_modes.last().unwrap();
|
||||
if old != Some(current) {
|
||||
self.apply_blend_mode(current);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> Option<Bitmap> {
|
||||
let bitmap = &self.bitmaps[&bitmap];
|
||||
bitmap.get_pixels()
|
||||
|
|
|
@ -86,6 +86,9 @@ pub trait RenderBackend: Downcast {
|
|||
fn deactivate_mask(&mut self);
|
||||
fn pop_mask(&mut self);
|
||||
|
||||
fn push_blend_mode(&mut self, blend: swf::BlendMode);
|
||||
fn pop_blend_mode(&mut self);
|
||||
|
||||
fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> Option<Bitmap>;
|
||||
fn register_bitmap(&mut self, bitmap: Bitmap) -> Result<BitmapHandle, Error>;
|
||||
// Frees memory used by the bitmap. After this call, `handle` can no longer
|
||||
|
|
|
@ -57,6 +57,9 @@ impl RenderBackend for NullRenderer {
|
|||
fn deactivate_mask(&mut self) {}
|
||||
fn pop_mask(&mut self) {}
|
||||
|
||||
fn push_blend_mode(&mut self, _blend_mode: swf::BlendMode) {}
|
||||
fn pop_blend_mode(&mut self) {}
|
||||
|
||||
fn get_bitmap_pixels(&mut self, _bitmap: BitmapHandle) -> Option<Bitmap> {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use ruffle_render::tessellator::{
|
|||
};
|
||||
use ruffle_render::transform::Transform;
|
||||
use ruffle_web_common::JsResult;
|
||||
use swf::Color;
|
||||
use swf::{BlendMode, Color};
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{
|
||||
HtmlCanvasElement, OesVertexArrayObject, WebGl2RenderingContext as Gl2, WebGlBuffer,
|
||||
|
@ -86,7 +86,7 @@ pub struct WebGlRenderBackend {
|
|||
is_transparent: bool,
|
||||
|
||||
active_program: *const ShaderProgram,
|
||||
blend_func: (u32, u32),
|
||||
blend_modes: Vec<BlendMode>,
|
||||
mult_color: Option<[f32; 4]>,
|
||||
add_color: Option<[f32; 4]>,
|
||||
|
||||
|
@ -216,7 +216,6 @@ impl WebGlRenderBackend {
|
|||
let gradient_program = ShaderProgram::new(&gl, &texture_vertex, &gradient_fragment)?;
|
||||
|
||||
gl.enable(Gl::BLEND);
|
||||
gl.blend_func(Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// Necessary to load RGB textures (alignment defaults to 4).
|
||||
gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1);
|
||||
|
@ -248,13 +247,15 @@ impl WebGlRenderBackend {
|
|||
is_transparent,
|
||||
|
||||
active_program: std::ptr::null(),
|
||||
blend_func: (Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA),
|
||||
blend_modes: vec![],
|
||||
mult_color: None,
|
||||
add_color: None,
|
||||
bitmap_registry: Default::default(),
|
||||
next_bitmap_handle: BitmapHandle(0),
|
||||
};
|
||||
|
||||
renderer.push_blend_mode(BlendMode::Normal);
|
||||
|
||||
let color_quad_mesh = renderer.build_quad_mesh(&renderer.color_program)?;
|
||||
renderer.meshes.push(color_quad_mesh);
|
||||
let bitmap_quad_mesh = renderer.build_quad_mesh(&renderer.bitmap_program)?;
|
||||
|
@ -658,6 +659,24 @@ impl WebGlRenderBackend {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_blend_mode(&mut self, mode: BlendMode) {
|
||||
match mode {
|
||||
BlendMode::Add => {
|
||||
self.gl.blend_equation(Gl::FUNC_ADD);
|
||||
// Add RGB values, use normal alpha blending
|
||||
self.gl
|
||||
.blend_func_separate(Gl::ONE, Gl::ONE, Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
_ => {
|
||||
if mode != BlendMode::Normal {
|
||||
log::warn!("Webgl backend does not yet support blendmode {:?}", mode);
|
||||
}
|
||||
self.gl.blend_equation(Gl::FUNC_ADD);
|
||||
self.gl.blend_func(Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderBackend for WebGlRenderBackend {
|
||||
|
@ -874,8 +893,7 @@ impl RenderBackend for WebGlRenderBackend {
|
|||
|
||||
self.bind_vertex_array(Some(&draw.vao));
|
||||
|
||||
let (program, src_blend, dst_blend) =
|
||||
(&self.bitmap_program, Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA);
|
||||
let program = &self.bitmap_program;
|
||||
|
||||
// Set common render state, while minimizing unnecessary state changes.
|
||||
// TODO: Using designated layout specifiers in WebGL2/OpenGL ES 3, we could guarantee that uniforms
|
||||
|
@ -888,11 +906,6 @@ impl RenderBackend for WebGlRenderBackend {
|
|||
|
||||
self.mult_color = None;
|
||||
self.add_color = None;
|
||||
|
||||
if (src_blend, dst_blend) != self.blend_func {
|
||||
self.gl.blend_func(src_blend, dst_blend);
|
||||
self.blend_func = (src_blend, dst_blend);
|
||||
}
|
||||
}
|
||||
|
||||
program.uniform_matrix4fv(&self.gl, ShaderUniform::WorldMatrix, &world_matrix);
|
||||
|
@ -969,10 +982,10 @@ impl RenderBackend for WebGlRenderBackend {
|
|||
|
||||
self.bind_vertex_array(Some(&draw.vao));
|
||||
|
||||
let (program, src_blend, dst_blend) = match &draw.draw_type {
|
||||
DrawType::Color => (&self.color_program, Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA),
|
||||
DrawType::Gradient(_) => (&self.gradient_program, Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA),
|
||||
DrawType::Bitmap { .. } => (&self.bitmap_program, Gl::ONE, Gl::ONE_MINUS_SRC_ALPHA),
|
||||
let program = match &draw.draw_type {
|
||||
DrawType::Color => &self.color_program,
|
||||
DrawType::Gradient(_) => &self.gradient_program,
|
||||
DrawType::Bitmap { .. } => &self.bitmap_program,
|
||||
};
|
||||
|
||||
// Set common render state, while minimizing unnecessary state changes.
|
||||
|
@ -986,11 +999,6 @@ impl RenderBackend for WebGlRenderBackend {
|
|||
|
||||
self.mult_color = None;
|
||||
self.add_color = None;
|
||||
|
||||
if (src_blend, dst_blend) != self.blend_func {
|
||||
self.gl.blend_func(src_blend, dst_blend);
|
||||
self.blend_func = (src_blend, dst_blend);
|
||||
}
|
||||
}
|
||||
|
||||
program.uniform_matrix4fv(&self.gl, ShaderUniform::WorldMatrix, &world_matrix);
|
||||
|
@ -1116,8 +1124,6 @@ impl RenderBackend for WebGlRenderBackend {
|
|||
self.set_stencil_state();
|
||||
|
||||
let program = &self.color_program;
|
||||
let src_blend = Gl::ONE;
|
||||
let dst_blend = Gl::ONE_MINUS_SRC_ALPHA;
|
||||
|
||||
// Set common render state, while minimizing unnecessary state changes.
|
||||
// TODO: Using designated layout specifiers in WebGL2/OpenGL ES 3, we could guarantee that uniforms
|
||||
|
@ -1130,11 +1136,6 @@ impl RenderBackend for WebGlRenderBackend {
|
|||
|
||||
self.mult_color = None;
|
||||
self.add_color = None;
|
||||
|
||||
if (src_blend, dst_blend) != self.blend_func {
|
||||
self.gl.blend_func(src_blend, dst_blend);
|
||||
self.blend_func = (src_blend, dst_blend);
|
||||
}
|
||||
};
|
||||
|
||||
self.color_program
|
||||
|
@ -1193,6 +1194,22 @@ impl RenderBackend for WebGlRenderBackend {
|
|||
self.mask_state_dirty = true;
|
||||
}
|
||||
|
||||
fn push_blend_mode(&mut self, blend: BlendMode) {
|
||||
if self.blend_modes.last() != Some(&blend) {
|
||||
self.apply_blend_mode(blend);
|
||||
}
|
||||
self.blend_modes.push(blend);
|
||||
}
|
||||
|
||||
fn pop_blend_mode(&mut self) {
|
||||
let old = self.blend_modes.pop();
|
||||
// We never pop our base 'BlendMode::Normal'
|
||||
let current = *self.blend_modes.last().unwrap();
|
||||
if old != Some(current) {
|
||||
self.apply_blend_mode(current);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> Option<Bitmap> {
|
||||
self.bitmap_registry.get(&bitmap).map(|e| e.bitmap.clone())
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ use ruffle_render::transform::Transform;
|
|||
use std::num::NonZeroU32;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use swf::Color;
|
||||
use swf::{BlendMode, Color};
|
||||
pub use wgpu;
|
||||
|
||||
type Error = Box<dyn std::error::Error>;
|
||||
|
@ -158,6 +158,7 @@ pub struct WgpuRenderBackend<T: RenderTarget> {
|
|||
quad_vbo: wgpu::Buffer,
|
||||
quad_ibo: wgpu::Buffer,
|
||||
quad_tex_transforms: wgpu::Buffer,
|
||||
blend_modes: Vec<BlendMode>,
|
||||
bitmap_registry: FnvHashMap<BitmapHandle, RegistryData>,
|
||||
next_bitmap_handle: BitmapHandle,
|
||||
}
|
||||
|
@ -519,6 +520,7 @@ impl<T: RenderTarget> WgpuRenderBackend<T> {
|
|||
quad_vbo,
|
||||
quad_ibo,
|
||||
quad_tex_transforms,
|
||||
blend_modes: vec![BlendMode::Normal],
|
||||
bitmap_registry: Default::default(),
|
||||
next_bitmap_handle: BitmapHandle(0),
|
||||
})
|
||||
|
@ -779,6 +781,10 @@ impl<T: RenderTarget> WgpuRenderBackend<T> {
|
|||
|
||||
Mesh { draws }
|
||||
}
|
||||
|
||||
fn blend_mode(&self) -> BlendMode {
|
||||
*self.blend_modes.last().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
|
||||
|
@ -996,6 +1002,7 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
|
|||
fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform, smoothing: bool) {
|
||||
if let Some(entry) = self.bitmap_registry.get(&bitmap) {
|
||||
let texture = &entry.texture_wrapper;
|
||||
let blend_mode = self.blend_mode();
|
||||
let frame = if let Some(frame) = &mut self.current_frame {
|
||||
frame.get()
|
||||
} else {
|
||||
|
@ -1028,7 +1035,7 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
|
|||
self.descriptors
|
||||
.pipelines
|
||||
.bitmap_pipelines
|
||||
.pipeline_for(self.mask_state),
|
||||
.pipeline_for(blend_mode, self.mask_state),
|
||||
);
|
||||
frame
|
||||
.render_pass
|
||||
|
@ -1080,6 +1087,7 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
|
|||
}
|
||||
|
||||
fn render_shape(&mut self, shape: ShapeHandle, transform: &Transform) {
|
||||
let blend_mode = self.blend_mode();
|
||||
let frame = if let Some(frame) = &mut self.current_frame {
|
||||
frame.get()
|
||||
} else {
|
||||
|
@ -1135,7 +1143,7 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
|
|||
self.descriptors
|
||||
.pipelines
|
||||
.color_pipelines
|
||||
.pipeline_for(self.mask_state),
|
||||
.pipeline_for(blend_mode, self.mask_state),
|
||||
);
|
||||
}
|
||||
DrawType::Gradient { bind_group, .. } => {
|
||||
|
@ -1143,7 +1151,7 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
|
|||
self.descriptors
|
||||
.pipelines
|
||||
.gradient_pipelines
|
||||
.pipeline_for(self.mask_state),
|
||||
.pipeline_for(blend_mode, self.mask_state),
|
||||
);
|
||||
frame.render_pass.set_bind_group(2, bind_group, &[]);
|
||||
}
|
||||
|
@ -1157,7 +1165,7 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
|
|||
self.descriptors
|
||||
.pipelines
|
||||
.bitmap_pipelines
|
||||
.pipeline_for(self.mask_state),
|
||||
.pipeline_for(blend_mode, self.mask_state),
|
||||
);
|
||||
frame.render_pass.set_bind_group(2, bind_group, &[]);
|
||||
frame.render_pass.set_bind_group(
|
||||
|
@ -1194,6 +1202,7 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
|
|||
}
|
||||
|
||||
fn draw_rect(&mut self, color: Color, matrix: &ruffle_render::matrix::Matrix) {
|
||||
let blend_mode = self.blend_mode();
|
||||
let frame = if let Some(frame) = &mut self.current_frame {
|
||||
frame.get()
|
||||
} else {
|
||||
|
@ -1224,7 +1233,7 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
|
|||
self.descriptors
|
||||
.pipelines
|
||||
.color_pipelines
|
||||
.pipeline_for(self.mask_state),
|
||||
.pipeline_for(blend_mode, self.mask_state),
|
||||
);
|
||||
|
||||
frame
|
||||
|
@ -1378,6 +1387,14 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
|
|||
};
|
||||
}
|
||||
|
||||
fn push_blend_mode(&mut self, blend: BlendMode) {
|
||||
self.blend_modes.push(blend);
|
||||
}
|
||||
|
||||
fn pop_blend_mode(&mut self) {
|
||||
self.blend_modes.pop();
|
||||
}
|
||||
|
||||
fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> Option<Bitmap> {
|
||||
self.bitmap_registry.get(&bitmap).map(|e| e.bitmap.clone())
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use crate::{Error, MaskState, Vertex};
|
||||
use enum_map::{enum_map, EnumMap};
|
||||
use enum_map::{Enum, EnumMap};
|
||||
use swf::BlendMode;
|
||||
use wgpu::vertex_attr_array;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ShapePipeline {
|
||||
pub mask_pipelines: EnumMap<MaskState, wgpu::RenderPipeline>,
|
||||
pub pipelines: EnumMap<BlendMode, EnumMap<MaskState, wgpu::RenderPipeline>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -22,8 +23,59 @@ pub struct Pipelines {
|
|||
}
|
||||
|
||||
impl ShapePipeline {
|
||||
pub fn pipeline_for(&self, mask_state: MaskState) -> &wgpu::RenderPipeline {
|
||||
&self.mask_pipelines[mask_state]
|
||||
pub fn pipeline_for(
|
||||
&self,
|
||||
blend_mode: BlendMode,
|
||||
mask_state: MaskState,
|
||||
) -> &wgpu::RenderPipeline {
|
||||
&self.pipelines[blend_mode][mask_state]
|
||||
}
|
||||
|
||||
/// Builds of a nested `EnumMap` that maps a `BlendMode` and `MaskState` to
|
||||
/// a `RenderPipeline`. The provided callback is used to construct the `RenderPipeline`
|
||||
/// for each possible `(BlendMode, MaskState)` pair.
|
||||
fn build(mut f: impl FnMut(BlendMode, MaskState) -> wgpu::RenderPipeline) -> Self {
|
||||
let blend_array: [EnumMap<MaskState, wgpu::RenderPipeline>; BlendMode::LENGTH] = (0
|
||||
..BlendMode::LENGTH)
|
||||
.map(|blend_enum| {
|
||||
let blend_mode = BlendMode::from_usize(blend_enum);
|
||||
let mask_array: [wgpu::RenderPipeline; MaskState::LENGTH] = (0..MaskState::LENGTH)
|
||||
.map(|mask_enum| {
|
||||
let mask_state = MaskState::from_usize(mask_enum);
|
||||
f(blend_mode, mask_state)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
EnumMap::from_array(mask_array)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
ShapePipeline {
|
||||
pipelines: EnumMap::from_array(blend_array),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn blend_mode_to_state(mode: BlendMode) -> Option<wgpu::BlendState> {
|
||||
match mode {
|
||||
BlendMode::Normal => Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
|
||||
BlendMode::Add => {
|
||||
Some(wgpu::BlendState {
|
||||
// Add src and dst RGB values together
|
||||
color: wgpu::BlendComponent {
|
||||
src_factor: wgpu::BlendFactor::One,
|
||||
dst_factor: wgpu::BlendFactor::One,
|
||||
operation: wgpu::BlendOperation::Add,
|
||||
},
|
||||
alpha: wgpu::BlendComponent::OVER,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
log::warn!("Webgpu backend does not yet support blend mode {:?}", mode);
|
||||
Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,7 +345,7 @@ fn create_shape_pipeline(
|
|||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let mask_render_state = |mask_name, stencil_state, write_mask| {
|
||||
let mask_render_state = |mask_name, stencil_state, write_mask, blend| {
|
||||
device.create_render_pipeline(&create_pipeline_descriptor(
|
||||
create_debug_label!("{} pipeline {}", name, mask_name).as_deref(),
|
||||
shader,
|
||||
|
@ -313,7 +365,7 @@ fn create_shape_pipeline(
|
|||
}),
|
||||
&[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
|
||||
blend,
|
||||
write_mask,
|
||||
})],
|
||||
vertex_buffers_layout,
|
||||
|
@ -321,36 +373,53 @@ fn create_shape_pipeline(
|
|||
))
|
||||
};
|
||||
|
||||
let mask_pipelines = enum_map! {
|
||||
MaskState::NoMask => mask_render_state("no mask", wgpu::StencilFaceState {
|
||||
compare: wgpu::CompareFunction::Always,
|
||||
fail_op: wgpu::StencilOperation::Keep,
|
||||
depth_fail_op: wgpu::StencilOperation::Keep,
|
||||
pass_op: wgpu::StencilOperation::Keep,
|
||||
},
|
||||
wgpu::ColorWrites::ALL),
|
||||
MaskState::DrawMaskStencil => mask_render_state("draw mask stencil", wgpu::StencilFaceState {
|
||||
compare: wgpu::CompareFunction::Equal,
|
||||
fail_op: wgpu::StencilOperation::Keep,
|
||||
depth_fail_op: wgpu::StencilOperation::Keep,
|
||||
pass_op: wgpu::StencilOperation::IncrementClamp,
|
||||
},
|
||||
wgpu::ColorWrites::empty()),
|
||||
MaskState::DrawMaskedContent => mask_render_state("draw masked content", wgpu::StencilFaceState {
|
||||
ShapePipeline::build(|blend_mode, mask_state| {
|
||||
let blend = blend_mode_to_state(blend_mode);
|
||||
match mask_state {
|
||||
MaskState::NoMask => mask_render_state(
|
||||
"no mask",
|
||||
wgpu::StencilFaceState {
|
||||
compare: wgpu::CompareFunction::Always,
|
||||
fail_op: wgpu::StencilOperation::Keep,
|
||||
depth_fail_op: wgpu::StencilOperation::Keep,
|
||||
pass_op: wgpu::StencilOperation::Keep,
|
||||
},
|
||||
wgpu::ColorWrites::ALL,
|
||||
blend,
|
||||
),
|
||||
MaskState::DrawMaskStencil => mask_render_state(
|
||||
"draw mask stencil",
|
||||
wgpu::StencilFaceState {
|
||||
compare: wgpu::CompareFunction::Equal,
|
||||
fail_op: wgpu::StencilOperation::Keep,
|
||||
depth_fail_op: wgpu::StencilOperation::Keep,
|
||||
pass_op: wgpu::StencilOperation::IncrementClamp,
|
||||
},
|
||||
wgpu::ColorWrites::empty(),
|
||||
blend,
|
||||
),
|
||||
MaskState::DrawMaskedContent => mask_render_state(
|
||||
"draw masked content",
|
||||
wgpu::StencilFaceState {
|
||||
compare: wgpu::CompareFunction::Equal,
|
||||
fail_op: wgpu::StencilOperation::Keep,
|
||||
depth_fail_op: wgpu::StencilOperation::Keep,
|
||||
pass_op: wgpu::StencilOperation::Keep,
|
||||
},
|
||||
wgpu::ColorWrites::ALL),
|
||||
MaskState::ClearMaskStencil => mask_render_state("clear mask stencil", wgpu::StencilFaceState {
|
||||
wgpu::ColorWrites::ALL,
|
||||
blend,
|
||||
),
|
||||
MaskState::ClearMaskStencil => mask_render_state(
|
||||
"clear mask stencil",
|
||||
wgpu::StencilFaceState {
|
||||
compare: wgpu::CompareFunction::Equal,
|
||||
fail_op: wgpu::StencilOperation::Keep,
|
||||
depth_fail_op: wgpu::StencilOperation::Keep,
|
||||
pass_op: wgpu::StencilOperation::DecrementClamp,
|
||||
},
|
||||
wgpu::ColorWrites::empty()),
|
||||
};
|
||||
|
||||
ShapePipeline { mask_pipelines }
|
||||
wgpu::ColorWrites::empty(),
|
||||
blend,
|
||||
),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ libflate = {version = "1.2", optional = true}
|
|||
log = "0.4"
|
||||
flate2 = {version = "1.0", optional = true}
|
||||
lzma-rs = {version = "0.2.0", optional = true }
|
||||
enum-map = "2.4.0"
|
||||
|
||||
[features]
|
||||
default = ["flate2", "lzma"]
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
//! <https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf>
|
||||
use crate::string::SwfStr;
|
||||
use bitflags::bitflags;
|
||||
use enum_map::Enum;
|
||||
|
||||
mod color;
|
||||
mod fixed;
|
||||
|
@ -459,7 +460,7 @@ pub struct GradientBevelFilter {
|
|||
pub num_passes: u8,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)]
|
||||
#[derive(Default, Clone, Copy, Debug, Eq, FromPrimitive, PartialEq, Enum)]
|
||||
pub enum BlendMode {
|
||||
#[default]
|
||||
Normal = 0,
|
||||
|
|
|
@ -227,6 +227,7 @@ swf_tests! {
|
|||
(as3_dictionary_in, "avm2/dictionary_in", 1),
|
||||
(as3_dictionary_namespaces, "avm2/dictionary_namespaces", 1),
|
||||
(as3_displayobject_alpha, "avm2/displayobject_alpha", 1),
|
||||
(as3_displayobject_blendmode, "avm2/displayobject_blendmode", 1, img = true),
|
||||
(as3_displayobject_hittestobject, "avm2/displayobject_hittestobject", 1),
|
||||
(as3_displayobject_hittestpoint, "avm2/displayobject_hittestpoint", 2),
|
||||
(as3_displayobject_name, "avm2/displayobject_name", 4),
|
||||
|
@ -1301,6 +1302,11 @@ fn run_swf(
|
|||
.with_log(TestLogBackend::new(trace_output.clone()))
|
||||
.with_navigator(NullNavigatorBackend::with_base_path(base_path, &executor))
|
||||
.with_max_execution_duration(Duration::from_secs(300))
|
||||
.with_viewport_dimensions(
|
||||
movie.width().to_pixels() as u32,
|
||||
movie.height().to_pixels() as u32,
|
||||
1.0,
|
||||
)
|
||||
.with_movie(movie)
|
||||
.build();
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 702 B |
|
@ -0,0 +1 @@
|
|||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue