makepad/platform/shader/src/shader_ir_hlsl.rs
Admin 9aadd43f22 platform: HAL-only on every platform; the old backends are deleted
Every native platform now runs on the HAL (makepad-hal) alone: macOS and
iOS/tvOS on Metal, Windows on D3D11 (with device-loss recovery), Linux on
Vulkan (X11, Wayland and direct KMS, multi-GPU laptops with per-output
modes, mirror/extended arrangements and pacing) plus a GLES backend, and
Android on Vulkan with media, camera, network, haptics and file access on
the HAL. The old per-OS backends, the hal cfg and OpenHarmony are gone.
GPU memory: the HAL reads the device budget, keeps a UI reserve and raises
GpuPressure; failed allocations wait for room. One font service per host,
a frame profiler, held events for hidden views, automation for tests. Our
own generated Vulkan, Wayland and Windows bindings replace vendored ones.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 12:26:21 +02:00

368 lines
17 KiB
Rust

//! HLSL (shader model 5) from the shader IR: the one source D3D11 compiles
//! twice, `vertex_main` with `vs_5_0` and `pixel_main` with `ps_5_0`.
//!
//! Registers: uniform buffers at `b<buffer_index>`, the dyn uniforms at b2,
//! the scope uniforms after the uniform buffers, the draw's first instance at
//! b13 (`_mp_inst_base`, zero when unbound); textures at t0.. in io
//! order and the instance word buffer after them; samplers at s0.. in the
//! compile's sampler order. Geometry semantics follow D3D11's input layout
//! (`GEOM<letter><chunk>`).
use crate::shader_ir::*;
use crate::shader_ir_draw::{build_hlsl_entries, HlslDrawInfo};
use crate::shader_ir_text::*;
use crate::shader_output::*;
use std::fmt::Write;
/// The declaration of the buffer a vertex shader reads its instance records
/// from; its register follows.
const HLSL_INSTANCE_BUFFER_DECL: &str = "Buffer<uint> _mp_inst : register(t";
/// The t register of the instance buffer `hlsl` reads its instance records
/// from, or None when the shader has no instance fields.
pub fn hlsl_instance_buffer_register(hlsl: &str) -> Option<u32> {
let start = hlsl.find(HLSL_INSTANCE_BUFFER_DECL)? + HLSL_INSTANCE_BUFFER_DECL.len();
let end = start + hlsl[start..].find(')')?;
hlsl[start..end].parse().ok()
}
/// The HLSL source of a draw shader compiled to IR.
pub fn hlsl_draw_source(output: &ShaderOutput, types: &impl crate::shader_types::ShaderTypes) -> String {
hlsl_draw(output, types, false).0
}
/// The pick variant (`ShaderOutput::pick`): the same shader writing the 24-bit id of what is drawn
/// at each pixel instead of its colours (the draw's id, or the id a sampled 2D texture's twin
/// holds there), as the Metal and SPIR-V variants do. The draw's id and the twin mask come in
/// `_pk` at b12; twin `k` (the k-th texture) at t(textures + 1 + k), after the instance words.
pub fn hlsl_draw_pick_source(output: &ShaderOutput, types: &impl crate::shader_types::ShaderTypes) -> String {
hlsl_draw(output, types, true).0
}
pub fn hlsl_draw(output: &ShaderOutput, types: &impl crate::shader_types::ShaderTypes, pick: bool) -> (String, HlslDrawInfo) {
let mut ir = output.ir.clone();
let info = build_hlsl_entries(output, &mut ir, types);
let mut io_tys = Vec::new();
for io in &output.io {
io_tys.push(match io.kind {
ShaderIoKind::Texture(_) | ShaderIoKind::Sampler(_) => TY_VOID,
_ => ir.pod_ty(types, io.ty),
});
}
let vertex_main = ir.function_by_name("vertex_main").unwrap();
let fragment_main = ir.function_by_name("fragment_main").unwrap();
let funcs = callees_first(&ir, &[vertex_main, fragment_main]);
let used = ir.globals_used(&funcs);
let mut cx = TextCx::new(&ir, Dialect::Hlsl, false);
// The pick twins: the shader's 2D textures among its first 32.
let ntex = output.io.iter().filter(|io| matches!(io.kind, ShaderIoKind::Texture(_))).count();
let mut twins = Vec::new();
if pick {
let mut k = 0u32;
for io in &output.io {
if let ShaderIoKind::Texture(tt) = io.kind {
if k < 32 && tt == TextureType::Texture2d {
if let Some(g) = ir.find_global(IrGlobalKind::Io(IrIo::Texture(tt)), io.name) {
cx.pick_twins.push((g, k));
twins.push((k, cx.plain_global_name(g)));
}
}
k += 1;
}
}
}
// Function bodies first: they tell which helpers the module needs.
let mut bodies = String::new();
for f in &funcs {
if *f == vertex_main || *f == fragment_main {
continue;
}
cx.function(*f, &mut bodies);
}
let mut entries = String::new();
entry(&mut cx, vertex_main, "VertexIn", "VertexOut", "vertex_main", Some(&info.vertex_semantics), &mut entries);
let fragment_outputs: Vec<(String, u32)> = ir.functions[fragment_main as usize].entry.as_ref().unwrap().outputs.iter()
.filter_map(|io| match io.binding { IrIoBinding::Location(l) => Some((io.name.clone(), l)), _ => None }).collect();
let pick_out = pick && !fragment_outputs.is_empty();
if pick_out {
let mut pixel = String::new();
entry(&mut cx, fragment_main, "VertexOut", "PixelOut", "pixel_main", None, &mut pixel);
// The body's `return out_data;` returns the colours; the pick variant turns them into the
// id of what is drawn there.
let mut f = String::from("struct PickOut {\n");
for (name, l) in &fragment_outputs {
writeln!(f, " float4 {} : SV_TARGET{};", name, l).ok();
}
f.push_str("};\n");
let first = ir.functions[fragment_main as usize].entry.as_ref().unwrap().outputs.iter().find(|io| matches!(io.binding, IrIoBinding::Location(_)));
let cover = match first {
Some(io) if *ir.get(io.ty) == IrTy::Vec(4, IrScalar::F32) => format!("_mp_pick_cover(c.{})", io.name),
_ => "1.0".to_string(),
};
f.push_str("PickOut _mp_pick_out(PixelOut c) {\n");
f.push_str(" uint pid = _pick_w > 0.0 ? _pick_id : _pk.x;\n");
writeln!(f, " if ({} < 0.1 || pid == 0u) discard;", cover).ok();
f.push_str(" float4 pc = float4(float(pid & 255u), float((pid >> 8) & 255u), float((pid >> 16) & 255u), 255.0) / 255.0;\n PickOut o;\n");
for (name, _) in &fragment_outputs {
writeln!(f, " o.{} = pc;", name).ok();
}
f.push_str(" return o;\n}\n");
let pixel = pixel
.replacen("PixelOut pixel_main(VertexOut input) {", "PickOut pixel_main(VertexOut input) {\n _pick_mask = _pk.y;", 1)
.replace("return out_data;", "return _mp_pick_out(out_data);");
let at = pixel.find("PickOut pixel_main(").unwrap_or(pixel.len());
entries.push_str(&pixel[..at]);
entries.push_str(&f);
entries.push_str(&pixel[at..]);
} else {
entry(&mut cx, fragment_main, "VertexOut", "PixelOut", "pixel_main", None, &mut entries);
}
let mut out = String::new();
out.push_str("#pragma pack_matrix(row_major)\n");
let mut done = vec![false; ir.structs.len()];
for s in 0..ir.structs.len() {
struct_def(&cx, s as u32, &mut done, &mut out);
}
let mut k = 0;
while k < cx.hlsl_ctors.len() {
let s = cx.hlsl_ctors[k];
let name = cx.struct_name(s);
let sty = ir.types.iter().position(|t| *t == IrTy::Struct(s)).unwrap_or(0) as TyId;
let mut params = Vec::new();
let mut sets = String::new();
for (i, fd) in ir.structs[s as usize].fields.iter().enumerate() {
params.push(cx.decl(fd.ty, &format!("p{}", i)));
write!(sets, " r.{} = p{};", cx.field_name(sty, i as u32), i).ok();
}
writeln!(out, "{0} _mp_make_{0}({1}) {{ {0} r;{2} return r; }}", name, params.join(", "), sets).ok();
k += 1;
}
// Constant buffers.
let mut max_buffer = None;
for (i, io) in output.io.iter().enumerate() {
if let (ShaderIoKind::UniformBuffer, Some(b)) = (&io.kind, io.buffer_index) {
max_buffer = Some(max_buffer.map_or(b, |m: usize| m.max(b)));
writeln!(out, "cbuffer cb_{0} : register(b{1}) {{ {2} unibuf_{0}; }};", io.name, b, cx.ty_name(io_tys[i])).ok();
}
}
for (kind, block, register, prefix) in [(0, "IoUniform", 2, "uni_"), (1, "IoScopeUniform", max_buffer.map_or(3, |m| m + 1), "su_")] {
let mut first = true;
for (i, io) in output.io.iter().enumerate() {
let wanted = match io.kind {
ShaderIoKind::Uniform => kind == 0,
ShaderIoKind::ScopeUniform => kind == 1,
_ => false,
};
if !wanted {
continue;
}
if first {
writeln!(out, "cbuffer {} : register(b{}) {{", block, register).ok();
first = false;
}
writeln!(out, " {};", cx.decl(io_tys[i], &format!("{}{}", prefix, io.name))).ok();
}
if !first {
out.push_str("};\n");
}
}
// Textures, the instance buffer, samplers.
let mut tex = 0;
for io in &output.io {
if let ShaderIoKind::Texture(tt) = io.kind {
writeln!(out, "{} tex_{} : register(t{});", hlsl_texture_type(tt), io.name, tex).ok();
tex += 1;
}
}
if info.instance_slots > 0 {
writeln!(out, "{}{});", HLSL_INSTANCE_BUFFER_DECL, tex).ok();
}
for (i, s) in output.samplers.iter().enumerate() {
let t = if s.compare { "SamplerComparisonState" } else { "SamplerState" };
writeln!(out, "{} _s{} : register(s{});", t, i, i).ok();
}
if pick {
for (k, name) in &twins {
writeln!(out, "Texture2D {}__pick : register(t{});", name, ntex as u32 + 1 + k).ok();
}
out.push_str(HLSL_PICK);
}
// Per-invocation globals.
out.push_str("static uint _mp_iter = 0;\n");
for (g, glob) in ir.globals.iter().enumerate() {
if !used.get(g).copied().unwrap_or(false) {
continue;
}
let name = cx.global_name(g as GlobalId);
match glob.kind {
IrGlobalKind::Io(IrIo::Geometry | IrIo::DynInstance | IrIo::RustInstance | IrIo::Varying | IrIo::FragmentOutput(_))
| IrGlobalKind::VertexPosition
| IrGlobalKind::InstanceIndex => {
writeln!(out, "static {};", cx.decl(glob.ty, &name)).ok();
}
IrGlobalKind::ViewId => out.push_str("static const int VIEW_ID = 0;\n"),
IrGlobalKind::InstanceBase => out.push_str("cbuffer _mp_draw : register(b13) { uint _mp_inst_base; };\n"),
_ => {}
}
}
out.push_str(HLSL_HELPERS);
if cx.needs_inverse {
out.push_str(HLSL_INVERSE4);
}
out.push_str(&bodies);
out.push_str(&entries);
(out, info)
}
fn struct_def(cx: &TextCx, s: u32, done: &mut Vec<bool>, out: &mut String) {
if done[s as usize] {
return;
}
done[s as usize] = true;
let st = &cx.m.structs[s as usize];
if st.synth.is_some() {
return;
}
for f in &st.fields {
let mut t = f.ty;
while let IrTy::Array(e, _) = cx.m.get(t) {
t = *e;
}
if let IrTy::Struct(inner) = cx.m.get(t) {
struct_def(cx, *inner, done, out);
}
}
let sty = cx.m.types.iter().position(|t| *t == IrTy::Struct(s)).unwrap_or(0) as TyId;
writeln!(out, "struct {} {{", cx.struct_name(s)).ok();
for (k, f) in st.fields.iter().enumerate() {
writeln!(out, " {};", cx.decl(f.ty, &cx.field_name(sty, k as u32))).ok();
}
out.push_str("};\n");
}
fn semantic(b: IrIoBinding, fragment_output: bool) -> String {
match b {
IrIoBinding::Builtin(bi) => match bi {
IrBuiltinIo::Position => "SV_POSITION",
IrBuiltinIo::VertexIndex => "SV_VertexID",
IrBuiltinIo::InstanceIndex => "SV_InstanceID",
IrBuiltinIo::ViewIndex => "SV_ViewID",
IrBuiltinIo::FrontFacing => "SV_IsFrontFace",
IrBuiltinIo::FragDepth => "SV_Depth",
}
.to_string(),
IrIoBinding::Location(l) => {
if fragment_output {
format!("SV_TARGET{}", l)
} else {
format!("TEXCOORD{}", l)
}
}
}
}
/// An entry point and its output struct (and its input struct when
/// `in_semantics` names them: the vertex stage's; the pixel stage takes the
/// vertex stage's output).
fn entry(cx: &mut TextCx, fi: FuncId, in_struct: &str, out_struct: &str, name: &str, in_semantics: Option<&Vec<String>>, out: &mut String) {
let m = cx.m;
let f = &m.functions[fi as usize];
let e = f.entry.as_ref().unwrap();
let fragment = e.stage == IrStage::Fragment;
if let Some(sems) = in_semantics {
writeln!(out, "struct {} {{", in_struct).ok();
for (k, io) in e.inputs.iter().enumerate() {
let sem = match sems.get(k) {
Some(s) => s.clone(),
None => semantic(io.binding, false),
};
writeln!(out, " {} : {};", cx.decl(io.ty, &io.name), sem).ok();
}
out.push_str("};\n");
}
writeln!(out, "struct {} {{", out_struct).ok();
for io in &e.outputs {
let interp = if !fragment && m.scalar_of(io.ty).map(|s| !s.is_float()).unwrap_or(false) { "nointerpolation " } else { "" };
writeln!(out, " {}{} : {};", interp, cx.decl(io.ty, &io.name), semantic(io.binding, fragment)).ok();
}
out.push_str("};\n");
writeln!(out, "{0} {1}({2} input) {{\n {0} out_data = ({0})0;", out_struct, name, in_struct).ok();
cx.stmts(f, &f.body, 1, out);
out.push_str("}\n");
}
/// The pick variant's state and helpers: the winning twin id and its coverage; `_pk` is
/// (draw id, twin mask).
const HLSL_PICK: &str = "cbuffer _mp_pick : register(b12) { uint4 _pk; };
static uint _pick_mask = 0u;
static uint _pick_id = 0u;
static float _pick_w = 0.0;
float _mp_pick_cover(float4 c) { return max(c.w, max(c.x, max(c.y, c.z))); }
uint _mp_pick_id(float4 e) { return uint(e.x * 255.0 + 0.5) | (uint(e.y * 255.0 + 0.5) << 8) | (uint(e.z * 255.0 + 0.5) << 16); }
float4 _mp_pick_sample(uint b, Texture2D tw, float4 c, float2 uv) {
float w = _mp_pick_cover(c);
// straight-line inside one real branch: FXC compiles nested flow per inlined sample slowly
[branch] if ((_pick_mask & (1u << b)) != 0u && w > _pick_w) {
uint tw_w, tw_h; tw.GetDimensions(tw_w, tw_h);
int2 hi = int2(tw_w, tw_h) - 1;
int2 q = int2(clamp(uv, float2(0.0, 0.0), float2(0.99999, 0.99999)) * float2(tw_w, tw_h));
// the texel and its four neighbours: the first id found
uint i0 = _mp_pick_id(tw.Load(int3(clamp(q, int2(0, 0), hi), 0)));
uint i1 = _mp_pick_id(tw.Load(int3(clamp(q + int2(1, 0), int2(0, 0), hi), 0)));
uint i2 = _mp_pick_id(tw.Load(int3(clamp(q + int2(-1, 0), int2(0, 0), hi), 0)));
uint i3 = _mp_pick_id(tw.Load(int3(clamp(q + int2(0, 1), int2(0, 0), hi), 0)));
uint i4 = _mp_pick_id(tw.Load(int3(clamp(q + int2(0, -1), int2(0, 0), hi), 0)));
uint id = i0 != 0u ? i0 : (i1 != 0u ? i1 : (i2 != 0u ? i2 : (i3 != 0u ? i3 : i4)));
_pick_w = id != 0u ? w : _pick_w;
_pick_id = id != 0u ? id : _pick_id;
}
return c;
}
";
const HLSL_HELPERS: &str = "float2 _mp_unpack2f16(float x) { uint u = asuint(x); return float2(f16tof32(u & 0xffff), f16tof32(u >> 16)); }
float4 _mp_unpack4u8(float x) { uint u = asuint(x); return float4(float(u & 0xff), float((u >> 8) & 0xff), float((u >> 16) & 0xff), float((u >> 24) & 0xff)) * (1.0 / 255.0); }
float2 _mp_unpack_half2x16(uint u) { return float2(f16tof32(u & 0xffff), f16tof32(u >> 16)); }
float2 _mp_unpack_unorm2x16(uint u) { return float2(float(u & 0xffff), float(u >> 16)) * (1.0 / 65535.0); }
float2 _mp_unpack_snorm2x16(uint u) { int2 i = int2(int(u << 16), int(u)) >> 16; return clamp(float2(i) / 32767.0, -1.0, 1.0); }
float4 _mp_unpack_unorm4x8(uint u) { return float4(float(u & 0xff), float((u >> 8) & 0xff), float((u >> 16) & 0xff), float((u >> 24) & 0xff)) * (1.0 / 255.0); }
float4 _mp_unpack_snorm4x8(uint u) { int4 i = int4(int(u << 24), int(u << 16), int(u << 8), int(u)) >> 24; return clamp(float4(i) / 127.0, -1.0, 1.0); }
float2 _mpTexSize2D(Texture2D tex) { uint w, h; tex.GetDimensions(w, h); return float2(w, h); }
float2 _mpTexSize2D(Texture2D<float> tex) { uint w, h; tex.GetDimensions(w, h); return float2(w, h); }
#define _mp_asinh(x) log((x) + sqrt((x) * (x) + 1.0))
#define _mp_acosh(x) log((x) + sqrt((x) * (x) - 1.0))
#define _mp_atanh(x) (0.5 * log((1.0 + (x)) / (1.0 - (x))))
";
/// HLSL has no matrix inverse: the cofactor expansion (the same numbers
/// as WGSL's helper: `m[i]` is the IR's column i here too).
const HLSL_INVERSE4: &str = "float4x4 _mp_inverse4(float4x4 m) {
float a00 = m[0][0]; float a01 = m[0][1]; float a02 = m[0][2]; float a03 = m[0][3];
float a10 = m[1][0]; float a11 = m[1][1]; float a12 = m[1][2]; float a13 = m[1][3];
float a20 = m[2][0]; float a21 = m[2][1]; float a22 = m[2][2]; float a23 = m[2][3];
float a30 = m[3][0]; float a31 = m[3][1]; float a32 = m[3][2]; float a33 = m[3][3];
float b00 = a00 * a11 - a01 * a10; float b01 = a00 * a12 - a02 * a10;
float b02 = a00 * a13 - a03 * a10; float b03 = a01 * a12 - a02 * a11;
float b04 = a01 * a13 - a03 * a11; float b05 = a02 * a13 - a03 * a12;
float b06 = a20 * a31 - a21 * a30; float b07 = a20 * a32 - a22 * a30;
float b08 = a20 * a33 - a23 * a30; float b09 = a21 * a32 - a22 * a31;
float b10 = a21 * a33 - a23 * a31; float b11 = a22 * a33 - a23 * a32;
float det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
float inv = 1.0 / det;
return float4x4(
float4(a11 * b11 - a12 * b10 + a13 * b09, a02 * b10 - a01 * b11 - a03 * b09, a31 * b05 - a32 * b04 + a33 * b03, a22 * b04 - a21 * b05 - a23 * b03) * inv,
float4(a12 * b08 - a10 * b11 - a13 * b07, a00 * b11 - a02 * b08 + a03 * b07, a32 * b02 - a30 * b05 - a33 * b01, a20 * b05 - a22 * b02 + a23 * b01) * inv,
float4(a10 * b10 - a11 * b08 + a13 * b06, a01 * b08 - a00 * b10 - a03 * b06, a30 * b04 - a31 * b02 + a33 * b00, a21 * b02 - a20 * b04 - a23 * b00) * inv,
float4(a11 * b07 - a10 * b09 - a12 * b06, a00 * b09 - a01 * b07 + a02 * b06, a31 * b01 - a30 * b03 - a32 * b00, a20 * b03 - a21 * b01 + a22 * b00) * inv);
}
";
pub fn hlsl_draw_source_into(output: &ShaderOutput, types: &impl crate::shader_types::ShaderTypes, out: &mut String) {
out.push_str(&hlsl_draw_source(output, types));
}