Some of the tests are currently disabled because (separate from this PR)
we can't actually run `toString()` on a Vector.<Object>, due to our
broken vector handling.
* avm2+tests: Allow setting prototype to undefined
This is used internally by Adobe Flex in many places. Also add test.
* avm2: Set prototype when constructing a Function with null prototype
* tests: Expand prototype_set_null test
* chore: fmt
The special meta key `defaultValue` should be used to set
the default `value` field. Also, `index` is the position
within *just* the image or texture params, not within
all of the params.
Interfaces should only be listed when calling describeType
with a class instance (not with a `Class` object).
Additionally, AS3 methods can actually be displayed under
some circumstances. This is due to weird legacy behavior
that avmplus implements. A full fix requirs us to implement
namespace versioning, but we can get closer by special-casing
playerglobals (which is special-cased by namespace versioning
anyway).
The 'DisplayObject.visible' flag seems to be ignored for mask objects
(and their descendants) - mouse picking still takes them into account,
and they get rendered as part of the mask.
This commit changes display object rendering and AVM2 mouse picking
to ignore DisplayObject.visibility when dealing with mask objects/
descendants.
The suggested changes in the feedback to the pull request have been
implemented.
Therefore, this commit consists of multiple smaller changes:
- The unload_movie method has been renamed to avm1_unload_movie.
- The movieclip_default_state test now doesn't test
getTextSnapshot().getCount() because the underlying methods haven't
been implemented in AVM1 yet.
This should be reverted when they have been implemented or stubbed in
AVM1.
Two tests testing the different states of a MovieClip have been added.
A MovieClip can be in different specific states (namely: default state,
initial loading state, error state, image state, success state and
unloaded state). A state of a MovieClip consists of the values of all
properties and the results of some getter functions of the MovieClip.
To make sure that all of these states are implemented correctly and will
continue to be so in the future, two MovieClip state tests have been
added.
The movieclip_default_state test tests the default state of a MovieClip
after it has been created with createEmptyMovieClip.
The movieclip_state_values test has MovieClips going through all other
states and tests if the values are correct in each.
These tests currently only test the MovieClip states when trying to load
a local file since Ruffle tests currently don’t support tests using
remote URLs. Test cases for remote URLs are still included and can be
used if Ruffle starts supporting remote URLs at some point.
A HelperTest, which can be used to find out about how the state of a
MovieClip changes when trying to load and unload it, has been included
as well.
The behaviour of when the results of getBounds / getRect calls on
MovieClips with invalid bounds are 6710886.35 and when they are
6710886.4 for each corner of the rectangle is rather complex.
To make sure it works correctly in every case and will continue to do so
in the future, eight tests testing the results of getBounds calls under
these circumstances have been added.
This needs to be tested in several tests because an internal state
determining the results can change irreversibly. Making sure that it
changes correctly on different occasions takes several tests.
All NavigatorBackend implementations have been refactored, resulting in
improved code quality, less duplicated code, more consistent and easier
to understand procedures, additional error handling and better error
messages.
A resolve_url method has been added to the NavigatorBackend trait. It
takes a URL and and resolves it to the actual URL from which a file can
be fetched (including handling of relative links and pre-processing). It
has been implemented in each NavigatorBackend implementation.
Duplicated code has been put into new public functions in
core/src/backend/navigator.rs which are called by all NavigatorBackend
implementations.
ExternalNavigatorBackend:
- The navigate_to_url and fetch methods have been adapted to use
resolve_url, removing redundant code.
- Error handling has been added in case that the URL can't be converted
to a PathBuf.
- A TODO about differences between the flash player fetch and the
Ruffle fetch implementation has been added.
WebNavigatorBackend:
- The previous resolve_url method exclusively to the WebNavigatorBackend
has been replaced by the new resolve_url method. It is used by
navigate_to_url and fetch.
- resolve_url now always pre-processes the URL if it's valid (even if no
base_url exists) and explicitly returns whether the URL can be parsed.
- navigate_to_url now traces an explanatory error each if the URL can't
be parsed or is local.
- fetch now returns an explanatory error each if the URL can't be parsed
or is local (previously, a vague "Got JS error" has been returned).
TestNavigatorBackend & NullNavigatorBackend:
- fetch pre-processes the URL now (using the resolve_url implementation).
- If the URL isn't local, an explanatory error is returned (previously,
it was just an "Invalid URL" error).
- If the URL can't be parsed, an explanatory error with the reason is
returned (previously, it was just an "Invalid URL" error).
Additionally, error messages in all NavigatorBackend implementations
have been improved and made more consistent, e.g. if a local file can't
be read.
A load_error_swf function has been added to the Loader. It makes the
MovieClip enter the error state in which some attributes have certain
error values to signal that no valid file could be loaded. This happens
if no file could be loaded or if the loaded content is no valid
supported content.
The function creates an error state movie stub using the new
SwfMovie::error_movie function (which uses a new default_error_header
function) and configures remaining variables with the
movie_not_available method.
One TODO in order for the error state to be completely implemented has
been added.
Since the error state of the MovieClip includes the final URL of the SWF
file obtained after any redirects, the load_error_swf and
movie_loader_error functions (now) take an swf_url attribute.
To get this URL in case no file could be loaded, the
NavigatorBackend::fetch method has been changed to return an
ErrorResponse struct (including the url and the actual error) in the
error case. The Response struct returned in the success case has been
renamed to SuccessResponse.
All fetch implementations have been adapted accordingly. Code has been
adjusted to return the actual error where that's needed.
Documentation has been added and improved.
* avm2: Do not remove an EditText's selection on unfocus (fix#9006)
There are significant differences between how selection and caret info
work between AVM1 and AVM2.
In AVM1, there is only a single, global selection, which applies to
whichever element is currently focused. Therefore changing the focus
necessarily erases any information of what was selected before. There
can also be no active selection at all.
In AVM2, every text field has its own independent selection info. It is
NOT optional, the default selection is just a caret at position 0. If a
field loses focus, the selection is not rendered, but it is still
present. Movies such as #9006 rely on the same selection still being
there once you give back focus to the field.
Ruffle's model of selections (an Option per text field) is different
from both the AVM1 model (an optional singleton) and the AVM2 model
(mandatory per text field). This fix does not change that, it is only a
narrow fix targeted at 9006.
* avm2: implement selectionBegin/selectionEnd/caretIndex and add a test.
The selection test validates a few situations, including the behaviour
when unfocusing that was fixed in the previous commit.
The test does not validate how the selection changes after replacing
text, as there are still some inaccuracies there.
* avm2: Additional selection fixes needed after merging with master.
1. The default caret for an AVM2 textbox is at the end of the textbox,
not the beginning.
2. Selection should not be changed when focusing on a textbox in AVM2.
3. Fixed a test whose output.txt didn't actually match the flash player
output.
* avm2: Make the selection AVM checks compatible with mixed AVM, and revert the on_focus_changed parameters
---------
Co-authored-by: Nathan Adams <dinnerbone@dinnerbone.com>
Since this functionality was not enabled before, the reference image is
not deblocked. This commit disables deblocking for the test to match the
reference image, and eliminate deblocking as a source of differences.
When using a 'Loader', properties on the 'contentLoaderInfo' become
set during specific events in the load sequence. In particular,
'LoaderInfo.bytesTotal' becomes available during the first 'progress'
event.
Also, 'LoaderInfo.parameters' is now properly set from the URL query
parameters. In Flash player, this work even with filesystem urls
(e.g. 'file:///some/path/to/file.txt?paramOne=valOne' will load
a file named 'file.txt', setting and expose the parameter 'paramOne'
with value 'valOne' in `LoaderInfo.parameters`). This required some
cleanup to the desktop and test NavigatorBackend impls to strip
out query parameters when loading a parameter from disk.
Previously, we would set `SwfMovie.parameters` manually from the url.
Now, the various `SwfMovie` constructors automatically extract
query parameters from the provided url. Outside of `SwfMovie`,
we only append *extra* parameters (e.g. those set from `flashvars`).
This makes CPMStar ads work, since the loaded SWF needs to access
`LoaderInfo.parameters`
The 'winding' argument and filling behavior described in the docs
are not yet implemented. However, this implementation is good enough
for Scratch to render its default cat image.
We were previously only ever checking children,
and not attributes.
In order to avoid matching both attributes and elements
with a given name in 'descendants', `E4xNode::matches_name`
now checks `is_attribute` on the provided `Multiname`. This
requries changing several other parts of the codebase to
properly set this flag on `Multinames` provided by ActionScript.
You can also specify a `tick_rate` in order to get ticks faster or slower than the stage frame rate.
We also enable video decode and ticks on the netstream tests, since our netstream impl requires ticks.
`XML(someXMLObj)` and `XMLList(someXMLList)` perform the
normal cast behavior (returning the same object), instead
of creating a new object like other arguments do.
This also applies to `new XMLList(someXMLObj)`
and `XML(singleElemXMLList)`
* wpgu: Initial implementation of PixelBender shader execution
The implementation is split across four crates:
* `ruffle_render` now holds the main PixelBender bytecode parsing
implementation (previously, this was in `ruffle_core`).
* `ruffle_core` holds some helper functions for converting between
AVM2 `Value`s and the PixelBender vector types.
* `naga-pixelbender` (newly created) constructs a Naga `Module`
from parsed PixelBender bytecode
* `ruffle_render_wgpu` sets up the render pipeline for the shader
constructed by `naga-pixelbender`, and actually executes the shader.
The Actionscript-side shader parameters are passed in through uniforms.
This allows us to cache the compiled `naga::Module` and associated
wgpu types inside `ShaderData`, when it's first created. Each invocation
of a `ShaderJob` only needs to create a bind group and render pass.
Limitations:
* Only a few of the PixelBender opcodes are implemented - however, this is
enough to get Stemlands cannon rotation working, as well as a cool
"donut" shader that I found and included as a test.
* PixelBender matrix types are not supported.
* Only BitmapData is supported as an input/output type - Flash Player
also supports using Vector and ByteArray
* ShaderJob execution is always synchronous.
* Adjust comments
* Address review comments
We create a separate child domain, which is accessible
from the Stage and the root movieclip.
This prevents ActionScript from loading classes into the
special playerglobals domain (Domain.parentDomain is modified
to return null instead of the playerglobals domain when applicable),
so the native method lookup logic will never run for user code.
We now support deleting named children, as well as attributes.
Additionally, I've fixed our handling of `XML.parent()` - we now
properly set the parent when a child is created, and clear the parent
when `delete` is used.
If a child named 'foo' is removed by the timeline (without
having been previous added/removed from the timeline by ActionScript),
then the 'foo' field in the parent will be set to null. This occurs
even if the 'foo' field in the parent is not currently set to
the child 'foo' (e.g. 'this.foo = someOtherObject' was executed by
ActionScript).
The Adobe Animate compiler rejects a subclass that contains
a non-private field with the same name as a field in an ancestor
class (e.g. 'pub var foo:String' in both the subclass and superclass).
Unfortunately, Flash Player accepts this code, and creates a distinct
field for each class (even though they have the same namespace and
name). I suspect that this is caused by the optimizer replacing the
field accesses with internal slot accesses.
I've added an ignored test demonstrating this behavior - getting Ruffle
to reproduce it will be tricky.
When we run a 'goto', a weird "nested frame" gets triggered.
Previously, we were only calling `construct_frame` on the target
MovieClip as part of this "nested frame". However, Flash Player
seems to treat this (in some ways) like a normal frame - *all*
objects on the Stage (and orphans) have `construct_frame` called.
In particular, `gotoAndStop`/`gotoAndPlay` is called during
an "enterFrame" event handler, then unrelated objects on the Stage
will have their children constructed during the execution of
`gotoAndStop`/`gotoAndPlay`. The same logic holds for frame scripts.
This fixes a bug in Steamlands, which relies on children on the main
timeline being constructed immediately following a call to `gotoAndStop`
on an orphan (originally triggered from an "enterFrame" handler).
QName can contain characters like '.' and '<' as part of the
package or class name (though this requires editing the SWF
or using a nonstandard compiler). This broke our attempt to parse
generic type paramters by looking for '.<'
Instead, our 'Vector' special casing now operates on the unparsed
'AVMString' name, instead of attempting to construct a 'QName'.
This means that we don't need to handle generic paramters at
all for obfuscated names (which will never start with '__AS3__'
or 'Vector.<')
This fixes a bug in Red Ball 4v3, which has an obfuscated class
'!D.<H'
If we have two PlaceObject tags in the same frame with the same depth,
only the first one actually places an object. The second one is ignored
(Flash Player logs a warning).
We now parse PixelBender bytecode, and populate the parameters
from the bytecode on `ShaderData`. This is enough to progress
Steamlands, which needs to access dynamically set properties
on `ShaderData`
Bytecode execution is not implemented yet.
When we create a DisplayObject from ActionScript, we should always
run `construct_frame` on it, regardless of what frame phase we
are currently in.
This fixes a regression in Fancy Pants World 4 Part 1, where entering
the first door produced an error.
Flash Player performs `x + width` and `y + height` as floating
point operations before `round_to_even`. This affects the extent
covered by a `Rectangle` in various BitmapData methods, as the sum
of two values might be large enough to be rounded up to a larger
value (when rounding `x` and `width` individually would have
produced a smaller overall extent).
These are directly set on the underlying navigator's HTTP
request type, and get printed out in our test navigator backend.
No validation of the header names is performed - on web, this will be
enforced by the browser.
When we iterate over a render list (in order to call
`enter_frame`, `construct_frame, etc.) we need to be sure
that we iterate over all of the original `DisplayObjects`
in the list, even if the list is modified during iteration
(e.g. some ActionScript code calls `parent.addChildAt`).
Previously, `RenderIter` would repeatedly call `child_by_index`
on the original `DisplayObjectContainer`, up to the original
child count. If new DisplayObjects were inserted into the list
during iteration, we could miss some of the original DisplayObjects
in the list (as they are now at a greater position in the list).
To solve this, we now store the render list as an `Rc<Vec>`,
and use `Rc::make_mut` to modify it. See the comments
for more details.
With SWF version < 30, Context3D.configureBackBuffer throws
an error with a less informative message when the width/height
are out of range. Additionally, it seems to special case
the case width=0, height=0, antiAlias=0. enableDepthAndStencil=false,
and does *not* throw an error. This is relied on by Sniper Team.
The Flash Player 'Matrix3D.recompose' method throws exceptions under
certain circumstances with the "quaternion" orientation style.
I haven't yet figured this out yet, so I've marked that case as a stub.
All of the implementations are based on the OpenFL code, with some
tweaks to match Flash Player's behavior.
TextFields have a very unusual behavior - if they are selectable
and have `was_static`, they *block* the dispatch of mouse events when
they're targeted (not even the Stage will receive the event).
This only occurs when the TextField is actually targeted
(which requires mouseEnabled=true). With mouseEnabled=false,
the event will be dispatched with an ancestor as the target,
following the usual logic.
Also, TextFields now properly propagate mouse picks to
their parent if mouseEnabled=false. Previously, setting
mouseEnabled=false for a TextField made all mouse picks
cause a miss on it. This was the cause of the Turbo Kids
regression.
Fixes#10245
This can actually affect runtime behavior - if the return type is
declared as 'int', then an instance of a custom class will get
coerced to 0 when returned by the function.
'Plants vs Zombies Demo' relies on this - it has a function which
incorrectly returns an object instead of an array index, but the
value gets silently coerced to 0 under Flash Player.
This is based on URLLoader, and doesn't actualy 'stream' data -
it all becomes available once the request finishes. However, this
is good enough to get Sniper Team working.
We now run all of the completion logic (including adding
the new DisplayObject as a child) in `Loader::movie_loader_complete`.
Previously, some of this logic was run from `Loader::preload_tick`,
which meant that loaded images did not have the logic run.
Also, `BitmapData` and `Bitmap` instances (with corresponding AVM2
objects) are now properly constructed for loaded images.
In a previous PR, I introduced an optimization that used
`copy_texture_to_texture` to copy directly from a BitmapData GPU
texture to a Stage3D GPU texture.
Unfortunately, this optimization is incorrect. A BitmapData GPU
texture can be modified at any time by normal AVM2 code - in
particular, in might be modified before we submit the encoded
`copy_texture_to_texture` command. This shows up in Sniper Team,
which re-uses BitmapData objects for multiple distinct textures.
The previous 'optimization' resulted in the wrong BitmapData contents
getting uploaded to a texture (since it was changed before the copy
command was submitted).
When multisampling is enabled, we should create a new multisampled texture,
and use the existing texture as the resolve buffer. We also need to
call `update_has_depth_texture` to keep our pipeline aware of whether
or not we currently have a depth buffer attached.
Makes progress on #10641 (it has a stack overflow after
this PR, due to an unrelated issue).
wgpu requires buffer copy sizes and offsets to be 4-byte aligned.
Unfortunately, ActionScript can perform 2-byte aligned uploads
into an IndexBuffer3D.
To support this, we now keep a copy of the IndexBuffer3D on the CPU.
When performing an upload to the buffer, we round the offset down
and the size up to the nearest 4-byte aligned value. The cpu buffer
is used to fill out the write with existing data, so that we don't
corrupt the contents of the GPU buffer.
To avoid introducing a new RefCell, I've changed IndexBuffer3D
to use a `Box` instead of an `Rc` to store the trait object.
This allows us to pass a mutable reference down to the backend.
Early class construction is tricky - `Object` defines properties
that need to get copied into subclass instance vtables, but `Class`
defines `prototype`, which needs to be copied into the *class* vtable
of `Object`.
To accomplish this, I've split out instance vtable initialization
into a separate `init_instance_vtable`. We call
`object_class.init_instance_vtable` before
`class_class.init_instance_vtable`, but do things in the opposite
order for `into_finished_class` (`class_class.into_finished_class` is
called before `object_class.into_finished_class`)
It's possible to have a DefineSprite tag with multiple frames,
but with a corresponding SymbolClass that directly extends
`Sprite` (and therefore does *not* extend `MovieClip`). When this
happens, Flash Player stops after the first frame.
Doing `super.someNonGetter` gives you back a function object.
We were previously attempting to call normal methods as though
they were getters. Additionally, we were failing to properly
get the property from the superclass vtable.
If a SWF contains multiple DefineFont tags with the same
font name (but different font IDs), the first tag will win
when a font is looked up by *name*. This affects the behavior
of EditText objects, which can have embedded HTML like
`<font face="MyFontName">` which performs a font lookup by name.
This fixes Fancy Pants World 4 Part 3, which contains two
DefineFont3 tags with the name FancyFont. The second font is
missing many glyphs, so using it causes us to be unable to
render the squiggle and life count text.
When we receieve a nonzero 'antiAlias' parameter, we create
create a non-multisampled resolve buffer to use with WGPU.
Several tests were already requesting antialiasing, so their
output images are now anti-aliased without any changes to
the tests themselves.
If you use a `Loader` to load an SWF containing a class that shadows
an already-defined class, the class definition from the Loader SWF
will be ignoredin favor of the already-defined class. This commit
applies this log to symbol classes as well - the symbol registry for the class
should continue to point to the existing MovieClip in the parent.
This results in the child SWF instantiating the class from the parent
SWF when the child places the affected movie clip on the timeline.
This fixes a bug in Fancy Pants World 4 Part 3, where the sub-level
SWF was replacing the symbol class entry for the parent 'shipInteract'
class with the dummy clip provided in the sub-level SWF (instead
of continuing to use the correct clip from the parent SWF).
Previously, we were scaling down the source image to fit into
the smaller sourceRect, instead of cropping at the original scale.
This broke the background textures in Fancy Pants World 4 Part 2,
as the scaled-down output image resulted in a smaller rectangle
being returned from 'getColorBoundsRect'
We now crop the image by properly constructing the UV-coordinate
transformation matrix. We were also using the wrong value for the
'destPoint' y coordinate, which I fixed.
This slightly changes the image output of two tests - the new images
now more closely match the Flash output.
Calling `Hash::write_bytes` isn't guaranteed to be equivalent to a
sequence of `Hash::write_u8`.
Additionally, make sure the hash is truly prefix-free by hashing the
length first.
This was a leftover from before we started usiung vec4 everywhere
for compatibility with AGAL. There are a few specific opcodes that
don't need extension, but it doesn't depend on the destination
register.
In the process, I fixed a bug where we were clearing the depth
and stencil buffers with the incorrect value.
This makes Fancy Pants World 4 Part 1 playable to completion
(though there are still some rendering issues that need
to be fixed).
This factors out the early-resolution logic I added in `op_coerce`,
making it useable during paramter resolution as well. This lets
a static initializer reference the containing class in parameter
types, even though the ClassObject hasn't yet been initialized.
We were missing the initial 'set_skip_next_enter_frame(true)'
call, and we weren't properly clearing it in `enter_frame`.
Loaders appear to have the same behavior as MovieClips.
This makes us correctly run the first framescript for the loaded
SWF.
Despite not being MovieClips, Loader instances appear to get
the same kind of orphan handling - you can instantiate a
Loader and call 'Loader.load' without ever adding it
to a parent, and the loader will still run.
I've changed the movie code to work with a new `DisplayObjectWeak`
enum. Currently, this just supports `MovieClip` and `Loader`,
but it can easily be extended if we ever need other weak display
objects.
This also fixes a bug where we were adding the loaded MovieClip
as a child of the Loader slightly too early.
This includes the 'GetDescendants' opcode, which is used by the
the 'xml..elementName' syntax. The 'XMLList.toXMLString()
impl makes it much easier to write tests for this.
We only support values that are neither XML nor XMLList,
since we can't yet properly stringify those.
Attempting to modify an existing attribute throws an error.
* Bump bitflags to 2.0.0
* Sprinkle Clone, Copy, Eq, PartialEq, and Debug derives where needed
* Call `bits` on bitflags, as it is now a method
* Switch from `from_bits_truncate` to `from_bits_retain` on bitflags where needed
* Bump h263-rs for the bitflags 2.0.0 dependency
As part of porting to bitflags 2.0.0, see:
https://kodraus.github.io/rust/2022/10/07/bitflags2.html#upgrading-to-2x
The XML call handler is implemented as 'new XML(arg)',
so we get all of the related string coercions for free.
Our various native tables are starting to get somewhat wasteful -
if we add any more, we might want to consider a more compact
representation.
When we skip running a frame for a MovieCilp, we skip all
of its children as well. However, this skip 'counts' as
a skip for any children that already wanted to skip their next
frame. For example, say we create three objects in ActionScript,
and arrange them like 'obj1 -> obj2 -> obj3'.
The first 'obj1.enter_frame' call will not run a new frame
for any of the objects, but next time, 'obj1.enter_frame'
will run a new frame for all of the objects.
This fixes jacksmith, which was missing a frame1-framescript
due to 'enter_frame' getting incorrectly run for a deeply
nested child.
If you call 'BitmapData.dispose()', any Bitmap objects using it will
continue to report the original 'width' and 'height' values to
ActionScript. The values only refresh if you explicitly do
'Bitmap.bitmapData = bitmapDataObject' (including with the same
object).
Fancy Pants Adventure World 4 relies on this - it calls
BitmapData.dispose(), and then uses the width and height from
a previously-constructed Bitmap object.
When a DisplayObject is removed from its parent by a RemoveTag, it still runs its framescript for the current frame (but with 'this.parent == null'). It then stops executing entirely, unlike ActionScript-removed orphans, which continue to execute indefinitely.
Additionally, objects created by ActionScript during a frame skip their next 'enterFrame' logic (but still receive an enterFrame event). This results in the currentFrame lagging one frame behind objects that were placed by the timeline during the same frame.
The combination of these two changes lets us greatly simplify frame lifecycle handling for orphan movies. Most of the orphan stages were unencessary, and the remaining ones run in the same phase as the normal Stage-descendant objects.
The stage alignment settings viewport_scale_factor should *not* be
applied to `Stage.transform.matrix`, which is only ever changed
as a result of explicit modification from ActionScript. Instead,
alignment and scaling are performed a separate step, which is
transparent to ActionScript.
I've implemented this through a new `viewport_matrix` field,
which is used during stage rendering and mouse coordinate
transformation.
This makes Stage3D instances properly scale - previously, they
would render unscaled. The linux standalone Flash Player doesn't
seem to use HiDPI mode, so I didn't realize that this was a bug
until now.
In the process of implementing this, I discovered and fixed a bug
with how we handle changing the viewport size under winit.
Calling `self.window.set_inner_size` does not immediately take
effect (at least on X11) - calling `self.window.inner_size()`
will report the old size until the next resize event.
Since build our Stage matrices from `self.window.inner_size()`
(and start running the SWF) immediately after `RuffleEvent::OnMetadata`,
we would run a few SWF frames with an incorrect viewport size. This
is visible to SWFs that have the scale mode set to "noScale", and
could break SWFs that expect the initial viewport size to be
the movie size. I've fixed this by delaying SWF execution until
we get a Resize event (if `self.window.inner_size()` does not
immediately report the size we set).
When an XML object has simple content, you can call non-XML
methods directly on it - it will internally be stringified,
and the method will be called on the resulting string.
This lets `new XML("<p>Some content</p>".split(' ')` work.
Similarly, an XMLList object with a single XML child will
forward non-XML method calls to that object.
This PR implements this logic (based heavily on avmplus)
I've also renamed these methods to 'avm1_unload' and
'avm1_removed', to make it clear that they don't
apply to AVM2.
This was causing us to incorrectly skip mouse picks,
and remove masks.
I think this might have been broken by
https://github.com/ruffle-rs/ruffle/pull/9506, but we didn't have
proper test coverage.
If we execute a 'coerce' opcode for a class while it's being
initialized (which can happen by running a method from a static
initializer), we'll be unable to resolve the ClassObject using
`resolve_type`.
This is the only case where this can happen - any
superinterfaces/superclass will already be fully initialized
when we're running a class initializer. Therefore, we can
try to lookup the class from the `Domain`, and check if it
directly matches the class of the object we're coercing
(ignoring superclasses and interfaces).
This doesn't perfectly match Flash's behavior - I haven't been
able to reproduce the values produces when the DisplayObject
starts out with certain 'Matrix' values (a non-zero 'b' or 'd').
Howver, when the 'b' and 'd' matrix values are both 0, setting
'dobj.rotation = NaN' has no effect on the matrix, while
'dobj.scaleX = NaN' and 'dobj.scaleY = NaN' both treat 'NaN'
as 0 for the purposes of updating the matrix.
This fixes the tack shooter in Bloons Tower Defense 3, which
tries to set 'rotation = NaN' for spawned tacks.
This is necessary to make Steambirds get past the preloader screen.
All of the previous tests continue to pass with this change.
This commit modifies the existing test to start from within
the symbol_class constructor, instead of a frame script. In
this situation, a freshly-created orphan with a framescript will
run directly after the constructor returns, *before* an enterFrame
handler for the same orphan. I've verified that this modified test
fails without my change.
It's possible to call 'start()' on a timer
that has currentCount >= repeatCount. This will
cause the timer to tick exactly once, and then stop agian.
We were incorrectly reporting 'timer.running' in such a scenario:
'running' should be reported as 'true' up until just before the
'TimerEvent.TIMER_COMPLETE' is fired.
This fixes gaining money from bloon popping / level completion
in BTD5.
Previously, the Vector$ classes were only exported in the internal 'AS3.vec' namespace, which is used by older ActionScript code. However, newer ActionScript code can also access these classes through the public 'AS3.vec' namespace, via 'getDefintionByName'.
We now export these classes in both namespaces. In the public 'AS3.vec' namespace, they are exported like 'Vector.' instead of 'Vector$uint'
We still want to propagate these hits to the parent, which may
be able to handle them. My existing tests missed this case,
since all of the parent objects had content which was
behind the child content. When the only clickable content
comes from a child with 'mouseEnabled=false', we should
still fire an event targeting the parent (when applicable
based on the parent's flags).
This fixes dragging on the background (without any scenery present)
in Steambirds.
When a MovieClip is an 'orphan' (it has no parent),
it still has frames run (including frame scripts). Some SWFS
like SteamBirds and 'This is the Only Level TOO' rely on this behavior,
so we need to implement it.
The overall idea is straightforward - we keep a global list of
orphan movies, which we add to whenever we unset the parent for a movie.
This list stores weak references for consistency with Flash.
When we run a frame, we process entries in the root movie list,
in addition to the normal recursive processing from the `Stage`.
However, exactly matching Flash's output turned out to be quite tricky.
The particular sequence of calls I make in `run_all_phases_avm2` makes Ruffle
pass two complicated test cases, but there could still be lurking bugs.
This is enough to get SteamBirds to the first level (which doesn't
render due to a different error).
We were previously performing a redundant 'self.hit_test_shape'
call in 'avm2_mouse_pick'. All of the logic in that function
is handled in `avm2_mouse_pick.` Additionally, this call happened
before we tested out children, which would result in us targeting
a parent's drawing instead of aa child.
Surprisingly, Flash allows mutliple classes in an inheritance chain
to hav a linked `class_symbol`. When we instantiate a `DisplayObject`,
we need to stop at the first such `class_symbol` we find. This means
that any fields set from named children will *only* be set in the
first class we find, not in any of the parent classes (as their
corresponding library symbol will not be instantiated).
Previously, we would continue looping even after we found a
`class_symbol`, resulting in the furthest ancestor *winning*
the `set_object2` call.
This is a very large diff, but most of it comes from test files and
output.
This PR ads partial support for the following Stage3D shader features:
* Normal (square), rectangle, and cube textures
* Varying and temporary registers
* Lots of opcodes
The combination of these allows us to get a raytracing program
fully working in Ruffle. I've included it as image test.
Currently, this test is very slow (about 90 seconds on my machine),
as the code I'm using (https://github.com/saharan/OGSL) includes
its own shader language and compiler. THe raytracing demo
first compiles its own shader language to AGAL, and then starts
rendering the scene.
Limitations:
* Many opcodes are still unimplemented
* Most non-default texture options (e.g. mipmaps) are not implemented
We were previously calling `get_property` to determine if a `toJSON`
property exists, but that produces an error if the method is missing
on a sealed class.
Additionally, JSON serialization wasn't taking into account properties
from the vtable. All public properties (including fields, const fields,
and getter methods) get serialized.
Unfortunately, our vtable property order currently doesn't match
Flash's. I've hand-edited the test output for now (all of the actual
properties are there, just in a different order), and added a note
This includes all of the XML elements described in 'describeType' docs.
Unfortunately, the order of elements produced by Flash depends on
the iteration order of internal hashtables. As a result, the test
manually stringifies an XML object, sorting the stringified children,
to produce consistent output between Flash and Ruffle.
This requires the ability to do a limited 'set_property',
as well as `get_enumerant_value`.
To prevent modification of XMLLists derived from queries,
I've introduced a `target` field on `XMLList`. This is
`None` for lists created with `new XMLList()`, and `Some`
when the list was derived from a query on an existing `XML`
/`XMLList`. We only allow `set_property` when `target` is `None`:
this is enough for filtering to work, and prevents silent incorrect
execution when trying to modify an existing node.
When rendering to an offscreen texture for `Bitmapdata.draw`,
we first render to a temporary frame buffer, and then copy the contents
of the frame buffer back to the target texture. However, this results
in blend modes being incorrectly applied - for example, rendering with
BlendMode.SUBTRACT will subtract against the framebuffer (which starts
with each pixel as 0x00000000), instead of the previous BitmapData
contents.
To fix this, we now use our texture target as the frame buffer
when performing `render_offscreen`. This ensure that we blend
over existing pixels (taking into account the `blendMode` provided
in the `BitmapData.draw` call).
When multisampling is enabled, we use a copy pipeline to copy
the existing contents of our texture to a fresh multisampled frame
buffer (the non-multisampled texture target becomes our resolve buffer).
The Adobe Animate compiler can emit a 'newclass' opcode for
a concrete class before the 'newclass' opcodes for the interfaces
it implements. As a result, we cannot rely on looking up an interface
`ClassObject` when resolving a class's interfaces.
We now store a map of exported classes in `Domain`, and use this
to lookup interfaces before their `ClassObject`s have been created.
Additionally, `link_interfaces` was failing to consider superinterfaces,
which meant that methods from superinterfaces were not being copied
into the vtable. I've fixed this along with the other changes.
* Remove ^M characters from Dictionary test
Using Github's automatic line ending conversion to CRLF.
* Remove ^M characters from dictionary_delete test
* Remove ^M characters from dictionary_in test
The mouse picking behavior in AVM2 interacts in complicated
ways with `mouseEnabled` and `mouseChildren.` It's sufficiently
different from AVM1 that I decided to split the logic into separate
`mouse_pick_avm1` and `mouse_pick_avm2` methods.
The `mouseChildren` property is now fully implemented.
Additionally, the `click_block` tests now work correctly
under Ruffle.
Combined with the orphan-movie PR, this is enough to make
SteamBirds fully playable (though performance greatly degrades
over a course of a level).
These getters were previously calling `local_to_global`
with the unused localX/localY coordinate set to 0. Howver,
`local_to_global` does a matrix multiplication, which in general
will depend on both the x and y values. This was causing the getters
to return incorrect results when any of the `transform.matrix` values
included a non-diagonal matrix.
We now call `local_to_transform` with the real `localX` and `localY`
values.
This is needed by the Newgrounds API. We don't have the ability
to make fake requests to HTTP urls in our test frameworks,
so I haven't added any tests for this. However, I tested locally
that this allows the Newgrounds API to work (and got a medal
in Cloud Wars).
When removing a clip, first check if it has an unload event listener somewhere
it's hierarchy.
If it does, enqueue the removal to happen on the next frame, by moving it to a negative depth.
Previously, we would strip trailing newlines from the contents
of 'output.txt' files, and skip adding a trailing newline after
the final recorded `trace` call.
To reduce the amount of processing we do of expected/ruffle output,
and to make fpcompare tests easier, I've removed the special handling
of newlines. When recording `trace` calls, we now build up a single
large `String`, with a newline after every `trace` message. When
reading in an 'output.txt' file, we do not strip any newlines.
This required adding trailings newlines to lots of 'output.txt' files,
to match the behavior of Ruffle and Flash (the last 'trace' message
ends with a newline, just like every other 'trace' message).
This is a port of a similar regression test written for AVM1.
AVM1 also has a test for hitTestPoint with shapeFlag=true, but it can't
be ported for now, because the implementation of AVM2 hit testing is not
yet accurate enough for it.
* tests: Add a test for issue #8630
* core: No-op gotos in AS3 do not actually do anything, even though they emit events
Fixes issue #8630
* tests: Add more tests for various #8630-adjacent cases
* tests: Ignore the tests with script removal as they expect MovieClip children to be nulled upon removal
* chore: Case sensitive filesystem fix
Co-authored-by: Adrian Wielgosik <adrian.wielgosik@gmail.com>
This PR implements core 'stage3D' APIs. We are now able
to render at least two demos from the Context3D docs - a simple
triangle render, and a rotating cube.
Implemented in this PR:
* Stage3D access and Context3D creation
* IndexBuffer3D and VertexBuffer3D creation, uploading, and usage
* Program3D uploading and usage (via `naga-agal`)
* Context3D: configureBackBuffer, clear, drawTriangles, and present
Not yet implemented:
* Any 'dispose()' methods
* Depth and stencil buffers
* Context3D texture apis
* Scissor rectangle
General implementation strategy:
A new `Object` variant is added for each of the Stage3D objects
(VertexBuffer3D, Program3D, etc). This stores a handle to the
parent `Context3D`, and (depending on the object) a handle
to the underlying native resource, via `Rc<dyn
SomeRenderBackendTrait>`).
Calling methods on Context3D does not usually result in an immediate
call to a `wgpu` method. Instead, we queue up commands in our
`Context3D` instance, and execute them all on a call to `present`.
This avoids some nasty wgpu lifetime issues, and is very similar
to the approah we use for normal rendering.
The actual rendering happens on a `Texture`, with dimensions
determined by `createBackBuffer`. During 'Stage' rendering,
we render all of these Stage3D textures *behind* the normal
stage (but in front of the overall stage background color).
This is linked to the legacy DisplayObject::Text, which can
only be created by Flash CS6 (but is allowed in AVM2 swfs).
The 'StaticText' class cannot be constructed from ActionScript.
To support this, I've added support for native initializers to
playerglobal. This allows us to throw an exception in the
ActionScript constructor in Test.as, and do nothing in the native
intiializer (so that we can construct it from a DisplayObject).
I've left StaticText.text unimplemented for now, since it will require
dealing with Glyphs
Co-authored-by: kmeisthax <dcrkid@yahoo.com>
This is the first part of the Stage3D implementation, and can
be reviewed independently.
Stage3D shaders use the Adobe Graphics Assembly Language (AGAL),
which is a binary shader format. It supports vertex attributes,
varying registers, program constants (uniforms), and texture sampling.
This PR only implements a few parts of AGAL:
* The 'mov' and 'm44' opcodes
* Vertex attributes, varying registers, program constants, and 'output'
registers (position or color, depending on shader type)
This is sufficient to get a non-trivial Stage3D program
running (the rotating cube demo from the Adobe docs).
The output of `naga-agal` is a `naga::Module`. This can be passed
directly to wgpu, or compiled into a shader language using
a Naga backend (glsl, wgsl, SPIR-V, etc). The test suite
output WGSL files, and uses the 'insta' crate to compare against
saved files on disk.
Currently, the only real way to write AGAL bytecode is using
the Adobe-provided 'AGALMiniAssembler' flash class.
This class assembles the textual reprentation of AGAL into
the binary format.
To make writing tests easier, I've added a 'agal_compiler' test, which
can easily be modified to add more Agal textual assembly.
This PR fixes a numbe of interconnected bugs:
* We weren't consistently uploading a dirty BitmapData to the render
backend before drawing to/from it.
* BitmapData.draw should *not* add a fill color - it should draw over
the current contents of the BitmapData
* After drawing to a non-transparent BitmapData, we need to manually
set the opacity back to 255 for each pixel (the drawing process
takes transparency into account, but the opacity information is
thrown away at the end).
These methods were incorrectly treating the argument as a local name,
instead of a qualified name. Additionally, 'getDefinition' now throws
an AVM error.
Previously, we would display an empty string for the method name.
We can now store a `&'static str` again in `NativeMethod`,
instead of needing a `Cow`
Now that we have a custom `Error` enum, this is very straightforwawrd.
I've converted `getDefinitionByName` return an AVM error, since this
is commonly used by games to test for a class.