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:
Aaron Hill 2022-07-25 11:31:40 -05:00 committed by Mike Welsh
parent 1e18fc2227
commit f7205a02a9
18 changed files with 275 additions and 64 deletions

View File

@ -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

1
Cargo.lock generated
View File

@ -3497,6 +3497,7 @@ dependencies = [
"bitstream-io",
"byteorder",
"encoding_rs",
"enum-map",
"flate2",
"libflate",
"log",

View File

@ -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)),

View File

@ -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();
}

View File

@ -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()

View File

@ -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

View File

@ -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
}

View File

@ -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())
}

View File

@ -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())
}

View File

@ -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,
),
}
})
}

View File

@ -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"]

View File

@ -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,

View File

@ -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

View File

@ -0,0 +1 @@

Binary file not shown.

Binary file not shown.

Binary file not shown.