Skip to content
Merged
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
2 changes: 1 addition & 1 deletion core/src/avm2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ pub use crate::avm2::multiname::Multiname;
pub use crate::avm2::namespace::{CommonNamespaces, Namespace};
pub use crate::avm2::object::{
ArrayObject, BitmapDataObject, ClassObject, EventObject, LoaderInfoObject, Object,
SharedObjectObject, SoundChannelObject, StageObject, TObject,
SharedObjectObject, SoundChannelObject, Stage3DObject, StageObject, TObject,
};
pub use crate::avm2::qname::QName;
pub use crate::avm2::value::Value;
Expand Down
14 changes: 2 additions & 12 deletions core/src/avm2/globals/flash/display/Stage3D.as
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,20 @@ package flash.display {
private native function requestContext3D_internal(context3DRenderMode:String, profiles:Vector.<String>):void;

public function requestContext3D(context3DRenderMode:String = "auto", profile:String = "baseline"):void {
// Several SWFS (the examples from the Context3D documentation, and the Starling framework)
// rely on the `context3DCreate` being fired asynchronously - they initialize variables
// after the call to `requestContext3D`, and then use those variables in the event handler.
// Currently, we create a `Context3D` synchronously, so we need to delay the event dispatch
var stage3d = this;
this.checkProfile(profile);
setTimeout(function() {
stage3d.requestContext3D_internal(context3DRenderMode, Vector.<String>([profile]));
}, 0);
this.requestContext3D_internal(context3DRenderMode, Vector.<String>([profile]));
}

[API("692")]
public function requestContext3DMatchingProfiles(profiles:Vector.<String>):void {
var stage3d = this;
var profiles = profiles.concat();
if (profiles.length == 0) {
throw new ArgumentError("Error #2008: Parameter profiles must be one of the accepted values.", 2008);
}
for each (var profile in profiles) {
this.checkProfile(profile);
}
setTimeout(function() {
stage3d.requestContext3D_internal("auto", profiles);
}, 0);
this.requestContext3D_internal("auto", profiles);
}

private function checkProfile(profile:String):Boolean {
Expand Down
6 changes: 1 addition & 5 deletions core/src/avm2/globals/flash/display/stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,11 +429,7 @@ pub fn get_stage3ds<'gc>(

if let Some(stage) = this.as_display_object().and_then(|this| this.as_stage()) {
let storage = VectorStorage::from_values(
stage
.stage3ds()
.iter()
.map(|obj| Value::Object(*obj))
.collect(),
stage.stage3ds().iter().map(|s| (*s).into()).collect(),
false,
Some(activation.avm2().classes().stage3d.inner_class_definition()),
);
Expand Down
23 changes: 10 additions & 13 deletions core/src/avm2/globals/flash/display/stage_3d.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use crate::avm2::globals::methods::flash_events_event_dispatcher as event_dispatcher_methods;
use crate::avm2::object::{Context3DObject, EventObject};
use crate::avm2::parameters::ParametersExt;
use crate::avm2::{Activation, Error, Value};
use ruffle_render::backend::Context3DProfile;
Expand Down Expand Up @@ -53,19 +51,18 @@ pub fn request_context3d_internal<'gc>(
})
.unwrap();

if this_stage3d.context3d().is_none() {
let context = activation.context.renderer.create_context3d(profile)?;
let context3d_obj = Context3DObject::from_context(activation, context, this_stage3d);
this_stage3d.set_context3d(Some(context3d_obj), activation.gc());
// Several SWFS (the examples from the Context3D documentation, and the
// Starling framework) rely on the `context3DCreate` being fired
// asynchronously - they initialize variables after the call to
// `requestContext3D`, and then use those variables in the event handler.

let event = EventObject::bare_default_event(activation.context, "context3DCreate");
// Our context3D creation is synchronous, so we need to delay it. Set the
// parameters that the context3d was requested with here, then actually
// create the context3d at the end of this frame in
// `frame_lifecycle::run_all_phases_avm2` and dispatch the context3DCreate
// event.

this.call_method(
event_dispatcher_methods::DISPATCH_EVENT,
&[event.into()],
activation,
)?;
}
this_stage3d.set_requesting_context3d(activation.gc(), profile);

Ok(Value::Undefined)
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/avm2/globals/flash/display3D/context_3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,6 @@ pub fn dispose<'gc>(
this.as_context_3d()
.unwrap()
.stage3d()
.set_context3d(None, activation.gc());
.clear_context3d(activation.gc());
Ok(Value::Undefined)
}
1 change: 0 additions & 1 deletion core/src/avm2/globals/flash/events/EventDispatcher.as
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ package flash.events {

public native function hasEventListener(type:String):Boolean;

[Ruffle(NativeCallable)]
public function dispatchEvent(event:Event):Boolean {
// Some SWFs rely on the getter for `target` being called
if (event.target) {
Expand Down
17 changes: 8 additions & 9 deletions core/src/avm2/object/context3d_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

use crate::avm2::Error;
use crate::avm2::activation::Activation;
use crate::avm2::object::TObject;
use crate::avm2::object::script_object::ScriptObjectData;
use crate::avm2::object::{Object, TObject};
use crate::avm2::value::Value;
use crate::avm2_stub_method;
use crate::bitmap::bitmap_data::BitmapRawData;
use crate::context::RenderContext;
use crate::context::{RenderContext, UpdateContext};
use gc_arena::{Collect, Gc, GcWeak};
use naga_agal::AgalError;
use ruffle_common::utils::HasPrefixField;
Expand Down Expand Up @@ -35,21 +35,20 @@ pub struct Context3DObjectWeak<'gc>(pub GcWeak<'gc, Context3DData<'gc>>);

impl<'gc> Context3DObject<'gc> {
pub fn from_context(
activation: &mut Activation<'_, 'gc>,
context: Box<dyn Context3D>,
context: &mut UpdateContext<'gc>,
context3d: Box<dyn Context3D>,
stage3d: Stage3DObject<'gc>,
) -> Object<'gc> {
let class = activation.avm2().classes().context3d;
) -> Self {
let class = context.avm2.classes().context3d;

Context3DObject(Gc::new(
activation.gc(),
context.gc(),
Context3DData {
base: ScriptObjectData::new(class),
render_context: Cell::new(Some(context)),
render_context: Cell::new(Some(context3d)),
stage3d,
},
))
.into()
}

pub fn stage3d(self) -> Stage3DObject<'gc> {
Expand Down
61 changes: 52 additions & 9 deletions core/src/avm2/object/stage3d_object.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
//! Object representation for Stage3D objects

use crate::avm2::Avm2;
use crate::avm2::object::script_object::ScriptObjectData;
use crate::avm2::object::{Object, TObject};
use crate::avm2::object::{Context3DObject, EventObject, TObject};
use crate::context::UpdateContext;
use core::fmt;
use gc_arena::barrier::unlock;
use gc_arena::lock::Lock;
use gc_arena::{Collect, Gc, GcWeak, Mutation};
use ruffle_common::utils::HasPrefixField;
use ruffle_render::backend::Context3DProfile;
use std::cell::Cell;

#[derive(Clone, Collect, Copy)]
Expand All @@ -33,18 +35,49 @@
context.gc(),
Stage3DObjectData {
base: ScriptObjectData::new(class),
context3d: Lock::new(None),
context3d_status: Lock::new(Context3DStatus::None),
visible: Cell::new(true),
},
))
}

