diff --git a/core/src/avm2/globals/flash/display/shader_job.rs b/core/src/avm2/globals/flash/display/shader_job.rs index ca1882b54826..9cbc093d712f 100644 --- a/core/src/avm2/globals/flash/display/shader_job.rs +++ b/core/src/avm2/globals/flash/display/shader_job.rs @@ -15,13 +15,45 @@ use crate::avm2_stub_method; use ruffle_render::backend::{PixelBenderOutput, PixelBenderTarget}; use ruffle_render::bitmap::PixelRegion; use ruffle_render::pixel_bender::{ - OUT_COORD_NAME, PixelBenderParam, PixelBenderParamQualifier, PixelBenderShaderHandle, - PixelBenderType, + OUT_COORD_NAME, PixelBenderMetadata, PixelBenderParam, PixelBenderParamQualifier, + PixelBenderShaderHandle, PixelBenderType, PixelBenderTypeOpcode, }; use ruffle_render::pixel_bender_support::{ FloatPixelData, ImageInputTexture, PixelBenderShaderArgument, }; +/// Get the default value for a shader parameter from its metadata. +/// If no default is found, returns a empty value of the appropriate type. +fn get_default_shader_param_value( + metadata: &[PixelBenderMetadata], + param_type: PixelBenderTypeOpcode, +) -> PixelBenderType { + for meta in metadata { + if meta.key == "defaultValue" { + return meta.value.clone(); + } + } + + match param_type { + PixelBenderTypeOpcode::TFloat => PixelBenderType::TFloat(0.0), + PixelBenderTypeOpcode::TFloat2 => PixelBenderType::TFloat2(0.0, 0.0), + PixelBenderTypeOpcode::TFloat3 => PixelBenderType::TFloat3(0.0, 0.0, 0.0), + PixelBenderTypeOpcode::TFloat4 => PixelBenderType::TFloat4(0.0, 0.0, 0.0, 0.0), + PixelBenderTypeOpcode::TFloat2x2 => PixelBenderType::TFloat2x2([0.0; 4]), + PixelBenderTypeOpcode::TFloat3x3 => PixelBenderType::TFloat3x3([0.0; 9]), + PixelBenderTypeOpcode::TFloat4x4 => PixelBenderType::TFloat4x4([0.0; 16]), + PixelBenderTypeOpcode::TInt => PixelBenderType::TInt(0), + PixelBenderTypeOpcode::TInt2 => PixelBenderType::TInt2(0, 0), + PixelBenderTypeOpcode::TInt3 => PixelBenderType::TInt3(0, 0, 0), + PixelBenderTypeOpcode::TInt4 => PixelBenderType::TInt4(0, 0, 0, 0), + PixelBenderTypeOpcode::TString => PixelBenderType::TString(String::new()), + PixelBenderTypeOpcode::TBool => PixelBenderType::TBool(0), + PixelBenderTypeOpcode::TBool2 => PixelBenderType::TBool2(0, 0), + PixelBenderTypeOpcode::TBool3 => PixelBenderType::TBool3(0, 0, 0), + PixelBenderTypeOpcode::TBool4 => PixelBenderType::TBool4(0, 0, 0, 0), + } +} + pub fn get_shader_args<'gc>( shader_obj: Object<'gc>, activation: &mut Activation<'_, 'gc>, @@ -63,7 +95,10 @@ pub fn get_shader_args<'gc>( .map(|(index, param)| { match param { PixelBenderParam::Normal { - param_type, name, .. + param_type, + name, + metadata, + .. } => { if name == OUT_COORD_NAME { // Pass in a dummy value - this will be ignored in favor of the actual pixel coordinate @@ -76,22 +111,21 @@ pub fn get_shader_args<'gc>( .get_dynamic_property(AvmString::new_utf8(activation.gc(), name)) .expect("Missing normal property"); - let shader_param = shader_param - .as_object() - .expect("Shader property is not an object"); - - if !shader_param.is_of_type( - activation - .avm2() - .classes() - .shaderparameter - .inner_class_definition(), - ) { - panic!("Expected shader parameter to be of class ShaderParameter"); - } - - let value = shader_param.get_slot(shader_parameter_slots::_VALUE); - let pb_val = PixelBenderType::from_avm2_value(activation, value, param_type)?; + let pb_val = if let Some(shader_param) = shader_param.as_object() + && shader_param.is_of_type( + activation + .avm2() + .classes() + .shaderparameter + .inner_class_definition(), + ) { + let value = shader_param.get_slot(shader_parameter_slots::_VALUE); + PixelBenderType::from_avm2_value(activation, value, param_type)? + } else { + // The ShaderParameter was replaced with a primitive or non-ShaderParameter object. + // Flash ignores this and uses the default value from shader metadata. + get_default_shader_param_value(metadata, *param_type) + }; Ok(PixelBenderShaderArgument::ValueInput { index: index as u8, diff --git a/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/Test.as b/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/Test.as new file mode 100644 index 000000000000..86553ec56000 --- /dev/null +++ b/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/Test.as @@ -0,0 +1,164 @@ +package { + import flash.display.Sprite; + import flash.display.Shader; + import flash.display.ShaderJob; + import flash.display.ShaderParameter; + import flash.display.ShaderInput; + import flash.display.BitmapData; + import flash.utils.ByteArray; + + public class Test extends Sprite { + [Embed(source="passthrough.pbj", mimeType="application/octet-stream")] + private static var PassthroughShader:Class; + + public function Test() { + trace("=== Testing shader parameter values actually received by shader ==="); + trace(""); + + // Test 1: Normal array value via .value + trace("=== Test 1: Normal - shader.data.amount.value = [0.5] ==="); + runShader(function(shader:Shader):void { + shader.data.amount.value = [0.5]; + }); + + // Test 2: Array set directly on shader.data + trace(""); + trace("=== Test 2: Array on data - shader.data.amount = [0.75] ==="); + runShader(function(shader:Shader):void { + shader.data.amount = [0.75]; + }); + + // Test 3: Primitive number set directly + trace(""); + trace("=== Test 3: Primitive number - shader.data.amount = 0.299 ==="); + runShader(function(shader:Shader):void { + shader.data.amount = 0.299; + }); + + // Test 4: Integer + trace(""); + trace("=== Test 4: Integer - shader.data.amount = 1 ==="); + runShader(function(shader:Shader):void { + shader.data.amount = 1; + }); + + // Test 5: Zero + trace(""); + trace("=== Test 5: Zero - shader.data.amount = 0 ==="); + runShader(function(shader:Shader):void { + shader.data.amount = 0; + }); + + // Test 6: Negative number + trace(""); + trace("=== Test 6: Negative - shader.data.amount = -0.5 ==="); + runShader(function(shader:Shader):void { + shader.data.amount = -0.5; + }); + + // Test 7: Boolean true + trace(""); + trace("=== Test 7: Boolean true - shader.data.amount = true ==="); + runShader(function(shader:Shader):void { + shader.data.amount = true; + }); + + // Test 8: Boolean false + trace(""); + trace("=== Test 8: Boolean false - shader.data.amount = false ==="); + runShader(function(shader:Shader):void { + shader.data.amount = false; + }); + + // Test 9: String that looks like number + trace(""); + trace("=== Test 9: String '0.123' - shader.data.amount = '0.123' ==="); + runShader(function(shader:Shader):void { + shader.data.amount = "0.123"; + }); + + // Test 10: String non-numeric + trace(""); + trace("=== Test 10: String 'hello' - shader.data.amount = 'hello' ==="); + runShader(function(shader:Shader):void { + shader.data.amount = "hello"; + }); + + // Test 11: null + trace(""); + trace("=== Test 11: null - shader.data.amount = null ==="); + runShader(function(shader:Shader):void { + shader.data.amount = null; + }); + + // Test 12: undefined + trace(""); + trace("=== Test 12: undefined - shader.data.amount = undefined ==="); + runShader(function(shader:Shader):void { + shader.data.amount = undefined; + }); + + // Test 13: NaN + trace(""); + trace("=== Test 13: NaN - shader.data.amount = NaN ==="); + runShader(function(shader:Shader):void { + shader.data.amount = NaN; + }); + + // Test 14: Infinity + trace(""); + trace("=== Test 14: Infinity - shader.data.amount = Infinity ==="); + runShader(function(shader:Shader):void { + shader.data.amount = Infinity; + }); + + // Test 15: Empty array + trace(""); + trace("=== Test 15: Empty array - shader.data.amount = [] ==="); + runShader(function(shader:Shader):void { + shader.data.amount = []; + }); + + // Test 16: Object + trace(""); + trace("=== Test 16: Object - shader.data.amount = {valueOf: 0.777} ==="); + runShader(function(shader:Shader):void { + shader.data.amount = {valueOf: function():Number { return 0.777; }}; + }); + + trace(""); + trace("=== Done ==="); + } + + private function runShader(setup:Function):void { + var shader:Shader = new Shader(new PassthroughShader() as ByteArray); + var output:BitmapData = new BitmapData(1, 1, false, 0x000000); + + // Apply the test setup (sets the parameter) + setup(shader); + + // Check what's stored + trace("Stored value: " + shader.data.amount); + trace("Stored type: " + typeof shader.data.amount); + + // Run the shader + try { + var job:ShaderJob = new ShaderJob(shader, output, 1, 1); + job.start(true); + + // Read the output pixel - shader outputs amount as RGB + var pixel:uint = output.getPixel(0, 0); + var r:uint = (pixel >> 16) & 0xFF; + var g:uint = (pixel >> 8) & 0xFF; + var b:uint = pixel & 0xFF; + + // Convert back to float (0-255 -> 0.0-1.0) + var receivedValue:Number = r / 255.0; + trace("Output pixel RGB: " + r + ", " + g + ", " + b); + trace("Shader received (approx): " + receivedValue.toFixed(4)); + } catch (e:Error) { + trace("ERROR: " + e); + } + } + } +} diff --git a/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/output.txt b/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/output.txt new file mode 100644 index 000000000000..38fab89aabeb --- /dev/null +++ b/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/output.txt @@ -0,0 +1,99 @@ +=== Testing shader parameter values actually received by shader === + +=== Test 1: Normal - shader.data.amount.value = [0.5] === +Stored value: [object ShaderParameter] +Stored type: object +Output pixel RGB: 128, 128, 128 +Shader received (approx): 0.5020 + +=== Test 2: Array on data - shader.data.amount = [0.75] === +Stored value: 0.75 +Stored type: object +Output pixel RGB: 0, 0, 0 +Shader received (approx): 0.0000 + +=== Test 3: Primitive number - shader.data.amount = 0.299 === +Stored value: 0.299 +Stored type: number +Output pixel RGB: 0, 0, 0 +Shader received (approx): 0.0000 + +=== Test 4: Integer - shader.data.amount = 1 === +Stored value: 1 +Stored type: number +Output pixel RGB: 0, 0, 0 +Shader received (approx): 0.0000 + +=== Test 5: Zero - shader.data.amount = 0 === +Stored value: 0 +Stored type: number +Output pixel RGB: 0, 0, 0 +Shader received (approx): 0.0000 + +=== Test 6: Negative - shader.data.amount = -0.5 === +Stored value: -0.5 +Stored type: number +Output pixel RGB: 0, 0, 0 +Shader received (approx): 0.0000 + +=== Test 7: Boolean true - shader.data.amount = true === +Stored value: true +Stored type: boolean +Output pixel RGB: 0, 0, 0 +Shader received (approx): 0.0000 + +=== Test 8: Boolean false - shader.data.amount = false === +Stored value: false +Stored type: boolean +Output pixel RGB: 0, 0, 0 +Shader received (approx): 0.0000 + +=== Test 9: String '0.123' - shader.data.amount = '0.123' === +Stored value: 0.123 +Stored type: string +Output pixel RGB: 0, 0, 0 +Shader received (approx): 0.0000 + +=== Test 10: String 'hello' - shader.data.amount = 'hello' === +Stored value: hello +Stored type: string +Output pixel RGB: 0, 0, 0 +Shader received (approx): 0.0000 + +=== Test 11: null - shader.data.amount = null === +Stored value: null +Stored type: object +Output pixel RGB: 0, 0, 0 +Shader received (approx): 0.0000 + +=== Test 12: undefined - shader.data.amount = undefined === +Stored value: undefined +Stored type: undefined +Output pixel RGB: 0, 0, 0 +Shader received (approx): 0.0000 + +=== Test 13: NaN - shader.data.amount = NaN === +Stored value: NaN +Stored type: number +Output pixel RGB: 0, 0, 0 +Shader received (approx): 0.0000 + +=== Test 14: Infinity - shader.data.amount = Infinity === +Stored value: Infinity +Stored type: number +Output pixel RGB: 0, 0, 0 +Shader received (approx): 0.0000 + +=== Test 15: Empty array - shader.data.amount = [] === +Stored value: +Stored type: object +Output pixel RGB: 0, 0, 0 +Shader received (approx): 0.0000 + +=== Test 16: Object - shader.data.amount = {valueOf: 0.777} === +Stored value: [object Object] +Stored type: object +Output pixel RGB: 0, 0, 0 +Shader received (approx): 0.0000 + +=== Done === diff --git a/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/passthrough.pbj b/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/passthrough.pbj new file mode 100644 index 000000000000..729839d68194 Binary files /dev/null and b/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/passthrough.pbj differ diff --git a/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/passthrough.pbk b/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/passthrough.pbk new file mode 100644 index 000000000000..361320b6ff8c --- /dev/null +++ b/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/passthrough.pbk @@ -0,0 +1,21 @@ + + +kernel PassthroughParam +< namespace : "Ruffle Tests"; + vendor : "Ruffle"; + version : 1; +> +{ + output pixel4 dst; + + parameter float amount + < + defaultValue: 0.0; + >; + + void evaluatePixel() + { + // Output the parameter value as RGB, alpha = 1 + dst = pixel4(amount, amount, amount, 1.0); + } +} diff --git a/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/test.swf b/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/test.swf new file mode 100644 index 000000000000..83393833c611 Binary files /dev/null and b/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/test.swf differ diff --git a/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/test.toml b/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/test.toml new file mode 100644 index 000000000000..7492d841fe48 --- /dev/null +++ b/tests/tests/swfs/avm2/pixelbender_shaderdata_setter/test.toml @@ -0,0 +1,4 @@ +num_frames = 1 + +[player_options] +with_renderer = { optional = false, sample_count = 4 }