vello/piet-gpu-derive/src/glsl.rs
Raph Levien b423e6c25d Warning cleanup
This patch gets rid of warnings and runs cargo fmt.

A lot of the warnings were unused items (especially in DX12 land). At
some point we might want to bring some of that back, at which point it
might be useful to refer to what was deleted in this commit.
2021-10-23 09:21:19 -07:00

670 lines
22 KiB
Rust

//! Generation of GLSL struct definitions and accessor functions.
use std::fmt::Write;
use std::ops::Deref;
use crate::layout::{LayoutModule, LayoutType, LayoutTypeDef};
use crate::parse::{GpuScalar, GpuType};
pub fn gen_glsl(module: &LayoutModule) -> String {
let mut r = String::new();
writeln!(
&mut r,
"// SPDX-License-Identifier: Apache-2.0 OR MIT OR Unlicense\n"
)
.unwrap();
writeln!(&mut r, "// Code auto-generated by piet-gpu-derive\n").unwrap();
// Note: GLSL needs definitions before uses. We could do a topological sort here,
// but easiest for now to just require that in spec.
for name in &module.def_names {
gen_refdef(&mut r, &name);
}
for name in &module.def_names {
match module.defs.get(name).unwrap() {
(size, LayoutTypeDef::Struct(fields)) => {
gen_struct_def(&mut r, name, fields);
gen_item_def(&mut r, name, size.size);
}
(size, LayoutTypeDef::Enum(en)) => {
gen_enum_def(&mut r, name, en);
gen_item_def(&mut r, name, size.size);
gen_tag_def(&mut r, name);
}
}
}
for name in &module.def_names {
let def = module.defs.get(name).unwrap();
let is_mem = !module.name.eq(&"state") && !module.name.eq(&"scene");
match def {
(_size, LayoutTypeDef::Struct(fields)) => {
gen_struct_read(&mut r, &module.name, &name, is_mem, fields);
if module.gpu_write {
gen_struct_write(&mut r, &module.name, &name, is_mem, fields);
}
}
(_size, LayoutTypeDef::Enum(en)) => {
gen_enum_read(&mut r, &module.name, &name, is_mem, en);
if module.gpu_write {
gen_enum_write(&mut r, &module.name, &name, is_mem, en);
}
}
}
}
r
}
fn gen_refdef(r: &mut String, name: &str) {
writeln!(r, "struct {}Ref {{", name).unwrap();
writeln!(r, " uint offset;").unwrap();
writeln!(r, "}};\n").unwrap();
}
fn gen_struct_def(r: &mut String, name: &str, fields: &[(String, usize, LayoutType)]) {
writeln!(r, "struct {} {{", name).unwrap();
for (name, _offset, ty) in fields {
writeln!(r, " {} {};", glsl_type(&ty.ty), name).unwrap();
}
writeln!(r, "}};\n").unwrap();
}
fn gen_enum_def(r: &mut String, name: &str, variants: &[(String, Vec<(usize, LayoutType)>)]) {
for (i, (var_name, _payload)) in variants.iter().enumerate() {
writeln!(r, "#define {}_{} {}", name, var_name, i).unwrap();
}
}
fn gen_item_def(r: &mut String, name: &str, size: usize) {
writeln!(r, "#define {}_size {}\n", name, size).unwrap();
writeln!(
r,
"{}Ref {}_index({}Ref ref, uint index) {{",
name, name, name
)
.unwrap();
writeln!(
r,
" return {}Ref(ref.offset + index * {}_size);",
name, name
)
.unwrap();
writeln!(r, "}}\n").unwrap();
}
fn gen_tag_def(r: &mut String, name: &str) {
writeln!(r, "struct {}Tag {{", name).unwrap();
writeln!(r, " uint tag;").unwrap();
writeln!(r, " uint flags;").unwrap();
writeln!(r, "}};\n").unwrap();
}
fn gen_struct_read(
r: &mut String,
bufname: &str,
name: &str,
is_mem: bool,
fields: &[(String, usize, LayoutType)],
) {
write!(r, "{} {}_read(", name, name).unwrap();
if is_mem {
write!(r, "Alloc a, ").unwrap();
}
writeln!(r, "{}Ref ref) {{", name).unwrap();
writeln!(r, " uint ix = ref.offset >> 2;").unwrap();
let coverage = crate::layout::struct_coverage(fields, false);
for (i, fields) in coverage.iter().enumerate() {
if !fields.is_empty() {
if is_mem {
writeln!(r, " uint raw{} = read_mem(a, ix + {});", i, i).unwrap();
} else {
writeln!(r, " uint raw{} = {}[ix + {}];", i, bufname, i).unwrap();
}
}
}
writeln!(r, " {} s;", name).unwrap();
let mut preload: bool = false;
for (name, offset, ty) in fields {
let (setup, extract) = gen_extract(*offset, &ty.ty, preload);
writeln!(r, "{} s.{} = {};", setup, name, extract).unwrap();
if let GpuType::Scalar(GpuScalar::F16) = &ty.ty {
if offset % 4 == 0 {
preload = true;
continue;
}
}
preload = false;
}
writeln!(r, " return s;").unwrap();
writeln!(r, "}}\n").unwrap();
}
fn gen_enum_read(
r: &mut String,
bufname: &str,
name: &str,
is_mem: bool,
variants: &[(String, Vec<(usize, LayoutType)>)],
) {
if is_mem {
writeln!(r, "{}Tag {}_tag(Alloc a, {}Ref ref) {{", name, name, name).unwrap();
writeln!(r, " uint tag_and_flags = read_mem(a, ref.offset >> 2);").unwrap();
} else {
writeln!(r, "{}Tag {}_tag({}Ref ref) {{", name, name, name).unwrap();
writeln!(r, " uint tag_and_flags = {}[ref.offset >> 2];", bufname).unwrap();
}
writeln!(
r,
" return {}Tag(tag_and_flags & 0xffff, tag_and_flags >> 16);",
name
)
.unwrap();
writeln!(r, "}}\n").unwrap();
for (var_name, payload) in variants {
let payload_ix = if payload.len() == 1 {
Some(0)
} else if payload.len() == 2 {
if matches!(payload[0].1.ty, GpuType::Scalar(GpuScalar::TagFlags)) {
Some(1)
} else {
None
}
} else {
None
};
if let Some(payload_ix) = payload_ix {
if let GpuType::InlineStruct(structname) = &payload[payload_ix].1.ty {
if is_mem {
writeln!(
r,
"{} {}_{}_read(Alloc a, {}Ref ref) {{",
structname, name, var_name, name
)
.unwrap();
writeln!(
r,
" return {}_read(a, {}Ref(ref.offset + {}));",
structname, structname, payload[0].0
)
.unwrap();
} else {
writeln!(
r,
"{} {}_{}_read({}Ref ref) {{",
structname, name, var_name, name
)
.unwrap();
writeln!(
r,
" return {}_read({}Ref(ref.offset + {}));",
structname, structname, payload[0].0
)
.unwrap();
}
writeln!(r, "}}\n").unwrap();
}
}
// TODO: support for variants that aren't one struct.
}
}
fn gen_extract(offset: usize, ty: &GpuType, preload: bool) -> (String, String) {
match ty {
GpuType::Scalar(scalar) => {
let setup = match scalar {
GpuScalar::F16 => {
if preload {
String::new()
} else {
let ix = offset / 4;
format!(" vec2 halves{} = unpackHalf2x16(raw{});\n", ix, ix)
}
}
_ => String::new(),
};
(setup, gen_extract_scalar(offset, scalar))
}
GpuType::Vector(scalar, size) => {
let is_f16 = match scalar {
GpuScalar::F16 => true,
_ => false,
};
let mut setup = String::new();
let mut extract = glsl_type(ty);
extract.push_str("(");
for i in 0..*size {
if i != 0 {
extract.push_str(", ");
}
if is_f16 && i % 2 == 0 {
let ix = (offset + i * scalar.size()) / 4;
let s = format!(" vec2 halves{} = unpackHalf2x16(raw{});\n", ix, ix);
setup.push_str(&s);
};
let el_offset = offset + i * scalar.size();
extract.push_str(&gen_extract_scalar(el_offset, scalar));
}
extract.push_str(")");
(setup, extract)
}
GpuType::InlineStruct(name) => (
String::new(),
format!(
"{}_read({}Ref({}))",
name,
name,
simplified_add("ref.offset", offset)
),
),
GpuType::Ref(inner) => {
if let GpuType::InlineStruct(name) = inner.deref() {
(
String::new(),
format!(
"{}Ref({})",
name,
gen_extract_scalar(offset, &GpuScalar::U32)
),
)
} else {
panic!("only know how to deal with Ref of struct")
}
}
}
}
fn gen_extract_scalar(offset: usize, ty: &GpuScalar) -> String {
match ty {
GpuScalar::F16 | GpuScalar::F32 => extract_fbits(offset, ty.size()),
GpuScalar::U8 | GpuScalar::U16 | GpuScalar::U32 => extract_ubits(offset, ty.size()),
GpuScalar::I8 | GpuScalar::I16 | GpuScalar::I32 => extract_ibits(offset, ty.size()),
GpuScalar::TagFlags => format!("0 /* TODO */"),
}
}
fn extract_ubits(offset: usize, nbytes: usize) -> String {
if nbytes == 4 {
return format!("raw{}", offset / 4);
}
let mask = (1 << (nbytes * 8)) - 1;
if offset % 4 == 0 {
format!("raw{} & 0x{:x}", offset / 4, mask)
} else if offset % 4 + nbytes == 4 {
format!("raw{} >> {}", offset / 4, (offset % 4) * 8)
} else {
format!("(raw{} >> {}) & 0x{:x}", offset / 4, (offset % 4) * 8, mask)
}
}
fn extract_ibits(offset: usize, nbytes: usize) -> String {
if nbytes == 4 {
return format!("int(raw{})", offset / 4);
}
if offset % 4 + nbytes == 4 {
format!("int(raw{}) >> {}", offset / 4, (offset % 4) * 8)
} else {
format!(
"int(raw{} << {}) >> {}",
offset / 4,
((4 - nbytes) - offset % 4) * 8,
(4 - nbytes) * 8
)
}
}
fn extract_fbits(offset: usize, nbytes: usize) -> String {
match nbytes {
4 => format!("uintBitsToFloat(raw{})", offset / 4),
2 => match offset % 4 {
0 => {
let ix = offset / 4;
format!("halves{}.x", ix)
}
2 => format!("halves{}.y", offset / 4),
_ => panic!("unexpected packing of f16 at offset {}", offset % 4),
},
_ => {
panic!("unexpected extraction of float with nbytes = {}", nbytes);
}
}
}
// Writing
fn is_f16(ty: &GpuType) -> bool {
match ty {
GpuType::Scalar(GpuScalar::F16) => true,
GpuType::Vector(GpuScalar::F16, _) => true,
_ => false,
}
}
fn is_f16_pair(field_ixs: &[usize], fields: &[(String, usize, LayoutType)]) -> bool {
if field_ixs.len() == 2 {
fields.iter().all(|(_, _, t)| is_f16(&t.ty))
} else {
false
}
}
fn gen_struct_write(
r: &mut String,
bufname: &str,
name: &str,
is_mem: bool,
fields: &[(String, usize, LayoutType)],
) {
write!(r, "void {}_write(", name).unwrap();
if is_mem {
write!(r, "Alloc a, ").unwrap();
}
writeln!(r, "{}Ref ref, {} s) {{", name, name).unwrap();
writeln!(r, " uint ix = ref.offset >> 2;").unwrap();
let coverage = crate::layout::struct_coverage(fields, true);
for (i, field_ixs) in coverage.iter().enumerate() {
let mut pieces = Vec::new();
if is_f16_pair(field_ixs, fields) {
let (ix0, ix1) = (field_ixs[0], field_ixs[1]);
let inner0 = format!("s.{}", fields[ix0].0);
let inner1 = format!("s.{}", fields[ix1].0);
pieces.push(format!("packHalf2x16(vec2({}, {}))", &inner0, &inner1));
} else {
for field_ix in field_ixs {
let (name, offset, ty) = &fields[*field_ix];
match &ty.ty {
GpuType::Scalar(scalar) => {
let inner = format!("s.{}", name);
pieces.push(gen_pack_bits_scalar(scalar, *offset, &inner));
}
GpuType::Vector(scalar, len) => {
let size = scalar.size();
let ix_lo = (i * 4 - offset) / size;
let ix_hi = ((4 + i * 4 - offset) / size).min(*len);
match scalar {
GpuScalar::F16 => {
if ix_hi - ix_lo == 2 {
let inner0 =
format!("s.{}.{}", name, &"xyzw"[ix_lo..ix_lo + 1]);
let inner1 =
format!("s.{}.{}", name, &"xyzw"[ix_lo + 1..ix_hi]);
pieces.push(format!(
"packHalf2x16(vec2({}, {}))",
&inner0, &inner1
));
} else {
let ix = ix_lo;
let scalar_offset = offset + ix * size;
let inner = format!("s.{}.{}", name, &"xyzw"[ix..ix + 1]);
pieces.push(gen_pack_bits_scalar(
scalar,
scalar_offset,
&inner,
));
}
}
_ => {
for ix in ix_lo..ix_hi {
let scalar_offset = offset + ix * size;
let inner = format!("s.{}.{}", name, &"xyzw"[ix..ix + 1]);
pieces.push(gen_pack_bits_scalar(
scalar,
scalar_offset,
&inner,
));
}
}
}
}
GpuType::InlineStruct(structname) => {
writeln!(
r,
" {}_write({}Ref({}), s.{});",
structname,
structname,
simplified_add("ref.offset", *offset),
name
)
.unwrap();
}
GpuType::Ref(_) => pieces.push(format!("s.{}.offset", name)),
}
}
}
if !pieces.is_empty() {
if is_mem {
write!(r, " write_mem(a, ix + {}, ", i).unwrap();
} else {
write!(r, " {}[ix + {}] = ", bufname, i).unwrap();
}
for (j, piece) in pieces.iter().enumerate() {
if j != 0 {
write!(r, " | ").unwrap();
}
write!(r, "{}", piece).unwrap();
}
if is_mem {
write!(r, ")").unwrap();
}
writeln!(r, ";").unwrap();
}
}
writeln!(r, "}}\n").unwrap();
}
fn gen_pack_bits_scalar(ty: &GpuScalar, offset: usize, inner: &str) -> String {
let shift = (offset % 4) * 8;
let bits = match ty {
GpuScalar::F16 => format!("packHalf2x16(vec2({}, 0.0)) & 0xffff", inner),
GpuScalar::F32 => format!("floatBitsToUint({})", inner),
// Note: this doesn't mask small unsigned int types; the caller is
// responsible for making sure they don't overflow.
GpuScalar::U8 | GpuScalar::U16 | GpuScalar::U32 => inner.into(),
GpuScalar::I8 => {
if shift == 24 {
format!("uint({})", inner)
} else {
format!("(uint({}) & 0xff)", inner)
}
}
GpuScalar::I16 => {
if shift == 16 {
format!("uint({})", inner)
} else {
format!("(uint({}) & 0xffff)", inner)
}
}
GpuScalar::I32 => format!("uint({})", inner),
GpuScalar::TagFlags => format!("0"),
};
if shift == 0 {
bits
} else {
format!("({} << {})", bits, shift)
}
}
fn gen_enum_write(
r: &mut String,
bufname: &str,
name: &str,
is_mem: bool,
variants: &[(String, Vec<(usize, LayoutType)>)],
) {
for (var_name, payload) in variants {
if payload.is_empty() {
if is_mem {
writeln!(
r,
"void {}_{}_write(Alloc a, {}Ref ref) {{",
name, var_name, name
)
.unwrap();
writeln!(
r,
" write_mem(a, ref.offset >> 2, {}_{});",
name, var_name
)
.unwrap();
} else {
writeln!(r, "void {}_{}_write({}Ref ref) {{", name, var_name, name).unwrap();
writeln!(
r,
" {}[ref.offset >> 2] = {}_{};",
bufname, name, var_name
)
.unwrap();
}
writeln!(r, "}}\n").unwrap();
} else if payload.len() == 1 {
if let GpuType::InlineStruct(structname) = &payload[0].1.ty {
if is_mem {
writeln!(
r,
"void {}_{}_write(Alloc a, {}Ref ref, {} s) {{",
name, var_name, name, structname
)
.unwrap();
writeln!(
r,
" write_mem(a, ref.offset >> 2, {}_{});",
name, var_name
)
.unwrap();
writeln!(
r,
" {}_write(a, {}Ref(ref.offset + {}), s);",
structname, structname, payload[0].0
)
.unwrap();
} else {
writeln!(
r,
"void {}_{}_write({}Ref ref, {} s) {{",
name, var_name, name, structname
)
.unwrap();
writeln!(
r,
" {}[ref.offset >> 2] = {}_{};",
bufname, name, var_name
)
.unwrap();
writeln!(
r,
" {}_write({}Ref(ref.offset + {}), s);",
structname, structname, payload[0].0
)
.unwrap();
}
writeln!(r, "}}\n").unwrap();
}
} else if payload.len() == 2
&& matches!(payload[0].1.ty, GpuType::Scalar(GpuScalar::TagFlags))
{
if let GpuType::InlineStruct(structname) = &payload[1].1.ty {
if is_mem {
writeln!(
r,
"void {}_{}_write(Alloc a, {}Ref ref, uint flags, {} s) {{",
name, var_name, name, structname
)
.unwrap();
writeln!(
r,
" write_mem(a, ref.offset >> 2, (flags << 16) | {}_{});",
name, var_name
)
.unwrap();
writeln!(
r,
" {}_write(a, {}Ref(ref.offset + {}), s);",
structname, structname, payload[0].0
)
.unwrap();
} else {
writeln!(
r,
"void {}_{}_write({}Ref ref, uint flags, {} s) {{",
name, var_name, name, structname
)
.unwrap();
writeln!(
r,
" {}[ref.offset >> 2] = (flags << 16) | {}_{};",
bufname, name, var_name
)
.unwrap();
writeln!(
r,
" {}_write({}Ref(ref.offset + {}), s);",
structname, structname, payload[0].0
)
.unwrap();
}
writeln!(r, "}}\n").unwrap();
}
}
// TODO: support for variants that aren't one struct.
}
}
// Utility functions
fn glsl_type(ty: &GpuType) -> String {
match ty {
GpuType::Scalar(scalar) => glsl_scalar(scalar).into(),
GpuType::Vector(scalar, size) => {
if *size == 1 {
glsl_scalar(scalar).into()
} else {
format!("{}{}", glsl_vecname(scalar), size)
}
}
GpuType::InlineStruct(name) => name.clone(),
GpuType::Ref(inner) => {
if let GpuType::InlineStruct(name) = inner.deref() {
format!("{}Ref", name)
} else {
panic!("only know how to deal with Ref of struct")
}
}
}
}
// GLSL type that can contain the scalar value.
fn glsl_scalar(s: &GpuScalar) -> &'static str {
match s {
GpuScalar::F16 | GpuScalar::F32 => "float",
GpuScalar::I8 | GpuScalar::I16 | GpuScalar::I32 => "int",
GpuScalar::U8 | GpuScalar::U16 | GpuScalar::U32 | GpuScalar::TagFlags => "uint",
}
}
fn glsl_vecname(s: &GpuScalar) -> &'static str {
match s {
GpuScalar::F16 | GpuScalar::F32 => "vec",
GpuScalar::I8 | GpuScalar::I16 | GpuScalar::I32 => "ivec",
GpuScalar::U8 | GpuScalar::U16 | GpuScalar::U32 | GpuScalar::TagFlags => "uvec",
}
}
/// If `c = 0`, return `"var_name"`, else `"var_name + c"`
fn simplified_add(var_name: &str, c: usize) -> String {
if c == 0 {
String::from(var_name)
} else {
format!("{} + {}", var_name, c)
}
}