Skip to content

Commit

Permalink
avm2: Implement DisplayObject.blendShader (#12238)
Browse files Browse the repository at this point in the history
  • Loading branch information
Aaron1011 authored Jul 26, 2023
1 parent 6a7063a commit 583caa3
Show file tree
Hide file tree
Showing 31 changed files with 628 additions and 172 deletions.
2 changes: 1 addition & 1 deletion core/src/avm1/globals/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ fn set_blend_mode<'gc>(
) -> Result<(), Error<'gc>> {
// No-op if value is not a valid blend mode.
if let Some(mode) = value.as_blend_mode() {
this.set_blend_mode(activation.context.gc_context, mode);
this.set_blend_mode(activation.context.gc_context, mode.into());
} else {
tracing::error!("Unknown blend mode {value:?}");
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/avm1/globals/movie_clip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1686,7 +1686,7 @@ fn set_blend_mode<'gc>(
) -> Result<(), Error<'gc>> {
// No-op if value is not a valid blend mode.
if let Some(mode) = value.as_blend_mode() {
this.set_blend_mode(activation.context.gc_context, mode);
this.set_blend_mode(activation.context.gc_context, mode.into());
} else {
tracing::error!("Unknown blend mode {value:?}");
}
Expand Down
1 change: 1 addition & 0 deletions core/src/avm2/globals/flash/display/DisplayObject.as
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ package flash.display {

public native function getRect(targetCoordinateSpace:DisplayObject):Rectangle;

public native function set blendShader(value:Shader):void;
}

}
36 changes: 33 additions & 3 deletions core/src/avm2/globals/flash/display/display_object.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! `flash.display.DisplayObject` builtin/prototype
use crate::avm2::activation::Activation;
use crate::avm2::error::{argument_error, illegal_operation_error, make_error_2008};
use crate::avm2::error::{argument_error, illegal_operation_error, make_error_2008, type_error};
use crate::avm2::filters::FilterAvm2Ext;
use crate::avm2::object::{Object, TObject};
use crate::avm2::parameters::ParametersExt;
Expand All @@ -16,10 +16,11 @@ use crate::string::AvmString;
use crate::types::{Degrees, Percent};
use crate::vminterface::Instantiator;
use crate::{avm2_stub_getter, avm2_stub_setter};
use ruffle_render::blend::ExtendedBlendMode;
use ruffle_render::filters::Filter;
use std::str::FromStr;
use swf::Rectangle;
use swf::Twips;
use swf::{BlendMode, Rectangle};

pub fn display_object_allocator<'gc>(
class: ClassObject<'gc>,
Expand Down Expand Up @@ -752,7 +753,7 @@ pub fn set_blend_mode<'gc>(
if let Some(dobj) = this.as_display_object() {
let mode = args.get_string(activation, 0)?;

if let Ok(mode) = BlendMode::from_str(&mode.to_string()) {
if let Ok(mode) = ExtendedBlendMode::from_str(&mode.to_string()) {
dobj.set_blend_mode(activation.context.gc_context, mode);
} else {
tracing::error!("Unknown blend mode {}", mode);
Expand Down Expand Up @@ -1032,3 +1033,32 @@ pub fn set_opaque_background<'gc>(

Ok(Value::Undefined)
}

pub fn set_blend_shader<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(dobj) = this.as_display_object() {
let Some(shader_data) = args
.get_object(activation, 0, "shader")?
.get_public_property("data", activation)?
.as_object()
else {
return Err(Error::AvmError(type_error(
activation,
"Error #2007: Parameter data must be non-null.",
2007,
)?));
};

let shader_handle = shader_data
.as_shader_data()
.expect("Shader.data is not a ShaderData instance")
.pixel_bender_shader()
.expect("Missing compiled PixelBender shader");

dobj.set_blend_shader(activation.context.gc_context, Some(shader_handle));
}
Ok(Value::Undefined)
}
7 changes: 5 additions & 2 deletions core/src/bitmap/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::context::{RenderContext, UpdateContext};
use crate::display_object::TDisplayObject;
use gc_arena::MutationContext;
use ruffle_render::bitmap::{PixelRegion, PixelSnapping};
use ruffle_render::commands::{CommandHandler, CommandList};
use ruffle_render::commands::{CommandHandler, CommandList, RenderBlendMode};
use ruffle_render::filters::Filter;
use ruffle_render::matrix::Matrix;
use ruffle_render::quality::StageQuality;
Expand Down Expand Up @@ -1518,7 +1518,10 @@ pub fn draw<'gc>(
render_context.commands
} else {
let mut commands = CommandList::new();
commands.blend(render_context.commands, blend_mode);
commands.blend(
render_context.commands,
RenderBlendMode::Builtin(blend_mode),
);
commands
};

