wpgu: Initial implementation of PixelBender shader execution (#11441)
* 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
This commit is contained in:
parent
f33b9eea3c
commit
69fce3f7f8
|
@ -2810,6 +2810,16 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "naga-pixelbender"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bitflags 2.3.1",
|
||||||
|
"naga",
|
||||||
|
"ruffle_render",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "naga_oil"
|
name = "naga_oil"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
@ -3793,6 +3803,7 @@ name = "ruffle_render"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"approx",
|
"approx",
|
||||||
|
"byteorder",
|
||||||
"clap",
|
"clap",
|
||||||
"downcast-rs",
|
"downcast-rs",
|
||||||
"enum-map",
|
"enum-map",
|
||||||
|
@ -3803,6 +3814,8 @@ dependencies = [
|
||||||
"jpeg-decoder",
|
"jpeg-decoder",
|
||||||
"lru",
|
"lru",
|
||||||
"lyon",
|
"lyon",
|
||||||
|
"num-derive",
|
||||||
|
"num-traits",
|
||||||
"png",
|
"png",
|
||||||
"ruffle_wstr",
|
"ruffle_wstr",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -3859,9 +3872,11 @@ dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"gc-arena",
|
"gc-arena",
|
||||||
"image",
|
"image",
|
||||||
|
"indexmap",
|
||||||
"lru",
|
"lru",
|
||||||
"naga",
|
"naga",
|
||||||
"naga-agal",
|
"naga-agal",
|
||||||
|
"naga-pixelbender",
|
||||||
"naga_oil",
|
"naga_oil",
|
||||||
"ouroboros",
|
"ouroboros",
|
||||||
"profiling",
|
"profiling",
|
||||||
|
|
|
@ -38,6 +38,7 @@ version = "0.1.0"
|
||||||
gc-arena = { git = "https://github.com/kyren/gc-arena", rev = "63dab12871321e0e5ada10ff1f1de8f4cf1764f9" }
|
gc-arena = { git = "https://github.com/kyren/gc-arena", rev = "63dab12871321e0e5ada10ff1f1de8f4cf1764f9" }
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||||
|
naga = { version = "0.12.2", features = ["validate", "wgsl-out"] }
|
||||||
|
|
||||||
# Don't optimize build scripts and macros.
|
# Don't optimize build scripts and macros.
|
||||||
[profile.release.build-override]
|
[profile.release.build-override]
|
||||||
|
|
|
@ -11,6 +11,7 @@ pub mod loader_info;
|
||||||
pub mod morph_shape;
|
pub mod morph_shape;
|
||||||
pub mod movie_clip;
|
pub mod movie_clip;
|
||||||
pub mod shader_data;
|
pub mod shader_data;
|
||||||
|
pub mod shader_job;
|
||||||
pub mod shader_parameter;
|
pub mod shader_parameter;
|
||||||
pub mod shape;
|
pub mod shape;
|
||||||
pub mod simple_button;
|
pub mod simple_button;
|
||||||
|
|
|
@ -2,6 +2,7 @@ package flash.display {
|
||||||
import flash.utils.ByteArray;
|
import flash.utils.ByteArray;
|
||||||
import __ruffle__.stub_constructor;
|
import __ruffle__.stub_constructor;
|
||||||
|
|
||||||
|
[Ruffle(InstanceAllocator)]
|
||||||
public final dynamic class ShaderData {
|
public final dynamic class ShaderData {
|
||||||
public function ShaderData(bytecode:ByteArray) {
|
public function ShaderData(bytecode:ByteArray) {
|
||||||
this.init(bytecode);
|
this.init(bytecode);
|
||||||
|
|
|
@ -6,8 +6,13 @@ package flash.display {
|
||||||
import flash.events.EventDispatcher;
|
import flash.events.EventDispatcher;
|
||||||
|
|
||||||
public class ShaderJob extends EventDispatcher {
|
public class ShaderJob extends EventDispatcher {
|
||||||
|
|
||||||
|
private var _shader:Shader;
|
||||||
|
private var _target:Object;
|
||||||
|
|
||||||
public function ShaderJob(shader:Shader = null, target:Object = null, width:int = 0, height:int = 0) {
|
public function ShaderJob(shader:Shader = null, target:Object = null, width:int = 0, height:int = 0) {
|
||||||
|
this._shader = shader;
|
||||||
|
this._target = target;
|
||||||
stub_constructor("flash.display.ShaderJob");
|
stub_constructor("flash.display.ShaderJob");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,9 +20,7 @@ package flash.display {
|
||||||
stub_method("flash.display.ShaderJob", "cancel")
|
stub_method("flash.display.ShaderJob", "cancel")
|
||||||
}
|
}
|
||||||
|
|
||||||
public function start(waitForCompletion:Boolean = false):void {
|
public native function start(waitForCompletion:Boolean = false):void;
|
||||||
stub_method("flash.display.ShaderJob", "start")
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get height():int {
|
public function get height():int {
|
||||||
stub_getter("flash.display.ShaderJob", "height");
|
stub_getter("flash.display.ShaderJob", "height");
|
||||||
|
@ -34,12 +37,19 @@ package flash.display {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get shader():Shader {
|
public function get shader():Shader {
|
||||||
stub_getter("flash.display.ShaderJob", "shader");
|
return this._shader;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set shader(value:Shader):void {
|
public function set shader(value:Shader):void {
|
||||||
stub_setter("flash.display.ShaderJob", "shader");
|
this._shader = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get target():Object {
|
||||||
|
return this._target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set target(value:Object):void {
|
||||||
|
this._target = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,12 +1,18 @@
|
||||||
|
use ruffle_render::pixel_bender::{
|
||||||
|
parse_shader, PixelBenderParam, PixelBenderParamQualifier, OUT_COORD_NAME,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
avm2::{
|
avm2::{
|
||||||
parameters::ParametersExt, string::AvmString, Activation, Error, Object, TObject, Value,
|
parameters::ParametersExt, string::AvmString, Activation, Error, Object, TObject, Value,
|
||||||
},
|
},
|
||||||
pixel_bender::{PixelBenderParam, PixelBenderParamQualifier},
|
pixel_bender::PixelBenderTypeExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::shader_parameter::make_shader_parameter;
|
use super::shader_parameter::make_shader_parameter;
|
||||||
|
|
||||||
|
pub use crate::avm2::object::shader_data_allocator;
|
||||||
|
|
||||||
/// Implements `ShaderData.init`, which is called from the constructor
|
/// Implements `ShaderData.init`, which is called from the constructor
|
||||||
pub fn init<'gc>(
|
pub fn init<'gc>(
|
||||||
activation: &mut Activation<'_, 'gc>,
|
activation: &mut Activation<'_, 'gc>,
|
||||||
|
@ -16,11 +22,11 @@ pub fn init<'gc>(
|
||||||
let mut this = this.unwrap();
|
let mut this = this.unwrap();
|
||||||
let bytecode = args.get_object(activation, 0, "bytecode")?;
|
let bytecode = args.get_object(activation, 0, "bytecode")?;
|
||||||
let bytecode = bytecode.as_bytearray().unwrap();
|
let bytecode = bytecode.as_bytearray().unwrap();
|
||||||
let shader = crate::pixel_bender::parse_shader(bytecode.bytes());
|
let shader = parse_shader(bytecode.bytes()).expect("Failed to parse PixelBender");
|
||||||
|
|
||||||
for meta in shader.metadata {
|
for meta in &shader.metadata {
|
||||||
let name = AvmString::new_utf8(activation.context.gc_context, &meta.key);
|
let name = AvmString::new_utf8(activation.context.gc_context, &meta.key);
|
||||||
let value = meta.value.into_avm2_value(activation)?;
|
let value = meta.value.as_avm2_value(activation)?;
|
||||||
this.set_public_property(name, value, activation)?;
|
this.set_public_property(name, value, activation)?;
|
||||||
}
|
}
|
||||||
this.set_public_property(
|
this.set_public_property(
|
||||||
|
@ -29,13 +35,14 @@ pub fn init<'gc>(
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
for (index, param) in shader.params.into_iter().enumerate() {
|
for (index, param) in shader.params.iter().enumerate() {
|
||||||
let name = match ¶m {
|
let name = match ¶m {
|
||||||
PixelBenderParam::Normal {
|
PixelBenderParam::Normal {
|
||||||
name, qualifier, ..
|
name, qualifier, ..
|
||||||
} => {
|
} => {
|
||||||
// Neither of these show up in Flash Player
|
// Neither of these show up in Flash Player
|
||||||
if name == "_OutCoord" || matches!(qualifier, PixelBenderParamQualifier::Output) {
|
if name == OUT_COORD_NAME || matches!(qualifier, PixelBenderParamQualifier::Output)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
name
|
name
|
||||||
|
@ -47,5 +54,15 @@ pub fn init<'gc>(
|
||||||
let param_obj = make_shader_parameter(activation, param, index)?;
|
let param_obj = make_shader_parameter(activation, param, index)?;
|
||||||
this.set_public_property(name, param_obj, activation)?;
|
this.set_public_property(name, param_obj, activation)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let shader_handle = activation
|
||||||
|
.context
|
||||||
|
.renderer
|
||||||
|
.compile_pixelbender_shader(shader)
|
||||||
|
.expect("Failed to compile PixelBender shader");
|
||||||
|
|
||||||
|
this.as_shader_data()
|
||||||
|
.unwrap()
|
||||||
|
.set_pixel_bender_shader(shader_handle, activation.context.gc_context);
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
use ruffle_render::{
|
||||||
|
bitmap::PixelRegion,
|
||||||
|
pixel_bender::{
|
||||||
|
PixelBenderParam, PixelBenderParamQualifier, PixelBenderShaderArgument, PixelBenderType,
|
||||||
|
OUT_COORD_NAME,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
avm2::{string::AvmString, Activation, Error, Object, TObject, Value},
|
||||||
|
avm2_stub_method,
|
||||||
|
pixel_bender::PixelBenderTypeExt,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Implements `ShaderJob.start`.
|
||||||
|
pub fn start<'gc>(
|
||||||
|
activation: &mut Activation<'_, 'gc>,
|
||||||
|
this: Option<Object<'gc>>,
|
||||||
|
_args: &[Value<'gc>],
|
||||||
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
|
let this = this.unwrap();
|
||||||
|
|
||||||
|
avm2_stub_method!(
|
||||||
|
activation,
|
||||||
|
"flash.display.ShaderJob",
|
||||||
|
"start",
|
||||||
|
"async execution and non-BitmapData inputs"
|
||||||
|
);
|
||||||
|
|
||||||
|
// FIXME - determine what errors Flash Player throws here
|
||||||
|
// instead of using `expect`
|
||||||
|
let shader = this
|
||||||
|
.get_public_property("shader", activation)?
|
||||||
|
.as_object()
|
||||||
|
.expect("Missing Shader object");
|
||||||
|
let shader_data = shader
|
||||||
|
.get_public_property("data", activation)?
|
||||||
|
.as_object()
|
||||||
|
.expect("Missing ShaderData object")
|
||||||
|
.as_shader_data()
|
||||||
|
.expect("ShaderData object is not a ShaderData instance");
|
||||||
|
|
||||||
|
let shader_handle = shader_data.pixel_bender_shader();
|
||||||
|
let shader_handle = shader_handle
|
||||||
|
.as_ref()
|
||||||
|
.expect("ShaderData object has no shader");
|
||||||
|
let shader = shader_handle.0.parsed_shader();
|
||||||
|
|
||||||
|
let arguments: Vec<_> = shader
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.flat_map(|(index, param)| {
|
||||||
|
match param {
|
||||||
|
PixelBenderParam::Normal {
|
||||||
|
qualifier,
|
||||||
|
param_type,
|
||||||
|
name,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if matches!(qualifier, PixelBenderParamQualifier::Output) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == OUT_COORD_NAME {
|
||||||
|
// Pass in a dummy value - this will be ignored in favor of the actual pixel coordinate
|
||||||
|
return Some(PixelBenderShaderArgument::ValueInput {
|
||||||
|
index: index as u8,
|
||||||
|
value: PixelBenderType::TFloat2(f32::NAN, f32::NAN),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let shader_param = shader_data
|
||||||
|
.get_public_property(
|
||||||
|
AvmString::new_utf8(activation.context.gc_context, name),
|
||||||
|
activation,
|
||||||
|
)
|
||||||
|
.expect("Missing normal property");
|
||||||
|
|
||||||
|
let shader_param = shader_param
|
||||||
|
.as_object()
|
||||||
|
.expect("Shader property is not an object");
|
||||||
|
|
||||||
|
let value = shader_param
|
||||||
|
.get_public_property("value", activation)
|
||||||
|
.expect("Missing value property");
|
||||||
|
let pb_val = PixelBenderType::from_avm2_value(activation, value, param_type)
|
||||||
|
.expect("Failed to convert AVM2 value to PixelBenderType");
|
||||||
|
|
||||||
|
Some(PixelBenderShaderArgument::ValueInput {
|
||||||
|
index: index as u8,
|
||||||
|
value: pb_val,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
PixelBenderParam::Texture {
|
||||||
|
index,
|
||||||
|
channels,
|
||||||
|
name,
|
||||||
|
} => {
|
||||||
|
let shader_input = shader_data
|
||||||
|
.get_public_property(
|
||||||
|
AvmString::new_utf8(activation.context.gc_context, name),
|
||||||
|
activation,
|
||||||
|
)
|
||||||
|
.expect("Missing property")
|
||||||
|
.as_object()
|
||||||
|
.expect("Shader input is not an object");
|
||||||
|
|
||||||
|
let input = shader_input
|
||||||
|
.get_public_property("input", activation)
|
||||||
|
.expect("Missing input property");
|
||||||
|
let input = input
|
||||||
|
.as_object()
|
||||||
|
.expect("ShaderInput.input is not an object");
|
||||||
|
|
||||||
|
let bitmap = input.as_bitmap_data().expect(
|
||||||
|
"ShaderInput.input is not a BitmapData (FIXE - support other types)",
|
||||||
|
);
|
||||||
|
|
||||||
|
// FIXME - this really only needs to be a CPU->GPU sync
|
||||||
|
let bitmap = bitmap.sync();
|
||||||
|
let mut bitmap_data = bitmap.write(activation.context.gc_context);
|
||||||
|
bitmap_data.update_dirty_texture(activation.context.renderer);
|
||||||
|
|
||||||
|
Some(PixelBenderShaderArgument::ImageInput {
|
||||||
|
index: *index,
|
||||||
|
channels: *channels,
|
||||||
|
name: name.clone(),
|
||||||
|
texture: bitmap_data
|
||||||
|
.bitmap_handle(activation.context.renderer)
|
||||||
|
.expect("Missing input BitmapHandle"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let target = this
|
||||||
|
.get_public_property("target", activation)?
|
||||||
|
.as_object()
|
||||||
|
.expect("ShaderJob.target is not an object");
|
||||||
|
|
||||||
|
let target_bitmap = target
|
||||||
|
.as_bitmap_data()
|
||||||
|
.expect("ShaderJob.target is not a BitmapData (FIXE - support other types)")
|
||||||
|
.sync();
|
||||||
|
|
||||||
|
// Perform both a GPU->CPU and CPU->GPU sync before writing to it.
|
||||||
|
// FIXME - are both necessary?
|
||||||
|
let mut target_bitmap_data = target_bitmap.write(activation.context.gc_context);
|
||||||
|
target_bitmap_data.update_dirty_texture(activation.context.renderer);
|
||||||
|
|
||||||
|
let target_handle = target_bitmap_data
|
||||||
|
.bitmap_handle(activation.context.renderer)
|
||||||
|
.expect("Missing handle");
|
||||||
|
|
||||||
|
let sync_handle = activation
|
||||||
|
.context
|
||||||
|
.renderer
|
||||||
|
.run_pixelbender_shader(shader_handle.clone(), &arguments, target_handle)
|
||||||
|
.expect("Failed to run shader");
|
||||||
|
|
||||||
|
let width = target_bitmap_data.width();
|
||||||
|
let height = target_bitmap_data.height();
|
||||||
|
target_bitmap_data.set_gpu_dirty(sync_handle, PixelRegion::for_whole_size(width, height));
|
||||||
|
|
||||||
|
Ok(Value::Undefined)
|
||||||
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
|
use ruffle_render::pixel_bender::PixelBenderParam;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
avm2::{string::AvmString, Activation, Error, Multiname, TObject, Value},
|
avm2::{string::AvmString, Activation, Error, Multiname, TObject, Value},
|
||||||
pixel_bender::PixelBenderParam,
|
pixel_bender::PixelBenderTypeExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn make_shader_parameter<'gc>(
|
pub fn make_shader_parameter<'gc>(
|
||||||
activation: &mut Activation<'_, 'gc>,
|
activation: &mut Activation<'_, 'gc>,
|
||||||
param: PixelBenderParam,
|
param: &PixelBenderParam,
|
||||||
index: usize,
|
index: usize,
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
let ns = activation.avm2().flash_display_internal;
|
let ns = activation.avm2().flash_display_internal;
|
||||||
|
@ -29,7 +31,7 @@ pub fn make_shader_parameter<'gc>(
|
||||||
obj.set_property(&Multiname::new(ns, "_type"), type_name.into(), activation)?;
|
obj.set_property(&Multiname::new(ns, "_type"), type_name.into(), activation)?;
|
||||||
for meta in metadata {
|
for meta in metadata {
|
||||||
let name = AvmString::new_utf8(activation.context.gc_context, &meta.key);
|
let name = AvmString::new_utf8(activation.context.gc_context, &meta.key);
|
||||||
let value = meta.value.clone().into_avm2_value(activation)?;
|
let value = meta.value.clone().as_avm2_value(activation)?;
|
||||||
obj.set_public_property(name, value, activation)?;
|
obj.set_public_property(name, value, activation)?;
|
||||||
}
|
}
|
||||||
obj.set_public_property(
|
obj.set_public_property(
|
||||||
|
@ -47,7 +49,7 @@ pub fn make_shader_parameter<'gc>(
|
||||||
.construct(activation, &[])?;
|
.construct(activation, &[])?;
|
||||||
obj.set_property(
|
obj.set_property(
|
||||||
&Multiname::new(ns, "_channels"),
|
&Multiname::new(ns, "_channels"),
|
||||||
channels.into(),
|
(*channels).into(),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
obj.set_property(&Multiname::new(ns, "_index"), index.into(), activation)?;
|
obj.set_property(&Multiname::new(ns, "_index"), index.into(), activation)?;
|
||||||
|
|
|
@ -51,6 +51,7 @@ mod proxy_object;
|
||||||
mod qname_object;
|
mod qname_object;
|
||||||
mod regexp_object;
|
mod regexp_object;
|
||||||
mod script_object;
|
mod script_object;
|
||||||
|
mod shader_data_object;
|
||||||
mod sound_object;
|
mod sound_object;
|
||||||
mod soundchannel_object;
|
mod soundchannel_object;
|
||||||
mod stage3d_object;
|
mod stage3d_object;
|
||||||
|
@ -104,6 +105,9 @@ pub use crate::avm2::object::proxy_object::{proxy_allocator, ProxyObject, ProxyO
|
||||||
pub use crate::avm2::object::qname_object::{q_name_allocator, QNameObject, QNameObjectWeak};
|
pub use crate::avm2::object::qname_object::{q_name_allocator, QNameObject, QNameObjectWeak};
|
||||||
pub use crate::avm2::object::regexp_object::{reg_exp_allocator, RegExpObject, RegExpObjectWeak};
|
pub use crate::avm2::object::regexp_object::{reg_exp_allocator, RegExpObject, RegExpObjectWeak};
|
||||||
pub use crate::avm2::object::script_object::{ScriptObject, ScriptObjectData, ScriptObjectWeak};
|
pub use crate::avm2::object::script_object::{ScriptObject, ScriptObjectData, ScriptObjectWeak};
|
||||||
|
pub use crate::avm2::object::shader_data_object::{
|
||||||
|
shader_data_allocator, ShaderDataObject, ShaderDataObjectWeak,
|
||||||
|
};
|
||||||
pub use crate::avm2::object::sound_object::{
|
pub use crate::avm2::object::sound_object::{
|
||||||
sound_allocator, QueuedPlay, SoundData, SoundObject, SoundObjectWeak,
|
sound_allocator, QueuedPlay, SoundData, SoundObject, SoundObjectWeak,
|
||||||
};
|
};
|
||||||
|
@ -166,6 +170,7 @@ pub use crate::avm2::object::xml_object::{xml_allocator, XmlObject, XmlObjectWea
|
||||||
TextureObject(TextureObject<'gc>),
|
TextureObject(TextureObject<'gc>),
|
||||||
Program3DObject(Program3DObject<'gc>),
|
Program3DObject(Program3DObject<'gc>),
|
||||||
NetStreamObject(NetStreamObject<'gc>),
|
NetStreamObject(NetStreamObject<'gc>),
|
||||||
|
ShaderDataObject(ShaderDataObject<'gc>),
|
||||||
}
|
}
|
||||||
)]
|
)]
|
||||||
pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy {
|
pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy {
|
||||||
|
@ -1281,6 +1286,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_shader_data(&self) -> Option<ShaderDataObject<'gc>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Initialize the bitmap data in this object, if it's capable of
|
/// Initialize the bitmap data in this object, if it's capable of
|
||||||
/// supporting said data.
|
/// supporting said data.
|
||||||
///
|
///
|
||||||
|
@ -1394,6 +1403,7 @@ impl<'gc> Object<'gc> {
|
||||||
Self::TextureObject(o) => WeakObject::TextureObject(TextureObjectWeak(GcCell::downgrade(o.0))),
|
Self::TextureObject(o) => WeakObject::TextureObject(TextureObjectWeak(GcCell::downgrade(o.0))),
|
||||||
Self::Program3DObject(o) => WeakObject::Program3DObject(Program3DObjectWeak(GcCell::downgrade(o.0))),
|
Self::Program3DObject(o) => WeakObject::Program3DObject(Program3DObjectWeak(GcCell::downgrade(o.0))),
|
||||||
Self::NetStreamObject(o) => WeakObject::NetStreamObject(NetStreamObjectWeak(GcCell::downgrade(o.0))),
|
Self::NetStreamObject(o) => WeakObject::NetStreamObject(NetStreamObjectWeak(GcCell::downgrade(o.0))),
|
||||||
|
Self::ShaderDataObject(o) => WeakObject::ShaderDataObject(ShaderDataObjectWeak(GcCell::downgrade(o.0))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1448,6 +1458,7 @@ pub enum WeakObject<'gc> {
|
||||||
TextureObject(TextureObjectWeak<'gc>),
|
TextureObject(TextureObjectWeak<'gc>),
|
||||||
Program3DObject(Program3DObjectWeak<'gc>),
|
Program3DObject(Program3DObjectWeak<'gc>),
|
||||||
NetStreamObject(NetStreamObjectWeak<'gc>),
|
NetStreamObject(NetStreamObjectWeak<'gc>),
|
||||||
|
ShaderDataObject(ShaderDataObjectWeak<'gc>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> WeakObject<'gc> {
|
impl<'gc> WeakObject<'gc> {
|
||||||
|
@ -1485,6 +1496,7 @@ impl<'gc> WeakObject<'gc> {
|
||||||
Self::TextureObject(o) => TextureObject(o.0.upgrade(mc)?).into(),
|
Self::TextureObject(o) => TextureObject(o.0.upgrade(mc)?).into(),
|
||||||
Self::Program3DObject(o) => Program3DObject(o.0.upgrade(mc)?).into(),
|
Self::Program3DObject(o) => Program3DObject(o.0.upgrade(mc)?).into(),
|
||||||
Self::NetStreamObject(o) => NetStreamObject(o.0.upgrade(mc)?).into(),
|
Self::NetStreamObject(o) => NetStreamObject(o.0.upgrade(mc)?).into(),
|
||||||
|
Self::ShaderDataObject(o) => ShaderDataObject(o.0.upgrade(mc)?).into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
//! Object representation for `ShaderData`
|
||||||
|
|
||||||
|
use crate::avm2::activation::Activation;
|
||||||
|
use crate::avm2::object::script_object::ScriptObjectData;
|
||||||
|
use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject};
|
||||||
|
use crate::avm2::value::Value;
|
||||||
|
use crate::avm2::Error;
|
||||||
|
use core::fmt;
|
||||||
|
use gc_arena::{Collect, GcCell, GcWeakCell, MutationContext};
|
||||||
|
use ruffle_render::pixel_bender::PixelBenderShaderHandle;
|
||||||
|
use std::cell::{Ref, RefMut};
|
||||||
|
|
||||||
|
/// A class instance allocator that allocates ShaderData objects.
|
||||||
|
pub fn shader_data_allocator<'gc>(
|
||||||
|
class: ClassObject<'gc>,
|
||||||
|
activation: &mut Activation<'_, 'gc>,
|
||||||
|
) -> Result<Object<'gc>, Error<'gc>> {
|
||||||
|
let base = ScriptObjectData::new(class);
|
||||||
|
|
||||||
|
Ok(ShaderDataObject(GcCell::allocate(
|
||||||
|
activation.context.gc_context,
|
||||||
|
ShaderDataObjectData { base, shader: None },
|
||||||
|
))
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Collect, Copy)]
|
||||||
|
#[collect(no_drop)]
|
||||||
|
pub struct ShaderDataObject<'gc>(pub GcCell<'gc, ShaderDataObjectData<'gc>>);
|
||||||
|
|
||||||
|
#[derive(Clone, Collect, Copy, Debug)]
|
||||||
|
#[collect(no_drop)]
|
||||||
|
pub struct ShaderDataObjectWeak<'gc>(pub GcWeakCell<'gc, ShaderDataObjectData<'gc>>);
|
||||||
|
|
||||||
|
impl fmt::Debug for ShaderDataObject<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("ShaderDataObject")
|
||||||
|
.field("ptr", &self.0.as_ptr())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> ShaderDataObject<'gc> {
|
||||||
|
pub fn pixel_bender_shader(&self) -> Ref<'_, Option<PixelBenderShaderHandle>> {
|
||||||
|
Ref::map(self.0.read(), |read| &read.shader)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_pixel_bender_shader(
|
||||||
|
&self,
|
||||||
|
shader: PixelBenderShaderHandle,
|
||||||
|
mc: MutationContext<'gc, '_>,
|
||||||
|
) {
|
||||||
|
self.0.write(mc).shader = Some(shader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Collect)]
|
||||||
|
#[collect(no_drop)]
|
||||||
|
pub struct ShaderDataObjectData<'gc> {
|
||||||
|
/// Base script object
|
||||||
|
base: ScriptObjectData<'gc>,
|
||||||
|
|
||||||
|
#[collect(require_static)]
|
||||||
|
shader: Option<PixelBenderShaderHandle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> TObject<'gc> for ShaderDataObject<'gc> {
|
||||||
|
fn base(&self) -> Ref<ScriptObjectData<'gc>> {
|
||||||
|
Ref::map(self.0.read(), |read| &read.base)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn base_mut(&self, mc: MutationContext<'gc, '_>) -> RefMut<ScriptObjectData<'gc>> {
|
||||||
|
RefMut::map(self.0.write(mc), |write| &mut write.base)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_ptr(&self) -> *const ObjectPtr {
|
||||||
|
self.0.as_ptr() as *const ObjectPtr
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_of(&self, _mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
|
Ok(Value::Object(Object::from(*self)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_shader_data(&self) -> Option<ShaderDataObject<'gc>> {
|
||||||
|
Some(*self)
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,7 +34,7 @@ mod library;
|
||||||
pub mod limits;
|
pub mod limits;
|
||||||
pub mod loader;
|
pub mod loader;
|
||||||
mod locale;
|
mod locale;
|
||||||
mod pixel_bender;
|
pub mod pixel_bender;
|
||||||
mod player;
|
mod player;
|
||||||
mod prelude;
|
mod prelude;
|
||||||
mod streams;
|
mod streams;
|
||||||
|
|
|
@ -1,535 +1,142 @@
|
||||||
//! Pixel bender bytecode parsing code.
|
use ruffle_render::pixel_bender::{PixelBenderType, PixelBenderTypeOpcode};
|
||||||
//! This is heavling based on https://github.com/jamesward/pbjas and https://github.com/HaxeFoundation/format/tree/master/format/pbj
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
|
|
||||||
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
|
|
||||||
use num_traits::FromPrimitive;
|
|
||||||
use std::{
|
|
||||||
fmt::{Display, Formatter},
|
|
||||||
io::Read,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
avm2::{Activation, ArrayObject, ArrayStorage, Error, Value},
|
avm2::{Activation, ArrayObject, ArrayStorage, Error, TObject, Value},
|
||||||
ecma_conversions::f64_to_wrapping_i32,
|
ecma_conversions::f64_to_wrapping_i32,
|
||||||
string::AvmString,
|
string::AvmString,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[repr(u8)]
|
pub trait PixelBenderTypeExt {
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
fn from_avm2_value<'gc>(
|
||||||
pub enum PixelBenderType {
|
activation: &mut Activation<'_, 'gc>,
|
||||||
TFloat(f32) = 0x1,
|
value: Value<'gc>,
|
||||||
TFloat2(f32, f32) = 0x2,
|
kind: &PixelBenderTypeOpcode,
|
||||||
TFloat3(f32, f32, f32) = 0x3,
|
) -> Result<Self, Error<'gc>>
|
||||||
TFloat4(f32, f32, f32, f32) = 0x4,
|
where
|
||||||
TFloat2x2([f32; 4]) = 0x5,
|
Self: Sized;
|
||||||
TFloat3x3([f32; 9]) = 0x6,
|
|
||||||
TFloat4x4([f32; 16]) = 0x7,
|
fn as_avm2_value<'gc>(
|
||||||
TInt(i16) = 0x8,
|
&self,
|
||||||
TInt2(i16, i16) = 0x9,
|
activation: &mut Activation<'_, 'gc>,
|
||||||
TInt3(i16, i16, i16) = 0xA,
|
) -> Result<Value<'gc>, Error<'gc>>;
|
||||||
TInt4(i16, i16, i16, i16) = 0xB,
|
|
||||||
TString(String) = 0xC,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PixelBenderType {
|
impl PixelBenderTypeExt for PixelBenderType {
|
||||||
pub fn into_avm2_value<'gc>(
|
fn from_avm2_value<'gc>(
|
||||||
self,
|
activation: &mut Activation<'_, 'gc>,
|
||||||
|
value: Value<'gc>,
|
||||||
|
kind: &PixelBenderTypeOpcode,
|
||||||
|
) -> Result<Self, Error<'gc>>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let is_float = matches!(
|
||||||
|
kind,
|
||||||
|
PixelBenderTypeOpcode::TFloat
|
||||||
|
| PixelBenderTypeOpcode::TFloat2
|
||||||
|
| PixelBenderTypeOpcode::TFloat3
|
||||||
|
| PixelBenderTypeOpcode::TFloat4
|
||||||
|
);
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Value::String(s) => Ok(PixelBenderType::TString(s.to_string())),
|
||||||
|
Value::Number(n) => Ok(PixelBenderType::TFloat(n as f32)),
|
||||||
|
Value::Integer(i) => Ok(PixelBenderType::TInt(i as i16)),
|
||||||
|
Value::Object(o) => {
|
||||||
|
if let Some(array) = o.as_array_storage() {
|
||||||
|
if is_float {
|
||||||
|
let mut vals = array.iter().map(|val| {
|
||||||
|
val.expect("Array with hole")
|
||||||
|
.coerce_to_number(activation)
|
||||||
|
.unwrap() as f32
|
||||||
|
});
|
||||||
|
match kind {
|
||||||
|
PixelBenderTypeOpcode::TFloat => {
|
||||||
|
Ok(PixelBenderType::TFloat(vals.next().unwrap()))
|
||||||
|
}
|
||||||
|
PixelBenderTypeOpcode::TFloat2 => Ok(PixelBenderType::TFloat2(
|
||||||
|
vals.next().unwrap(),
|
||||||
|
vals.next().unwrap(),
|
||||||
|
)),
|
||||||
|
PixelBenderTypeOpcode::TFloat3 => Ok(PixelBenderType::TFloat3(
|
||||||
|
vals.next().unwrap(),
|
||||||
|
vals.next().unwrap(),
|
||||||
|
vals.next().unwrap(),
|
||||||
|
)),
|
||||||
|
PixelBenderTypeOpcode::TFloat4 => Ok(PixelBenderType::TFloat4(
|
||||||
|
vals.next().unwrap(),
|
||||||
|
vals.next().unwrap(),
|
||||||
|
vals.next().unwrap(),
|
||||||
|
vals.next().unwrap(),
|
||||||
|
)),
|
||||||
|
_ => unreachable!("Unexpected float kind {kind:?}"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut vals = array.iter().map(|val| {
|
||||||
|
val.expect("Array with hole")
|
||||||
|
.coerce_to_i32(activation)
|
||||||
|
.unwrap() as i16
|
||||||
|
});
|
||||||
|
match kind {
|
||||||
|
PixelBenderTypeOpcode::TInt => {
|
||||||
|
Ok(PixelBenderType::TInt(vals.next().unwrap()))
|
||||||
|
}
|
||||||
|
PixelBenderTypeOpcode::TInt2 => Ok(PixelBenderType::TInt2(
|
||||||
|
vals.next().unwrap(),
|
||||||
|
vals.next().unwrap(),
|
||||||
|
)),
|
||||||
|
PixelBenderTypeOpcode::TInt3 => Ok(PixelBenderType::TInt3(
|
||||||
|
vals.next().unwrap(),
|
||||||
|
vals.next().unwrap(),
|
||||||
|
vals.next().unwrap(),
|
||||||
|
)),
|
||||||
|
PixelBenderTypeOpcode::TInt4 => Ok(PixelBenderType::TInt4(
|
||||||
|
vals.next().unwrap(),
|
||||||
|
vals.next().unwrap(),
|
||||||
|
vals.next().unwrap(),
|
||||||
|
vals.next().unwrap(),
|
||||||
|
)),
|
||||||
|
_ => unreachable!("Unexpected int kind {kind:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Unexpected object {o:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected value {value:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn as_avm2_value<'gc>(
|
||||||
|
&self,
|
||||||
activation: &mut Activation<'_, 'gc>,
|
activation: &mut Activation<'_, 'gc>,
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
// Flash appears to use a uint/int if the float has no fractional part
|
// Flash appears to use a uint/int if the float has no fractional part
|
||||||
let cv = |f: f32| -> Value<'gc> {
|
let cv = |f: &f32| -> Value<'gc> {
|
||||||
if f.fract() == 0.0 {
|
if f.fract() == 0.0 {
|
||||||
f64_to_wrapping_i32(f as f64).into()
|
f64_to_wrapping_i32(*f as f64).into()
|
||||||
} else {
|
} else {
|
||||||
f.into()
|
(*f).into()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let vals: Vec<Value<'gc>> = match self {
|
let vals: Vec<Value<'gc>> = match self {
|
||||||
PixelBenderType::TString(string) => {
|
PixelBenderType::TString(string) => {
|
||||||
return Ok(AvmString::new_utf8(activation.context.gc_context, string).into());
|
return Ok(AvmString::new_utf8(activation.context.gc_context, string).into());
|
||||||
}
|
}
|
||||||
PixelBenderType::TInt(i) => return Ok(i.into()),
|
PixelBenderType::TInt(i) => return Ok((*i).into()),
|
||||||
PixelBenderType::TFloat(f) => vec![cv(f)],
|
PixelBenderType::TFloat(f) => vec![cv(f)],
|
||||||
PixelBenderType::TFloat2(f1, f2) => vec![cv(f1), cv(f2)],
|
PixelBenderType::TFloat2(f1, f2) => vec![cv(f1), cv(f2)],
|
||||||
PixelBenderType::TFloat3(f1, f2, f3) => vec![cv(f1), cv(f2), cv(f3)],
|
PixelBenderType::TFloat3(f1, f2, f3) => vec![cv(f1), cv(f2), cv(f3)],
|
||||||
PixelBenderType::TFloat4(f1, f2, f3, f4) => vec![cv(f1), cv(f2), cv(f3), cv(f4)],
|
PixelBenderType::TFloat4(f1, f2, f3, f4) => vec![cv(f1), cv(f2), cv(f3), cv(f4)],
|
||||||
PixelBenderType::TFloat2x2(floats) => floats.iter().map(|f| cv(*f)).collect(),
|
PixelBenderType::TFloat2x2(floats) => floats.iter().map(|f| cv(f)).collect(),
|
||||||
PixelBenderType::TFloat3x3(floats) => floats.iter().map(|f| cv(*f)).collect(),
|
PixelBenderType::TFloat3x3(floats) => floats.iter().map(|f| cv(f)).collect(),
|
||||||
PixelBenderType::TFloat4x4(floats) => floats.iter().map(|f| cv(*f)).collect(),
|
PixelBenderType::TFloat4x4(floats) => floats.iter().map(|f| cv(f)).collect(),
|
||||||
PixelBenderType::TInt2(i1, i2) => vec![i1.into(), i2.into()],
|
PixelBenderType::TInt2(i1, i2) => vec![(*i1).into(), (*i2).into()],
|
||||||
PixelBenderType::TInt3(i1, i2, i3) => vec![i1.into(), i2.into(), i3.into()],
|
PixelBenderType::TInt3(i1, i2, i3) => vec![(*i1).into(), (*i2).into(), (*i3).into()],
|
||||||
PixelBenderType::TInt4(i1, i2, i3, i4) => {
|
PixelBenderType::TInt4(i1, i2, i3, i4) => {
|
||||||
vec![i1.into(), i2.into(), i3.into(), i4.into()]
|
vec![(*i1).into(), (*i2).into(), (*i3).into(), (*i4).into()]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let storage = ArrayStorage::from_args(&vals);
|
let storage = ArrayStorage::from_args(&vals);
|
||||||
Ok(ArrayObject::from_storage(activation, storage)?.into())
|
Ok(ArrayObject::from_storage(activation, storage)?.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME - come up with a way to reduce duplication here
|
|
||||||
#[derive(num_derive::FromPrimitive, Debug, PartialEq)]
|
|
||||||
pub enum PixelBenderTypeOpcode {
|
|
||||||
TFloat = 0x1,
|
|
||||||
TFloat2 = 0x2,
|
|
||||||
TFloat3 = 0x3,
|
|
||||||
TFloat4 = 0x4,
|
|
||||||
TFloat2x2 = 0x5,
|
|
||||||
TFloat3x3 = 0x6,
|
|
||||||
TFloat4x4 = 0x7,
|
|
||||||
TInt = 0x8,
|
|
||||||
TInt2 = 0x9,
|
|
||||||
TInt3 = 0xA,
|
|
||||||
TInt4 = 0xB,
|
|
||||||
TString = 0xC,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(num_derive::FromPrimitive, Debug, PartialEq)]
|
|
||||||
pub enum PixelBenderParamQualifier {
|
|
||||||
Input = 1,
|
|
||||||
Output = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for PixelBenderTypeOpcode {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{}",
|
|
||||||
match self {
|
|
||||||
PixelBenderTypeOpcode::TFloat => "float",
|
|
||||||
PixelBenderTypeOpcode::TFloat2 => "float2",
|
|
||||||
PixelBenderTypeOpcode::TFloat3 => "float3",
|
|
||||||
PixelBenderTypeOpcode::TFloat4 => "float4",
|
|
||||||
PixelBenderTypeOpcode::TFloat2x2 => "matrix2x2",
|
|
||||||
PixelBenderTypeOpcode::TFloat3x3 => "matrix3x3",
|
|
||||||
PixelBenderTypeOpcode::TFloat4x4 => "matrix4x4",
|
|
||||||
PixelBenderTypeOpcode::TInt => "int",
|
|
||||||
PixelBenderTypeOpcode::TInt2 => "int2",
|
|
||||||
PixelBenderTypeOpcode::TInt3 => "int3",
|
|
||||||
PixelBenderTypeOpcode::TInt4 => "int4",
|
|
||||||
PixelBenderTypeOpcode::TString => "string",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(num_derive::FromPrimitive, Debug, PartialEq)]
|
|
||||||
pub enum Opcode {
|
|
||||||
Nop = 0x0,
|
|
||||||
Add = 0x1,
|
|
||||||
Sub = 0x2,
|
|
||||||
Mul = 0x3,
|
|
||||||
Rcp = 0x4,
|
|
||||||
Div = 0x5,
|
|
||||||
Atan2 = 0x6,
|
|
||||||
Pow = 0x7,
|
|
||||||
Mod = 0x8,
|
|
||||||
Min = 0x9,
|
|
||||||
Max = 0xA,
|
|
||||||
Step = 0xB,
|
|
||||||
Sin = 0xC,
|
|
||||||
Cos = 0xD,
|
|
||||||
Tan = 0xE,
|
|
||||||
Asin = 0xF,
|
|
||||||
Acos = 0x10,
|
|
||||||
Atan = 0x11,
|
|
||||||
Exp = 0x12,
|
|
||||||
Exp2 = 0x13,
|
|
||||||
Log = 0x14,
|
|
||||||
Log2 = 0x15,
|
|
||||||
Sqrt = 0x16,
|
|
||||||
RSqrt = 0x17,
|
|
||||||
Abs = 0x18,
|
|
||||||
Sign = 0x19,
|
|
||||||
Floor = 0x1A,
|
|
||||||
Ceil = 0x1B,
|
|
||||||
Fract = 0x1C,
|
|
||||||
Mov = 0x1D,
|
|
||||||
FloatToInt = 0x1E,
|
|
||||||
IntToFloat = 0x1F,
|
|
||||||
MatMatMul = 0x20,
|
|
||||||
VecMatMul = 0x21,
|
|
||||||
MatVecMul = 0x22,
|
|
||||||
Normalize = 0x23,
|
|
||||||
Length = 0x24,
|
|
||||||
Distance = 0x25,
|
|
||||||
DotProduct = 0x26,
|
|
||||||
CrossProduct = 0x27,
|
|
||||||
Equal = 0x28,
|
|
||||||
NotEqual = 0x29,
|
|
||||||
LessThan = 0x2A,
|
|
||||||
LessThanEqual = 0x2B,
|
|
||||||
LogicalNot = 0x2C,
|
|
||||||
LogicalAnd = 0x2D,
|
|
||||||
LogicalOr = 0x2E,
|
|
||||||
LogicalXor = 0x2F,
|
|
||||||
SampleNearest = 0x30,
|
|
||||||
SampleLinear = 0x31,
|
|
||||||
LoadIntOrFloat = 0x32,
|
|
||||||
Loop = 0x33,
|
|
||||||
If = 0x34,
|
|
||||||
Else = 0x35,
|
|
||||||
EndIf = 0x36,
|
|
||||||
FloatToBool = 0x37,
|
|
||||||
BoolToFloat = 0x38,
|
|
||||||
IntToBool = 0x39,
|
|
||||||
BoolToInt = 0x3A,
|
|
||||||
VectorEqual = 0x3B,
|
|
||||||
VectorNotEqual = 0x3C,
|
|
||||||
BoolAny = 0x3D,
|
|
||||||
BoolAll = 0x3E,
|
|
||||||
PBJMeta1 = 0xA0,
|
|
||||||
PBJParam = 0xA1,
|
|
||||||
PBJMeta2 = 0xA2,
|
|
||||||
PBJParamTexture = 0xA3,
|
|
||||||
Name = 0xA4,
|
|
||||||
Version = 0xA5,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Operation {
|
|
||||||
Nop,
|
|
||||||
Normal {
|
|
||||||
opcode: Opcode,
|
|
||||||
dst: u16,
|
|
||||||
mask: u8,
|
|
||||||
src: u32,
|
|
||||||
other: u8,
|
|
||||||
},
|
|
||||||
LoadInt {
|
|
||||||
dst: u16,
|
|
||||||
mask: u8,
|
|
||||||
val: i32,
|
|
||||||
},
|
|
||||||
LoadFloat {
|
|
||||||
dst: u16,
|
|
||||||
mask: u8,
|
|
||||||
val: f32,
|
|
||||||
},
|
|
||||||
If {
|
|
||||||
src: u32,
|
|
||||||
},
|
|
||||||
SampleNearest {
|
|
||||||
dst: u16,
|
|
||||||
src: u32,
|
|
||||||
mask: u8,
|
|
||||||
tf: u8,
|
|
||||||
},
|
|
||||||
SampleLinear {
|
|
||||||
dst: u16,
|
|
||||||
src: u32,
|
|
||||||
mask: u8,
|
|
||||||
tf: u8,
|
|
||||||
},
|
|
||||||
Else,
|
|
||||||
EndIf,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct PixelBenderShader {
|
|
||||||
pub name: String,
|
|
||||||
pub version: i32,
|
|
||||||
pub params: Vec<PixelBenderParam>,
|
|
||||||
pub metadata: Vec<PixelBenderMetadata>,
|
|
||||||
pub operations: Vec<Operation>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum PixelBenderParam {
|
|
||||||
Normal {
|
|
||||||
qualifier: PixelBenderParamQualifier,
|
|
||||||
param_type: PixelBenderTypeOpcode,
|
|
||||||
reg: u16,
|
|
||||||
mask: u8,
|
|
||||||
name: String,
|
|
||||||
metadata: Vec<PixelBenderMetadata>,
|
|
||||||
},
|
|
||||||
Texture {
|
|
||||||
index: u8,
|
|
||||||
channels: u8,
|
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct PixelBenderMetadata {
|
|
||||||
pub key: String,
|
|
||||||
pub value: PixelBenderType,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses PixelBender bytecode
|
|
||||||
pub fn parse_shader(mut data: &[u8]) -> PixelBenderShader {
|
|
||||||
let mut shader = PixelBenderShader {
|
|
||||||
name: String::new(),
|
|
||||||
version: 0,
|
|
||||||
params: Vec::new(),
|
|
||||||
metadata: Vec::new(),
|
|
||||||
operations: Vec::new(),
|
|
||||||
};
|
|
||||||
let data = &mut data;
|
|
||||||
let mut metadata = Vec::new();
|
|
||||||
while !data.is_empty() {
|
|
||||||
read_op(data, &mut shader, &mut metadata).unwrap();
|
|
||||||
}
|
|
||||||
// Any metadata left in the vec is associated with our final parameter.
|
|
||||||
apply_metadata(&mut shader, &mut metadata);
|
|
||||||
shader
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_op<R: Read>(
|
|
||||||
data: &mut R,
|
|
||||||
shader: &mut PixelBenderShader,
|
|
||||||
metadata: &mut Vec<PixelBenderMetadata>,
|
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let raw = data.read_u8()?;
|
|
||||||
let opcode = Opcode::from_u8(raw).expect("Unknown opcode");
|
|
||||||
match opcode {
|
|
||||||
Opcode::Nop => {
|
|
||||||
assert_eq!(data.read_u32::<LittleEndian>()?, 0);
|
|
||||||
assert_eq!(data.read_u16::<LittleEndian>()?, 0);
|
|
||||||
shader.operations.push(Operation::Nop);
|
|
||||||
}
|
|
||||||
Opcode::PBJMeta1 | Opcode::PBJMeta2 => {
|
|
||||||
let meta_type = data.read_u8()?;
|
|
||||||
let meta_key = read_string(data)?;
|
|
||||||
let meta_value = read_value(data, PixelBenderTypeOpcode::from_u8(meta_type).unwrap())?;
|
|
||||||
metadata.push(PixelBenderMetadata {
|
|
||||||
key: meta_key,
|
|
||||||
value: meta_value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Opcode::PBJParam => {
|
|
||||||
let qualifier = data.read_u8()?;
|
|
||||||
let param_type = data.read_u8()?;
|
|
||||||
let reg = data.read_u16::<LittleEndian>()?;
|
|
||||||
let mask = data.read_u8()?;
|
|
||||||
let name = read_string(data)?;
|
|
||||||
|
|
||||||
let param_type = PixelBenderTypeOpcode::from_u8(param_type).unwrap_or_else(|| {
|
|
||||||
panic!("Unexpected param type {param_type}");
|
|
||||||
});
|
|
||||||
let qualifier = PixelBenderParamQualifier::from_u8(qualifier)
|
|
||||||
.unwrap_or_else(|| panic!("Unexpected param qualifier {qualifier:?}"));
|
|
||||||
apply_metadata(shader, metadata);
|
|
||||||
|
|
||||||
shader.params.push(PixelBenderParam::Normal {
|
|
||||||
qualifier,
|
|
||||||
param_type,
|
|
||||||
reg,
|
|
||||||
mask,
|
|
||||||
name,
|
|
||||||
metadata: Vec::new(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Opcode::PBJParamTexture => {
|
|
||||||
let index = data.read_u8()?;
|
|
||||||
let channels = data.read_u8()?;
|
|
||||||
let name = read_string(data)?;
|
|
||||||
apply_metadata(shader, metadata);
|
|
||||||
|
|
||||||
shader.params.push(PixelBenderParam::Texture {
|
|
||||||
index,
|
|
||||||
channels,
|
|
||||||
name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Opcode::Name => {
|
|
||||||
let len = data.read_u16::<LittleEndian>()?;
|
|
||||||
let mut string_bytes = vec![0; len as usize];
|
|
||||||
data.read_exact(&mut string_bytes)?;
|
|
||||||
shader.name = String::from_utf8(string_bytes)?;
|
|
||||||
}
|
|
||||||
Opcode::Version => {
|
|
||||||
shader.version = data.read_i32::<LittleEndian>()?;
|
|
||||||
}
|
|
||||||
Opcode::If => {
|
|
||||||
assert_eq!(read_uint24(data)?, 0);
|
|
||||||
let src = read_uint24(data)?;
|
|
||||||
assert_eq!(data.read_u8()?, 0);
|
|
||||||
shader.operations.push(Operation::If { src });
|
|
||||||
}
|
|
||||||
Opcode::Else => {
|
|
||||||
assert_eq!(data.read_u32::<LittleEndian>()?, 0);
|
|
||||||
assert_eq!(read_uint24(data)?, 0);
|
|
||||||
shader.operations.push(Operation::Else);
|
|
||||||
}
|
|
||||||
Opcode::EndIf => {
|
|
||||||
assert_eq!(data.read_u32::<LittleEndian>()?, 0);
|
|
||||||
assert_eq!(read_uint24(data)?, 0);
|
|
||||||
shader.operations.push(Operation::EndIf);
|
|
||||||
}
|
|
||||||
Opcode::LoadIntOrFloat => {
|
|
||||||
let dst = data.read_u16::<LittleEndian>()?;
|
|
||||||
let mask = data.read_u8()?;
|
|
||||||
assert_eq!(mask & 0xF, 0);
|
|
||||||
if dst & 0x8000 != 0 {
|
|
||||||
let val = data.read_i32::<LittleEndian>()?;
|
|
||||||
shader.operations.push(Operation::LoadInt {
|
|
||||||
dst: dst - 0x8000,
|
|
||||||
mask,
|
|
||||||
val,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
let val = read_float(data)?;
|
|
||||||
shader
|
|
||||||
.operations
|
|
||||||
.push(Operation::LoadFloat { dst, mask, val })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode::SampleNearest | Opcode::SampleLinear => {
|
|
||||||
let dst = data.read_u16::<LittleEndian>()?;
|
|
||||||
let mask = data.read_u8()?;
|
|
||||||
let src = read_uint24(data)?;
|
|
||||||
let tf = data.read_u8()?;
|
|
||||||
match opcode {
|
|
||||||
Opcode::SampleNearest => {
|
|
||||||
shader
|
|
||||||
.operations
|
|
||||||
.push(Operation::SampleNearest { dst, mask, src, tf })
|
|
||||||
}
|
|
||||||
Opcode::SampleLinear => {
|
|
||||||
shader
|
|
||||||
.operations
|
|
||||||
.push(Operation::SampleLinear { dst, mask, src, tf })
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let dst = data.read_u16::<LittleEndian>()?;
|
|
||||||
let mask = data.read_u8()?;
|
|
||||||
let src = read_uint24(data)?;
|
|
||||||
assert_eq!(data.read_u8()?, 0, "Unexpected u8 for opcode {opcode:?}");
|
|
||||||
shader.operations.push(Operation::Normal {
|
|
||||||
opcode,
|
|
||||||
dst,
|
|
||||||
mask,
|
|
||||||
src,
|
|
||||||
other: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_string<R: Read>(data: &mut R) -> Result<String, Box<dyn std::error::Error>> {
|
|
||||||
let mut string = String::new();
|
|
||||||
let mut b = data.read_u8()?;
|
|
||||||
while b != 0 {
|
|
||||||
string.push(b as char);
|
|
||||||
b = data.read_u8()?;
|
|
||||||
}
|
|
||||||
Ok(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_float<R: Read>(data: &mut R) -> Result<f32, Box<dyn std::error::Error>> {
|
|
||||||
Ok(data.read_f32::<BigEndian>()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_value<R: Read>(
|
|
||||||
data: &mut R,
|
|
||||||
opcode: PixelBenderTypeOpcode,
|
|
||||||
) -> Result<PixelBenderType, Box<dyn std::error::Error>> {
|
|
||||||
match opcode {
|
|
||||||
PixelBenderTypeOpcode::TFloat => Ok(PixelBenderType::TFloat(read_float(data)?)),
|
|
||||||
PixelBenderTypeOpcode::TFloat2 => Ok(PixelBenderType::TFloat2(
|
|
||||||
read_float(data)?,
|
|
||||||
read_float(data)?,
|
|
||||||
)),
|
|
||||||
PixelBenderTypeOpcode::TFloat3 => Ok(PixelBenderType::TFloat3(
|
|
||||||
read_float(data)?,
|
|
||||||
read_float(data)?,
|
|
||||||
read_float(data)?,
|
|
||||||
)),
|
|
||||||
PixelBenderTypeOpcode::TFloat4 => Ok(PixelBenderType::TFloat4(
|
|
||||||
read_float(data)?,
|
|
||||||
read_float(data)?,
|
|
||||||
read_float(data)?,
|
|
||||||
read_float(data)?,
|
|
||||||
)),
|
|
||||||
PixelBenderTypeOpcode::TFloat2x2 => Ok(PixelBenderType::TFloat2x2([
|
|
||||||
read_float(data)?,
|
|
||||||
read_float(data)?,
|
|
||||||
read_float(data)?,
|
|
||||||
read_float(data)?,
|
|
||||||
])),
|
|
||||||
PixelBenderTypeOpcode::TFloat3x3 => {
|
|
||||||
let mut floats: [f32; 9] = [0.0; 9];
|
|
||||||
for float in &mut floats {
|
|
||||||
*float = read_float(data)?;
|
|
||||||
}
|
|
||||||
Ok(PixelBenderType::TFloat3x3(floats))
|
|
||||||
}
|
|
||||||
PixelBenderTypeOpcode::TFloat4x4 => {
|
|
||||||
let mut floats: [f32; 16] = [0.0; 16];
|
|
||||||
for float in &mut floats {
|
|
||||||
*float = read_float(data)?;
|
|
||||||
}
|
|
||||||
Ok(PixelBenderType::TFloat4x4(floats))
|
|
||||||
}
|
|
||||||
PixelBenderTypeOpcode::TInt => Ok(PixelBenderType::TInt(data.read_i16::<LittleEndian>()?)),
|
|
||||||
PixelBenderTypeOpcode::TInt2 => Ok(PixelBenderType::TInt2(
|
|
||||||
data.read_i16::<LittleEndian>()?,
|
|
||||||
data.read_i16::<LittleEndian>()?,
|
|
||||||
)),
|
|
||||||
PixelBenderTypeOpcode::TInt3 => Ok(PixelBenderType::TInt3(
|
|
||||||
data.read_i16::<LittleEndian>()?,
|
|
||||||
data.read_i16::<LittleEndian>()?,
|
|
||||||
data.read_i16::<LittleEndian>()?,
|
|
||||||
)),
|
|
||||||
PixelBenderTypeOpcode::TInt4 => Ok(PixelBenderType::TInt4(
|
|
||||||
data.read_i16::<LittleEndian>()?,
|
|
||||||
data.read_i16::<LittleEndian>()?,
|
|
||||||
data.read_i16::<LittleEndian>()?,
|
|
||||||
data.read_i16::<LittleEndian>()?,
|
|
||||||
)),
|
|
||||||
PixelBenderTypeOpcode::TString => Ok(PixelBenderType::TString(read_string(data)?)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_uint24<R: Read>(data: &mut R) -> Result<u32, Box<dyn std::error::Error>> {
|
|
||||||
let mut src = data.read_u16::<LittleEndian>()? as u32;
|
|
||||||
src += data.read_u8()? as u32;
|
|
||||||
Ok(src)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The opcodes are laid out like this:
|
|
||||||
//
|
|
||||||
// ```
|
|
||||||
// PBJMeta1 (for overall program)
|
|
||||||
// PBJMeta1 (for overall program)
|
|
||||||
// PBJParam (param 1)
|
|
||||||
// ...
|
|
||||||
// PBJMeta1 (for param 1)
|
|
||||||
// PBJMeta1 (for param 1)
|
|
||||||
// ...
|
|
||||||
// PBJParam (param 2)
|
|
||||||
// ,,,
|
|
||||||
// PBJMeta2 (for param 2)
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// The metadata associated with parameter is determined by all of the metadata opcodes
|
|
||||||
// that come after it and before the next parameter opcode. The metadata opcodes
|
|
||||||
// that come before all params are associated with the overall program.
|
|
||||||
|
|
||||||
fn apply_metadata(shader: &mut PixelBenderShader, metadata: &mut Vec<PixelBenderMetadata>) {
|
|
||||||
// Reset the accumulated metadata Vec - we will start accumulating metadata for the next param
|
|
||||||
let metadata = std::mem::take(metadata);
|
|
||||||
if shader.params.is_empty() {
|
|
||||||
shader.metadata = metadata;
|
|
||||||
} else {
|
|
||||||
match shader.params.last_mut().unwrap() {
|
|
||||||
PixelBenderParam::Normal { metadata: meta, .. } => {
|
|
||||||
*meta = metadata;
|
|
||||||
}
|
|
||||||
param => {
|
|
||||||
if !metadata.is_empty() {
|
|
||||||
panic!("Tried to apply metadata to texture parameter {param:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -67,6 +67,7 @@ fn init() {
|
||||||
let subscriber = tracing_subscriber::fmt::Subscriber::builder()
|
let subscriber = tracing_subscriber::fmt::Subscriber::builder()
|
||||||
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
#[cfg(feature = "tracy")]
|
#[cfg(feature = "tracy")]
|
||||||
let subscriber = {
|
let subscriber = {
|
||||||
use tracing_subscriber::layer::SubscriberExt;
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
|
|
|
@ -25,6 +25,9 @@ serde = { version = "1.0.164", features = ["derive"] }
|
||||||
clap = { version = "4.3.3", features = ["derive"], optional = true }
|
clap = { version = "4.3.3", features = ["derive"], optional = true }
|
||||||
h263-rs-yuv = { git = "https://github.com/ruffle-rs/h263-rs", rev = "d5d78eb251c1ce1f1da57c63db14f0fdc77a4b36"}
|
h263-rs-yuv = { git = "https://github.com/ruffle-rs/h263-rs", rev = "d5d78eb251c1ce1f1da57c63db14f0fdc77a4b36"}
|
||||||
lru = "0.10.0"
|
lru = "0.10.0"
|
||||||
|
num-traits = "0.2"
|
||||||
|
num-derive = "0.3"
|
||||||
|
byteorder = "1.4"
|
||||||
|
|
||||||
[dependencies.jpeg-decoder]
|
[dependencies.jpeg-decoder]
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
|
|
@ -487,6 +487,22 @@ impl RenderBackend for WebCanvasRenderBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_quality(&mut self, _quality: StageQuality) {}
|
fn set_quality(&mut self, _quality: StageQuality) {}
|
||||||
|
|
||||||
|
fn compile_pixelbender_shader(
|
||||||
|
&mut self,
|
||||||
|
_shader: ruffle_render::pixel_bender::PixelBenderShader,
|
||||||
|
) -> Result<ruffle_render::pixel_bender::PixelBenderShaderHandle, Error> {
|
||||||
|
Err(Error::Unimplemented("compile_pixelbender_shader".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_pixelbender_shader(
|
||||||
|
&mut self,
|
||||||
|
_handle: ruffle_render::pixel_bender::PixelBenderShaderHandle,
|
||||||
|
_arguments: &[ruffle_render::pixel_bender::PixelBenderShaderArgument],
|
||||||
|
_target: BitmapHandle,
|
||||||
|
) -> Result<Box<dyn SyncHandle>, Error> {
|
||||||
|
Err(Error::Unimplemented("run_pixelbender_shader".into()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandHandler for WebCanvasRenderBackend {
|
impl CommandHandler for WebCanvasRenderBackend {
|
||||||
|
|
|
@ -9,10 +9,10 @@ version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "2.3.1"
|
bitflags = "2.3.1"
|
||||||
naga = "0.12.2"
|
naga = { workspace = true }
|
||||||
num-derive = "0.3.3"
|
num-derive = "0.3.3"
|
||||||
num-traits = "0.2.15"
|
num-traits = "0.2.15"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = "1.29.0"
|
insta = "1.29.0"
|
||||||
naga = { version = "0.12.2", features = ["wgsl-out", "validate"] }
|
naga = { workspace = true, features = ["wgsl-out", "validate"] }
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "naga-pixelbender"
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ruffle_render = { path = "../" }
|
||||||
|
naga = { workspace = true }
|
||||||
|
anyhow = "1.0.71"
|
||||||
|
bitflags = "2.3.1"
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,6 +4,7 @@ use crate::bitmap::{Bitmap, BitmapHandle, BitmapSource, PixelRegion, SyncHandle}
|
||||||
use crate::commands::CommandList;
|
use crate::commands::CommandList;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::filters::Filter;
|
use crate::filters::Filter;
|
||||||
|
use crate::pixel_bender::{PixelBenderShader, PixelBenderShaderArgument, PixelBenderShaderHandle};
|
||||||
use crate::quality::StageQuality;
|
use crate::quality::StageQuality;
|
||||||
use crate::shape_utils::DistilledShape;
|
use crate::shape_utils::DistilledShape;
|
||||||
use downcast_rs::{impl_downcast, Downcast};
|
use downcast_rs::{impl_downcast, Downcast};
|
||||||
|
@ -75,6 +76,18 @@ pub trait RenderBackend: Downcast {
|
||||||
fn name(&self) -> &'static str;
|
fn name(&self) -> &'static str;
|
||||||
|
|
||||||
fn set_quality(&mut self, quality: StageQuality);
|
fn set_quality(&mut self, quality: StageQuality);
|
||||||
|
|
||||||
|
fn compile_pixelbender_shader(
|
||||||
|
&mut self,
|
||||||
|
shader: PixelBenderShader,
|
||||||
|
) -> Result<PixelBenderShaderHandle, Error>;
|
||||||
|
|
||||||
|
fn run_pixelbender_shader(
|
||||||
|
&mut self,
|
||||||
|
handle: PixelBenderShaderHandle,
|
||||||
|
arguments: &[PixelBenderShaderArgument],
|
||||||
|
target: BitmapHandle,
|
||||||
|
) -> Result<Box<dyn SyncHandle>, Error>;
|
||||||
}
|
}
|
||||||
impl_downcast!(RenderBackend);
|
impl_downcast!(RenderBackend);
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ use crate::bitmap::{
|
||||||
};
|
};
|
||||||
use crate::commands::CommandList;
|
use crate::commands::CommandList;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
use crate::pixel_bender::{PixelBenderShader, PixelBenderShaderArgument, PixelBenderShaderHandle};
|
||||||
use crate::quality::StageQuality;
|
use crate::quality::StageQuality;
|
||||||
use crate::shape_utils::DistilledShape;
|
use crate::shape_utils::DistilledShape;
|
||||||
use swf::Color;
|
use swf::Color;
|
||||||
|
@ -98,4 +99,22 @@ impl RenderBackend for NullRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_quality(&mut self, _quality: StageQuality) {}
|
fn set_quality(&mut self, _quality: StageQuality) {}
|
||||||
|
|
||||||
|
fn run_pixelbender_shader(
|
||||||
|
&mut self,
|
||||||
|
_shader: PixelBenderShaderHandle,
|
||||||
|
_arguments: &[PixelBenderShaderArgument],
|
||||||
|
_target: BitmapHandle,
|
||||||
|
) -> Result<Box<dyn SyncHandle>, Error> {
|
||||||
|
Err(Error::Unimplemented("Pixel bender shader".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_pixelbender_shader(
|
||||||
|
&mut self,
|
||||||
|
_shader: PixelBenderShader,
|
||||||
|
) -> Result<PixelBenderShaderHandle, Error> {
|
||||||
|
Err(Error::Unimplemented(
|
||||||
|
"Pixel bender shader compilation".into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ pub mod bitmap;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod filters;
|
pub mod filters;
|
||||||
pub mod matrix;
|
pub mod matrix;
|
||||||
|
pub mod pixel_bender;
|
||||||
pub mod shape_utils;
|
pub mod shape_utils;
|
||||||
pub mod transform;
|
pub mod transform;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
|
@ -0,0 +1,636 @@
|
||||||
|
//! Pixel bender bytecode parsing code.
|
||||||
|
//! This is heavily based on https://github.com/jamesward/pbjas and https://github.com/HaxeFoundation/format/tree/master/format/pbj
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
|
||||||
|
use downcast_rs::{impl_downcast, Downcast};
|
||||||
|
use gc_arena::Collect;
|
||||||
|
use num_traits::FromPrimitive;
|
||||||
|
use std::{
|
||||||
|
fmt::{Debug, Display, Formatter},
|
||||||
|
io::Read,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::bitmap::BitmapHandle;
|
||||||
|
|
||||||
|
/// The name of a special parameter, which gets automatically filled in with the coordinates
|
||||||
|
/// of the pixel being processed.
|
||||||
|
pub const OUT_COORD_NAME: &str = "_OutCoord";
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Collect)]
|
||||||
|
#[collect(require_static)]
|
||||||
|
pub struct PixelBenderShaderHandle(pub Arc<dyn PixelBenderShaderImpl>);
|
||||||
|
|
||||||
|
pub trait PixelBenderShaderImpl: Downcast + Debug {
|
||||||
|
fn parsed_shader(&self) -> &PixelBenderShader;
|
||||||
|
}
|
||||||
|
impl_downcast!(PixelBenderShaderImpl);
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum PixelBenderType {
|
||||||
|
TFloat(f32) = 0x1,
|
||||||
|
TFloat2(f32, f32) = 0x2,
|
||||||
|
TFloat3(f32, f32, f32) = 0x3,
|
||||||
|
TFloat4(f32, f32, f32, f32) = 0x4,
|
||||||
|
TFloat2x2([f32; 4]) = 0x5,
|
||||||
|
TFloat3x3([f32; 9]) = 0x6,
|
||||||
|
TFloat4x4([f32; 16]) = 0x7,
|
||||||
|
TInt(i16) = 0x8,
|
||||||
|
TInt2(i16, i16) = 0x9,
|
||||||
|
TInt3(i16, i16, i16) = 0xA,
|
||||||
|
TInt4(i16, i16, i16, i16) = 0xB,
|
||||||
|
TString(String) = 0xC,
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME - come up with a way to reduce duplication here
|
||||||
|
#[derive(num_derive::FromPrimitive, Debug, PartialEq, Clone, Copy)]
|
||||||
|
pub enum PixelBenderTypeOpcode {
|
||||||
|
TFloat = 0x1,
|
||||||
|
TFloat2 = 0x2,
|
||||||
|
TFloat3 = 0x3,
|
||||||
|
TFloat4 = 0x4,
|
||||||
|
TFloat2x2 = 0x5,
|
||||||
|
TFloat3x3 = 0x6,
|
||||||
|
TFloat4x4 = 0x7,
|
||||||
|
TInt = 0x8,
|
||||||
|
TInt2 = 0x9,
|
||||||
|
TInt3 = 0xA,
|
||||||
|
TInt4 = 0xB,
|
||||||
|
TString = 0xC,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||||
|
pub enum PixelBenderRegChannel {
|
||||||
|
R = 0,
|
||||||
|
G = 1,
|
||||||
|
B = 2,
|
||||||
|
A = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PixelBenderRegChannel {
|
||||||
|
pub const RGBA: [PixelBenderRegChannel; 4] = [
|
||||||
|
PixelBenderRegChannel::R,
|
||||||
|
PixelBenderRegChannel::G,
|
||||||
|
PixelBenderRegChannel::B,
|
||||||
|
PixelBenderRegChannel::A,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub struct PixelBenderReg {
|
||||||
|
pub index: u32,
|
||||||
|
pub channels: Vec<PixelBenderRegChannel>,
|
||||||
|
pub kind: PixelBenderRegKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
pub enum PixelBenderRegKind {
|
||||||
|
Float,
|
||||||
|
Int,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(num_derive::FromPrimitive, Debug, PartialEq, Clone, Copy)]
|
||||||
|
pub enum PixelBenderParamQualifier {
|
||||||
|
Input = 1,
|
||||||
|
Output = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for PixelBenderTypeOpcode {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
PixelBenderTypeOpcode::TFloat => "float",
|
||||||
|
PixelBenderTypeOpcode::TFloat2 => "float2",
|
||||||
|
PixelBenderTypeOpcode::TFloat3 => "float3",
|
||||||
|
PixelBenderTypeOpcode::TFloat4 => "float4",
|
||||||
|
PixelBenderTypeOpcode::TFloat2x2 => "matrix2x2",
|
||||||
|
PixelBenderTypeOpcode::TFloat3x3 => "matrix3x3",
|
||||||
|
PixelBenderTypeOpcode::TFloat4x4 => "matrix4x4",
|
||||||
|
PixelBenderTypeOpcode::TInt => "int",
|
||||||
|
PixelBenderTypeOpcode::TInt2 => "int2",
|
||||||
|
PixelBenderTypeOpcode::TInt3 => "int3",
|
||||||
|
PixelBenderTypeOpcode::TInt4 => "int4",
|
||||||
|
PixelBenderTypeOpcode::TString => "string",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(num_derive::FromPrimitive, Debug, PartialEq, Clone, Copy)]
|
||||||
|
pub enum Opcode {
|
||||||
|
Nop = 0x0,
|
||||||
|
Add = 0x1,
|
||||||
|
Sub = 0x2,
|
||||||
|
Mul = 0x3,
|
||||||
|
Rcp = 0x4,
|
||||||
|
Div = 0x5,
|
||||||
|
Atan2 = 0x6,
|
||||||
|
Pow = 0x7,
|
||||||
|
Mod = 0x8,
|
||||||
|
Min = 0x9,
|
||||||
|
Max = 0xA,
|
||||||
|
Step = 0xB,
|
||||||
|
Sin = 0xC,
|
||||||
|
Cos = 0xD,
|
||||||
|
Tan = 0xE,
|
||||||
|
Asin = 0xF,
|
||||||
|
Acos = 0x10,
|
||||||
|
Atan = 0x11,
|
||||||
|
Exp = 0x12,
|
||||||
|
Exp2 = 0x13,
|
||||||
|
Log = 0x14,
|
||||||
|
Log2 = 0x15,
|
||||||
|
Sqrt = 0x16,
|
||||||
|
RSqrt = 0x17,
|
||||||
|
Abs = 0x18,
|
||||||
|
Sign = 0x19,
|
||||||
|
Floor = 0x1A,
|
||||||
|
Ceil = 0x1B,
|
||||||
|
Fract = 0x1C,
|
||||||
|
Mov = 0x1D,
|
||||||
|
FloatToInt = 0x1E,
|
||||||
|
IntToFloat = 0x1F,
|
||||||
|
MatMatMul = 0x20,
|
||||||
|
VecMatMul = 0x21,
|
||||||
|
MatVecMul = 0x22,
|
||||||
|
Normalize = 0x23,
|
||||||
|
Length = 0x24,
|
||||||
|
Distance = 0x25,
|
||||||
|
DotProduct = 0x26,
|
||||||
|
CrossProduct = 0x27,
|
||||||
|
Equal = 0x28,
|
||||||
|
NotEqual = 0x29,
|
||||||
|
LessThan = 0x2A,
|
||||||
|
LessThanEqual = 0x2B,
|
||||||
|
LogicalNot = 0x2C,
|
||||||
|
LogicalAnd = 0x2D,
|
||||||
|
LogicalOr = 0x2E,
|
||||||
|
LogicalXor = 0x2F,
|
||||||
|
SampleNearest = 0x30,
|
||||||
|
SampleLinear = 0x31,
|
||||||
|
LoadIntOrFloat = 0x32,
|
||||||
|
Loop = 0x33,
|
||||||
|
If = 0x34,
|
||||||
|
Else = 0x35,
|
||||||
|
EndIf = 0x36,
|
||||||
|
FloatToBool = 0x37,
|
||||||
|
BoolToFloat = 0x38,
|
||||||
|
IntToBool = 0x39,
|
||||||
|
BoolToInt = 0x3A,
|
||||||
|
VectorEqual = 0x3B,
|
||||||
|
VectorNotEqual = 0x3C,
|
||||||
|
BoolAny = 0x3D,
|
||||||
|
BoolAll = 0x3E,
|
||||||
|
PBJMeta1 = 0xA0,
|
||||||
|
PBJParam = 0xA1,
|
||||||
|
PBJMeta2 = 0xA2,
|
||||||
|
PBJParamTexture = 0xA3,
|
||||||
|
Name = 0xA4,
|
||||||
|
Version = 0xA5,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub enum Operation {
|
||||||
|
Nop,
|
||||||
|
Normal {
|
||||||
|
opcode: Opcode,
|
||||||
|
dst: PixelBenderReg,
|
||||||
|
src: PixelBenderReg,
|
||||||
|
},
|
||||||
|
LoadInt {
|
||||||
|
dst: PixelBenderReg,
|
||||||
|
val: i32,
|
||||||
|
},
|
||||||
|
LoadFloat {
|
||||||
|
dst: PixelBenderReg,
|
||||||
|
val: f32,
|
||||||
|
},
|
||||||
|
If {
|
||||||
|
src: PixelBenderReg,
|
||||||
|
},
|
||||||
|
SampleNearest {
|
||||||
|
dst: PixelBenderReg,
|
||||||
|
src: PixelBenderReg,
|
||||||
|
tf: u8,
|
||||||
|
},
|
||||||
|
SampleLinear {
|
||||||
|
dst: PixelBenderReg,
|
||||||
|
src: PixelBenderReg,
|
||||||
|
tf: u8,
|
||||||
|
},
|
||||||
|
Else,
|
||||||
|
EndIf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum PixelBenderShaderArgument {
|
||||||
|
ImageInput {
|
||||||
|
index: u8,
|
||||||
|
channels: u8,
|
||||||
|
name: String,
|
||||||
|
texture: BitmapHandle,
|
||||||
|
},
|
||||||
|
ValueInput {
|
||||||
|
index: u8,
|
||||||
|
value: PixelBenderType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub struct PixelBenderShader {
|
||||||
|
pub name: String,
|
||||||
|
pub version: i32,
|
||||||
|
pub params: Vec<PixelBenderParam>,
|
||||||
|
pub metadata: Vec<PixelBenderMetadata>,
|
||||||
|
pub operations: Vec<Operation>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub enum PixelBenderParam {
|
||||||
|
Normal {
|
||||||
|
qualifier: PixelBenderParamQualifier,
|
||||||
|
param_type: PixelBenderTypeOpcode,
|
||||||
|
reg: PixelBenderReg,
|
||||||
|
name: String,
|
||||||
|
metadata: Vec<PixelBenderMetadata>,
|
||||||
|
},
|
||||||
|
Texture {
|
||||||
|
index: u8,
|
||||||
|
channels: u8,
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct PixelBenderMetadata {
|
||||||
|
pub key: String,
|
||||||
|
pub value: PixelBenderType,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses PixelBender bytecode
|
||||||
|
pub fn parse_shader(mut data: &[u8]) -> Result<PixelBenderShader, Box<dyn std::error::Error>> {
|
||||||
|
let mut shader = PixelBenderShader {
|
||||||
|
name: String::new(),
|
||||||
|
version: 0,
|
||||||
|
params: Vec::new(),
|
||||||
|
metadata: Vec::new(),
|
||||||
|
operations: Vec::new(),
|
||||||
|
};
|
||||||
|
let data = &mut data;
|
||||||
|
let mut metadata = Vec::new();
|
||||||
|
while !data.is_empty() {
|
||||||
|
read_op(data, &mut shader, &mut metadata)?;
|
||||||
|
}
|
||||||
|
// Any metadata left in the vec is associated with our final parameter.
|
||||||
|
apply_metadata(&mut shader, &mut metadata);
|
||||||
|
Ok(shader)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_src_reg(val: u32, size: u8) -> Result<PixelBenderReg, Box<dyn std::error::Error>> {
|
||||||
|
const CHANNELS: [PixelBenderRegChannel; 4] = [
|
||||||
|
PixelBenderRegChannel::R,
|
||||||
|
PixelBenderRegChannel::G,
|
||||||
|
PixelBenderRegChannel::B,
|
||||||
|
PixelBenderRegChannel::A,
|
||||||
|
];
|
||||||
|
|
||||||
|
let swizzle = val >> 16;
|
||||||
|
let mut channels = Vec::new();
|
||||||
|
for i in 0..size {
|
||||||
|
channels.push(CHANNELS[(swizzle >> (6 - i * 2) & 3) as usize])
|
||||||
|
}
|
||||||
|
|
||||||
|
let kind = if val & 0x8000 != 0 {
|
||||||
|
PixelBenderRegKind::Int
|
||||||
|
} else {
|
||||||
|
PixelBenderRegKind::Float
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(PixelBenderReg {
|
||||||
|
// Mask off the 0x8000 bit
|
||||||
|
index: val & 0x7FFF,
|
||||||
|
channels,
|
||||||
|
kind,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_dst_reg(val: u16, mask: u8) -> Result<PixelBenderReg, Box<dyn std::error::Error>> {
|
||||||
|
let mut channels = Vec::new();
|
||||||
|
if mask & 0x8 != 0 {
|
||||||
|
channels.push(PixelBenderRegChannel::R);
|
||||||
|
}
|
||||||
|
if mask & 0x4 != 0 {
|
||||||
|
channels.push(PixelBenderRegChannel::G);
|
||||||
|
}
|
||||||
|
if mask & 0x2 != 0 {
|
||||||
|
channels.push(PixelBenderRegChannel::B);
|
||||||
|
}
|
||||||
|
if mask & 0x1 != 0 {
|
||||||
|
channels.push(PixelBenderRegChannel::A);
|
||||||
|
}
|
||||||
|
|
||||||
|
let kind = if val & 0x8000 != 0 {
|
||||||
|
PixelBenderRegKind::Int
|
||||||
|
} else {
|
||||||
|
PixelBenderRegKind::Float
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(PixelBenderReg {
|
||||||
|
// Mask off the 0x8000 bit
|
||||||
|
index: (val & 0x7FFF) as u32,
|
||||||
|
channels,
|
||||||
|
kind,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_op<R: Read>(
|
||||||
|
data: &mut R,
|
||||||
|
shader: &mut PixelBenderShader,
|
||||||
|
metadata: &mut Vec<PixelBenderMetadata>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let raw = data.read_u8()?;
|
||||||
|
let opcode = Opcode::from_u8(raw).expect("Unknown opcode");
|
||||||
|
match opcode {
|
||||||
|
Opcode::Nop => {
|
||||||
|
assert_eq!(data.read_u32::<LittleEndian>()?, 0);
|
||||||
|
assert_eq!(data.read_u16::<LittleEndian>()?, 0);
|
||||||
|
shader.operations.push(Operation::Nop);
|
||||||
|
}
|
||||||
|
Opcode::PBJMeta1 | Opcode::PBJMeta2 => {
|
||||||
|
let meta_type = data.read_u8()?;
|
||||||
|
let meta_key = read_string(data)?;
|
||||||
|
let meta_value = read_value(
|
||||||
|
data,
|
||||||
|
PixelBenderTypeOpcode::from_u8(meta_type)
|
||||||
|
.unwrap_or_else(|| panic!("Unexpected meta type {meta_type}")),
|
||||||
|
)?;
|
||||||
|
metadata.push(PixelBenderMetadata {
|
||||||
|
key: meta_key,
|
||||||
|
value: meta_value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Opcode::PBJParam => {
|
||||||
|
let qualifier = data.read_u8()?;
|
||||||
|
let param_type = data.read_u8()?;
|
||||||
|
let reg = data.read_u16::<LittleEndian>()?;
|
||||||
|
let mask = data.read_u8()?;
|
||||||
|
let name = read_string(data)?;
|
||||||
|
|
||||||
|
let param_type = PixelBenderTypeOpcode::from_u8(param_type).unwrap_or_else(|| {
|
||||||
|
panic!("Unexpected param type {param_type}");
|
||||||
|
});
|
||||||
|
let qualifier = PixelBenderParamQualifier::from_u8(qualifier)
|
||||||
|
.unwrap_or_else(|| panic!("Unexpected param qualifier {qualifier:?}"));
|
||||||
|
apply_metadata(shader, metadata);
|
||||||
|
|
||||||
|
match param_type {
|
||||||
|
PixelBenderTypeOpcode::TFloat2x2
|
||||||
|
| PixelBenderTypeOpcode::TFloat3x3
|
||||||
|
| PixelBenderTypeOpcode::TFloat4x4 => {
|
||||||
|
panic!("Unsupported param type {param_type:?}");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dst_reg = read_dst_reg(reg, mask)?;
|
||||||
|
|
||||||
|
shader.params.push(PixelBenderParam::Normal {
|
||||||
|
qualifier,
|
||||||
|
param_type,
|
||||||
|
reg: dst_reg,
|
||||||
|
name,
|
||||||
|
metadata: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Opcode::PBJParamTexture => {
|
||||||
|
let index = data.read_u8()?;
|
||||||
|
let channels = data.read_u8()?;
|
||||||
|
let name = read_string(data)?;
|
||||||
|
apply_metadata(shader, metadata);
|
||||||
|
|
||||||
|
shader.params.push(PixelBenderParam::Texture {
|
||||||
|
index,
|
||||||
|
channels,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Opcode::Name => {
|
||||||
|
let len = data.read_u16::<LittleEndian>()?;
|
||||||
|
let mut string_bytes = vec![0; len as usize];
|
||||||
|
data.read_exact(&mut string_bytes)?;
|
||||||
|
shader.name = String::from_utf8(string_bytes)?;
|
||||||
|
}
|
||||||
|
Opcode::Version => {
|
||||||
|
shader.version = data.read_i32::<LittleEndian>()?;
|
||||||
|
}
|
||||||
|
Opcode::If => {
|
||||||
|
assert_eq!(read_uint24(data)?, 0);
|
||||||
|
let src = read_uint24(data)?;
|
||||||
|
assert_eq!(data.read_u8()?, 0);
|
||||||
|
let src_reg = read_src_reg(src, 1)?;
|
||||||
|
shader.operations.push(Operation::If { src: src_reg });
|
||||||
|
}
|
||||||
|
Opcode::Else => {
|
||||||
|
assert_eq!(data.read_u32::<LittleEndian>()?, 0);
|
||||||
|
assert_eq!(read_uint24(data)?, 0);
|
||||||
|
shader.operations.push(Operation::Else);
|
||||||
|
}
|
||||||
|
Opcode::EndIf => {
|
||||||
|
assert_eq!(data.read_u32::<LittleEndian>()?, 0);
|
||||||
|
assert_eq!(read_uint24(data)?, 0);
|
||||||
|
shader.operations.push(Operation::EndIf);
|
||||||
|
}
|
||||||
|
Opcode::LoadIntOrFloat => {
|
||||||
|
let dst = data.read_u16::<LittleEndian>()?;
|
||||||
|
let mask = data.read_u8()?;
|
||||||
|
assert_eq!(mask & 0xF, 0);
|
||||||
|
let dst_reg = read_dst_reg(dst, mask >> 4)?;
|
||||||
|
match dst_reg.kind {
|
||||||
|
PixelBenderRegKind::Float => {
|
||||||
|
let val = read_float(data)?;
|
||||||
|
shader
|
||||||
|
.operations
|
||||||
|
.push(Operation::LoadFloat { dst: dst_reg, val })
|
||||||
|
}
|
||||||
|
PixelBenderRegKind::Int => {
|
||||||
|
let val = data.read_i32::<LittleEndian>()?;
|
||||||
|
shader
|
||||||
|
.operations
|
||||||
|
.push(Operation::LoadInt { dst: dst_reg, val })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Opcode::SampleNearest | Opcode::SampleLinear => {
|
||||||
|
let dst = data.read_u16::<LittleEndian>()?;
|
||||||
|
let mask = data.read_u8()?;
|
||||||
|
let src = read_uint24(data)?;
|
||||||
|
let tf = data.read_u8()?;
|
||||||
|
|
||||||
|
let dst_reg = read_dst_reg(dst, mask >> 4)?;
|
||||||
|
let src_reg = read_src_reg(src, 2)?;
|
||||||
|
|
||||||
|
match opcode {
|
||||||
|
Opcode::SampleNearest => shader.operations.push(Operation::SampleNearest {
|
||||||
|
dst: dst_reg,
|
||||||
|
src: src_reg,
|
||||||
|
tf,
|
||||||
|
}),
|
||||||
|
Opcode::SampleLinear => shader.operations.push(Operation::SampleLinear {
|
||||||
|
dst: dst_reg,
|
||||||
|
src: src_reg,
|
||||||
|
tf,
|
||||||
|
}),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let dst = data.read_u16::<LittleEndian>()?;
|
||||||
|
let mut mask = data.read_u8()?;
|
||||||
|
let size = (mask & 0x3) + 1;
|
||||||
|
let matrix = (mask >> 2) & 3;
|
||||||
|
let src = read_uint24(data)?;
|
||||||
|
assert_eq!(data.read_u8()?, 0, "Unexpected u8 for opcode {opcode:?}");
|
||||||
|
mask >>= 4;
|
||||||
|
|
||||||
|
let src_reg = read_src_reg(src, size)?;
|
||||||
|
let dst_reg = if matrix != 0 {
|
||||||
|
assert_eq!(src >> 16, 0);
|
||||||
|
assert_eq!(size, 1);
|
||||||
|
panic!("Matrix with mask {mask:b} matrix {matrix:b}");
|
||||||
|
} else {
|
||||||
|
read_dst_reg(dst, mask)?
|
||||||
|
};
|
||||||
|
shader.operations.push(Operation::Normal {
|
||||||
|
opcode,
|
||||||
|
dst: dst_reg,
|
||||||
|
src: src_reg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_string<R: Read>(data: &mut R) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let mut string = String::new();
|
||||||
|
let mut b = data.read_u8()?;
|
||||||
|
while b != 0 {
|
||||||
|
string.push(b as char);
|
||||||
|
b = data.read_u8()?;
|
||||||
|
}
|
||||||
|
Ok(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_float<R: Read>(data: &mut R) -> Result<f32, Box<dyn std::error::Error>> {
|
||||||
|
Ok(data.read_f32::<BigEndian>()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_value<R: Read>(
|
||||||
|
data: &mut R,
|
||||||
|
opcode: PixelBenderTypeOpcode,
|
||||||
|
) -> Result<PixelBenderType, Box<dyn std::error::Error>> {
|
||||||
|
match opcode {
|
||||||
|
PixelBenderTypeOpcode::TFloat => Ok(PixelBenderType::TFloat(read_float(data)?)),
|
||||||
|
PixelBenderTypeOpcode::TFloat2 => Ok(PixelBenderType::TFloat2(
|
||||||
|
read_float(data)?,
|
||||||
|
read_float(data)?,
|
||||||
|
)),
|
||||||
|
PixelBenderTypeOpcode::TFloat3 => Ok(PixelBenderType::TFloat3(
|
||||||
|
read_float(data)?,
|
||||||
|
read_float(data)?,
|
||||||
|
read_float(data)?,
|
||||||
|
)),
|
||||||
|
PixelBenderTypeOpcode::TFloat4 => Ok(PixelBenderType::TFloat4(
|
||||||
|
read_float(data)?,
|
||||||
|
read_float(data)?,
|
||||||
|
read_float(data)?,
|
||||||
|
read_float(data)?,
|
||||||
|
)),
|
||||||
|
PixelBenderTypeOpcode::TFloat2x2 => Ok(PixelBenderType::TFloat2x2([
|
||||||
|
read_float(data)?,
|
||||||
|
read_float(data)?,
|
||||||
|
read_float(data)?,
|
||||||
|
read_float(data)?,
|
||||||
|
])),
|
||||||
|
PixelBenderTypeOpcode::TFloat3x3 => {
|
||||||
|
let mut floats: [f32; 9] = [0.0; 9];
|
||||||
|
for float in &mut floats {
|
||||||
|
*float = read_float(data)?;
|
||||||
|
}
|
||||||
|
Ok(PixelBenderType::TFloat3x3(floats))
|
||||||
|
}
|
||||||
|
PixelBenderTypeOpcode::TFloat4x4 => {
|
||||||
|
let mut floats: [f32; 16] = [0.0; 16];
|
||||||
|
for float in &mut floats {
|
||||||
|
*float = read_float(data)?;
|
||||||
|
}
|
||||||
|
Ok(PixelBenderType::TFloat4x4(floats))
|
||||||
|
}
|
||||||
|
PixelBenderTypeOpcode::TInt => Ok(PixelBenderType::TInt(data.read_i16::<LittleEndian>()?)),
|
||||||
|
PixelBenderTypeOpcode::TInt2 => Ok(PixelBenderType::TInt2(
|
||||||
|
data.read_i16::<LittleEndian>()?,
|
||||||
|
data.read_i16::<LittleEndian>()?,
|
||||||
|
)),
|
||||||
|
PixelBenderTypeOpcode::TInt3 => Ok(PixelBenderType::TInt3(
|
||||||
|
data.read_i16::<LittleEndian>()?,
|
||||||
|
data.read_i16::<LittleEndian>()?,
|
||||||
|
data.read_i16::<LittleEndian>()?,
|
||||||
|
)),
|
||||||
|
PixelBenderTypeOpcode::TInt4 => Ok(PixelBenderType::TInt4(
|
||||||
|
data.read_i16::<LittleEndian>()?,
|
||||||
|
data.read_i16::<LittleEndian>()?,
|
||||||
|
data.read_i16::<LittleEndian>()?,
|
||||||
|
data.read_i16::<LittleEndian>()?,
|
||||||
|
)),
|
||||||
|
PixelBenderTypeOpcode::TString => Ok(PixelBenderType::TString(read_string(data)?)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_uint24<R: Read>(data: &mut R) -> Result<u32, Box<dyn std::error::Error>> {
|
||||||
|
let ch1 = data.read_u8()? as u32;
|
||||||
|
let ch2 = data.read_u8()? as u32;
|
||||||
|
let ch3 = data.read_u8()? as u32;
|
||||||
|
Ok(ch1 | (ch2 << 8) | (ch3 << 16))
|
||||||
|
}
|
||||||
|
|
||||||
|
// The opcodes are laid out like this:
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// PBJMeta1 (for overall program)
|
||||||
|
// PBJMeta1 (for overall program)
|
||||||
|
// PBJParam (param 1)
|
||||||
|
// ...
|
||||||
|
// PBJMeta1 (for param 1)
|
||||||
|
// PBJMeta1 (for param 1)
|
||||||
|
// ...
|
||||||
|
// PBJParam (param 2)
|
||||||
|
// ,,,
|
||||||
|
// PBJMeta2 (for param 2)
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// The metadata associated with parameter is determined by all of the metadata opcodes
|
||||||
|
// that come after it and before the next parameter opcode. The metadata opcodes
|
||||||
|
// that come before all params are associated with the overall program.
|
||||||
|
|
||||||
|
fn apply_metadata(shader: &mut PixelBenderShader, metadata: &mut Vec<PixelBenderMetadata>) {
|
||||||
|
// Reset the accumulated metadata Vec - we will start accumulating metadata for the next param
|
||||||
|
let metadata = std::mem::take(metadata);
|
||||||
|
match shader.params.last_mut() {
|
||||||
|
Some(PixelBenderParam::Normal { metadata: meta, .. }) => {
|
||||||
|
*meta = metadata;
|
||||||
|
}
|
||||||
|
Some(param) => {
|
||||||
|
if !metadata.is_empty() {
|
||||||
|
panic!("Tried to apply metadata to texture parameter {param:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
shader.metadata = metadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::pixel_bender::{
|
use crate::pixel_bender::{
|
||||||
Opcode, Operation, PixelBenderMetadata, PixelBenderParam, PixelBenderParamQualifier,
|
Opcode, Operation, PixelBenderMetadata, PixelBenderParam, PixelBenderParamQualifier,
|
||||||
PixelBenderShader, PixelBenderType, PixelBenderTypeOpcode,
|
PixelBenderReg, PixelBenderRegChannel, PixelBenderRegKind, PixelBenderShader, PixelBenderType,
|
||||||
|
PixelBenderTypeOpcode,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::parse_shader;
|
use super::parse_shader;
|
||||||
|
@ -42,8 +43,11 @@ fn simple_shader() {
|
||||||
PixelBenderParam::Normal {
|
PixelBenderParam::Normal {
|
||||||
qualifier: PixelBenderParamQualifier::Input,
|
qualifier: PixelBenderParamQualifier::Input,
|
||||||
param_type: PixelBenderTypeOpcode::TFloat2,
|
param_type: PixelBenderTypeOpcode::TFloat2,
|
||||||
reg: 0,
|
reg: PixelBenderReg {
|
||||||
mask: 12,
|
index: 0,
|
||||||
|
channels: vec![PixelBenderRegChannel::R, PixelBenderRegChannel::G],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
name: "_OutCoord".to_string(),
|
name: "_OutCoord".to_string(),
|
||||||
metadata: vec![],
|
metadata: vec![],
|
||||||
},
|
},
|
||||||
|
@ -55,16 +59,22 @@ fn simple_shader() {
|
||||||
PixelBenderParam::Normal {
|
PixelBenderParam::Normal {
|
||||||
qualifier: PixelBenderParamQualifier::Output,
|
qualifier: PixelBenderParamQualifier::Output,
|
||||||
param_type: PixelBenderTypeOpcode::TFloat4,
|
param_type: PixelBenderTypeOpcode::TFloat4,
|
||||||
reg: 1,
|
reg: PixelBenderReg {
|
||||||
mask: 15,
|
index: 1,
|
||||||
|
channels: PixelBenderRegChannel::RGBA.to_vec(),
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
name: "dst".to_string(),
|
name: "dst".to_string(),
|
||||||
metadata: vec![],
|
metadata: vec![],
|
||||||
},
|
},
|
||||||
PixelBenderParam::Normal {
|
PixelBenderParam::Normal {
|
||||||
qualifier: PixelBenderParamQualifier::Input,
|
qualifier: PixelBenderParamQualifier::Input,
|
||||||
param_type: PixelBenderTypeOpcode::TFloat2,
|
param_type: PixelBenderTypeOpcode::TFloat2,
|
||||||
reg: 0,
|
reg: PixelBenderReg {
|
||||||
mask: 3,
|
index: 0,
|
||||||
|
channels: vec![PixelBenderRegChannel::B, PixelBenderRegChannel::A],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
name: "size".to_string(),
|
name: "size".to_string(),
|
||||||
metadata: vec![
|
metadata: vec![
|
||||||
PixelBenderMetadata {
|
PixelBenderMetadata {
|
||||||
|
@ -90,8 +100,11 @@ fn simple_shader() {
|
||||||
PixelBenderParam::Normal {
|
PixelBenderParam::Normal {
|
||||||
qualifier: PixelBenderParamQualifier::Input,
|
qualifier: PixelBenderParamQualifier::Input,
|
||||||
param_type: PixelBenderTypeOpcode::TFloat,
|
param_type: PixelBenderTypeOpcode::TFloat,
|
||||||
reg: 2,
|
reg: PixelBenderReg {
|
||||||
mask: 8,
|
index: 2,
|
||||||
|
channels: vec![PixelBenderRegChannel::R],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
name: "radius".to_string(),
|
name: "radius".to_string(),
|
||||||
metadata: vec![
|
metadata: vec![
|
||||||
PixelBenderMetadata {
|
PixelBenderMetadata {
|
||||||
|
@ -136,103 +149,197 @@ fn simple_shader() {
|
||||||
operations: vec![
|
operations: vec![
|
||||||
Operation::Normal {
|
Operation::Normal {
|
||||||
opcode: Opcode::Rcp,
|
opcode: Opcode::Rcp,
|
||||||
dst: 2,
|
dst: PixelBenderReg {
|
||||||
mask: 64,
|
index: 2,
|
||||||
src: 2,
|
channels: vec![PixelBenderRegChannel::G],
|
||||||
other: 0,
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
|
src: PixelBenderReg {
|
||||||
|
index: 2,
|
||||||
|
channels: vec![PixelBenderRegChannel::R],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Operation::Normal {
|
Operation::Normal {
|
||||||
opcode: Opcode::Mul,
|
opcode: Opcode::Mul,
|
||||||
dst: 2,
|
dst: PixelBenderReg {
|
||||||
mask: 64,
|
index: 2,
|
||||||
src: 2,
|
channels: vec![PixelBenderRegChannel::G],
|
||||||
other: 0,
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
|
src: PixelBenderReg {
|
||||||
|
index: 2,
|
||||||
|
channels: vec![PixelBenderRegChannel::R],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Operation::Normal {
|
Operation::Normal {
|
||||||
opcode: Opcode::Rcp,
|
opcode: Opcode::Rcp,
|
||||||
dst: 2,
|
dst: PixelBenderReg {
|
||||||
mask: 49,
|
index: 2,
|
||||||
src: 176,
|
channels: vec![PixelBenderRegChannel::B, PixelBenderRegChannel::A],
|
||||||
other: 0,
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
|
src: PixelBenderReg {
|
||||||
|
index: 0,
|
||||||
|
channels: vec![PixelBenderRegChannel::B, PixelBenderRegChannel::A],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Operation::Normal {
|
Operation::Normal {
|
||||||
opcode: Opcode::Mul,
|
opcode: Opcode::Mul,
|
||||||
dst: 2,
|
dst: PixelBenderReg {
|
||||||
mask: 49,
|
index: 2,
|
||||||
src: 176,
|
channels: vec![PixelBenderRegChannel::B, PixelBenderRegChannel::A],
|
||||||
other: 0,
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
|
src: PixelBenderReg {
|
||||||
|
index: 0,
|
||||||
|
channels: vec![PixelBenderRegChannel::B, PixelBenderRegChannel::A],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Operation::Normal {
|
Operation::Normal {
|
||||||
opcode: Opcode::Mov,
|
opcode: Opcode::Mov,
|
||||||
dst: 3,
|
dst: PixelBenderReg {
|
||||||
mask: 193,
|
index: 3,
|
||||||
src: 82,
|
channels: vec![PixelBenderRegChannel::R, PixelBenderRegChannel::G],
|
||||||
other: 0,
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
|
src: PixelBenderReg {
|
||||||
|
index: 2,
|
||||||
|
channels: vec![PixelBenderRegChannel::G, PixelBenderRegChannel::G],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Operation::Normal {
|
Operation::Normal {
|
||||||
opcode: Opcode::Mul,
|
opcode: Opcode::Mul,
|
||||||
dst: 3,
|
dst: PixelBenderReg {
|
||||||
mask: 193,
|
index: 3,
|
||||||
src: 178,
|
channels: vec![PixelBenderRegChannel::R, PixelBenderRegChannel::G],
|
||||||
other: 0,
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
|
src: PixelBenderReg {
|
||||||
|
index: 2,
|
||||||
|
channels: vec![PixelBenderRegChannel::B, PixelBenderRegChannel::A],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Operation::Normal {
|
Operation::Normal {
|
||||||
opcode: Opcode::Mov,
|
opcode: Opcode::Mov,
|
||||||
dst: 2,
|
dst: PixelBenderReg {
|
||||||
mask: 97,
|
index: 2,
|
||||||
src: 19,
|
channels: vec![PixelBenderRegChannel::G, PixelBenderRegChannel::B],
|
||||||
other: 0,
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
|
src: PixelBenderReg {
|
||||||
|
index: 3,
|
||||||
|
channels: vec![PixelBenderRegChannel::R, PixelBenderRegChannel::G],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Operation::SampleNearest {
|
Operation::SampleNearest {
|
||||||
dst: 3,
|
dst: PixelBenderReg {
|
||||||
mask: 241,
|
index: 3,
|
||||||
src: 16,
|
channels: PixelBenderRegChannel::RGBA.to_vec(),
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
|
src: PixelBenderReg {
|
||||||
|
index: 0,
|
||||||
|
channels: vec![PixelBenderRegChannel::R, PixelBenderRegChannel::G],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
tf: 0,
|
tf: 0,
|
||||||
},
|
},
|
||||||
Operation::LoadFloat {
|
Operation::LoadFloat {
|
||||||
dst: 4,
|
dst: PixelBenderReg {
|
||||||
mask: 128,
|
index: 4,
|
||||||
|
channels: vec![PixelBenderRegChannel::R],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
val: 100.0,
|
val: 100.0,
|
||||||
},
|
},
|
||||||
Operation::LoadFloat {
|
Operation::LoadFloat {
|
||||||
dst: 4,
|
dst: PixelBenderReg {
|
||||||
mask: 64,
|
index: 4,
|
||||||
|
channels: vec![PixelBenderRegChannel::G],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
val: 0.0,
|
val: 0.0,
|
||||||
},
|
},
|
||||||
Operation::LoadFloat {
|
Operation::LoadFloat {
|
||||||
dst: 4,
|
dst: PixelBenderReg {
|
||||||
mask: 32,
|
index: 4,
|
||||||
|
channels: vec![PixelBenderRegChannel::B],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
val: 100.0,
|
val: 100.0,
|
||||||
},
|
},
|
||||||
Operation::LoadFloat {
|
Operation::LoadFloat {
|
||||||
dst: 4,
|
dst: PixelBenderReg {
|
||||||
mask: 16,
|
index: 4,
|
||||||
|
channels: vec![PixelBenderRegChannel::A],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
val: 1.0,
|
val: 1.0,
|
||||||
},
|
},
|
||||||
Operation::Normal {
|
Operation::Normal {
|
||||||
opcode: Opcode::Mov,
|
opcode: Opcode::Mov,
|
||||||
dst: 5,
|
dst: PixelBenderReg {
|
||||||
mask: 243,
|
index: 5,
|
||||||
src: 30,
|
channels: vec![
|
||||||
other: 0,
|
PixelBenderRegChannel::R,
|
||||||
|
PixelBenderRegChannel::G,
|
||||||
|
PixelBenderRegChannel::B,
|
||||||
|
PixelBenderRegChannel::A,
|
||||||
|
],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
|
src: PixelBenderReg {
|
||||||
|
index: 3,
|
||||||
|
channels: vec![
|
||||||
|
PixelBenderRegChannel::R,
|
||||||
|
PixelBenderRegChannel::G,
|
||||||
|
PixelBenderRegChannel::B,
|
||||||
|
PixelBenderRegChannel::A,
|
||||||
|
],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Operation::Normal {
|
Operation::Normal {
|
||||||
opcode: Opcode::Add,
|
opcode: Opcode::Add,
|
||||||
dst: 5,
|
dst: PixelBenderReg {
|
||||||
mask: 243,
|
index: 5,
|
||||||
src: 31,
|
channels: vec![
|
||||||
other: 0,
|
PixelBenderRegChannel::R,
|
||||||
|
PixelBenderRegChannel::G,
|
||||||
|
PixelBenderRegChannel::B,
|
||||||
|
PixelBenderRegChannel::A,
|
||||||
|
],
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
|
src: PixelBenderReg {
|
||||||
|
index: 4,
|
||||||
|
channels: PixelBenderRegChannel::RGBA.to_vec(),
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Operation::Normal {
|
Operation::Normal {
|
||||||
opcode: Opcode::Mov,
|
opcode: Opcode::Mov,
|
||||||
dst: 1,
|
dst: PixelBenderReg {
|
||||||
mask: 243,
|
index: 1,
|
||||||
src: 32,
|
channels: PixelBenderRegChannel::RGBA.to_vec(),
|
||||||
other: 0,
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
|
src: PixelBenderReg {
|
||||||
|
index: 5,
|
||||||
|
channels: PixelBenderRegChannel::RGBA.to_vec(),
|
||||||
|
kind: PixelBenderRegKind::Float,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
let shader = parse_shader(shader);
|
let shader = parse_shader(shader).expect("Failed to parse shader");
|
||||||
assert_eq!(shader, expected, "Shader parsed incorrectly!");
|
assert_eq!(shader, expected, "Shader parsed incorrectly!");
|
||||||
}
|
}
|
|
@ -1099,6 +1099,24 @@ impl RenderBackend for WebGlRenderBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_quality(&mut self, _quality: StageQuality) {}
|
fn set_quality(&mut self, _quality: StageQuality) {}
|
||||||
|
|
||||||
|
fn compile_pixelbender_shader(
|
||||||
|
&mut self,
|
||||||
|
_shader: ruffle_render::pixel_bender::PixelBenderShader,
|
||||||
|
) -> Result<ruffle_render::pixel_bender::PixelBenderShaderHandle, BitmapError> {
|
||||||
|
Err(BitmapError::Unimplemented(
|
||||||
|
"compile_pixelbender_shader".into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_pixelbender_shader(
|
||||||
|
&mut self,
|
||||||
|
_handle: ruffle_render::pixel_bender::PixelBenderShaderHandle,
|
||||||
|
_arguments: &[ruffle_render::pixel_bender::PixelBenderShaderArgument],
|
||||||
|
_target: BitmapHandle,
|
||||||
|
) -> Result<Box<dyn SyncHandle>, BitmapError> {
|
||||||
|
Err(BitmapError::Unimplemented("run_pixelbender_shader".into()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandHandler for WebGlRenderBackend {
|
impl CommandHandler for WebGlRenderBackend {
|
||||||
|
|
|
@ -23,10 +23,12 @@ ouroboros = "0.15.6"
|
||||||
typed-arena = "2.0.2"
|
typed-arena = "2.0.2"
|
||||||
gc-arena = { workspace = true }
|
gc-arena = { workspace = true }
|
||||||
naga-agal = { path = "../naga-agal" }
|
naga-agal = { path = "../naga-agal" }
|
||||||
|
naga-pixelbender = { path = "../naga-pixelbender" }
|
||||||
downcast-rs = "1.2.0"
|
downcast-rs = "1.2.0"
|
||||||
profiling = { version = "1.0", default-features = false, optional = true }
|
profiling = { version = "1.0", default-features = false, optional = true }
|
||||||
naga = { version = "0.12.2", features = ["validate", "wgsl-out"] }
|
|
||||||
lru = "0.10.0"
|
lru = "0.10.0"
|
||||||
|
naga = { workspace = true }
|
||||||
|
indexmap = "1.9.3"
|
||||||
|
|
||||||
# desktop
|
# desktop
|
||||||
[target.'cfg(not(target_family = "wasm"))'.dependencies.futures]
|
[target.'cfg(not(target_family = "wasm"))'.dependencies.futures]
|
||||||
|
|
|
@ -20,6 +20,9 @@ use ruffle_render::bitmap::{
|
||||||
use ruffle_render::commands::CommandList;
|
use ruffle_render::commands::CommandList;
|
||||||
use ruffle_render::error::Error as BitmapError;
|
use ruffle_render::error::Error as BitmapError;
|
||||||
use ruffle_render::filters::Filter;
|
use ruffle_render::filters::Filter;
|
||||||
|
use ruffle_render::pixel_bender::{
|
||||||
|
PixelBenderShader, PixelBenderShaderArgument, PixelBenderShaderHandle,
|
||||||
|
};
|
||||||
use ruffle_render::quality::StageQuality;
|
use ruffle_render::quality::StageQuality;
|
||||||
use ruffle_render::shape_utils::DistilledShape;
|
use ruffle_render::shape_utils::DistilledShape;
|
||||||
use ruffle_render::tessellator::ShapeTessellator;
|
use ruffle_render::tessellator::ShapeTessellator;
|
||||||
|
@ -36,7 +39,7 @@ use tracing::instrument;
|
||||||
const TEXTURE_READS_BEFORE_PROMOTION: u8 = 5;
|
const TEXTURE_READS_BEFORE_PROMOTION: u8 = 5;
|
||||||
|
|
||||||
pub struct WgpuRenderBackend<T: RenderTarget> {
|
pub struct WgpuRenderBackend<T: RenderTarget> {
|
||||||
descriptors: Arc<Descriptors>,
|
pub(crate) descriptors: Arc<Descriptors>,
|
||||||
uniform_buffers_storage: BufferStorage<Transforms>,
|
uniform_buffers_storage: BufferStorage<Transforms>,
|
||||||
color_buffers_storage: BufferStorage<ColorAdjustments>,
|
color_buffers_storage: BufferStorage<ColorAdjustments>,
|
||||||
target: T,
|
target: T,
|
||||||
|
@ -48,7 +51,7 @@ pub struct WgpuRenderBackend<T: RenderTarget> {
|
||||||
viewport_scale_factor: f64,
|
viewport_scale_factor: f64,
|
||||||
texture_pool: TexturePool,
|
texture_pool: TexturePool,
|
||||||
offscreen_texture_pool: TexturePool,
|
offscreen_texture_pool: TexturePool,
|
||||||
offscreen_buffer_pool: Arc<BufferPool<wgpu::Buffer, BufferDimensions>>,
|
pub(crate) offscreen_buffer_pool: Arc<BufferPool<wgpu::Buffer, BufferDimensions>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WgpuRenderBackend<SwapChainTarget> {
|
impl WgpuRenderBackend<SwapChainTarget> {
|
||||||
|
@ -758,6 +761,22 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
|
||||||
}) => unreachable!("Buffer must be Borrowed as it was set to be Borrowed earlier"),
|
}) => unreachable!("Buffer must be Borrowed as it was set to be Borrowed earlier"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compile_pixelbender_shader(
|
||||||
|
&mut self,
|
||||||
|
shader: PixelBenderShader,
|
||||||
|
) -> Result<PixelBenderShaderHandle, BitmapError> {
|
||||||
|
self.compile_pixelbender_shader_impl(shader)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_pixelbender_shader(
|
||||||
|
&mut self,
|
||||||
|
shader: PixelBenderShaderHandle,
|
||||||
|
arguments: &[PixelBenderShaderArgument],
|
||||||
|
target_handle: BitmapHandle,
|
||||||
|
) -> Result<Box<dyn SyncHandle>, BitmapError> {
|
||||||
|
self.run_pixelbender_shader_impl(shader, arguments, target_handle)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn request_adapter_and_device(
|
pub async fn request_adapter_and_device(
|
||||||
|
|
|
@ -31,6 +31,7 @@ mod bitmaps;
|
||||||
mod context3d;
|
mod context3d;
|
||||||
mod globals;
|
mod globals;
|
||||||
mod pipelines;
|
mod pipelines;
|
||||||
|
mod pixel_bender;
|
||||||
pub mod target;
|
pub mod target;
|
||||||
mod uniform_buffer;
|
mod uniform_buffer;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,492 @@
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::num::NonZeroU64;
|
||||||
|
use std::{borrow::Cow, cell::Cell, sync::Arc};
|
||||||
|
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use ruffle_render::error::Error as BitmapError;
|
||||||
|
use ruffle_render::pixel_bender::{
|
||||||
|
PixelBenderShaderHandle, PixelBenderShaderImpl, PixelBenderType, OUT_COORD_NAME,
|
||||||
|
};
|
||||||
|
use ruffle_render::{
|
||||||
|
bitmap::{BitmapHandle, PixelRegion, SyncHandle},
|
||||||
|
pixel_bender::{PixelBenderParam, PixelBenderShader, PixelBenderShaderArgument},
|
||||||
|
};
|
||||||
|
use wgpu::util::StagingBelt;
|
||||||
|
use wgpu::{
|
||||||
|
BindGroupEntry, BindingResource, BlendComponent, BufferDescriptor, BufferUsages,
|
||||||
|
ColorTargetState, ColorWrites, FrontFace, ImageCopyTexture, RenderPipelineDescriptor,
|
||||||
|
SamplerBindingType, ShaderModuleDescriptor, TextureDescriptor, TextureFormat, TextureView,
|
||||||
|
VertexState,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
as_texture,
|
||||||
|
backend::WgpuRenderBackend,
|
||||||
|
descriptors::Descriptors,
|
||||||
|
pipelines::VERTEX_BUFFERS_DESCRIPTION_POS,
|
||||||
|
target::{RenderTarget, RenderTargetFrame, TextureTarget},
|
||||||
|
QueueSyncHandle, Texture,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PixelBenderWgpuShader {
|
||||||
|
bind_group_layout: wgpu::BindGroupLayout,
|
||||||
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
shader: PixelBenderShader,
|
||||||
|
float_parameters_buffer: wgpu::Buffer,
|
||||||
|
float_parameters_buffer_size: u64,
|
||||||
|
int_parameters_buffer: wgpu::Buffer,
|
||||||
|
int_parameters_buffer_size: u64,
|
||||||
|
staging_belt: RefCell<StagingBelt>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PixelBenderShaderImpl for PixelBenderWgpuShader {
|
||||||
|
fn parsed_shader(&self) -> &PixelBenderShader {
|
||||||
|
&self.shader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_cache_holder(handle: &PixelBenderShaderHandle) -> &PixelBenderWgpuShader {
|
||||||
|
<dyn PixelBenderShaderImpl>::downcast_ref(&*handle.0).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PixelBenderWgpuShader {
|
||||||
|
pub fn new(descriptors: &Descriptors, shader: PixelBenderShader) -> PixelBenderWgpuShader {
|
||||||
|
let mut layout_entries = vec![
|
||||||
|
// One sampler per filter/wrapping combination - see BitmapFilters
|
||||||
|
// An AGAL shader can use any of these samplers, so
|
||||||
|
// we need to bind them all.
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: naga_pixelbender::SAMPLER_CLAMP_NEAREST,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering),
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: naga_pixelbender::SAMPLER_CLAMP_LINEAR,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering),
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: naga_pixelbender::SAMPLER_CLAMP_BILINEAR,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering),
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: naga_pixelbender::SHADER_FLOAT_PARAMETERS_INDEX,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: naga_pixelbender::SHADER_INT_PARAMETERS_INDEX,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for param in &shader.params {
|
||||||
|
if let PixelBenderParam::Texture { index, .. } = param {
|
||||||
|
let binding = naga_pixelbender::TEXTURE_START_BIND_INDEX + *index as u32;
|
||||||
|
layout_entries.push(wgpu::BindGroupLayoutEntry {
|
||||||
|
binding,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
multisampled: false,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let globals_layout_label =
|
||||||
|
create_debug_label!("PixelBender bind group layout for {:?}", shader.name);
|
||||||
|
let bind_group_layout =
|
||||||
|
descriptors
|
||||||
|
.device
|
||||||
|
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: globals_layout_label.as_deref(),
|
||||||
|
entries: &layout_entries,
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline_layout_label =
|
||||||
|
create_debug_label!("PixelBender pipeline layout for {:?}", shader.name);
|
||||||
|
let pipeline_layout =
|
||||||
|
descriptors
|
||||||
|
.device
|
||||||
|
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: pipeline_layout_label.as_deref(),
|
||||||
|
bind_group_layouts: &[&bind_group_layout],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let shaders =
|
||||||
|
naga_pixelbender::ShaderBuilder::build(&shader).expect("Failed to compile shader");
|
||||||
|
|
||||||
|
let float_label =
|
||||||
|
create_debug_label!("PixelBender float parameters buffer for {:?}", shader.name);
|
||||||
|
|
||||||
|
let float_parameters_buffer = descriptors.device.create_buffer(&BufferDescriptor {
|
||||||
|
label: float_label.as_deref(),
|
||||||
|
size: shaders.float_parameters_buffer_size,
|
||||||
|
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let int_label =
|
||||||
|
create_debug_label!("PixelBender int parameters buffer for {:?}", shader.name);
|
||||||
|
|
||||||
|
let int_parameters_buffer = descriptors.device.create_buffer(&BufferDescriptor {
|
||||||
|
label: int_label.as_deref(),
|
||||||
|
size: shaders.int_parameters_buffer_size,
|
||||||
|
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let vertex_shader = descriptors
|
||||||
|
.device
|
||||||
|
.create_shader_module(ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Naga(Cow::Owned(shaders.vertex)),
|
||||||
|
});
|
||||||
|
|
||||||
|
let fragment_shader = descriptors
|
||||||
|
.device
|
||||||
|
.create_shader_module(ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Naga(Cow::Owned(shaders.fragment)),
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline = descriptors
|
||||||
|
.device
|
||||||
|
.create_render_pipeline(&RenderPipelineDescriptor {
|
||||||
|
label: create_debug_label!("RenderPipeline").as_deref(),
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: VertexState {
|
||||||
|
module: &vertex_shader,
|
||||||
|
entry_point: naga_pixelbender::SHADER_ENTRYPOINT,
|
||||||
|
buffers: &VERTEX_BUFFERS_DESCRIPTION_POS,
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &fragment_shader,
|
||||||
|
entry_point: naga_pixelbender::SHADER_ENTRYPOINT,
|
||||||
|
targets: &[Some(ColorTargetState {
|
||||||
|
format: TextureFormat::Rgba8Unorm,
|
||||||
|
// FIXME - what should this be?
|
||||||
|
blend: Some(wgpu::BlendState {
|
||||||
|
color: BlendComponent::OVER,
|
||||||
|
alpha: BlendComponent::OVER,
|
||||||
|
}),
|
||||||
|
write_mask: ColorWrites::all(),
|
||||||
|
})],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
front_face: FrontFace::Ccw,
|
||||||
|
cull_mode: None,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState {
|
||||||
|
count: 1,
|
||||||
|
mask: !0,
|
||||||
|
alpha_to_coverage_enabled: false,
|
||||||
|
},
|
||||||
|
multiview: Default::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
PixelBenderWgpuShader {
|
||||||
|
bind_group_layout,
|
||||||
|
pipeline,
|
||||||
|
shader,
|
||||||
|
float_parameters_buffer,
|
||||||
|
float_parameters_buffer_size: shaders.float_parameters_buffer_size,
|
||||||
|
int_parameters_buffer,
|
||||||
|
int_parameters_buffer_size: shaders.int_parameters_buffer_size,
|
||||||
|
// FIXME - come up with a good chunk size
|
||||||
|
staging_belt: RefCell::new(StagingBelt::new(8)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: RenderTarget> WgpuRenderBackend<T> {
|
||||||
|
pub(super) fn compile_pixelbender_shader_impl(
|
||||||
|
&mut self,
|
||||||
|
shader: PixelBenderShader,
|
||||||
|
) -> Result<PixelBenderShaderHandle, BitmapError> {
|
||||||
|
let handle = PixelBenderWgpuShader::new(&self.descriptors, shader);
|
||||||
|
Ok(PixelBenderShaderHandle(Arc::new(handle)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn run_pixelbender_shader_impl(
|
||||||
|
&mut self,
|
||||||
|
shader: PixelBenderShaderHandle,
|
||||||
|
arguments: &[PixelBenderShaderArgument],
|
||||||
|
target_handle: BitmapHandle,
|
||||||
|
) -> Result<Box<dyn SyncHandle>, BitmapError> {
|
||||||
|
let compiled_shader = &as_cache_holder(&shader);
|
||||||
|
let mut staging_belt = compiled_shader.staging_belt.borrow_mut();
|
||||||
|
|
||||||
|
let mut arguments = arguments.to_vec();
|
||||||
|
|
||||||
|
let target = as_texture(&target_handle);
|
||||||
|
let extent = wgpu::Extent3d {
|
||||||
|
width: target.width,
|
||||||
|
height: target.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut texture_target = TextureTarget {
|
||||||
|
size: extent,
|
||||||
|
texture: target.texture.clone(),
|
||||||
|
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||||
|
buffer: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let frame_output = texture_target
|
||||||
|
.get_next_texture()
|
||||||
|
.expect("TextureTargetFrame.get_next_texture is infallible");
|
||||||
|
|
||||||
|
let mut bind_group_entries = vec![
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: naga_pixelbender::SAMPLER_CLAMP_NEAREST,
|
||||||
|
resource: BindingResource::Sampler(&self.descriptors.bitmap_samplers.clamp_nearest),
|
||||||
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: naga_pixelbender::SAMPLER_CLAMP_LINEAR,
|
||||||
|
resource: BindingResource::Sampler(&self.descriptors.bitmap_samplers.clamp_linear),
|
||||||
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: naga_pixelbender::SAMPLER_CLAMP_BILINEAR,
|
||||||
|
// FIXME - create bilinear sampler
|
||||||
|
resource: BindingResource::Sampler(&self.descriptors.bitmap_samplers.clamp_linear),
|
||||||
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: naga_pixelbender::SHADER_FLOAT_PARAMETERS_INDEX,
|
||||||
|
resource: BindingResource::Buffer(wgpu::BufferBinding {
|
||||||
|
buffer: &compiled_shader.float_parameters_buffer,
|
||||||
|
offset: 0,
|
||||||
|
size: Some(
|
||||||
|
NonZeroU64::new(compiled_shader.float_parameters_buffer_size).unwrap(),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: naga_pixelbender::SHADER_INT_PARAMETERS_INDEX,
|
||||||
|
resource: BindingResource::Buffer(wgpu::BufferBinding {
|
||||||
|
buffer: &compiled_shader.int_parameters_buffer,
|
||||||
|
offset: 0,
|
||||||
|
size: Some(
|
||||||
|
NonZeroU64::new(compiled_shader.int_parameters_buffer_size).unwrap(),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut texture_views: IndexMap<u8, TextureView> = Default::default();
|
||||||
|
|
||||||
|
let mut render_command_encoder =
|
||||||
|
self.descriptors
|
||||||
|
.device
|
||||||
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
|
label: create_debug_label!("Render command encoder").as_deref(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut target_clone = None;
|
||||||
|
|
||||||
|
let mut float_offset = 0;
|
||||||
|
let mut int_offset = 0;
|
||||||
|
|
||||||
|
for input in &mut arguments {
|
||||||
|
match input {
|
||||||
|
PixelBenderShaderArgument::ImageInput { index, texture, .. } => {
|
||||||
|
// The input is the same as the output - we need to clone the input.
|
||||||
|
// We will write to the original output, and use a clone of the input as a texture input binding
|
||||||
|
if std::ptr::eq(
|
||||||
|
Arc::as_ptr(&texture.0) as *const (),
|
||||||
|
Arc::as_ptr(&target_handle.0) as *const (),
|
||||||
|
) {
|
||||||
|
let cached_fresh_handle = target_clone.get_or_insert_with(|| {
|
||||||
|
let extent = wgpu::Extent3d {
|
||||||
|
width: target.width,
|
||||||
|
height: target.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
};
|
||||||
|
let fresh_texture =
|
||||||
|
self.descriptors.device.create_texture(&TextureDescriptor {
|
||||||
|
label: Some("PixelBenderShader target clone"),
|
||||||
|
size: extent,
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||||
|
usage: wgpu::TextureUsages::COPY_DST
|
||||||
|
| wgpu::TextureUsages::TEXTURE_BINDING,
|
||||||
|
view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
|
||||||
|
});
|
||||||
|
render_command_encoder.copy_texture_to_texture(
|
||||||
|
ImageCopyTexture {
|
||||||
|
texture: &target.texture,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: Default::default(),
|
||||||
|
aspect: Default::default(),
|
||||||
|
},
|
||||||
|
ImageCopyTexture {
|
||||||
|
texture: &fresh_texture,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: Default::default(),
|
||||||
|
aspect: Default::default(),
|
||||||
|
},
|
||||||
|
extent,
|
||||||
|
);
|
||||||
|
|
||||||
|
BitmapHandle(Arc::new(Texture {
|
||||||
|
texture: Arc::new(fresh_texture),
|
||||||
|
bind_linear: Default::default(),
|
||||||
|
bind_nearest: Default::default(),
|
||||||
|
width: extent.width,
|
||||||
|
height: extent.height,
|
||||||
|
copy_count: Cell::new(0),
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
*texture = cached_fresh_handle.clone();
|
||||||
|
}
|
||||||
|
texture_views.insert(
|
||||||
|
*index,
|
||||||
|
as_texture(texture)
|
||||||
|
.texture
|
||||||
|
.create_view(&wgpu::TextureViewDescriptor::default()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
PixelBenderShaderArgument::ValueInput { index, value } => {
|
||||||
|
let param = &compiled_shader.shader.params[*index as usize];
|
||||||
|
|
||||||
|
let name = match param {
|
||||||
|
PixelBenderParam::Normal { name, .. } => name,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if name == OUT_COORD_NAME {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (value_vec, is_float): ([f32; 4], bool) = match value {
|
||||||
|
PixelBenderType::TFloat(f1) => ([*f1, 0.0, 0.0, 0.0], true),
|
||||||
|
PixelBenderType::TFloat2(f1, f2) => ([*f1, *f2, 0.0, 0.0], true),
|
||||||
|
PixelBenderType::TFloat3(f1, f2, f3) => ([*f1, *f2, *f3, 0.0], true),
|
||||||
|
PixelBenderType::TFloat4(f1, f2, f3, f4) => ([*f1, *f2, *f3, *f4], true),
|
||||||
|
PixelBenderType::TInt(i1) => ([*i1 as f32, 0.0, 0.0, 0.0], false),
|
||||||
|
PixelBenderType::TInt2(i1, i2) => {
|
||||||
|
([*i1 as f32, *i2 as f32, 0.0, 0.0], false)
|
||||||
|
}
|
||||||
|
PixelBenderType::TInt3(i1, i2, i3) => {
|
||||||
|
([*i1 as f32, *i2 as f32, *i3 as f32, 0.0], false)
|
||||||
|
}
|
||||||
|
PixelBenderType::TInt4(i1, i2, i3, i4) => {
|
||||||
|
([*i1 as f32, *i2 as f32, *i3 as f32, *i4 as f32], false)
|
||||||
|
}
|
||||||
|
_ => unreachable!("Unimplemented value {value:?}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Both float32 and int are 4 bytes
|
||||||
|
let component_size_bytes = 4;
|
||||||
|
|
||||||
|
let (buffer, vec4_count) = if is_float {
|
||||||
|
let res = (&compiled_shader.float_parameters_buffer, float_offset);
|
||||||
|
float_offset += 1;
|
||||||
|
res
|
||||||
|
} else {
|
||||||
|
let res = (&compiled_shader.int_parameters_buffer, int_offset);
|
||||||
|
int_offset += 1;
|
||||||
|
res
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut buffer_slice = staging_belt.write_buffer(
|
||||||
|
&mut render_command_encoder,
|
||||||
|
buffer,
|
||||||
|
vec4_count * 4 * component_size_bytes,
|
||||||
|
NonZeroU64::new(4 * component_size_bytes).unwrap(),
|
||||||
|
&self.descriptors.device,
|
||||||
|
);
|
||||||
|
buffer_slice.copy_from_slice(bytemuck::cast_slice(&value_vec));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This needs to be a separate loop, so that we can get references into `texture_views`
|
||||||
|
for input in &arguments {
|
||||||
|
match input {
|
||||||
|
PixelBenderShaderArgument::ImageInput { index, .. } => {
|
||||||
|
let binding = naga_pixelbender::TEXTURE_START_BIND_INDEX + *index as u32;
|
||||||
|
bind_group_entries.push(BindGroupEntry {
|
||||||
|
binding,
|
||||||
|
resource: BindingResource::TextureView(&texture_views[index]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
PixelBenderShaderArgument::ValueInput { .. } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let bind_group = self
|
||||||
|
.descriptors
|
||||||
|
.device
|
||||||
|
.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: &compiled_shader.bind_group_layout,
|
||||||
|
entries: &bind_group_entries,
|
||||||
|
});
|
||||||
|
|
||||||
|
staging_belt.finish();
|
||||||
|
|
||||||
|
let mut render_pass =
|
||||||
|
render_command_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("PixelBender render pass"),
|
||||||
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
view: frame_output.view(),
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
});
|
||||||
|
render_pass.set_bind_group(0, &bind_group, &[]);
|
||||||
|
render_pass.set_pipeline(&compiled_shader.pipeline);
|
||||||
|
|
||||||
|
render_pass.set_vertex_buffer(0, self.descriptors.quad.vertices_pos.slice(..));
|
||||||
|
render_pass.set_index_buffer(
|
||||||
|
self.descriptors.quad.indices.slice(..),
|
||||||
|
wgpu::IndexFormat::Uint32,
|
||||||
|
);
|
||||||
|
|
||||||
|
render_pass.draw_indexed(0..6, 0, 0..1);
|
||||||
|
|
||||||
|
drop(render_pass);
|
||||||
|
|
||||||
|
self.descriptors
|
||||||
|
.queue
|
||||||
|
.submit(Some(render_command_encoder.finish()));
|
||||||
|
|
||||||
|
staging_belt.recall();
|
||||||
|
|
||||||
|
Ok(Box::new(QueueSyncHandle::NotCopied {
|
||||||
|
handle: target_handle,
|
||||||
|
copy_area: PixelRegion::for_whole_size(extent.width, extent.height),
|
||||||
|
descriptors: self.descriptors.clone(),
|
||||||
|
pool: self.offscreen_buffer_pool.clone(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package {
|
||||||
|
import flash.display.BitmapData;
|
||||||
|
import flash.display.ShaderJob;
|
||||||
|
import flash.display.Shader;
|
||||||
|
import flash.display.Bitmap;
|
||||||
|
import flash.display.MovieClip;
|
||||||
|
|
||||||
|
public class Test {
|
||||||
|
|
||||||
|
[Embed(source = "mandelbrot.png")]
|
||||||
|
public static var MANDELBROT: Class;
|
||||||
|
|
||||||
|
// Shader from https://github.com/8bitavenue/Adobe-Pixel-Bender-Effects/blob/master/Donut%20Shader.cpp
|
||||||
|
[Embed(source = "donut.pbj", mimeType="application/octet-stream")]
|
||||||
|
public static var DONUT_BYTES: Class;
|
||||||
|
|
||||||
|
public function Test(main: MovieClip) {
|
||||||
|
var mandelbrot: Bitmap = new MANDELBROT();
|
||||||
|
main.addChild(new Bitmap(donut(mandelbrot.bitmapData.clone())));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function donut(input: BitmapData): BitmapData {
|
||||||
|
var shader = new ShaderJob(new Shader(new DONUT_BYTES()), input);
|
||||||
|
shader.shader.data.BlockCount.value = [56.5];
|
||||||
|
shader.shader.data.Min.value = [0.29];
|
||||||
|
shader.shader.data.Max.value = [0.51];
|
||||||
|
shader.shader.data.Width.value = [100.0];
|
||||||
|
shader.shader.data.Height.value = [100.0];
|
||||||
|
shader.shader.data.color.value = [0.34, 0.1, 0.2, 1];
|
||||||
|
shader.shader.data.src.input = input;
|
||||||
|
shader.start(true);
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
|
@ -0,0 +1,98 @@
|
||||||
|
<languageVersion : 1.0;>
|
||||||
|
kernel Donut
|
||||||
|
<
|
||||||
|
namespace : "8bitavenue";
|
||||||
|
vendor : "8bitavenue";
|
||||||
|
version : 1;
|
||||||
|
>
|
||||||
|
|
||||||
|
{
|
||||||
|
//Donut density
|
||||||
|
parameter float BlockCount
|
||||||
|
<
|
||||||
|
minValue: 1.0;
|
||||||
|
maxValue: 100.0;
|
||||||
|
defaultValue: 5.0;
|
||||||
|
>;
|
||||||
|
|
||||||
|
//Inner circle
|
||||||
|
parameter float Min
|
||||||
|
<
|
||||||
|
minValue: 0.0;
|
||||||
|
maxValue: 1.0;
|
||||||
|
defaultValue: 0.25;
|
||||||
|
>;
|
||||||
|
|
||||||
|
//Outer circle
|
||||||
|
parameter float Max
|
||||||
|
<
|
||||||
|
minValue: 0.0;
|
||||||
|
maxValue: 1.0;
|
||||||
|
defaultValue: 0.45;
|
||||||
|
>;
|
||||||
|
|
||||||
|
//Scale width
|
||||||
|
parameter float Width
|
||||||
|
<
|
||||||
|
minValue: 1.0;
|
||||||
|
maxValue: 1000.0;
|
||||||
|
defaultValue: 100.0;
|
||||||
|
>;
|
||||||
|
|
||||||
|
//Scale height
|
||||||
|
parameter float Height
|
||||||
|
<
|
||||||
|
minValue: 1.0;
|
||||||
|
maxValue: 1000.0;
|
||||||
|
defaultValue: 100.0;
|
||||||
|
>;
|
||||||
|
|
||||||
|
//Background color
|
||||||
|
parameter pixel4 color
|
||||||
|
<
|
||||||
|
minValue: float4(0.0,0.0,0.0,0.0);
|
||||||
|
maxValue: float4(1.0,1.0,1.0,1.0);
|
||||||
|
defaultValue: float4(0.2, 0.2, 0.2, 1.0);
|
||||||
|
>;
|
||||||
|
|
||||||
|
//Input image
|
||||||
|
input image4 src;
|
||||||
|
|
||||||
|
//Output image
|
||||||
|
output pixel4 dst;
|
||||||
|
|
||||||
|
//Apply this filter
|
||||||
|
void evaluatePixel()
|
||||||
|
{
|
||||||
|
//Calculate block size
|
||||||
|
float myblockcount = BlockCount/5.0;
|
||||||
|
float BlockSize = 1.0/myblockcount;
|
||||||
|
|
||||||
|
float2 temp = outCoord();
|
||||||
|
temp.x = temp.x/Width;
|
||||||
|
temp.y = temp.y/Height;
|
||||||
|
|
||||||
|
//Calculate block position and center
|
||||||
|
float2 blockPos = floor(temp * myblockcount);
|
||||||
|
float2 blockCenter = blockPos * BlockSize + BlockSize * 0.5;
|
||||||
|
|
||||||
|
//Pixel distance from center
|
||||||
|
float dist = length(temp - blockCenter) * myblockcount;
|
||||||
|
|
||||||
|
//If pixel is inside inner circle
|
||||||
|
//or outside outer circle then color
|
||||||
|
//it with background color
|
||||||
|
//otherwise color it with the color
|
||||||
|
//of the pixel at the center
|
||||||
|
if(dist < Min || dist > Max)
|
||||||
|
{
|
||||||
|
dst = color;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
blockCenter.x = blockCenter.x * Width;
|
||||||
|
blockCenter.y = blockCenter.y * Height;
|
||||||
|
dst = sampleNearest(src, blockCenter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 232 KiB |
Binary file not shown.
After Width: | Height: | Size: 115 KiB |
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,7 @@
|
||||||
|
num_frames = 1
|
||||||
|
|
||||||
|
[image_comparison]
|
||||||
|
tolerance = 1
|
||||||
|
|
||||||
|
[player_options]
|
||||||
|
with_renderer = { optional = false, sample_count = 1 }
|
Binary file not shown.
|
@ -0,0 +1,36 @@
|
||||||
|
<languageVersion : 1.0;>
|
||||||
|
|
||||||
|
kernel DoNothing
|
||||||
|
<
|
||||||
|
namespace: "Adobe::Example";
|
||||||
|
vendor: "Adobe examples";
|
||||||
|
version: 1;
|
||||||
|
description: "A shader that does nothing, but does it well.";
|
||||||
|
>
|
||||||
|
{
|
||||||
|
|
||||||
|
output pixel4 dst;
|
||||||
|
|
||||||
|
parameter float radius
|
||||||
|
<
|
||||||
|
description: "The radius of the effect";
|
||||||
|
minValue: 0.0;
|
||||||
|
maxValue: 50.0;
|
||||||
|
defaultValue: 25.0;
|
||||||
|
>;
|
||||||
|
|
||||||
|
parameter float otherParam
|
||||||
|
<
|
||||||
|
description: "Other param";
|
||||||
|
minValue: 0.0;
|
||||||
|
maxValue: 255.0;
|
||||||
|
defaultValue: 25.0;
|
||||||
|
>;
|
||||||
|
|
||||||
|
input image4 src;
|
||||||
|
|
||||||
|
void evaluatePixel()
|
||||||
|
{
|
||||||
|
dst = float4((otherParam + radius) / 255.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,4 @@
|
||||||
num_frames = 1
|
num_frames = 1
|
||||||
|
|
||||||
|
[player_options]
|
||||||
|
with_renderer = { optional = false, sample_count = 1 }
|
||||||
|
|
Loading…
Reference in New Issue