naga-agal: Fix handling of sampler overrides

After some testing, and looking at OpenFL, I believe I've
determined the correct behavior for AGAL sampling:

Each time a Context3D.setProgram or Context3D.setSamplerStateAt
call is made, the sampler config for the used texture slot(s)
is updated with the new wrapping/filter behavior. For setProgram,
this comes from all of the 'tex' opcodes used within the program.

However, when the 'ignoresampler' flag is set in a 'tex' opcode,
the setProgram call does *not* override the existing sampler config.
As a result, that program will sample with the behavior determined
by the most recent setSamplerStateAt or setProgram call involving
the used texture slot(s).

Previously, we were always overriding the opcode sampler config
with the values from Context3D.setSamplerStateAt. However, I didn't
realize that the order of the calls matter, so none of my tests ended
up observing the effect of 'ignoresampler'.

We now need to process AGAL bytecode twice - a quick initial
parse to determine the sampler configs (which need to be updated
when we call 'setProgram'), and a second time when to build the
Naga module (which needs to wait until we have the vertex attributes
available, which can be changed by ActionScript after setting
the program).
This commit is contained in:
Aaron Hill 2023-11-25 16:58:09 -05:00
parent f427cd58bb
commit 837143bb12
14 changed files with 350 additions and 63 deletions

View File