Expand Down
65 changes: 34 additions & 31 deletions core/src/debug_ui/display_object.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod search;

use ruffle_render::blend::ExtendedBlendMode;
pub use search::DisplayObjectSearchWindow;

use crate::avm1::TObject as _;
Expand All @@ -13,7 +14,7 @@ use egui::collapsing_header::CollapsingState;
use egui::{Button, Checkbox, CollapsingHeader, ComboBox, Grid, Id, TextEdit, Ui, Widget, Window};
use ruffle_wstr::{WStr, WString};
use std::borrow::Cow;
use swf::{BlendMode, ColorTransform, Fixed8};
use swf::{ColorTransform, Fixed8};

const DEFAULT_DEBUG_COLORS: [[f32; 3]; 10] = [
[0.00, 0.39, 0.00], // "darkgreen" / #006400
Expand All @@ -28,21 +29,22 @@ const DEFAULT_DEBUG_COLORS: [[f32; 3]; 10] = [
[1.00, 0.87, 0.68], // "navajowhite" / #ffdead
];

const ALL_BLEND_MODES: [BlendMode; 14] = [
BlendMode::Normal,
BlendMode::Layer,
BlendMode::Multiply,
BlendMode::Screen,
BlendMode::Lighten,
BlendMode::Darken,
BlendMode::Difference,
BlendMode::Add,
BlendMode::Subtract,
BlendMode::Invert,
BlendMode::Alpha,
BlendMode::Erase,
BlendMode::Overlay,
BlendMode::HardLight,
const ALL_BLEND_MODES: [ExtendedBlendMode; 15] = [
ExtendedBlendMode::Normal,
ExtendedBlendMode::Layer,
ExtendedBlendMode::Multiply,
ExtendedBlendMode::Screen,
ExtendedBlendMode::Lighten,
ExtendedBlendMode::Darken,
ExtendedBlendMode::Difference,
ExtendedBlendMode::Add,
ExtendedBlendMode::Subtract,
ExtendedBlendMode::Invert,
ExtendedBlendMode::Alpha,
ExtendedBlendMode::Erase,
ExtendedBlendMode::Overlay,
ExtendedBlendMode::HardLight,
ExtendedBlendMode::Shader,
];

#[derive(Debug, Eq, PartialEq, Hash, Default, Copy, Clone)]
Expand Down Expand Up @@ -680,22 +682,23 @@ fn display_object_type(object: DisplayObject) -> &'static str {
}
}

fn blend_mode_name(mode: BlendMode) -> &'static str {
fn blend_mode_name(mode: ExtendedBlendMode) -> &'static str {
match mode {
BlendMode::Normal => "Normal",
BlendMode::Layer => "Layer",
BlendMode::Multiply => "Multiply",
BlendMode::Screen => "Screen",
BlendMode::Lighten => "Lighten",
BlendMode::Darken => "Darken",
BlendMode::Difference => "Difference",
BlendMode::Add => "Add",
BlendMode::Subtract => "Subtract",
BlendMode::Invert => "Invert",
BlendMode::Alpha => "Alpha",
BlendMode::Erase => "Erase",
BlendMode::Overlay => "Overlay",
BlendMode::HardLight => "HardLight",
ExtendedBlendMode::Normal => "Normal",
ExtendedBlendMode::Layer => "Layer",
ExtendedBlendMode::Multiply => "Multiply",
ExtendedBlendMode::Screen => "Screen",
ExtendedBlendMode::Lighten => "Lighten",
ExtendedBlendMode::Darken => "Darken",
ExtendedBlendMode::Difference => "Difference",
ExtendedBlendMode::Add => "Add",
ExtendedBlendMode::Subtract => "Subtract",
ExtendedBlendMode::Invert => "Invert",
ExtendedBlendMode::Alpha => "Alpha",
ExtendedBlendMode::Erase => "Erase",
ExtendedBlendMode::Overlay => "Overlay",
ExtendedBlendMode::HardLight => "HardLight",
ExtendedBlendMode::Shader => "Shader",
}
}

