Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 53 additions & 19 deletions core/src/avm2/globals/flash/display/shader_job.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,45 @@
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();
}

Check warning on line 34 in core/src/avm2/globals/flash/display/shader_job.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (34)
}

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),

Check warning on line 53 in core/src/avm2/globals/flash/display/shader_job.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (37–53)
}
}

pub fn get_shader_args<'gc>(
shader_obj: Object<'gc>,
activation: &mut Activation<'_, 'gc>,
Expand Down Expand Up @@ -63,7 +95,10 @@
.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
Expand All @@ -76,22 +111,21 @@
.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,
Expand Down
164 changes: 164 additions & 0 deletions tests/tests/swfs/avm2/pixelbender_shaderdata_setter/Test.as
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
99 changes: 99 additions & 0 deletions tests/tests/swfs/avm2/pixelbender_shaderdata_setter/output.txt
Original file line number Diff line number Diff line change
@@ -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 ===
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<languageVersion : 1.0;>

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);
}
}
Binary file not shown.
4 changes: 4 additions & 0 deletions tests/tests/swfs/avm2/pixelbender_shaderdata_setter/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
num_frames = 1

[player_options]
with_renderer = { optional = false, sample_count = 4 }
Loading