pub fn context3d(self) -> Option<Object<'gc>> {
self.0.context3d.get()
pub fn context3d(self) -> Option<Context3DObject<'gc>> {
match self.0.context3d_status.get() {
Context3DStatus::Ready(object) => Some(object),
_ => None,
}
}

pub fn set_context3d(self, context3d: Option<Object<'gc>>, mc: &Mutation<'gc>) {
unlock!(Gc::write(mc, self.0), Stage3DObjectData, context3d).set(context3d)
pub fn set_requesting_context3d(self, mc: &Mutation<'gc>, profile: Context3DProfile) {
self.set_status(mc, Context3DStatus::Requested { profile });
}

pub fn clear_context3d(self, mc: &Mutation<'gc>) {
self.set_status(mc, Context3DStatus::None);
}

pub fn update_context3d_status(self, context: &mut UpdateContext<'gc>) {
if let Context3DStatus::Requested { profile } = self.0.context3d_status.get() {
let context3d = match context.renderer.create_context3d(profile) {
Ok(context3d) => context3d,
Err(err) => {
tracing::error!("Failed to create Context3d: {}", err);

Check warning on line 64 in core/src/avm2/object/stage3d_object.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (63–64)
// TODO the docs say FP dispatches an "error" event here
return;

Check warning on line 66 in core/src/avm2/object/stage3d_object.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (66)
}
};

let context3d_obj = Context3DObject::from_context(context, context3d, self);
self.set_status(context.gc(), Context3DStatus::Ready(context3d_obj));

let event = EventObject::bare_default_event(context, "context3DCreate");

Avm2::dispatch_event(context, event, self.into());
}
}

fn set_status(self, mc: &Mutation<'gc>, status: Context3DStatus<'gc>) {
unlock!(Gc::write(mc, self.0), Stage3DObjectData, context3d_status).set(status);
}

pub fn visible(self) -> bool {
Expand All @@ -63,9 +96,8 @@
/// Base script object
base: ScriptObjectData<'gc>,

/// The context3D object associated with this Stage3D object,
/// if it's been created with `requestContext3D`
context3d: Lock<Option<Object<'gc>>>,
/// The state context3D object associated with this Stage3D object.
context3d_status: Lock<Context3DStatus<'gc>>,
visible: Cell<bool>,
}

Expand All @@ -74,3 +106,14 @@
HasPrefixField::as_prefix_gc(self.0)
}
}

