| //! 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) |
| } |
| } |