Expand Down
56 changes: 46 additions & 10 deletions core/src/display_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ use crate::vminterface::Instantiator;
use bitflags::bitflags;
use gc_arena::{Collect, MutationContext};
use ruffle_macros::enum_trait_object;
use ruffle_render::pixel_bender::PixelBenderShaderHandle;
use ruffle_render::transform::{Transform, TransformStack};
use std::cell::{Ref, RefMut};
use std::fmt::Debug;
use std::hash::Hash;
use std::sync::Arc;
use swf::{BlendMode, ColorTransform, Fixed8};
use swf::{ColorTransform, Fixed8};

mod avm1_button;
mod avm2_button;
Expand Down Expand Up @@ -50,7 +51,8 @@ pub use morph_shape::{MorphShape, MorphShapeStatic};
pub use movie_clip::{MovieClip, MovieClipWeak, Scene};
use ruffle_render::backend::{BitmapCacheEntry, RenderBackend};
use ruffle_render::bitmap::{BitmapHandle, BitmapInfo, PixelSnapping};
use ruffle_render::commands::{CommandHandler, CommandList};
use ruffle_render::blend::ExtendedBlendMode;
use ruffle_render::commands::{CommandHandler, CommandList, RenderBlendMode};
use ruffle_render::filters::Filter;
pub use stage::{Stage, StageAlign, StageDisplayState, StageScaleMode, WindowMode};
pub use text::Text;
Expand Down Expand Up @@ -221,7 +223,10 @@ pub struct DisplayObjectBase<'gc> {
/// The blend mode used when rendering this display object.
/// Values other than the default `BlendMode::Normal` implicitly cause cache-as-bitmap behavior.
#[collect(require_static)]
blend_mode: BlendMode,
blend_mode: ExtendedBlendMode,

#[collect(require_static)]
blend_shader: Option<PixelBenderShaderHandle>,

