When we were using a fork of quick-xml, we modified the actual
unescape method. Now that we're using the crates.io release again,
we need to go through our `custom_unescape` function.
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.
These functions are intentionally no-ops in Ruffle because it has
no concept of a player dirty region, so unmark them as stubs.
The only observable difference is that Flash Player will sometimes
not re-render a `Bitmap` instance on the stage immediately if it's
`BitmapData` is locked and changed, but this is only temporary and
depends on the redraw behavior of the Flash Player.
* `global_to_local` returns `None` if the object has zero scale.
* Adjust AVM `globalToLocal` methods to return the untransformed
point on failure.
* Add `DisplayObject::mouse_to_local` to handle AVM `mouseX`
and `mouseY` coordinates. For zero scale objects, these end up
returning values based on the twips-to-pixels scale,
divided by 20.
* Add `Matrix::determinant`.
* Rename `Matrix::invert` to `inverse`.
* `Matrix::inverse` return an `Option`, with `None` returned
for non-invertible matrices.
* AMV `Matrix::invert` duplicates the code as the behavior is
different (works in f64 and not twips, etc.)
I've moved our special entity handling logic into
a `custom_unescape` function. This lets us move off
of our fork of `quick-xml` back onto the crates.io release
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.
Previously, the `ApplicationDomain` constructor ignored its argument,
instead of constructing a new domain with the specified domain as
the parent.
Additionally, we were incorrectly executing code with
`Activation::from_nothing` in several places, causing
`ApplicationDomain.currentDomain` to return the system domain
instead of the correct parent domain. I've introduced a new method
`Activation::from_domain`, which allows explicitly passing in the
domain. Internally, we now store an `Option<Domain>`, and panic
when calling `caller_domain` with a `None` domain. Several places
in the codebase have been adjusted to pass in the correct domain.
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).
Instead of queueing up these events in the `Activation`,
we can fire them immediately by making `AudioManager::update_sounds`
a freestanding method that takes in an `UpdateContext`
Avoid panic in ChildContainer.replace_at_depth() panic when previous child is not in render list.
---------
Co-authored-by: Gleb Piskunov <emgfc@ya.ru>
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.
So far this just sticks the stream into the playback list and kicks off a download; we do not actually support decoding, seeking, or any of the other things that we expect `play` to do.
Calling `StreamManager::tick` advances all streams to the appropriate time. This is an unlocked timestep to support things like non-stage-FPS video and the like.
* Pixels with 0 alpha are not affected by color transforms.
* Color channels should be clamped to the 0-255 range.
* A color transform with only an alpha multiplier of >1 has no
effect.
These are used in the Rust handler, but were not correctly set in the AS bindings, leading to errors such as "Attempted to call flash::text::TextField::getTextFormat with 2 arguments (more than 0 is prohibited)"
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.
It was incorrectly declared as public method, which lead to
spurious 'missing override' errors in classes extending Array
that define a 'removeAt' method.
We don't need to perform a sync when getting the width/height,
getting or setting the 'disposed' status, or uploading to
a Context3D texture.
The Context3D change (using `copy_texture_to_texture` instead
of relying on the CPU pixels) has the added advantage of avoiding
a validation error when our source image row length isn't aligned
to `COPY_BYTES_PER_ROW_ALIGNMENT`
This dramatically speeds up the Fancy Pants World 4 loading time
(on a branch with my XML prs merged). Without this change, my
machine spends around 10 seconds on a blank white screen after
clicking 'Play'. With this change, the time spent on that screen
is reduced to around 1-2 seconds.
`SoundChannel.position` was being updated by the audio manager each
tick, but the Flash Player only updates and caches the position
when `SoundChannel.position` is accessed. In contrast, an AVM1
`Sound` will constantly update its position.
This means accessing `position` only once after a sound has
finished playing will return 0.
Fixes#9952.
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.
None of these formats can currently be implemented
correctly with wgpu, so we just use Rgba8Unorm instead.
The handling of opaque compressed textures is a little
sketchy - it should work for 'normal' SWFs that upload
an opaque BitmapData, but we might need to manually
adjust the alpha values if
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
We were ignoreing 'data32PerVertex'.
To make the code clearer, I've renamed the variable to
'data32_per_vertex', and made it a 'u8' (as it has a maximum of 64)
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.
Webgl doesn't support BGRA textures, so this lets us use
Stage3D textures on the web backend. As a bonus, this speeds up
uploading an BitmapData to a Context3dTextureFormat.BGRA texture,
since we no longer need to change the format before copying.
This makes Solarmax2 playable on the web backend.
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).
This makes #3294 (rollercoaster-creator-2) fully playable.
Missing is any (*) matching for child()/elements() and the existing attributes() method.
Also missing is support for number indexes with child().
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.
Depending on when loading completes, calling
`catchup_display_object_to_frame` might trigger an
`addedToStageEvent` inside the loaded SWF. The event listener
will expect the SWF content to have 'DisplayObject.stage' accessible,
so we need to make sure that we've added our loaded content as
a child of the `Loader` *before* any event handlers run.
I've been unable to come up with a self-contained test for this,
but it's necessary for Bloons Tower Defense 5
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.
Previously there were multiple implementations scattered across the
codebase. Unify them to a single place, in a more "Rusty" way (now
it's called via dot notation, rather than as a free function).
This makes Vector consistenht with the other implementations
of `get_enumerant_name`. This also fixes a bug where AMF object
serialization would loop all te way to `u32::MAX` when serializing
a vector, because it would never see `Value::Undefined` and break.
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.
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).
Since `initial_data` was removed from `Character::Bitmap` in #9143,
it now holds a single field. Move back to an unnamed field, which
aligns with the other `Character` enum variants.
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.
The Newgrounds API checks `Security.sandboxType` to see if it should
run in debug mode or not (which determines whether or not medals
can actually be unlocked).
For now, desktop continues to use `localTrusted` as the default,
while web now uses `remote`. We might want to make this configurable
at some point, but this should be good enough for now (and better
match Flash's behavior).
Flash supports calling `Sound.play`, `SoundChannel.stop`, and
`SoundChannel.soundTransform` while a sound load is in progress
(e.g. immediately after calling `Sound.load`).
To support this, we queue up information inside `SoundObject`
and `SoundChannelObject` when a load is in progress. When a load
completes, we trigger any queued `Sound.play` and `SoundChannel.stop`
calls, and apply the most recent `SoundChannel.soundTransform`
If we're going to overwrite the CPU pixels with the result of a
GPU operation, make sure the GPU texture is up to date with the
latest CPU pixels. I've also renamed the method to
`overwrite_cpu_pixels_from_gpu` to better reflect how it should
be used.
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.
* Take two: Delay reading image back from render backend using `SyncHandle`
This allows us to avoid blocking immediately after a `BitmapData.draw` call.
Instead, we only attempt to use the `SyncHandle` when performing an operation
that requires the CPU-side pixels (e.g. BitmapData.getPixel or BitmapData.setPixel).
In the best case, the SWF will never explicitly access the pixels of
the target BitmapData, removing the need to ever copy back the render backend
image to our BitmapData. If the SWF doesn't require access to the pixels immediately,
we can delay copying the pixels until they're actually needed, hopefully allowing
the render backend to finish processing the BitmapData.draw operation in
the backenground before we need the result.
Now that the CPU and GPU pixels can be intentionally out of sync with
each other, we need to ensure that we don't accidentally expose 'stale'
CPU-side pixels to ActionScript (which needs to remain unaware of
our internal laziness). We now use a wrapper type `BitmapDataWrapper`
to enforce that the `SyncHandle` is consumed before accessing the
underlying `BitmapData.
* core: Skip GPU->CPU sync for source and target BitmapData during draw
* Introduce DirtyState enum
This change makes it so that if there is a goto to a specific frame,
then a frame script is registered for that frame, and then a goto to the
same frame again, the frame script will not be skipped. At least one movie
appears to depend on this behaviour.
Now that a `Bitmap` always stores a `BitmapData`, we can read the pixels
directly from the `BitmapData`, instead of duplicating them in an
`initial_data` field
This makes `Bitmap` delegate to `BitmapData` for
all of the bitmap-related information (handle, width, and height).
As a result, we now unconditionally store a `BitmapData` in `Bitmap`.
As a result, swapping the underling `BitmapData` instance will
automatically change the properties (and rendered image) of a `Bitmap`.
This required some refactoring in the render backends in order to
get access to a `BitmapHandle` through `BitmapData`.
In future versions of `gc-arena`, the `Debug` impl. of `Gc`
and `GcCell` will print the pointed-to value, which will cause
derived `Debug` impls. to enter an infinite recursion.
As such, this manually implements `Debug` on types wrapping a
`Gc/GcCell` to maintain the current behavior.
Previous behaviour defaulted to undefined and applied the format to the
range [0,0) instead of defaulting to -1 and applying the format to the
full length of the TextField.
This code was always wrong; and only saved from breaking by other wrong code elsewhere. Specifically:
* `SimpleButton.construct_frame` sets the wrong initial state
* but `MovieClip.instantiate_child` fires frame events before `post_instantiation`
* and `SimpleButton.post_instantiation` sets the correct state
This works now because all object placement and removal happens in `enter_frame`. Constructing those objects right away causes them to drop added events.
We cannot remove other instances of `construct_frame`, however - those are in places where we actually do expect constructors to run, not just see things get placed.
This also centralizes all the code we added in the prior commit into `on_construction_complete`, which should be called whenever an AVM2 object finishes construction.
This has a few other knock-on effects:
* AVM2 added-to-timeline events are fired by each object after it constructs its AVM2 side. This is opposed to before when we fired them after object instantiation and placement. This also gets rid of a prior hack we had for the AVM2 root movie getting added to the stage - or, more accurately, adopts it for everything.
* The supercall constructor for `DisplayObject` runs `construct_frame` on all children. This matches Flash Player behavior.
NOTE: This currently breaks the `placed_with_name` check, so there's going to be a lot of spurious can't set warnings
Usage of TObject::as_script_object was always followed by an unwrap, and
only SuperObject returned None.
The name change is intended to make clearer the fact that using the
returned object may bypass special behavior.
* 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>
`BitmapHandle` now holds `Arc<dyn BitmapHandleImpl>`.
This allows us to move all of the per-bitmap backend data into
`BitmapHandle`, instead of holding an id to a backend-specific
hashmap.
This fixes the memory leak issue with bitmaps. Once the AVM side of a
bitmap (`Bitmap`/`BitmapData`) gets garbage-collected, the
`BitmapHandle` will get dropped, freeing all of the GPU resources
assoicated with the bitmap.
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).
We only called `get_bitmap_pixels` when creating a `BitmapData`
for an SWF-provided `Bitmap`. We now store the initial pixels
in `Character::Bitmap`, and use them to initialize a `BitmapData`
when needed.
This lets us simplify the wgpu backend, which no longer needs
to store a `Bitmap` object. In addition to saving space for
`BitmapData` objects that lack an SWF `Bitmap`, this will make
it easier to move data from `bitmap_registry` into `BitmapHandle`
itself.
D.O.s removed by the timeline may only be removed from the depth list
(if they were manipulated by AS3 scripts), but their unload method
would still be called, which is wrong.
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 done by:
- using the global constant pool instead of a fresh empty one:
- OK, as no call-site is directly executing arbitrary bytecode that
could care about the contents of the constant pool.
- pre-allocating the global scope object in the `Avm1` context
- using the global scope directly instead of allocating a local scope:
- OK, because no call-site is directly defining locals on the
returned Activation's scope.
Currently, we rely on ShapeTessellator being able to get a BitmapHandle
without a RenderBackend. With the upcoming BitmapData refactor,
we will always need a RenderBackend to get a BitmapHandle, which creates
borrow-checker issues in ShapeTessellator (which is stored in a
RenderBackend).
To solve this, we split BitmapSource.bitmap into two methods -
BitmapSource.bitmap and BitmapSource.bitmap_handle. ShapeTessellator
continues to use BitmapSource.bitmap, and uses the u16 bitmap id
instead of a BitmapHandle. The BitmapSource.bitmap_handle method
is used inside each render backend to convert the id to a BitmapHandle,
avoiding borrow-checker issues.
`tag_length` isn't really necessary since each tag is read using
a dedicated `swf::Reader`, which keeps track of the tag boundary
internally.
As a result, `tag_len` can be avoided passed around many times in
`movie_clip.rs`.
The call to `super_init` will initialize a `DisplayObject` (if unset)
in the `Sprite` constructor, using exactly the same logic that we're
attempting to use. The code in `MovieClip` is unreachable, and can
be deleted.
The desktop player now takes a `--spoof-url` argument, which overrides
the movie URL provided to ActionScript. This does not affect non-root
movies loaded through `Loader`.
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).
Change `Bitmap::new()` to accept a `ruffle_render::bitmap::Bitmap`
directly, instead of `width`, `height` and `bitmap_handle`. As a
consequence, all `RenderBackend::register_bitmap_*` methods are no
longer necessary - we can use `ruffle_redner::utils::*` to obtain
a `ruffle_render::bitmap::Bitmap` right before calling `Bitmap::new()`.
`NaN` and large numbers are actually treated like `i32::MIN`. As described
in https://github.com/ruffle-rs/ruffle/issues/7772#issuecomment-1235977709,
Flash (at least 7+) fails to convert `i32::MIN` to a string due to a bug.
Since `i32::MIN` has no `i32` positive counterpart, digits extracted using
modulo are actually negative and characters before ASCII `'0'` are selected.
Flash 6- somehow return just `0` in those cases. This was Ruffle's behavior
up until this commit. This commit matches Ruffle behavior to latest Flash
Player, as we usually prefer. A `TODO` is still there in case we ever add
player version emulation.
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`
Instead of propagating the underlying compression library errors.
Also, make `ByteArray.deflate` and `ByteArray.inflate` pure-ActionScript
methods that call into the native `ByteArray.compress` and `ByteArray.uncompress`
native methods, respectively.
Since `serde-wasm-bindgen` doesn't support `#[serde(default)]` (https://github.com/cloudflare/serde-wasm-bindgen/issues/20),
we no longer able to deserialize a partial `Config` object. As a solution,
take care to pass a full object from the TypeScript side.
And make it generic, as a first step towards making it a general-purpose
data structure for the whole codebase. Some potential replacements are:
* `BoundingBox` in `render/src/bounding_box.rs`.
* `BoxBounds` in `core/src/html/dimensions.rs`.
* Parameters to a bunch of `BitmapData` methods in
`core/src/bitmap/bitmap_data.rs`.
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.
Main changes:
* Merge `ColorTransformParams` into `ColorTransformObject`, as it's only relevant for AVM1.
* Make `BitmapData::color_transform` work with a generic `ColorTransform`, which uses fixed-point
arithmetic.
Note that Ruffle still calculates color transforms slightly different from Flash. This is probably
caused by inaccuracy of the current `ColorTransformObject` to `ColorTransform` conversion and/or the
`ColorTransform` application logic itself. Since this requires further research, it'll be fixed in a
future PR.
While doing this I also noticed that we were incorrectly producing AmfValue::Integer if our
target AMF version is AMF0. This is incorrect because AMF0 is based on AS2, which does not
recognise Integer as a valid type, and therefore must be represented with Number instead.
This prevents the root from being constructed until we know its `SymbolClass`, and is roughly equivalent to delaying the work until `ShowFrame`. (We don't actually run any work on `ShowFrame` in our current movieclip impl.)
This is currently somewhat buggy, `homestuck_02791.swf` stops at 12% for some reason. I tried handing it both compressed and uncompressed lengths with no luck.
This requires estimating a count of bytes loaded of the original compressed stream, even though we only have uncompressed byte totals at this point. I instead rescale the uncompressed bytes by the compressed count to roughly estimate what would be the `bytesLoaded` had we been actually streaming bytes in and preloading them synchronously.
Backends that need synchronous preload behavior now explicitly ask for it as follows:
* `tests` - repeatedly call `preload` in a loop with an exhausted execution limit to stress-test the chunked preload
* `exporter`, `scanner` - synchronous/unlimited preload to match prior behavior
These may change in the future.
Actions are abstract; here we're using it to count bytes loaded (as a proxy for execution time). AVM code could potentially be adapted to count operations run instead.
An "empty clip" is any clip created by the `new` or `new_with_avm2` function, intended for dynamically-created movie clips with no association to a movie symbol.
The preload frame counting logic starts from one and continues to the end of the file, which results in a completely preloaded movie having one more frame "loaded" than there is in the file. This fixes that.
As usual, also bump its helper crates (`js-sys`, `web-sys` and
`wasm-bindgen-futures`) to the latest versions.
Due to https://github.com/rustwasm/wasm-bindgen/pull/3031, use the
`serde-wasm-bindgen` crate as a replacement to the deprecated
`JsValue::from_serde` function.
Previously, we would only use mergeAlpha if alphaBitmapData
and alphaPoint. However, mergeAlpha can be used even when
those parameters are null.
Some of the AVM1 argument handling was also incorrect - I've fixed
it, and extended the existing test with the output-based test
added for AVM2.
In several cases, the current code seems preferable to the
code required by `clippy::bool_to_int_with_if`. Let's suppress
this for now to get the build passing, and decide later if this
is something that we want to enable.
Previously, we would always use a transparent background,
even if the BitmapData is not transparent. This would normally
be corrected on the next frame when we copied the pixels to the
CPU. However, if an SWF ran `BitmapData.draw` on every frame,
this would never be corrected.
Our AVM2 `SharedObject` support is now *almost* equivalent
to our avm1 `SharedObject` support. We implement serialization
and deserialization for primitives, arrays, and `Object` instances
with local properties. We also implement serialization for `Date`,
but not `Xml` (since our AVM2 `Xml` class is just a stub at the moment).
This is enough to make 'This is the only level too' save level
progress to disk.
Currently, we always serialize to AMF3. When we implement
the `defaultObjectEncoding` and `objectEncoding`, we'll need
to adjust this.
An AVM2 movie can repeatedly remove and add a DisplayObject from/to
a parent. This was causing SolarMax to stop working after advancing
to the next level.
We now check if a BitmapData has been disposed by checking
for a zero width or height (which cannot happen otherwise).
As a result, we no longer need the 'disposed' field on the AVM1
BitmapData object.
* avm2: Implement `BitmapData.draw` for `wgpu` backend
This method requires us to have the ability to render directly to a
texture. Fortunately, the `wgpu` backend already supports this in
the form of `TextureTarget`. However, the rendering code required
some refactoring in order to avoid creating duplicate `wgpu` resources.
The current implementation blocks on copying the pixels back
from the GPU to the CPU, so that we can immediately set them in
the Ruffle `BitmapData`. This is likely very inefficient, but will
work for a first implementation.
In the future, we could explore allowing the CPU image data and GPU
texture to be out of sync, and only synchronized when explicitly
necessary (e.g. on `getPixel` or `setPixel` calls).
* Rename `with_offscreen_backend` to `render_offscreen` and use Bitmap
* Don't panic when backend doesn't implement `render_offscreen`
We already have `&mut self` available whenever we write to it,
and we never made use of the `Clone` impl. As far as I can tell,
we don't have any unimplemented features that would require a `GcCell`.
The stub implementation was breaking code that relied on being
able to set a value for 'mask' and then retrieve it
(which used to work on a dynamic class like `MovieClip`).
Change `set_matrix` and `set_color_transform` to accept owned structs,
instead of references. This allows callers that already have an owned
struct to pass it directly, thus saving an unnecessary borrow + clone.
This also aligns with other methods, such as `set_sound_transform`,
which currently accepts an owned struct.
The existing `Object` enum representation is problematic for inherited
native objects, since "regular" `ScriptObject`s cannot be turned into
native objects, but rather a completely new native object needs to be
created. `TObject::create_bare_object` is an attempt to aid this
situation, but it works only for `ActionExtends` inheritance, and not
when the user manually wires up `prototype`/`__proto__` (#701).
In Flash, it seems like derived constructors initially have a "regular"
`this` object. But once the `super()` constructor is invoked, the same
`this` object becomes a native object.
To allow this in Ruffle, introduce a new `NativeObject` enum, and
store it as a member in `ScriptObject`. For a start, move `TextFormatObject`
from the `Object` enum to `NativeObject`. The plan is to gradually
move all `Object` enum variants to `NativeObject`, except for `ScriptObject`.
For now, I've left 'dropTarget' unimplemented - unlike in
AVM1, the drop target can be non-interactive objects like `Shape`,
so we'll need additional refactoring to implement it.
This allows 'This is the only level too' to be playable
It was only used to make structs `#[derive(gc_arena::Collect)]`, and
generally it doesn't make much sense that `render` needs to be GC-aware.
So instead annotate `render` fields in `core` with `#[collect(require_static)]`.
This brings us closer to matching the Flash Player
enumeration behavior. Unfortunately, the precise enumeration
order for ScriptObject properties depends on the precise
order in the internal avmplus hashmap. This order is deterministic,
but adding/removing a property effectively randomizes it. Hopefully
there aren't any SWFS that depend on the *exact* order.
Handle removed clips inline in `Avm1::run_frame`, such that
`DisplayObject::prev_avm1_clip` is no longer used. Thus it can be
removed in a follow-up commit.
When we run a 'goto' where the initial and target frame are the same,
we need to skip triggering any sounds in the target frame.
Some games like 'This is the only level too' rely on this behavior:
they repeatedly run 'movieClip.gotoAndStop(current_frame_id)',
where 'current_frame_id' is the id of a frame that starts playing
a sound. Without this change, the sound will restart every frame
intead of playing exactly once.
We previously used 'coerce_to_object', which produced
an error with `Value::Null`. Instead, we can just ues
`value.as_type_of`, which will correctly handle `null`
The 'charCode' and 'keyCode' properties are now implemented
on `KeyboardEvent`
The input injection code we use does not support keyboard events,
so we can't yet write a regression test for this. However,
both 'You need to burn the rope' and 'This is the Only Level TOO'
now properly handle keyboard events with this PR.
* avm2: Implement call stack
* avm2: Class traits should have a special prefix
* avm2: Stack tracebacks should also contain error message
* avm2: Move method naming to Executable
* avm2: Handle getter and setter methods in tracebacks
* chore: Formatting
* chore: Add comments
* avm2: Make full_name write to a string, instead of creating a new one
* core: Make GcArena publicly accessible
* core: Add Deref impl for Either type
* desktop: Add AVM2 call stack to panic message
* avm2: Prefix native methods with a `/`
* chore: Appease clippy
* avm2: Check if method actually contains bytecode instead of unwrapping
* web: Add AVM2 stack trace to panic message
* chore: Formatting
* chore: Clippy
* avm2: Fix stack traces for free standing functions
* core: Remove global data from context
* core: Rename GcGlobalData to GcCallstack
* core: Introduce StaticCallstack, make GcArena private again
Co-authored-by: Adrian Wielgosik <4729533+adrian17@users.noreply.github.com>
Declare `NaN`, `Infinity` and `undefined` in ActionScript, similarly
to how `avmplus` does in its `actionscript.lang.as`.
Note that `null` is only removed, without an ActionScript declaration,
as it seems like `avmplus` neither declares it. Probably `null` is
only usable as a compile-time constant.