#[derive(Clone, Collect, Copy)]
#[collect(no_drop)]
pub enum Context3DStatus<'gc> {
None,
Requested {
#[collect(require_static)]
profile: Context3DProfile,
},
Ready(Context3DObject<'gc>),
}
25 changes: 18 additions & 7 deletions core/src/display_object/stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::avm1::Object as Avm1Object;
use crate::avm2::object::Stage3DObject;
use crate::avm2::{
Activation as Avm2Activation, Avm2, EventObject as Avm2EventObject, LoaderInfoObject,
Object as Avm2Object, StageObject as Avm2StageObject,
Stage3DObject as Avm2Stage3DObject, StageObject as Avm2StageObject,
};
use crate::backend::ui::MouseCursor;
use crate::config::Letterbox;
Expand Down Expand Up @@ -71,7 +71,7 @@ pub struct StageData<'gc> {
loader_info: Lock<Option<LoaderInfoObject<'gc>>>,

/// An array of AVM2 'Stage3D' instances
stage3ds: RefLock<Vec<Avm2Object<'gc>>>,
stage3ds: RefLock<Vec<Avm2Stage3DObject<'gc>>>,

/// A tracker for the current keyboard focused element
focus_tracker: FocusTracker<'gc>,
Expand Down Expand Up @@ -287,7 +287,7 @@ impl<'gc> Stage<'gc> {
context.renderer.set_quality(quality);
}