@ -15,7 +15,8 @@ use num_traits::FromPrimitive;
use crate::varying::VaryingRegisters; use crate::varying::VaryingRegisters;
use crate::{ use crate::{
types::*, Error, ShaderType, VertexAttributeFormat, MAX_VERTEX_ATTRIBUTES, SHADER_ENTRY_POINT, types::*, Error, ShaderType, VertexAttributeFormat, MAX_TEXTURES, MAX_VERTEX_ATTRIBUTES,
SHADER_ENTRY_POINT,
}; };
const VERTEX_PROGRAM_CONTANTS: u64 = 128; const VERTEX_PROGRAM_CONTANTS: u64 = 128;
@ -52,6 +53,15 @@ struct TextureSamplers {
repeat_u_clamp_v_nearest: Handle<Expression>, repeat_u_clamp_v_nearest: Handle<Expression>,
} }
#[derive(Copy, Clone)]
struct TextureBindingData {
// The `Expression::GlobalVariable` corresponding to the texture that we loaded.
expr: Handle<Expression>,
// The sample config values extracted from the opcode
// (which may override or be overridden by values set by Context3D.setSamplerStateAt)
sampler_config: SamplerConfig,
}
pub(crate) struct NagaBuilder<'a> { pub(crate) struct NagaBuilder<'a> {
pub(crate) module: Module, pub(crate) module: Module,
pub(crate) func: Function, pub(crate) func: Function,
@ -74,9 +84,9 @@ pub(crate) struct NagaBuilder<'a> {
pub(crate) varying_registers: VaryingRegisters, pub(crate) varying_registers: VaryingRegisters,
// Whenever we encounter a texture load at a particular index // Whenever we encounter a texture load at a particular index
// for the first time, we store an `Expression::GlobalVariable` // for the first time, we store the expression we generate,
// here corresponding to the texture that we loaded. // as well as the sampler parameters used.
texture_bindings: [Option<Handle<Expression>>; 8], texture_bindings: [Option<TextureBindingData>; MAX_TEXTURES],
// Whenever we read from a particular temporary register // Whenever we read from a particular temporary register
// for the first time, we create a new local variable // for the first time, we create a new local variable
@ -263,7 +273,7 @@ impl VertexAttributeFormat {
pub struct ShaderConfig<'a> { pub struct ShaderConfig<'a> {
pub shader_type: ShaderType, pub shader_type: ShaderType,
pub vertex_attributes: &'a [Option<VertexAttributeFormat>; 8], pub vertex_attributes: &'a [Option<VertexAttributeFormat>; 8],
pub sampler_overrides: &'a [Option<SamplerOverride>; 8], pub sampler_configs: &'a [SamplerConfig; 8],
pub version: AgalVersion, pub version: AgalVersion,
} }
@ -273,12 +283,14 @@ pub enum AgalVersion {
Agal2, Agal2,
} }
struct ParsedBytecode {
version: AgalVersion,
shader_type: ShaderType,
operations: Vec<(Opcode, DestField, SourceField, Source2)>,
}
impl<'a> NagaBuilder<'a> { impl<'a> NagaBuilder<'a> {
pub fn process_agal( fn parse_bytecode(mut agal: &[u8]) -> Result<ParsedBytecode> {
mut agal: &[u8],
vertex_attributes: &[Option<VertexAttributeFormat>; MAX_VERTEX_ATTRIBUTES],
sampler_overrides: &[Option<SamplerOverride>; 8],
) -> Result<Module> {
let data = &mut agal; let data = &mut agal;
let mut header = [0; 7]; let mut header = [0; 7];
@ -305,12 +317,7 @@ impl<'a> NagaBuilder<'a> {
_ => return Err(Error::InvalidShaderType(header[6])), _ => return Err(Error::InvalidShaderType(header[6])),
}; };
let mut builder = NagaBuilder::new(ShaderConfig { let mut operations = Vec::new();
shader_type,
vertex_attributes,
sampler_overrides,
version,
});
while !data.is_empty() { while !data.is_empty() {
let mut token = [0; 24]; let mut token = [0; 24];
@ -331,7 +338,63 @@ impl<'a> NagaBuilder<'a> {
token[16..24].try_into().unwrap(), token[16..24].try_into().unwrap(),
))?) ))?)
}; };
operations.push((opcode, dest, source1, source2))
}
Ok(ParsedBytecode {
version,
shader_type,
operations,
})
}
pub fn extract_sampler_configs(agal: &[u8]) -> Result<[Option<SamplerConfig>; MAX_TEXTURES]> {
let parsed = Self::parse_bytecode(agal)?;
let mut sampler_configs = [None; MAX_TEXTURES];
for (_opcode, _dest, _source1, source2) in parsed.operations {
if let Source2::Sampler(sampler_field) = source2 {
// When the 'ignore_sampler' field is set, we do not update the sampler config.
// The existing sampler value will end up getting used
// (which comes from a previous Context3D.setSamplerStateAt
// or Context3D.setProgram call)
if sampler_field.special.ignore_sampler {
continue;
}
let sampler_config = SamplerConfig {
wrapping: sampler_field.wrapping,
filter: sampler_field.filter,
mipmap: sampler_field.mipmap,
};
let index = sampler_field.reg_num as usize;
if sampler_configs[index].is_none() {
sampler_configs[index] = Some(sampler_config);
} else if sampler_configs[index] != Some(sampler_config) {
return Err(Error::SamplerConfigMismatch {
texture: index,
old: sampler_configs[index].unwrap(),
new: sampler_config,
});
}
}
}
Ok(sampler_configs)
}
pub fn build_module(
agal: &[u8],
vertex_attributes: &[Option<VertexAttributeFormat>; MAX_VERTEX_ATTRIBUTES],
sampler_configs: &[SamplerConfig; 8],
) -> Result<Module> {
let parsed = Self::parse_bytecode(agal)?;
let mut builder = NagaBuilder::new(ShaderConfig {
shader_type: parsed.shader_type,
vertex_attributes,
sampler_configs,
version: parsed.version,
});
for (opcode, dest, source1, source2) in parsed.operations {
builder.process_opcode(&opcode, &dest, &source1, &source2)?; builder.process_opcode(&opcode, &dest, &source1, &source2)?;
} }
builder.finish() builder.finish()
@ -737,8 +800,13 @@ impl<'a> NagaBuilder<'a> {
fn emit_texture_load( fn emit_texture_load(
&mut self, &mut self,
index: usize, index: usize,
dimension: Dimension, sampler_field: &SamplerField,
) -> Result<Handle<Expression>> { ) -> Result<Handle<Expression>> {
let sampler_config = SamplerConfig {
wrapping: sampler_field.wrapping,
filter: sampler_field.filter,
mipmap: sampler_field.mipmap,
};
if self.texture_bindings[index].is_none() { if self.texture_bindings[index].is_none() {
let global_var = self.module.global_variables.append( let global_var = self.module.global_variables.append(
GlobalVariable { GlobalVariable {
@ -750,7 +818,7 @@ impl<'a> NagaBuilder<'a> {
}), }),
// Note - we assume that a given texture is always sampled with the same dimension // Note - we assume that a given texture is always sampled with the same dimension
// (2d or cube) // (2d or cube)
ty: match dimension { ty: match sampler_field.dimension {
Dimension::TwoD => self.image2d, Dimension::TwoD => self.image2d,
Dimension::Cube => self.imagecube, Dimension::Cube => self.imagecube,
}, },
@ -758,13 +826,25 @@ impl<'a> NagaBuilder<'a> {
}, },
Span::UNDEFINED, Span::UNDEFINED,
); );
self.texture_bindings[index] = Some( self.texture_bindings[index] = Some(TextureBindingData {
self.func expr: self
.func
.expressions .expressions
.append(Expression::GlobalVariable(global_var), Span::UNDEFINED), .append(Expression::GlobalVariable(global_var), Span::UNDEFINED),
); sampler_config,
});
} }
Ok(self.texture_bindings[index].unwrap()) let data = self.texture_bindings[index].as_ref().unwrap();
// AGAL requires that a given texture ID always be sampled with the same settings
// within a program
if data.sampler_config != sampler_config {
return Err(Error::SamplerConfigMismatch {
texture: index,
old: data.sampler_config,
new: sampler_config,
});
}
Ok(self.texture_bindings[index].unwrap().expr)
} }
fn emit_source_field_load( fn emit_source_field_load(
@ -1145,25 +1225,12 @@ impl<'a> NagaBuilder<'a> {
panic!("Invalid sample register type {:?}", sampler_field); panic!("Invalid sample register type {:?}", sampler_field);
} }
let mut filter = sampler_field.filter; // Always take filter/wrapping from the shader config, which takes into account both the values
let mut wrapping = sampler_field.wrapping; // from the opcode and any Context3D.setSamplerStateAt calls.
// FIXME - refactor this to just bind the correct sampler at the proper index from the wgpu side.
// See https://github.com/openfl/openfl/issues/1332 let sampler_config = self.shader_config.sampler_configs[texture_id as usize];
let filter = sampler_config.filter;
// FIXME - Flash Player seems to unconditionally use sampler overrides, let wrapping = sampler_config.wrapping;
// regardless of whether or not `ignore_sampler` is set. I haven't
// found any real SWFs that use it, so let's panic so that get
// get a bug report if it ever happens.
if sampler_field.special.ignore_sampler {
panic!("Found ignore_sampler in {:?}", sampler_field);
}
if let Some(sampler_override) =
&self.shader_config.sampler_overrides[texture_id as usize]
{
filter = sampler_override.filter;
wrapping = sampler_override.wrapping;
}
let sampler_binding = match (filter, wrapping) { let sampler_binding = match (filter, wrapping) {
(Filter::Linear, Wrapping::Clamp) => texture_samplers.clamp_linear, (Filter::Linear, Wrapping::Clamp) => texture_samplers.clamp_linear,
@ -1229,7 +1296,7 @@ impl<'a> NagaBuilder<'a> {
} }
}; };
let image = self.emit_texture_load(texture_id as usize, sampler_field.dimension)?; let image = self.emit_texture_load(texture_id as usize, sampler_field)?;
let tex = self.evaluate_expr(Expression::ImageSample { let tex = self.evaluate_expr(Expression::ImageSample {
image, image,
sampler: sampler_binding, sampler: sampler_binding,

View File

@ -29,6 +29,11 @@ pub enum Error {
ReadError(std::io::Error), ReadError(std::io::Error),
InvalidOpcode(u32), InvalidOpcode(u32),
InvalidVersion(u32), InvalidVersion(u32),
SamplerConfigMismatch {
texture: usize,
old: SamplerConfig,
new: SamplerConfig,
},
} }
impl From<std::io::Error> for Error { impl From<std::io::Error> for Error {
@ -43,7 +48,7 @@ pub enum ShaderType {
Fragment, Fragment,
} }
pub use types::{Filter, Mipmap, SamplerOverride, Wrapping}; pub use types::{Filter, Mipmap, SamplerConfig, Wrapping};
/** /**
* Compiles an Adobe AGAL shader to a Naga Module. * Compiles an Adobe AGAL shader to a Naga Module.
@ -93,7 +98,13 @@ pub use types::{Filter, Mipmap, SamplerOverride, Wrapping};
pub fn agal_to_naga( pub fn agal_to_naga(
agal: &[u8], agal: &[u8],
vertex_attributes: &[Option<VertexAttributeFormat>; MAX_VERTEX_ATTRIBUTES], vertex_attributes: &[Option<VertexAttributeFormat>; MAX_VERTEX_ATTRIBUTES],
sampler_overrides: &[Option<SamplerOverride>; MAX_TEXTURES], sampler_configs: &[SamplerConfig; MAX_TEXTURES],
) -> Result<Module, Error> { ) -> Result<Module, Error> {
NagaBuilder::process_agal(agal, vertex_attributes, sampler_overrides) NagaBuilder::build_module(agal, vertex_attributes, sampler_configs)
}
pub fn extract_sampler_configs(
agal: &[u8],
) -> Result<[Option<SamplerConfig>; MAX_TEXTURES], Error> {
NagaBuilder::extract_sampler_configs(agal)
} }

View File

@ -186,12 +186,22 @@ pub struct SamplerField {
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct SamplerOverride { pub struct SamplerConfig {
pub wrapping: Wrapping, pub wrapping: Wrapping,
pub filter: Filter, pub filter: Filter,
pub mipmap: Mipmap, pub mipmap: Mipmap,
} }
impl Default for SamplerConfig {
fn default() -> Self {
SamplerConfig {
wrapping: Wrapping::Clamp,
filter: Filter::Nearest,
mipmap: Mipmap::Disable,
}
}
}
impl SamplerField { impl SamplerField {
pub fn parse(val: u64) -> Result<SamplerField, Error> { pub fn parse(val: u64) -> Result<SamplerField, Error> {
let reg_num = (val & 0xFFFF) as u16; let reg_num = (val & 0xFFFF) as u16;

View File

@ -1,6 +1,5 @@
--- ---
source: render/naga-agal/tests/wgsl.rs source: render/naga-agal/tests/wgsl.rs
assertion_line: 105
expression: output expression: output
--- ---
struct FragmentOutput { struct FragmentOutput {
@ -2675,7 +2674,7 @@ fn main(@location(0) param: vec4<f32>) -> FragmentOutput {
} }
} }
let _e5727: vec4<f32> = temporary9_; let _e5727: vec4<f32> = temporary9_;
let _e5731: vec4<f32> = textureSample(texture0_, sampler2_, _e5727.xyzz.xyz); let _e5731: vec4<f32> = textureSample(texture0_, sampler3_, _e5727.xyzz.xyz);
temporary1_ = _e5731; temporary1_ = _e5731;
let _e5732: vec4<f32> = temporary11_; let _e5732: vec4<f32> = temporary11_;
let _e5736: vec4<f32> = constant_registers[16u]; let _e5736: vec4<f32> = constant_registers[16u];

View File

@ -22,7 +22,7 @@ pub fn to_wgsl(module: &Module) -> String {
// Making this a macro gives us a better span in 'inta' // Making this a macro gives us a better span in 'inta'
macro_rules! test_shader { macro_rules! test_shader {
($shader:expr, $attrs:expr, $shader_type:expr $(,)?) => { ($shader:expr, $attrs:expr, $shader_type:expr $(,)?) => {
let module = agal_to_naga(&$shader, $attrs, &[None; 8]).unwrap(); let module = agal_to_naga(&$shader, $attrs, &[Default::default(); 8]).unwrap();
let output = to_wgsl(&module); let output = to_wgsl(&module);
insta::assert_display_snapshot!(output); insta::assert_display_snapshot!(output);
}; };

View File

@ -1,5 +1,5 @@
use naga::valid::{Capabilities, ValidationFlags, Validator}; use naga::valid::{Capabilities, ValidationFlags, Validator};
use naga_agal::{Filter, SamplerOverride, Wrapping}; use naga_agal::{Filter, SamplerConfig, Wrapping};
use ruffle_render::backend::{ use ruffle_render::backend::{
Context3DTextureFilter, Context3DTriangleFace, Context3DVertexBufferFormat, Context3DWrapMode, Context3DTextureFilter, Context3DTriangleFace, Context3DVertexBufferFormat, Context3DWrapMode,
Texture, Texture,
@ -84,7 +84,13 @@ pub struct CurrentPipeline {
dirty: Cell<bool>, dirty: Cell<bool>,
sampler_override: [Option<SamplerOverride>; 8], // Sampler configuration information for each texture slot.
// This is updated by `Context3D.setSamplerStateAt`, as well
// as in `Context3D.setProgram` (based on the sampling opcodes
// in the program). All texture slots have a sampler set by default
// (which allows rendering with an 'ignoresampler' tex opcode,
// and no calls to Context3D.setSamplerStateAt)
sampler_configs: [SamplerConfig; 8],
} }
#[derive(Clone)] #[derive(Clone)]
@ -159,12 +165,20 @@ impl CurrentPipeline {
target_format: TextureFormat::Rgba8Unorm, target_format: TextureFormat::Rgba8Unorm,
sampler_override: [None; 8], sampler_configs: [SamplerConfig::default(); 8],
} }
} }
pub fn set_shaders(&mut self, shaders: Option<Rc<ShaderPairAgal>>) { pub fn set_shaders(&mut self, shaders: Option<Rc<ShaderPairAgal>>) {
self.dirty.set(true); self.dirty.set(true);
self.shaders = shaders; self.shaders = shaders;
if let Some(shaders) = &self.shaders {
for (i, sampler_config) in shaders.fragment_sampler_configs().iter().enumerate() {
// When we call `Context3D.setProgram`, sampler configs from the fragment shader override
// any previously set sampler configs (if 'ignoresampler' was set in the program, then the corresponding
// array entry will be `None`).
self.sampler_configs[i] = sampler_config.unwrap_or(self.sampler_configs[i]);
}
}
} }
pub fn update_texture_at(&mut self, index: usize, texture: Option<BoundTextureData>) { pub fn update_texture_at(&mut self, index: usize, texture: Option<BoundTextureData>) {
@ -325,7 +339,7 @@ impl CurrentPipeline {
descriptors, descriptors,
ShaderCompileData { ShaderCompileData {
vertex_attributes: agal_attributes, vertex_attributes: agal_attributes,
sampler_overrides: self.sampler_override, sampler_configs: self.sampler_configs,
bound_textures: self.bound_textures.clone(), bound_textures: self.bound_textures.clone(),
}, },
); );
@ -538,7 +552,7 @@ impl CurrentPipeline {
wrap: ruffle_render::backend::Context3DWrapMode, wrap: ruffle_render::backend::Context3DWrapMode,
filter: ruffle_render::backend::Context3DTextureFilter, filter: ruffle_render::backend::Context3DTextureFilter,
) { ) {
let sampler_override = SamplerOverride { let sampler_config = SamplerConfig {
wrapping: match wrap { wrapping: match wrap {
Context3DWrapMode::Clamp => Wrapping::Clamp, Context3DWrapMode::Clamp => Wrapping::Clamp,
Context3DWrapMode::Repeat => Wrapping::Repeat, Context3DWrapMode::Repeat => Wrapping::Repeat,
@ -553,10 +567,8 @@ impl CurrentPipeline {
// FIXME - implement this // FIXME - implement this
mipmap: naga_agal::Mipmap::Disable, mipmap: naga_agal::Mipmap::Disable,
}; };
if self.sampler_override[sampler] != Some(sampler_override) {
self.dirty.set(true); self.dirty.set(true);
self.sampler_override[sampler] = Some(sampler_override); self.sampler_configs[sampler] = sampler_config;
}
} }
} }

View File

@ -1,5 +1,5 @@
use lru::LruCache; use lru::LruCache;
use naga_agal::{SamplerOverride, VertexAttributeFormat}; use naga_agal::{SamplerConfig, VertexAttributeFormat};
use ruffle_render::backend::ShaderModule; use ruffle_render::backend::ShaderModule;
use std::{ use std::{
borrow::Cow, borrow::Cow,
@ -22,7 +22,9 @@ use crate::descriptors::Descriptors;
pub struct ShaderPairAgal { pub struct ShaderPairAgal {
vertex_bytecode: Vec<u8>, vertex_bytecode: Vec<u8>,
fragment_bytecode: Vec<u8>, fragment_bytecode: Vec<u8>,
fragment_sampler_configs: [Option<SamplerConfig>; 8],
// Caches compiled wgpu shader modules. The cache key represents all of the data // Caches compiled wgpu shader modules. The cache key represents all of the data
// that we need to pass to `naga_agal::agal_to_naga` to compile a shader. // that we need to pass to `naga_agal::agal_to_naga` to compile a shader.
compiled: RefCell<LruCache<ShaderCompileData, CompiledShaderProgram>>, compiled: RefCell<LruCache<ShaderCompileData, CompiledShaderProgram>>,
@ -38,14 +40,22 @@ pub struct CompiledShaderProgram {
impl ShaderPairAgal { impl ShaderPairAgal {
pub fn new(vertex_bytecode: Vec<u8>, fragment_bytecode: Vec<u8>) -> Self { pub fn new(vertex_bytecode: Vec<u8>, fragment_bytecode: Vec<u8>) -> Self {
let fragment_sampler_configs =
naga_agal::extract_sampler_configs(&fragment_bytecode).unwrap();
Self { Self {
vertex_bytecode, vertex_bytecode,
fragment_bytecode, fragment_bytecode,
fragment_sampler_configs,
// TODO - figure out a good size for this cache. // TODO - figure out a good size for this cache.
compiled: RefCell::new(LruCache::new(NonZeroUsize::new(2).unwrap())), compiled: RefCell::new(LruCache::new(NonZeroUsize::new(2).unwrap())),
} }
} }
pub fn fragment_sampler_configs(&self) -> &[Option<SamplerConfig>; 8] {
&self.fragment_sampler_configs
}
pub fn compile( pub fn compile(
&self, &self,
descriptors: &Descriptors, descriptors: &Descriptors,
@ -58,7 +68,7 @@ impl ShaderPairAgal {
let vertex_naga_module = naga_agal::agal_to_naga( let vertex_naga_module = naga_agal::agal_to_naga(
&self.vertex_bytecode, &self.vertex_bytecode,
&data.vertex_attributes, &data.vertex_attributes,
&data.sampler_overrides, &data.sampler_configs,
) )
.unwrap(); .unwrap();
let vertex_module = let vertex_module =
@ -72,7 +82,7 @@ impl ShaderPairAgal {
let fragment_naga_module = naga_agal::agal_to_naga( let fragment_naga_module = naga_agal::agal_to_naga(
&self.fragment_bytecode, &self.fragment_bytecode,
&data.vertex_attributes, &data.vertex_attributes,
&data.sampler_overrides, &data.sampler_configs,
) )
.unwrap(); .unwrap();
let fragment_module = let fragment_module =
@ -200,7 +210,7 @@ impl ShaderPairAgal {
#[derive(Hash, Eq, PartialEq, Clone)] #[derive(Hash, Eq, PartialEq, Clone)]
pub struct ShaderCompileData { pub struct ShaderCompileData {
pub sampler_overrides: [Option<SamplerOverride>; 8], pub sampler_configs: [SamplerConfig; 8],
pub vertex_attributes: [Option<VertexAttributeFormat>; MAX_VERTEX_ATTRIBUTES], pub vertex_attributes: [Option<VertexAttributeFormat>; MAX_VERTEX_ATTRIBUTES],
pub bound_textures: [Option<BoundTextureData>; 8], pub bound_textures: [Option<BoundTextureData>; 8],
} }

View File

@ -0,0 +1,169 @@
package {
import com.adobe.utils.AGALMiniAssembler;
import flash.display.Sprite;
import flash.display.Stage3D;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.display3D.Context3D;
import flash.display3D.Context3DBlendFactor;
import flash.display3D.Context3DCompareMode;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DRenderMode;
import flash.display3D.Context3DStencilAction;
import flash.display3D.Context3DTriangleFace;
import flash.display3D.Context3DVertexBufferFormat;
import flash.display3D.Context3DTextureFilter;
import flash.display3D.Context3DWrapMode;
import flash.display3D.IndexBuffer3D;
import flash.display3D.Program3D;
import flash.display3D.VertexBuffer3D;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.geom.Rectangle;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.ui.Keyboard;
import flash.utils.Timer;
import flash.display.MovieClip;
import flash.display.Stage;
import flash.display.BitmapData;
import flash.display.Bitmap;
// Based on example from https://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display3D/Context3D.html#setStencilActions
public class Test extends MovieClip {
public const viewWidth:Number = 500;
public const viewHeight:Number = 600;
private var stage3D:Stage3D;
private var renderContext:Context3D;
private var indexList:IndexBuffer3D;
private var vertexes:VertexBuffer3D;
private const VERTEX_SHADER:String =
"add op, va0, vc0 \n" + // copy position to output with offset vector
"mov v0, va1"; // copy uv to varying variable v0
private const FRAGMENT_SHADER:String =
"tex oc, v0, fs0 <2d,clamp,linear,mipnone,ignoresampler>";
private const FRAGMENT_SHADER_REPEAT:String =
"tex oc, v0, fs0 <2d,repeat,linear,mipnone>";
private var vertexAssembly:AGALMiniAssembler = new AGALMiniAssembler(false);
private var fragmentAssembly:AGALMiniAssembler = new AGALMiniAssembler(false);
private var fragmentAssemblyRepeat:AGALMiniAssembler = new AGALMiniAssembler(false);
private var programPair:Program3D;
public function Test() {
stage3D = this.stage.stage3Ds[0];
// Add event listener before requesting the context
stage3D.addEventListener(Event.CONTEXT3D_CREATE, contextCreated);
stage3D.requestContext3D(Context3DRenderMode.AUTO, "standard");
// Compile shaders
vertexAssembly.assemble(Context3DProgramType.VERTEX, VERTEX_SHADER, 2);
fragmentAssembly.assemble(Context3DProgramType.FRAGMENT, FRAGMENT_SHADER, 2);
fragmentAssemblyRepeat.assemble(Context3DProgramType.FRAGMENT, FRAGMENT_SHADER_REPEAT, 2);
}
// Note, context3DCreate event can happen at any time, such as when the hardware resources are taken by another process
private function contextCreated(event:Event):void {
renderContext = Stage3D(event.target).context3D;
renderContext.enableErrorChecking = true; // Can slow rendering - only turn on when developing/testing
renderContext.configureBackBuffer(viewWidth, viewHeight, 4, false);
// Create vertex index list for the triangles
var triangles:Vector.<uint> = Vector.<uint>([0, 3, 2,
0, 1, 3,
]);
indexList = renderContext.createIndexBuffer(triangles.length);
indexList.uploadFromVector(triangles, 0, triangles.length);
// Create vertexes
const dataPerVertex:int = 5;
var vertexData:Vector.<Number> = Vector.<Number>(
[
// x, y, z u, v
-.1, .1, 0, 0, 2,
.1, .1, 0, 2, 2,
-.1, -.1, 0, 0, 0,
.1, -.1, 0, 2, 0
]);
vertexes = renderContext.createVertexBuffer(vertexData.length / dataPerVertex, dataPerVertex);
vertexes.uploadFromVector(vertexData, 0, vertexData.length / dataPerVertex);
// Identify vertex data inputs for vertex program
renderContext.setVertexBufferAt(0, vertexes, 0, Context3DVertexBufferFormat.FLOAT_3); // va0 is position
renderContext.setVertexBufferAt(1, vertexes, 3, Context3DVertexBufferFormat.FLOAT_2); // va1 is texture uv coords
const size = 4;
var redGreen = new BitmapData(size, size, true, 0x0);
redGreen.fillRect(new Rectangle(0, 0, size / 2, size / 2), 0xFFFF0000);
redGreen.fillRect(new Rectangle(size / 2, 0, size / 2, size / 2), 0xFF00FF00);
redGreen.fillRect(new Rectangle(0, size / 2, size / 2, size / 2), 0xFF0000FF);
redGreen.fillRect(new Rectangle(size / 2, size / 2, size / 2, size / 2), 0xFFFF00FF);
var redGreenTexture = renderContext.createTexture(size, size, "bgra", false);
redGreenTexture.uploadFromBitmapData(redGreen);
// This modification is done after 'redGreenTexture.uploadFromBitmapData(redGreen)',
// so it should have no effect.
redGreen.fillRect(new Rectangle(0, 0, size, size), 0);
// Upload programs to render context
programPair = renderContext.createProgram();
programPair.upload(vertexAssembly.agalcode, fragmentAssembly.agalcode);
var repeatProgram = renderContext.createProgram();
repeatProgram.upload(vertexAssembly.agalcode, fragmentAssemblyRepeat.agalcode);
// Clear, setting stencil to 0
renderContext.clear(.3, .3, .3, 1, 1, 0);
var offsetVec = Vector.<Number>([-0.7, 0.7, 0, 0]);
// FIXME - implement and test anisotropic filters
renderContext.setTextureAt(0, redGreenTexture);
for each (var mode in [0, 1, 2, 3, 4, 5]) {
for each (var textureFilter in [Context3DTextureFilter.NEAREST, Context3DTextureFilter.LINEAR]) {
for each (var wrapMode in [Context3DWrapMode.CLAMP, Context3DWrapMode.CLAMP_U_REPEAT_V, Context3DWrapMode.REPEAT, Context3DWrapMode.REPEAT_U_CLAMP_V]) {
if (mode == 0) {
renderContext.setProgram(programPair);
} else if (mode == 1) {
renderContext.setSamplerStateAt(0, wrapMode, textureFilter, "mipnone");
renderContext.setProgram(repeatProgram);
renderContext.setProgram(programPair);
} else if (mode == 2) {
renderContext.setSamplerStateAt(0, wrapMode, textureFilter, "mipnone");
renderContext.setProgram(programPair);
} else if (mode == 3) {
renderContext.setSamplerStateAt(0, wrapMode, textureFilter, "mipnone");
renderContext.setProgram(repeatProgram);
} else if (mode == 4) {
renderContext.setSamplerStateAt(0, wrapMode, textureFilter, "mipnone");
renderContext.setProgram(programPair);
} else if (mode == 5) {
renderContext.setProgram(repeatProgram);
renderContext.setSamplerStateAt(0, wrapMode, textureFilter, "mipnone");
}
renderContext.setProgramConstantsFromVector("vertex", 0, offsetVec);
renderContext.drawTriangles(indexList, 0, 2);
offsetVec[0] += 0.3;
}
}
offsetVec[1] += -0.3;
offsetVec[0] = -0.7;
}
renderContext.present();
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -0,0 +1,9 @@
num_frames = 1
[image_comparisons.output]
tolerance = 1
# FIXME - investigate why this is so high
max_outliers = 1935
[player_options]
with_renderer = { optional = true, sample_count = 1 }

View File

@ -372,7 +372,7 @@ package com.adobe.utils
if ( optfound == null ) if ( optfound == null )
{ {
// todo check that it's a number... // todo check that it's a number...
//trace( "Warning, unknown sampler option: "+opts[k] ); trace( "Warning, unknown sampler option: "+opts[k] );
bias = Number(opts[k]); bias = Number(opts[k]);
if ( verbose ) if ( verbose )
trace( " bias: " + bias ); trace( " bias: " + bias );