render: Add 'naga-agal' crate to compile AGAL shaders to Naga
This is the first part of the Stage3D implementation, and can
be reviewed independently.
Stage3D shaders use the Adobe Graphics Assembly Language (AGAL),
which is a binary shader format. It supports vertex attributes,
varying registers, program constants (uniforms), and texture sampling.
This PR only implements a few parts of AGAL:
* The 'mov' and 'm44' opcodes
* Vertex attributes, varying registers, program constants, and 'output'
registers (position or color, depending on shader type)
This is sufficient to get a non-trivial Stage3D program
running (the rotating cube demo from the Adobe docs).
The output of `naga-agal` is a `naga::Module`. This can be passed
directly to wgpu, or compiled into a shader language using
a Naga backend (glsl, wgsl, SPIR-V, etc). The test suite
output WGSL files, and uses the 'insta' crate to compare against
saved files on disk.
Currently, the only real way to write AGAL bytecode is using
the Adobe-provided 'AGALMiniAssembler' flash class.
This class assembles the textual reprentation of AGAL into
the binary format.
To make writing tests easier, I've added a 'agal_compiler' test, which
can easily be modified to add more Agal textual assembly.
2022-09-23 00:44:49 +00:00
|
|
|
use std::io::Read;
|
|
|
|
|
|
|
|
use naga::{
|
|
|
|
ArraySize, BuiltIn, Constant, ConstantInner, EntryPoint, FunctionArgument, FunctionResult,
|
|
|
|
GlobalVariable, Interpolation, ScalarValue, ShaderStage, StructMember, SwizzleComponent,
|
|
|
|
};
|
|
|
|
use naga::{BinaryOperator, MathFunction};
|
|
|
|
use naga::{
|
|
|
|
Binding, Expression, Function, Handle, LocalVariable, Module, ScalarKind, Span, Statement,
|
|
|
|
Type, TypeInner, VectorSize,
|
|
|
|
};
|
|
|
|
use num_traits::FromPrimitive;
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
types::*, Error, ShaderType, VertexAttributeFormat, ENTRY_POINT, MAX_VERTEX_ATTRIBUTES,
|
|
|
|
};
|
|
|
|
|
|
|
|
const VERTEX_PROGRAM_CONTANTS: u64 = 128;
|
|
|
|
const FRAGMENT_PROGRAM_CONSTANTS: u64 = 28;
|
|
|
|
|
|
|
|
pub type Result<T> = std::result::Result<T, Error>;
|
|
|
|
|
|
|
|
pub(crate) struct NagaBuilder<'a> {
|
|
|
|
module: Module,
|
|
|
|
func: Function,
|
|
|
|
|
|
|
|
// This evaluate to a Pointer to the temporary 'main' destiation location
|
|
|
|
// (the output position for a vertex shader, or the output color for a fragment shader)
|
|
|
|
// which can be used with Expression::Load and Expression::Store
|
|
|
|
// This is needed because an AGAL shader can write to the output register
|
|
|
|
// multiple times.
|
|
|
|
dest: Handle<Expression>,
|
|
|
|
|
|
|
|
shader_config: ShaderConfig<'a>,
|
|
|
|
// Whenever we read from a varying register in a fragment shader,
|
|
|
|
// we create a new argument binding for it.
|
|
|
|
argument_expressions: Vec<Option<Handle<Expression>>>,
|
|
|
|
|
|
|
|
varying_pointers: Vec<Option<Handle<Expression>>>,
|
|
|
|
|
|
|
|
// An `Expression::GlobalVariables` for the uniform buffer
|
|
|
|
// that stores all of the program constants.
|
|
|
|
constant_registers: Handle<Expression>,
|
|
|
|
|
|
|
|
// The function return type being built up. Each time a vertex
|
|
|
|
// shader writes to a varying register, we add a new member to this
|
|
|
|
return_type: Type,
|
|
|
|
|
|
|
|
// The Naga representation of 'vec4f'
|
|
|
|
vec4f: Handle<Type>,
|
|
|
|
// The Naga representation of 'mat4x4f'
|
|
|
|
matrix4x4f: Handle<Type>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl VertexAttributeFormat {
|
|
|
|
fn to_naga_type(self, module: &mut Module) -> Handle<Type> {
|
|
|
|
if let VertexAttributeFormat::Float1 = self {
|
|
|
|
return module.types.insert(
|
|
|
|
Type {
|
|
|
|
name: None,
|
|
|
|
inner: TypeInner::Scalar {
|
|
|
|
kind: ScalarKind::Float,
|
|
|
|
width: 4,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Span::UNDEFINED,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
let (size, width, kind) = match self {
|
|
|
|
VertexAttributeFormat::Float1 => unreachable!(),
|
|
|
|
VertexAttributeFormat::Float2 => (VectorSize::Bi, 4, ScalarKind::Float),
|
|
|
|
VertexAttributeFormat::Float3 => (VectorSize::Tri, 4, ScalarKind::Float),
|
|
|
|
VertexAttributeFormat::Float4 => (VectorSize::Quad, 4, ScalarKind::Float),
|
|
|
|
VertexAttributeFormat::Bytes4 => (VectorSize::Quad, 1, ScalarKind::Uint),
|
|
|
|
};
|
|
|
|
|
|
|
|
module.types.insert(
|
|
|
|
Type {
|
|
|
|
name: None,
|
|
|
|
inner: TypeInner::Vector { size, kind, width },
|
|
|
|
},
|
|
|
|
Span::UNDEFINED,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn extend_to_float4(
|
|
|
|
&self,
|
|
|
|
base_expr: Handle<Expression>,
|
|
|
|
builder: &mut NagaBuilder,
|
|
|
|
) -> Result<Handle<Expression>> {
|
|
|
|
Ok(match self {
|
|
|
|
// This does 'vec4f(my_vec3, 1.0)'
|
|
|
|
VertexAttributeFormat::Float3 => {
|
|
|
|
let expr = base_expr;
|
|
|
|
let mut components = vec![];
|
|
|
|
for i in 0..3 {
|
|
|
|
components.push(builder.evaluate_expr(Expression::AccessIndex {
|
|
|
|
base: expr,
|
|
|
|
index: i,
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
components.push(builder.func.expressions.append(
|
|
|
|
Expression::Constant(builder.module.constants.append(
|
|
|
|
Constant {
|
|
|
|
name: None,
|
|
|
|
specialization: None,
|
|
|
|
inner: ConstantInner::Scalar {
|
|
|
|
width: 4,
|
|
|
|
value: ScalarValue::Float(1.0),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Span::UNDEFINED,
|
|
|
|
)),
|
|
|
|
Span::UNDEFINED,
|
|
|
|
));
|
|
|
|
builder.evaluate_expr(Expression::Compose {
|
|
|
|
ty: builder.vec4f,
|
|
|
|
components,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
VertexAttributeFormat::Float4 => base_expr,
|
|
|
|
_ => {
|
|
|
|
return Err(Error::Unimplemented(format!(
|
|
|
|
"Unsupported conversion from {:?} to float4",
|
|
|
|
self
|
|
|
|
)))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct ShaderConfig<'a> {
|
|
|
|
pub shader_type: ShaderType,
|
|
|
|
pub vertex_attributes: &'a [Option<VertexAttributeFormat>; 8],
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> NagaBuilder<'a> {
|
|
|
|
pub fn process_agal(
|
|
|
|
mut agal: &[u8],
|
|
|
|
vertex_attributes: &[Option<VertexAttributeFormat>; MAX_VERTEX_ATTRIBUTES],
|
|
|
|
) -> Result<Module> {
|
|
|
|
let data = &mut agal;
|
|
|
|
|
|
|
|
let mut header = [0; 7];
|
|
|
|
data.read_exact(&mut header)?;
|
|
|
|
|
|
|
|
if header[0..6] != [0xA0, 0x01, 0x00, 0x00, 0x00, 0xA1] {
|
|
|
|
return Err(Error::InvalidHeader);
|
|
|
|
}
|
|
|
|
|
|
|
|
let shader_type = match header[6] {
|
|
|
|
0x00 => ShaderType::Vertex,
|
|
|
|
0x01 => ShaderType::Fragment,
|
|
|
|
_ => return Err(Error::InvalidShaderType(header[6])),
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut builder = NagaBuilder::new(ShaderConfig {
|
|
|
|
shader_type,
|
|
|
|
vertex_attributes,
|
|
|
|
});
|
|
|
|
|
|
|
|
while !data.is_empty() {
|
|
|
|
let mut token = [0; 24];
|
|
|
|
data.read_exact(&mut token)?;
|
|
|
|
let raw_opcode = u32::from_le_bytes(token[0..4].try_into().unwrap());
|
|
|
|
|
|
|
|
// FIXME - this is a clippy false-positive
|
|
|
|
#[allow(clippy::or_fun_call)]
|
|
|
|
let opcode = Opcode::from_u32(raw_opcode).ok_or(Error::InvalidOpcode(raw_opcode))?;
|
|
|
|
|
|
|
|
let dest = DestField::parse(u32::from_le_bytes(token[4..8].try_into().unwrap()))?;
|
|
|
|
let source1 = SourceField::parse(u64::from_le_bytes(token[8..16].try_into().unwrap()))?;
|
|
|
|
|
|
|
|
let source2 = if let Opcode::Tex = opcode {
|
|
|
|
Source2::Sampler(SamplerField::parse(u64::from_le_bytes(
|
|
|
|
token[16..24].try_into().unwrap(),
|
|
|
|
))?)
|
|
|
|
} else {
|
|
|
|
Source2::SourceField(SourceField::parse(u64::from_le_bytes(
|
|
|
|
token[16..24].try_into().unwrap(),
|
|
|
|
))?)
|
|
|
|
};
|
|
|
|
|
|
|
|
builder.process_opcode(&opcode, &dest, &source1, &source2)?;
|
|
|
|
}
|
|
|
|
builder.finish()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn new(shader_config: ShaderConfig<'a>) -> Self {
|
|
|
|
let mut module = Module::default();
|
|
|
|
let mut func = Function::default();
|
|
|
|
|
|
|
|
let vec4f = VertexAttributeFormat::Float4.to_naga_type(&mut module);
|
|
|
|
|
|
|
|
let matrix4x4f = module.types.insert(
|
|
|
|
Type {
|
|
|
|
name: None,
|
|
|
|
inner: TypeInner::Matrix {
|
|
|
|
columns: VectorSize::Quad,
|
|
|
|
rows: VectorSize::Quad,
|
|
|
|
width: 4,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Span::UNDEFINED,
|
|
|
|
);
|
|
|
|
|
|
|
|
// The return type always has at least one component - the vec4f that's the 'main'
|
|
|
|
// output of our shader (the position for the vertex shader, and the color for the fragment shader)
|
|
|
|
let return_type = match shader_config.shader_type {
|
|
|
|
ShaderType::Vertex => Type {
|
|
|
|
name: None,
|
|
|
|
inner: TypeInner::Struct {
|
|
|
|
members: vec![StructMember {
|
|
|
|
name: None,
|
|
|
|
ty: vec4f,
|
|
|
|
binding: Some(Binding::BuiltIn(BuiltIn::Position { invariant: false })),
|
|
|
|
offset: 0,
|
|
|
|
}],
|
|
|
|
span: 16,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ShaderType::Fragment => Type {
|
|
|
|
name: None,
|
|
|
|
inner: TypeInner::Struct {
|
|
|
|
members: vec![StructMember {
|
|
|
|
name: None,
|
|
|
|
ty: vec4f,
|
|
|
|
binding: Some(Binding::Location {
|
|
|
|
location: 0,
|
|
|
|
interpolation: None,
|
|
|
|
sampling: None,
|
|
|
|
}),
|
|
|
|
offset: 0,
|
|
|
|
}],
|
|
|
|
span: 16,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
match shader_config.shader_type {
|
|
|
|
ShaderType::Vertex => {
|
|
|
|
func.result = Some(FunctionResult {
|
|
|
|
ty: vec4f,
|
|
|
|
binding: Some(Binding::BuiltIn(BuiltIn::Position { invariant: false })),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
ShaderType::Fragment => {
|
|
|
|
func.result = Some(FunctionResult {
|
|
|
|
ty: vec4f,
|
|
|
|
binding: Some(Binding::Location {
|
|
|
|
location: 0,
|
|
|
|
interpolation: None,
|
|
|
|
sampling: None,
|
|
|
|
}),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Holds the value we're going to return.
|
|
|
|
// This corresponds to RegisterType::Output
|
|
|
|
let output_temp_handle = func.local_variables.append(
|
|
|
|
LocalVariable {
|
|
|
|
name: Some("dest_temp".to_string()),
|
|
|
|
ty: vec4f,
|
|
|
|
init: None,
|
|
|
|
},
|
|
|
|
Span::UNDEFINED,
|
|
|
|
);
|
|
|
|
let dest = func.expressions.append(
|
|
|
|
Expression::LocalVariable(output_temp_handle),
|
|
|
|
Span::UNDEFINED,
|
|
|
|
);
|
|
|
|
|
|
|
|
let num_const_registers = module.constants.append(
|
|
|
|
Constant {
|
|
|
|
name: None,
|
|
|
|
specialization: None,
|
|
|
|
inner: ConstantInner::Scalar {
|
|
|
|
width: 4,
|
|
|
|
value: ScalarValue::Uint(match shader_config.shader_type {
|
|
|
|
ShaderType::Vertex => VERTEX_PROGRAM_CONTANTS,
|
|
|
|
ShaderType::Fragment => FRAGMENT_PROGRAM_CONSTANTS,
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Span::UNDEFINED,
|
|
|
|
);
|
|
|
|
|
|
|
|
let binding_num = match shader_config.shader_type {
|
|
|
|
ShaderType::Vertex => 0,
|
|
|
|
ShaderType::Fragment => 1,
|
|
|
|
};
|
|
|
|
|
|
|
|
let constant_registers_global = module.global_variables.append(
|
|
|
|
GlobalVariable {
|
|
|
|
name: Some("constant_registers".to_string()),
|
|
|
|
space: naga::AddressSpace::Uniform,
|
|
|
|
binding: Some(naga::ResourceBinding {
|
|
|
|
group: 0,
|
|
|
|
binding: binding_num,
|
|
|
|
}),
|
|
|
|
ty: module.types.insert(
|
|
|
|
Type {
|
|
|
|
name: None,
|
|
|
|
inner: TypeInner::Array {
|
|
|
|
base: vec4f,
|
|
|
|
size: ArraySize::Constant(num_const_registers),
|
|
|
|
stride: std::mem::size_of::<f32>() as u32 * 4,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Span::UNDEFINED,
|
|
|
|
),
|
|
|
|
init: None,
|
|
|
|
},
|
|
|
|
Span::UNDEFINED,
|
|
|
|
);
|
|
|
|
|
|
|
|
let constant_registers = func.expressions.append(
|
|
|
|
Expression::GlobalVariable(constant_registers_global),
|
|
|
|
Span::UNDEFINED,
|
|
|
|
);
|
|
|
|
|
|
|
|
NagaBuilder {
|
|
|
|
module,
|
|
|
|
func,
|
|
|
|
dest,
|
|
|
|
shader_config,
|
|
|
|
argument_expressions: vec![],
|
|
|
|
varying_pointers: vec![],
|
|
|
|
return_type,
|
|
|
|
matrix4x4f,
|
|
|
|
vec4f,
|
|
|
|
constant_registers,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_vertex_input(&mut self, index: usize) -> Result<Handle<Expression>> {
|
|
|
|
if index >= self.argument_expressions.len() {
|
|
|
|
self.argument_expressions.resize(index + 1, None);
|
|
|
|
|
|
|
|
// FIXME - this is a clippy false-positive
|
|
|
|
#[allow(clippy::or_fun_call)]
|
|
|
|
let ty = self.shader_config.vertex_attributes[index]
|
|
|
|
.as_ref()
|
|
|
|
.ok_or(Error::MissingVertexAttributeData(index))?
|
|
|
|
.to_naga_type(&mut self.module);
|
|
|
|
|
|
|
|
self.func.arguments.push(FunctionArgument {
|
|
|
|
name: None,
|
|
|
|
ty,
|
|
|
|
binding: Some(Binding::Location {
|
|
|
|
location: index as u32,
|
|
|
|
interpolation: None,
|
|
|
|
sampling: None,
|
|
|
|
}),
|
|
|
|
});
|
|
|
|
|
|
|
|
// Arguments map one-to-one to vertex attributes.
|
|
|
|
let expr = self
|
|
|
|
.func
|
|
|
|
.expressions
|
|
|
|
.append(Expression::FunctionArgument(index as u32), Span::UNDEFINED);
|
|
|
|
self.argument_expressions[index] = Some(expr);
|
|
|
|
}
|
|
|
|
Ok(self.argument_expressions[index].unwrap())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_varying_pointer(&mut self, index: usize) -> Result<Handle<Expression>> {
|
|
|
|
if index >= self.varying_pointers.len() {
|
|
|
|
self.varying_pointers.resize(index + 1, None);
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.varying_pointers[index].is_none() {
|
|
|
|
match self.shader_config.shader_type {
|
|
|
|
ShaderType::Vertex => {
|
|
|
|
// We can write to varying variables in the vertex shader,
|
|
|
|
// and the fragment shader will receive them is input.
|
|
|
|
// Therefore, we create a local variable for each varying,
|
|
|
|
// and return them at the end of the function.
|
|
|
|
let local = self.func.local_variables.append(
|
|
|
|
LocalVariable {
|
2022-10-26 23:46:09 +00:00
|
|
|
name: Some(format!("varying_{index}")),
|
render: Add 'naga-agal' crate to compile AGAL shaders to Naga
This is the first part of the Stage3D implementation, and can
be reviewed independently.
Stage3D shaders use the Adobe Graphics Assembly Language (AGAL),
which is a binary shader format. It supports vertex attributes,
varying registers, program constants (uniforms), and texture sampling.
This PR only implements a few parts of AGAL:
* The 'mov' and 'm44' opcodes
* Vertex attributes, varying registers, program constants, and 'output'
registers (position or color, depending on shader type)
This is sufficient to get a non-trivial Stage3D program
running (the rotating cube demo from the Adobe docs).
The output of `naga-agal` is a `naga::Module`. This can be passed
directly to wgpu, or compiled into a shader language using
a Naga backend (glsl, wgsl, SPIR-V, etc). The test suite
output WGSL files, and uses the 'insta' crate to compare against
saved files on disk.
Currently, the only real way to write AGAL bytecode is using
the Adobe-provided 'AGALMiniAssembler' flash class.
This class assembles the textual reprentation of AGAL into
the binary format.
To make writing tests easier, I've added a 'agal_compiler' test, which
can easily be modified to add more Agal textual assembly.
2022-09-23 00:44:49 +00:00
|
|
|
ty: self.vec4f,
|
|
|
|
init: None,
|
|
|
|
},
|
|
|
|
Span::UNDEFINED,
|
|
|
|
);
|
|
|
|
|
|
|
|
let expr = self
|
|
|
|
.func
|
|
|
|
.expressions
|
|
|
|
.append(Expression::LocalVariable(local), Span::UNDEFINED);
|
|
|
|
let _range = self
|
|
|
|
.func
|
|
|
|
.expressions
|
|
|
|
.range_from(self.func.expressions.len() - 1);
|
|
|
|
|
|
|
|
if let TypeInner::Struct { members, .. } = &mut self.return_type.inner {
|
|
|
|
members.push(StructMember {
|
2022-10-26 23:46:09 +00:00
|
|
|
name: Some(format!("varying_{index}")),
|
render: Add 'naga-agal' crate to compile AGAL shaders to Naga
This is the first part of the Stage3D implementation, and can
be reviewed independently.
Stage3D shaders use the Adobe Graphics Assembly Language (AGAL),
which is a binary shader format. It supports vertex attributes,
varying registers, program constants (uniforms), and texture sampling.
This PR only implements a few parts of AGAL:
* The 'mov' and 'm44' opcodes
* Vertex attributes, varying registers, program constants, and 'output'
registers (position or color, depending on shader type)
This is sufficient to get a non-trivial Stage3D program
running (the rotating cube demo from the Adobe docs).
The output of `naga-agal` is a `naga::Module`. This can be passed
directly to wgpu, or compiled into a shader language using
a Naga backend (glsl, wgsl, SPIR-V, etc). The test suite
output WGSL files, and uses the 'insta' crate to compare against
saved files on disk.
Currently, the only real way to write AGAL bytecode is using
the Adobe-provided 'AGALMiniAssembler' flash class.
This class assembles the textual reprentation of AGAL into
the binary format.
To make writing tests easier, I've added a 'agal_compiler' test, which
can easily be modified to add more Agal textual assembly.
2022-09-23 00:44:49 +00:00
|
|
|
ty: self.vec4f,
|
|
|
|
binding: Some(Binding::Location {
|
|
|
|
location: index as u32,
|
|
|
|
interpolation: Some(naga::Interpolation::Perspective),
|
|
|
|
sampling: None,
|
|
|
|
}),
|
|
|
|
offset: 0,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
unreachable!();
|
|
|
|
}
|
|
|
|
|
|
|
|
self.varying_pointers[index] = Some(expr);
|
|
|
|
}
|
|
|
|
ShaderType::Fragment => {
|
|
|
|
self.func.arguments.push(FunctionArgument {
|
|
|
|
name: None,
|
|
|
|
ty: self.vec4f,
|
|
|
|
binding: Some(Binding::Location {
|
|
|
|
location: index as u32,
|
|
|
|
interpolation: Some(Interpolation::Perspective),
|
|
|
|
sampling: None,
|
|
|
|
}),
|
|
|
|
});
|
|
|
|
|
|
|
|
let expr = self
|
|
|
|
.func
|
|
|
|
.expressions
|
|
|
|
.append(Expression::FunctionArgument(index as u32), Span::UNDEFINED);
|
|
|
|
self.varying_pointers[index] = Some(expr);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(self.varying_pointers[index].unwrap())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn emit_const_register_load(&mut self, index: usize) -> Result<Handle<Expression>> {
|
|
|
|
let index_const = self.module.constants.append(
|
|
|
|
Constant {
|
|
|
|
name: None,
|
|
|
|
specialization: None,
|
|
|
|
inner: ConstantInner::Scalar {
|
|
|
|
width: 4,
|
|
|
|
value: ScalarValue::Uint(index as u64),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Span::UNDEFINED,
|
|
|
|
);
|
|
|
|
let index_expr = self
|
|
|
|
.func
|
|
|
|
.expressions
|
|
|
|
.append(Expression::Constant(index_const), Span::UNDEFINED);
|
|
|
|
|
|
|
|
let register_pointer = self.func.expressions.append(
|
|
|
|
Expression::Access {
|
|
|
|
base: self.constant_registers,
|
|
|
|
index: index_expr,
|
|
|
|
},
|
|
|
|
Span::UNDEFINED,
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok(self.evaluate_expr(Expression::Load {
|
|
|
|
pointer: register_pointer,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn emit_varying_load(&mut self, index: usize) -> Result<Handle<Expression>> {
|
|
|
|
// A LocalVariable evaluates to a pointer, so we need to load it
|
|
|
|
let varying_expr = self.get_varying_pointer(index)?;
|
|
|
|
Ok(match self.shader_config.shader_type {
|
|
|
|
ShaderType::Vertex => self.evaluate_expr(Expression::Load {
|
|
|
|
pointer: varying_expr,
|
|
|
|
}),
|
|
|
|
ShaderType::Fragment => varying_expr,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn emit_source_field_load(
|
|
|
|
&mut self,
|
|
|
|
source: &SourceField,
|
|
|
|
extend_to_vec4: bool,
|
|
|
|
) -> Result<Handle<Expression>> {
|
|
|
|
let (mut base_expr, source_type) = match source.register_type {
|
|
|
|
// We can use a function argument directly - we don't need
|
|
|
|
// a separate Expression::Load
|
|
|
|
RegisterType::Attribute => (
|
|
|
|
self.get_vertex_input(source.reg_num as usize)?,
|
|
|
|
// FIXME - this is a clippy false-positive
|
|
|
|
#[allow(clippy::or_fun_call)]
|
|
|
|
self.shader_config.vertex_attributes[source.reg_num as usize]
|
|
|
|
.ok_or(Error::MissingVertexAttributeData(source.reg_num as usize))?,
|
|
|
|
),
|
|
|
|
RegisterType::Varying => (
|
|
|
|
self.emit_varying_load(source.reg_num as usize)?,
|
|
|
|
VertexAttributeFormat::Float4,
|
|
|
|
),
|
|
|
|
RegisterType::Constant => (
|
|
|
|
self.emit_const_register_load(source.reg_num as usize)?,
|
|
|
|
// Constants are always a vec4<f32>
|
|
|
|
VertexAttributeFormat::Float4,
|
|
|
|
),
|
|
|
|
_ => {
|
|
|
|
return Err(Error::Unimplemented(format!(
|
|
|
|
"Unimplemented source reg type {:?}",
|
|
|
|
source.register_type
|
|
|
|
)))
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if matches!(source.direct_mode, DirectMode::Indirect) {
|
|
|
|
return Err(Error::Unimplemented(
|
|
|
|
"Indirect addressing not implemented".to_string(),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
if extend_to_vec4 && source_type != VertexAttributeFormat::Float4 {
|
|
|
|
base_expr = source_type.extend_to_float4(base_expr, self)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Swizzle is 'xyzw', which is a no-op. Just return the base expression.
|
|
|
|
if source.swizzle == 0b11100100 {
|
|
|
|
return Ok(base_expr);
|
|
|
|
}
|
|
|
|
|
|
|
|
let swizzle_flags = [
|
|
|
|
source.swizzle & 0b11,
|
|
|
|
(source.swizzle >> 2) & 0b11,
|
|
|
|
(source.swizzle >> 4) & 0b11,
|
|
|
|
(source.swizzle >> 6) & 0b11,
|
|
|
|
];
|
|
|
|
|
|
|
|
let swizzle_components: [SwizzleComponent; 4] = swizzle_flags
|
|
|
|
.into_iter()
|
|
|
|
.map(|flag| match flag {
|
|
|
|
0b00 => SwizzleComponent::X,
|
|
|
|
0b01 => SwizzleComponent::Y,
|
|
|
|
0b10 => SwizzleComponent::Z,
|
|
|
|
0b11 => SwizzleComponent::W,
|
|
|
|
_ => unreachable!(),
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.try_into()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
Ok(self.func.expressions.append(
|
|
|
|
Expression::Swizzle {
|
|
|
|
size: VectorSize::Quad,
|
|
|
|
vector: base_expr,
|
|
|
|
pattern: swizzle_components,
|
|
|
|
},
|
|
|
|
Span::UNDEFINED,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn emit_dest_store(&mut self, dest: &DestField, expr: Handle<Expression>) -> Result<()> {
|
|
|
|
let base_expr = match dest.register_type {
|
|
|
|
RegisterType::Output => self.dest,
|
|
|
|
RegisterType::Varying => self.get_varying_pointer(dest.reg_num as usize)?,
|
|
|
|
_ => {
|
|
|
|
return Err(Error::Unimplemented(format!(
|
|
|
|
"Unimplemented dest reg type: {:?}",
|
|
|
|
dest
|
|
|
|
)))
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Optimization - use a Store instead of writing individual fields
|
|
|
|
// when we're writing to the entire output register.
|
|
|
|
if dest.write_mask.is_all() {
|
|
|
|
let store = Statement::Store {
|
|
|
|
pointer: base_expr,
|
|
|
|
value: expr,
|
|
|
|
};
|
|
|
|
self.func.body.push(store, Span::UNDEFINED);
|
|
|
|
} else {
|
|
|
|
for (i, mask) in [(0, Mask::X), (1, Mask::Y), (2, Mask::Z), (3, Mask::W)] {
|
|
|
|
if dest.write_mask.contains(mask) {
|
|
|
|
self.func.body.push(
|
|
|
|
Statement::Store {
|
|
|
|
pointer: self.func.expressions.append(
|
|
|
|
Expression::AccessIndex {
|
|
|
|
base: base_expr,
|
|
|
|
index: i,
|
|
|
|
},
|
|
|
|
Span::UNDEFINED,
|
|
|
|
),
|
|
|
|
value: expr,
|
|
|
|
},
|
|
|
|
Span::UNDEFINED,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a `Statement::Emit` covering `expr`
|
|
|
|
fn evaluate_expr(&mut self, expr: Expression) -> Handle<Expression> {
|
|
|
|
let prev_len = self.func.expressions.len();
|
|
|
|
let expr = self.func.expressions.append(expr, Span::UNDEFINED);
|
|
|
|
let range = self.func.expressions.range_from(prev_len);
|
|
|
|
self.func.body.push(Statement::Emit(range), Span::UNDEFINED);
|
|
|
|
expr
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_opcode(
|
|
|
|
&mut self,
|
|
|
|
opcode: &Opcode,
|
|
|
|
dest: &DestField,
|
|
|
|
source1: &SourceField,
|
|
|
|
source2: &Source2,
|
|
|
|
) -> Result<()> {
|
|
|
|
match opcode {
|
|
|
|
// Copy the source register to the destination register
|
|
|
|
Opcode::Mov => {
|
|
|
|
// On the ActionScript side, the user might have specified something *other* than
|
|
|
|
// vec4f. In that case, we need to extend the source to a vec4f if we're writing to
|
|
|
|
// a vec4f register.
|
|
|
|
// FIXME - do we need to do this extension in other cases?
|
|
|
|
let do_extend = matches!(
|
|
|
|
dest.register_type,
|
|
|
|
RegisterType::Output | RegisterType::Varying
|
|
|
|
);
|
|
|
|
let source = self.emit_source_field_load(source1, do_extend)?;
|
|
|
|
self.emit_dest_store(dest, source)?;
|
|
|
|
}
|
|
|
|
// Perform 'M * v', where M is a 4x4 matrix, and 'v' is a column vector.
|
|
|
|
Opcode::M44 => {
|
|
|
|
let source2 = match source2 {
|
|
|
|
Source2::SourceField(source2) => source2,
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Read each row of the matrix
|
|
|
|
let source2_row0 = self.emit_source_field_load(source2, false)?;
|
|
|
|
let source2_row1 = self.emit_source_field_load(
|
|
|
|
&SourceField {
|
|
|
|
reg_num: source2.reg_num + 1,
|
|
|
|
..source2.clone()
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
)?;
|
|
|
|
let source2_row2 = self.emit_source_field_load(
|
|
|
|
&SourceField {
|
|
|
|
reg_num: source2.reg_num + 2,
|
|
|
|
..source2.clone()
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
)?;
|
|
|
|
let source2_row3 = self.emit_source_field_load(
|
|
|
|
&SourceField {
|
|
|
|
reg_num: source2.reg_num + 3,
|
|
|
|
..source2.clone()
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
// FIXME - The naga spv backend hits an 'unreachable!'
|
|
|
|
// if we don't create a Statement::Emit for each of these,
|
|
|
|
// even though validation passes. We should investigate this
|
|
|
|
// and report it upstream.
|
|
|
|
let matrix = self.evaluate_expr(Expression::Compose {
|
|
|
|
ty: self.matrix4x4f,
|
|
|
|
components: vec![source2_row0, source2_row1, source2_row2, source2_row3],
|
|
|
|
});
|
|
|
|
|
|
|
|
// Naga interprets each component of the matrix as a *column*.
|
|
|
|
// However, the matrix is stored in memory as a *row*, so we need
|
|
|
|
// to transpose it.
|
|
|
|
let matrix = self.evaluate_expr(Expression::Math {
|
|
|
|
fun: MathFunction::Transpose,
|
|
|
|
arg: matrix,
|
|
|
|
arg1: None,
|
|
|
|
arg2: None,
|
|
|
|
arg3: None,
|
|
|
|
});
|
|
|
|
|
|
|
|
let vector = self.emit_source_field_load(source1, true)?;
|
|
|
|
|
|
|
|
let multiply = self.evaluate_expr(Expression::Binary {
|
|
|
|
op: BinaryOperator::Multiply,
|
|
|
|
left: matrix,
|
|
|
|
right: vector,
|
|
|
|
});
|
|
|
|
|
|
|
|
self.emit_dest_store(dest, multiply)?;
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
return Err(Error::Unimplemented(format!(
|
|
|
|
"Unimplemented opcode: {:?}",
|
|
|
|
opcode
|
|
|
|
)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn finish(mut self) -> Result<Module> {
|
|
|
|
// Load the 'main' output (a position or color) from our temporary location.
|
|
|
|
let dest_load = self.evaluate_expr(Expression::Load { pointer: self.dest });
|
|
|
|
let mut components = vec![dest_load];
|
|
|
|
|
|
|
|
// If the vertex shader wrote to any varying registers, we need to
|
|
|
|
// return them as well.
|
|
|
|
if let ShaderType::Vertex = self.shader_config.shader_type {
|
|
|
|
for i in 0..self.varying_pointers.len() {
|
|
|
|
if self.varying_pointers[i].is_some() {
|
|
|
|
components.push(self.emit_varying_load(i)?);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We're consuming 'self', so just store store garbage here so that we can continue
|
|
|
|
// to use methods on 'self'
|
|
|
|
let return_ty = std::mem::replace(
|
|
|
|
&mut self.return_type,
|
|
|
|
Type {
|
|
|
|
name: None,
|
|
|
|
inner: TypeInner::Scalar {
|
|
|
|
kind: ScalarKind::Float,
|
|
|
|
width: 0,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
// Finalize the return type, and do emit the actual return
|
|
|
|
let return_ty = self.module.types.insert(return_ty, Span::UNDEFINED);
|
|
|
|
self.func.result = Some(FunctionResult {
|
|
|
|
ty: return_ty,
|
|
|
|
binding: None,
|
|
|
|
});
|
|
|
|
|
|
|
|
let return_expr = self.evaluate_expr(Expression::Compose {
|
|
|
|
ty: return_ty,
|
|
|
|
components,
|
|
|
|
});
|
|
|
|
|
|
|
|
self.func.body.push(
|
|
|
|
Statement::Return {
|
|
|
|
value: Some(return_expr),
|
|
|
|
},
|
|
|
|
Span::UNDEFINED,
|
|
|
|
);
|
|
|
|
|
|
|
|
let entry_point = EntryPoint {
|
|
|
|
name: ENTRY_POINT.to_string(),
|
|
|
|
stage: match self.shader_config.shader_type {
|
|
|
|
ShaderType::Vertex => ShaderStage::Vertex,
|
|
|
|
ShaderType::Fragment => ShaderStage::Fragment,
|
|
|
|
},
|
|
|
|
early_depth_test: None,
|
|
|
|
workgroup_size: [0; 3],
|
|
|
|
function: self.func,
|
|
|
|
};
|
|
|
|
|
|
|
|
self.module.entry_points.push(entry_point);
|
|
|
|
Ok(self.module)
|
|
|
|
}
|
|
|
|
}
|