pub fn stage3ds(&self) -> Ref<'_, Vec<Avm2Object<'gc>>> {
pub fn stage3ds(&self) -> Ref<'_, Vec<Avm2Stage3DObject<'gc>>> {
self.0.stage3ds.borrow()
}

Expand Down Expand Up @@ -626,11 +626,10 @@ impl<'gc> Stage<'gc> {
// layer, and gets applied when we start the frame (before
// `render_viewport` is called).
for stage3d in self.stage3ds().iter() {
let stage3d = stage3d.as_stage_3d().unwrap();
if stage3d.visible()
&& let Some(context3d) = stage3d.context3d()
{
context3d.as_context_3d().unwrap().render(context);
context3d.render(context);
}
}

Expand Down Expand Up @@ -793,6 +792,18 @@ impl<'gc> Stage<'gc> {
pub fn focus_tracker(self) -> FocusTracker<'gc> {
self.0.focus_tracker
}

/// Updates the status of all the Stage3Ds.
///
/// If any of them have requested a context, they will create one and set
/// it on themselves.
pub fn check_requested_context3ds(self, context: &mut UpdateContext<'gc>) {
let stage3ds = self.stage3ds();

for stage3d in &*stage3ds {
stage3d.update_context3d_status(context);
}
}
}

impl<'gc> TDisplayObject<'gc> for Stage<'gc> {
Expand Down Expand Up @@ -824,8 +835,8 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> {
Avm2StageObject::for_display_object(context.gc(), self.into(), stage_constr);

// Always create 4 Stage3D instances for now, which matches the flash projector behavior
let stage3ds: Vec<Avm2Object<'gc>> =
(0..4).map(|_| Stage3DObject::new(context).into()).collect();
let stage3ds: Vec<Avm2Stage3DObject<'gc>> =
(0..4).map(|_| Stage3DObject::new(context)).collect();

let write = Gc::write(context.gc(), self.0);
unlock!(write, StageData, avm2_object).set(Some(avm2_stage));
Expand Down
3 changes: 3 additions & 0 deletions core/src/frame_lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ pub fn run_all_phases_avm2(context: &mut UpdateContext<'_>) {
*context.frame_phase = FramePhase::Exit;
broadcast_frame_exited(context);

// The correct time to run context3DCreated events seems to be here
stage.check_requested_context3ds(context);

// We cannot easily remove dead `GcWeak` instances from the orphan list
// inside `each_orphan_movie`, since the callback may modify the orphan list.
// Instead, we do one cleanup at the end of the frame.
Expand Down
32 changes: 32 additions & 0 deletions tests/tests/swfs/avm2/context3d_creation/Test.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package {
import flash.display.MovieClip;
import flash.display.Stage3D;
import flash.events.Event;

public class Test extends MovieClip {
public function Test() {
var s3d:Stage3D = stage.stage3Ds[0];;
var self:Test = this;

trace(this.currentFrame);

addEventListener("enterFrame", function():void {
trace("Running enterFrame, currentFrame=" + self.currentFrame);
});
addEventListener("frameConstructed", function():void {
trace("Running frameConstructed, currentFrame=" + self.currentFrame);
});
addEventListener("exitFrame", function():void {
trace("Running exitFrame, currentFrame=" + self.currentFrame);
});

s3d.addEventListener("context3DCreate", context3DCreateHandler);
s3d.requestContext3D();
}

public function context3DCreateHandler(e:Event):void {
trace("context3DCreate dispatched, currentFrame=" + this.currentFrame);
trace("Stack trace from within context3DCreate: " + new Error().getStackTrace());
}
}
}
9 changes: 9 additions & 0 deletions tests/tests/swfs/avm2/context3d_creation/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
1
Running frameConstructed, currentFrame=1
Running exitFrame, currentFrame=1
context3DCreate dispatched, currentFrame=1
Stack trace from within context3DCreate: Error
at Test/context3DCreateHandler()
Running enterFrame, currentFrame=2
Running frameConstructed, currentFrame=2
Running exitFrame, currentFrame=2
Binary file added tests/tests/swfs/avm2/context3d_creation/test.swf
Binary file not shown.
4 changes: 4 additions & 0 deletions tests/tests/swfs/avm2/context3d_creation/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
num_frames = 2

[player_options]
with_renderer = { optional = false, quality = "low" }
Loading
Loading