From 361440f4b5e30e37df61f3f0a44ddb04e66eb641 Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Mon, 18 May 2026 15:18:05 -0700 Subject: [PATCH 1/2] avm2: Fix stack trace for context3DCreated event handler Previously, the asynchronicity of context3d creation was implemented entirely in AS using setTimeout. This added extra methods to the stack trace. Asynchronicity for context3D creation is now handled entirely in Rust. This removes extra methods from the stack trace from inside the event handler. --- core/src/avm2.rs | 2 +- .../src/avm2/globals/flash/display/Stage3D.as | 14 +- core/src/avm2/globals/flash/display/stage.rs | 6 +- .../avm2/globals/flash/display/stage_3d.rs | 23 ++-- .../globals/flash/display3D/context_3d.rs | 2 +- .../globals/flash/events/EventDispatcher.as | 1 - core/src/avm2/object/context3d_object.rs | 17 ++- core/src/avm2/object/stage3d_object.rs | 61 ++++++-- core/src/display_object/stage.rs | 25 +++- core/src/frame_lifecycle.rs | 3 + .../output.ruffle.txt | 130 ------------------ .../avm2/stage3d_agal_upload_errors/test.toml | 1 - 12 files changed, 96 insertions(+), 189 deletions(-) delete mode 100644 tests/tests/swfs/avm2/stage3d_agal_upload_errors/output.ruffle.txt diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 40083888b156..563ffbb036aa 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -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; diff --git a/core/src/avm2/globals/flash/display/Stage3D.as b/core/src/avm2/globals/flash/display/Stage3D.as index 122bb642bd1b..5290f8db63a0 100644 --- a/core/src/avm2/globals/flash/display/Stage3D.as +++ b/core/src/avm2/globals/flash/display/Stage3D.as @@ -15,20 +15,12 @@ package flash.display { private native function requestContext3D_internal(context3DRenderMode:String, profiles:Vector.):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.([profile])); - }, 0); + this.requestContext3D_internal(context3DRenderMode, Vector.([profile])); } [API("692")] public function requestContext3DMatchingProfiles(profiles:Vector.):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); @@ -36,9 +28,7 @@ package flash.display { 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 { diff --git a/core/src/avm2/globals/flash/display/stage.rs b/core/src/avm2/globals/flash/display/stage.rs index 5100913eed48..f47f063f8f78 100644 --- a/core/src/avm2/globals/flash/display/stage.rs +++ b/core/src/avm2/globals/flash/display/stage.rs @@ -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()), ); diff --git a/core/src/avm2/globals/flash/display/stage_3d.rs b/core/src/avm2/globals/flash/display/stage_3d.rs index 141ae8a1a57f..a695f7a6d05a 100644 --- a/core/src/avm2/globals/flash/display/stage_3d.rs +++ b/core/src/avm2/globals/flash/display/stage_3d.rs @@ -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; @@ -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) } diff --git a/core/src/avm2/globals/flash/display3D/context_3d.rs b/core/src/avm2/globals/flash/display3D/context_3d.rs index 1eb3ae95f678..3424d3d5dec2 100644 --- a/core/src/avm2/globals/flash/display3D/context_3d.rs +++ b/core/src/avm2/globals/flash/display3D/context_3d.rs @@ -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) } diff --git a/core/src/avm2/globals/flash/events/EventDispatcher.as b/core/src/avm2/globals/flash/events/EventDispatcher.as index de98bf1ba37e..3e95ecd91d54 100644 --- a/core/src/avm2/globals/flash/events/EventDispatcher.as +++ b/core/src/avm2/globals/flash/events/EventDispatcher.as @@ -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) { diff --git a/core/src/avm2/object/context3d_object.rs b/core/src/avm2/object/context3d_object.rs index d3e915b22097..d20f71848e2c 100644 --- a/core/src/avm2/object/context3d_object.rs +++ b/core/src/avm2/object/context3d_object.rs @@ -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; @@ -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, + context: &mut UpdateContext<'gc>, + context3d: Box, 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> { diff --git a/core/src/avm2/object/stage3d_object.rs b/core/src/avm2/object/stage3d_object.rs index f7713ac676cb..a0118157ee7b 100644 --- a/core/src/avm2/object/stage3d_object.rs +++ b/core/src/avm2/object/stage3d_object.rs @@ -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)] @@ -33,18 +35,49 @@ impl<'gc> Stage3DObject<'gc> { 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> { - self.0.context3d.get() + pub fn context3d(self) -> Option> { + match self.0.context3d_status.get() { + Context3DStatus::Ready(object) => Some(object), + _ => None, + } } - pub fn set_context3d(self, context3d: Option>, 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); + // TODO the docs say FP dispatches an "error" event here + return; + } + }; + + 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 { @@ -63,9 +96,8 @@ pub struct Stage3DObjectData<'gc> { /// Base script object base: ScriptObjectData<'gc>, - /// The context3D object associated with this Stage3D object, - /// if it's been created with `requestContext3D` - context3d: Lock>>, + /// The state context3D object associated with this Stage3D object. + context3d_status: Lock>, visible: Cell, } @@ -74,3 +106,14 @@ impl<'gc> TObject<'gc> for Stage3DObject<'gc> { 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>), +} diff --git a/core/src/display_object/stage.rs b/core/src/display_object/stage.rs index cc190cf08854..46fe8deb6ba8 100644 --- a/core/src/display_object/stage.rs +++ b/core/src/display_object/stage.rs @@ -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; @@ -71,7 +71,7 @@ pub struct StageData<'gc> { loader_info: Lock>>, /// An array of AVM2 'Stage3D' instances - stage3ds: RefLock>>, + stage3ds: RefLock>>, /// A tracker for the current keyboard focused element focus_tracker: FocusTracker<'gc>, @@ -287,7 +287,7 @@ impl<'gc> Stage<'gc> { context.renderer.set_quality(quality); } - pub fn stage3ds(&self) -> Ref<'_, Vec>> { + pub fn stage3ds(&self) -> Ref<'_, Vec>> { self.0.stage3ds.borrow() } @@ -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); } } @@ -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> { @@ -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> = - (0..4).map(|_| Stage3DObject::new(context).into()).collect(); + let stage3ds: Vec> = + (0..4).map(|_| Stage3DObject::new(context)).collect(); let write = Gc::write(context.gc(), self.0); unlock!(write, StageData, avm2_object).set(Some(avm2_stage)); diff --git a/core/src/frame_lifecycle.rs b/core/src/frame_lifecycle.rs index ed817cce415f..91bdb52df526 100644 --- a/core/src/frame_lifecycle.rs +++ b/core/src/frame_lifecycle.rs @@ -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. diff --git a/tests/tests/swfs/avm2/stage3d_agal_upload_errors/output.ruffle.txt b/tests/tests/swfs/avm2/stage3d_agal_upload_errors/output.ruffle.txt deleted file mode 100644 index e9f01f6c8f6d..000000000000 --- a/tests/tests/swfs/avm2/stage3d_agal_upload_errors/output.ruffle.txt +++ /dev/null @@ -1,130 +0,0 @@ -valid: uploaded -sampler_conflict: Error: Error #3696: AGAL validation failed: Second use of sampler register needs to specify the exact same properties. At token 2 of fragment program. - at flash.display3D::Program3D/upload() - at Test/tryUpload() - at Test/contextCreated() - at flash.events::EventDispatcher/dispatchEventInternal() - at flash.events::EventDispatcher/dispatchEvent() - at flash.display::Stage3D/requestContext3D_internal() - at MethodInfo-3048() -bad_header: ArgumentError: Error #3612: Programs must be in little endian format. - at flash.display3D::Program3D/upload() - at Test/tryUpload() - at Test/contextCreated() - at flash.events::EventDispatcher/dispatchEventInternal() - at flash.events::EventDispatcher/dispatchEvent() - at flash.display::Stage3D/requestContext3D_internal() - at MethodInfo-3048() -truncated: ArgumentError: Error #3612: Programs must be in little endian format. - at flash.display3D::Program3D/upload() - at Test/tryUpload() - at Test/contextCreated() - at flash.events::EventDispatcher/dispatchEventInternal() - at flash.events::EventDispatcher/dispatchEvent() - at flash.display::Stage3D/requestContext3D_internal() - at MethodInfo-3048() -empty: ArgumentError: Error #3615: AGAL validation failed: Program size below minimum length for program. - at flash.display3D::Program3D/upload() - at Test/tryUpload() - at Test/contextCreated() - at flash.events::EventDispatcher/dispatchEventInternal() - at flash.events::EventDispatcher/dispatchEvent() - at flash.display::Stage3D/requestContext3D_internal() - at MethodInfo-3048() -bad_version: Error: Error #3615: AGAL validation failed: Program size below minimum length for fragment program. - at flash.display3D::Program3D/upload() - at Test/tryUpload() - at Test/contextCreated() - at flash.events::EventDispatcher/dispatchEventInternal() - at flash.events::EventDispatcher/dispatchEvent() - at flash.display::Stage3D/requestContext3D_internal() - at MethodInfo-3048() -bad_shader_type: Error: Error #3615: AGAL validation failed: Program size below minimum length for fragment program. - at flash.display3D::Program3D/upload() - at Test/tryUpload() - at Test/contextCreated() - at flash.events::EventDispatcher/dispatchEventInternal() - at flash.events::EventDispatcher/dispatchEvent() - at flash.display::Stage3D/requestContext3D_internal() - at MethodInfo-3048() -bad_opcode: Error: Error #3620: AGAL validation failed: Invalid opcode, value out of range: 255 at token 1 of fragment program. - at flash.display3D::Program3D/upload() - at Test/tryUpload() - at Test/contextCreated() - at flash.events::EventDispatcher/dispatchEventInternal() - at flash.events::EventDispatcher/dispatchEvent() - at flash.display::Stage3D/requestContext3D_internal() - at MethodInfo-3048() -source_output: Error: Error #3646: AGAL validation failed: Can not read from output register for source operand 1 at token 1 of fragment program. - at flash.display3D::Program3D/upload() - at Test/tryUpload() - at Test/contextCreated() - at flash.events::EventDispatcher/dispatchEventInternal() - at flash.events::EventDispatcher/dispatchEvent() - at flash.display::Stage3D/requestContext3D_internal() - at MethodInfo-3048() -source_sampler: Error: Error #3638: AGAL validation failed: Sampler register only allowed as second operand in texture instructions for source operand 1 at token 1 of fragment program. - at flash.display3D::Program3D/upload() - at Test/tryUpload() - at Test/contextCreated() - at flash.events::EventDispatcher/dispatchEventInternal() - at flash.events::EventDispatcher/dispatchEvent() - at flash.display::Stage3D/requestContext3D_internal() - at MethodInfo-3048() -dest_constant: Error: Error #3652: AGAL validation failed: Constant registers can not be written to for destination operand at token 1 of fragment program. - at flash.display3D::Program3D/upload() - at Test/tryUpload() - at Test/contextCreated() - at flash.events::EventDispatcher/dispatchEventInternal() - at flash.events::EventDispatcher/dispatchEvent() - at flash.display::Stage3D/requestContext3D_internal() - at MethodInfo-3048() -dest_attribute: Error: Error #3651: AGAL validation failed: Attribute registers can not be written to for destination operand at token 1 of vertex program. - at flash.display3D::Program3D/upload() - at Test/tryUpload() - at Test/contextCreated() - at flash.events::EventDispatcher/dispatchEventInternal() - at flash.events::EventDispatcher/dispatchEvent() - at flash.display::Stage3D/requestContext3D_internal() - at MethodInfo-3048() -dest_sampler: Error: Error #3649: AGAL validation failed: Sampler registers can not be written to for destination operand at token 1 of fragment program. - at flash.display3D::Program3D/upload() - at Test/tryUpload() - at Test/contextCreated() - at flash.events::EventDispatcher/dispatchEventInternal() - at flash.events::EventDispatcher/dispatchEvent() - at flash.display::Stage3D/requestContext3D_internal() - at MethodInfo-3048() -dest_fragreg: Error: Error #3749: AGAL validation failed: Depth output register index out of bounds for destination operand at token 1 of fragment program. - at flash.display3D::Program3D/upload() - at Test/tryUpload() - at Test/contextCreated() - at flash.events::EventDispatcher/dispatchEventInternal() - at flash.events::EventDispatcher/dispatchEvent() - at flash.display::Stage3D/requestContext3D_internal() - at MethodInfo-3048() -indirect_frag: Error: Error #3639: AGAL validation failed: Indirect addressing only allowed in vertex programs for source operand 1 at token 1 of fragment program. - at flash.display3D::Program3D/upload() - at Test/tryUpload() - at Test/contextCreated() - at flash.events::EventDispatcher/dispatchEventInternal() - at flash.events::EventDispatcher/dispatchEvent() - at flash.display::Stage3D/requestContext3D_internal() - at MethodInfo-3048() -indirect_non_const: Error: Error #3640: AGAL validation failed: Indirect addressing only allowed into constant registers for source operand 1 at token 1 of vertex program. - at flash.display3D::Program3D/upload() - at Test/tryUpload() - at Test/contextCreated() - at flash.events::EventDispatcher/dispatchEventInternal() - at flash.events::EventDispatcher/dispatchEvent() - at flash.display::Stage3D/requestContext3D_internal() - at MethodInfo-3048() -indirect_valid: uploaded -source_fragreg: Error: Error #3749: AGAL validation failed: Depth output register index out of bounds for source operand 1 at token 1 of fragment program. - at flash.display3D::Program3D/upload() - at Test/tryUpload() - at Test/contextCreated() - at flash.events::EventDispatcher/dispatchEventInternal() - at flash.events::EventDispatcher/dispatchEvent() - at flash.display::Stage3D/requestContext3D_internal() - at MethodInfo-3048() diff --git a/tests/tests/swfs/avm2/stage3d_agal_upload_errors/test.toml b/tests/tests/swfs/avm2/stage3d_agal_upload_errors/test.toml index 4d3e8f2c1cb0..31da7456a680 100644 --- a/tests/tests/swfs/avm2/stage3d_agal_upload_errors/test.toml +++ b/tests/tests/swfs/avm2/stage3d_agal_upload_errors/test.toml @@ -1,5 +1,4 @@ num_frames = 10 -known_failure = true [player_options] with_renderer = { optional = false, quality = "low" } From b1916d001fcc0557c0572cbb3c46d8754c5c3622 Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Mon, 18 May 2026 15:59:01 -0700 Subject: [PATCH 2/2] tests: Add test for stack trace of context3DCreated event handler --- .../swfs/avm2/context3d_creation/Test.as | 32 ++++++++++++++++++ .../swfs/avm2/context3d_creation/output.txt | 9 +++++ .../swfs/avm2/context3d_creation/test.swf | Bin 0 -> 763 bytes .../swfs/avm2/context3d_creation/test.toml | 4 +++ 4 files changed, 45 insertions(+) create mode 100644 tests/tests/swfs/avm2/context3d_creation/Test.as create mode 100644 tests/tests/swfs/avm2/context3d_creation/output.txt create mode 100644 tests/tests/swfs/avm2/context3d_creation/test.swf create mode 100644 tests/tests/swfs/avm2/context3d_creation/test.toml diff --git a/tests/tests/swfs/avm2/context3d_creation/Test.as b/tests/tests/swfs/avm2/context3d_creation/Test.as new file mode 100644 index 000000000000..2730fd31ffd5 --- /dev/null +++ b/tests/tests/swfs/avm2/context3d_creation/Test.as @@ -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()); + } + } +} diff --git a/tests/tests/swfs/avm2/context3d_creation/output.txt b/tests/tests/swfs/avm2/context3d_creation/output.txt new file mode 100644 index 000000000000..a77edfba8824 --- /dev/null +++ b/tests/tests/swfs/avm2/context3d_creation/output.txt @@ -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 diff --git a/tests/tests/swfs/avm2/context3d_creation/test.swf b/tests/tests/swfs/avm2/context3d_creation/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..d3398a644a220d3a82f24e1b252bc8a116b91991 GIT binary patch literal 763 zcmVby}QYFo3^1XEv2M<1gRoWX+~!J(HUpJ77#~e zMwn55FeWrx8f}xBYytoAD7=ZU&_6zaPv9fqrm;vXV?OUa_vG$5H_0WCPXOs_fFg!Q zHVy!Fd~E#K?v7Y=Vbu0GMOrmJU+56QRCJPe}D(upULj87;5i&tdPdLu_>}y{*tB>H+MtS)9Ebe$N8QMfF z6GsJ1q@q-a6;iWUOKTY|t4(V;Ew4#rMnYJ{h{lyfGNnrLR9d50qF7cWL` zp(H_LNs3j93(72}c#h%{#q$&|QoKZ|PRTN*4@y{B#L9}OZ_wBi3V;D6=cWLX`0oh| zI4l4{vLXXP2?QtxD9jO1zz8X;1A~Ey4O219xM5Pmj2R{|Oj#TsU?JMtXkRpua3Z-f zFn%HN>z|}5#X(EGK1?AIYA5MeY1o^BFc(xTp=44Og@c?=U3c%n1-mm{K7&0@h*)p_du5w+((BKMn@kvT(2qk1Iv9O#BHCi?9!P8c+`M!au~s2olo35UVOVBqkPqz_2ZLfZr1$V8VspVsiih literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/context3d_creation/test.toml b/tests/tests/swfs/avm2/context3d_creation/test.toml new file mode 100644 index 000000000000..3b42017e6bf8 --- /dev/null +++ b/tests/tests/swfs/avm2/context3d_creation/test.toml @@ -0,0 +1,4 @@ +num_frames = 2 + +[player_options] +with_renderer = { optional = false, quality = "low" }