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.
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.
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.
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.
When rendering offscreen, we want the resulting image to use
premultiplied alpha, since the image will be stored in a texture.
However, when capturing an image in the exporter or test framework,
we want to use straight alpha, so that the resulting image can
be saved as a PNG.
Previously, we incorrectly used straight alpha everywhere, resulting
in incorrect output when using BitmapData.draw with transparency.
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.
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`
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`).
Fixes some issues with our winding # calculation which would cause
incorrect results for hitTest.
* The convention for handling an intersection at endpoints was
not the same between lines and bezier curves.
* The bezier curve winding # function was not properly handling
some cases where the curve was strictly y-monotonic.
* Simplify the code a bit so that ray-curve intersections are
returned in a consistent order based on upward/downward crossing.
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
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.
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 test output for this test is sensitive to where we cut off each frame, because it doesn't stop all of it's handlers at the end of the test. Flash Player will just print lines forever so the end of the test is entirely arbitrary.
Reverts #7267
The image tests for the upcoming 'DisplayObject.stageRect' support
differ between Linux and Windows, so we need this support again.
To avoid the Linux filename churn that we previously encountered,
we now only include the platform and graphics backend in the filename
(e.g. `expected-linux-Vulkan`). This may result in some unexpected
'mismatched image' test failures if GHA updates to a version of Lavapipe
that changes rendering output, but this should be relatively easy to
notice.