/// The opaque background color of this display object.
/// The bounding box of the display object will be filled with the given color. This also
Expand Down Expand Up @@ -269,6 +274,7 @@ impl<'gc> Default for DisplayObjectBase<'gc> {
maskee: None,
sound_transform: Default::default(),
blend_mode: Default::default(),
blend_shader: None,
opaque_background: Default::default(),
flags: DisplayObjectFlags::VISIBLE,
scroll_rect: None,
Expand Down Expand Up @@ -582,16 +588,24 @@ impl<'gc> DisplayObjectBase<'gc> {
changed
}

fn blend_mode(&self) -> BlendMode {
fn blend_mode(&self) -> ExtendedBlendMode {
self.blend_mode
}

fn set_blend_mode(&mut self, value: BlendMode) -> bool {
fn set_blend_mode(&mut self, value: ExtendedBlendMode) -> bool {
let changed = self.blend_mode != value;
self.blend_mode = value;
changed
}

fn blend_shader(&self) -> Option<PixelBenderShaderHandle> {
self.blend_shader.clone()
}

fn set_blend_shader(&mut self, value: Option<PixelBenderShaderHandle>) {
self.blend_shader = value;
}

/// The opaque background color of this display object.
/// The bounding box of the display object will be filled with this color.
fn opaque_background(&self) -> Option<Color> {
Expand Down Expand Up @@ -746,7 +760,7 @@ pub fn render_base<'gc>(this: DisplayObject<'gc>, context: &mut RenderContext<'_
}
context.transform_stack.push(this.base().transform());
let blend_mode = this.blend_mode();
let original_commands = if blend_mode != BlendMode::Normal {
let original_commands = if blend_mode != ExtendedBlendMode::Normal {
Some(std::mem::take(&mut context.commands))
} else {
None
Expand Down Expand Up @@ -902,7 +916,16 @@ pub fn render_base<'gc>(this: DisplayObject<'gc>, context: &mut RenderContext<'_

if let Some(original_commands) = original_commands {
let sub_commands = std::mem::replace(&mut context.commands, original_commands);
context.commands.blend(sub_commands, blend_mode);
let render_blend_mode = if let ExtendedBlendMode::Shader = blend_mode {
// Note - Flash appears to let you set `dobj.blendMode = BlendMode.SHADER` without
// having `dobj.blendShader` result, but the resulting rendered displayobject
// seems to be corrupted. For now, let's panic, and see if any swfs actually
// rely on this behavior.
RenderBlendMode::Shader(this.blend_shader().expect("Missing blend shader"))
} else {
RenderBlendMode::Builtin(blend_mode.try_into().unwrap())
};
context.commands.blend(sub_commands, render_blend_mode);
}

context.transform_stack.pop();
Expand Down Expand Up @@ -1573,13 +1596,13 @@ pub trait TDisplayObject<'gc>:

/// The blend mode used when rendering this display object.
/// Values other than the default `BlendMode::Normal` implicitly cause cache-as-bitmap behavior.
fn blend_mode(&self) -> BlendMode {
fn blend_mode(&self) -> ExtendedBlendMode {
self.base().blend_mode()
}

/// Sets the blend mode used when rendering this display object.
/// Values other than the default `BlendMode::Normal` implicitly cause cache-as-bitmap behavior.
fn set_blend_mode(&self, gc_context: MutationContext<'gc, '_>, value: BlendMode) {
fn set_blend_mode(&self, gc_context: MutationContext<'gc, '_>, value: ExtendedBlendMode) {
if self.base_mut(gc_context).set_blend_mode(value) {
if let Some(parent) = self.parent() {
// We don't need to invalidate ourselves, we're just toggling how the bitmap is rendered.
Expand All @@ -1591,6 +1614,19 @@ pub trait TDisplayObject<'gc>:
}
}

fn blend_shader(&self) -> Option<PixelBenderShaderHandle> {
self.base().blend_shader()
}

fn set_blend_shader(
&self,
gc_context: MutationContext<'gc, '_>,
value: Option<PixelBenderShaderHandle>,
) {
self.base_mut(gc_context).set_blend_shader(value);
self.set_blend_mode(gc_context, ExtendedBlendMode::Shader);
}

/// The opaque background color of this display object.
fn opaque_background(&self) -> Option<Color> {
self.base().opaque_background()
Expand Down Expand Up @@ -2009,7 +2045,7 @@ pub trait TDisplayObject<'gc>:
self.set_bitmap_cached_preference(context.gc_context, is_bitmap_cached);
}
if let Some(blend_mode) = place_object.blend_mode {
self.set_blend_mode(context.gc_context, blend_mode);
self.set_blend_mode(context.gc_context, blend_mode.into());
}
if self.swf_version() >= 11 {
if let Some(visible) = place_object.is_visible {
Expand Down
2 changes: 1 addition & 1 deletion core/src/display_object/avm1_button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ impl<'gc> Avm1Button<'gc> {
// Set transform of child (and modify previous child if it already existed)
child.set_matrix(context.gc_context, record.matrix.into());
child.set_color_transform(context.gc_context, record.color_transform);
child.set_blend_mode(context.gc_context, record.blend_mode);
child.set_blend_mode(context.gc_context, record.blend_mode.into());
child.set_filters(
context.gc_context,
record.filters.iter().map(Filter::from).collect(),
Expand Down
2 changes: 1 addition & 1 deletion core/src/display_object/avm2_button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ impl<'gc> Avm2Button<'gc> {

if swf_state != swf::ButtonState::HIT_TEST {
child.set_color_transform(context.gc_context, record.color_transform);
child.set_blend_mode(context.gc_context, record.blend_mode);
child.set_blend_mode(context.gc_context, record.blend_mode.into());
child.set_filters(
context.gc_context,
record.filters.iter().map(Filter::from).collect(),
Expand Down
Loading

0 comments on commit 583caa3

Please sign in to comment.