// AUTO-GENERATED by tools/gen_shader_externs.zig — do not edit.
// Edit the matching `*_io.zig` schema instead.
//
// The shader body imports this as `@import("io")` (the codegen
// output's named module).  Every external resource the shader
// touches — vertex attributes, varyings, uniforms, samplers,
// stage outputs — lives in this namespace.  Call `io.setup()`
// once at the top of `main` to install all the required
// `OpDecorate` calls; then the shader body just reads/writes
// the named decls.
//
// NATIVE-COMPATIBLE LAYOUT: every SPIR-V-only declaration
// (externs with `addrspace`, `std.gpu` re-exports, the
// `setup()` body that takes addresses of those externs,
// and the top-level sampler accessor functions whose
// bodies sample externs) lives inside the `_Spirv`
// namespace, wrapped in `if (_is_spirv) struct { ... } else
// struct {};`.  Zig 0.16 lazy-evaluates the comptime-dead
// branch, so on native targets the SPIR-V-only body is
// never type-checked; the emitted file compiles cleanly
// for any target.
//
// Shader bodies that use only `IoT(Ubo)`, `Out`, and
// `installSpirvEntry` (the standard new API) are
// native-importable for free.  The CPU dispatcher in
// `raster_shader.zig` can call `shaderMain(io)` directly per
// pixel; the `installSpirvEntry` call site is a no-op on
// non-SPIR-V targets.

const std = @import("std");
const _builtin = @import("builtin");
const _is_spirv = _builtin.target.cpu.arch.isSpirV();

// ---- _Spirv namespace: all SPIR-V-only declarations.
// On native targets, this whole struct body is comptime-dead;
// Zig 0.16 doesn't type-check the `if (_is_spirv) struct { ... }`
// branch when the condition is false.
const _Spirv = if (_is_spirv) struct {

// zm — for storageBuffer() / sampler helpers used by emitted decls.
const _sb = @import("shader_builtins");

// ---- Built-in GPU values, re-exported for symmetry --------
// These come from Zig's `std.spirv` namespace.  SPIR-V-only —
// `std.spirv.position_out` references a `.output` addrspace
// type and isn't representable on native targets.
pub const position_out = std.spirv.position_out;
pub const vertex_index = std.spirv.vertex_index;
pub const instance_index = std.spirv.instance_index;

// Interpolated inputs from vertex (location = field index).
pub extern const frag_world_pos: @Vector(3, f32) addrspace(.input);
pub const _location_frag_world_pos: u32 = 0;
pub extern const frag_world_normal: @Vector(3, f32) addrspace(.input);
pub const _location_frag_world_normal: u32 = 1;

// Stage outputs (location = field index; frag_depth = builtin).
pub extern var final_color: @Vector(4, f32) addrspace(.output);
pub const _location_final_color: u32 = 0;
pub extern var frag_depth: f32 addrspace(.output);

pub const UboWire = extern struct {
    base_color: @Vector(4, f32),
    light_dir: @Vector(4, f32),
};
pub extern const u: UboWire addrspace(.uniform);
pub const _binding_u: u32 = 0;

// ---- setup() ----------------------------------------------
//
// Install all the SPIR-V `OpDecorate` calls this shader needs.
// Call once at the top of `main` BEFORE touching any of the
// extern decls above.  Replaces the boilerplate that used to
// open every shader body:
//
//     zm.location(&io.frag_tex_coord, io._location_frag_tex_coord);
//     zm.location(&io.out_color, io._location_out_color);
//     zm.binding(&io.u, 0, io._binding_u);
//
// `noinline` keeps spirv-opt from folding the call into main
// — the asm decorations attach to the referenced variables,
// not to the call site, so call elision is safe but the
// stable function boundary helps when reading optimized SPIR-V.
pub noinline fn setup() void {
    @setRuntimeSafety(false);
    zm_location(&frag_world_pos, _location_frag_world_pos);
    zm_location(&frag_world_normal, _location_frag_world_normal);
    zm_location(&final_color, _location_final_color);
    zm_binding(&u, 2, _binding_u);
}

} else struct {};

pub const Ubo = struct {
    base_color: @Vector(4, f32),
    light_dir: @Vector(4, f32),
};

// ---- New API: IoT / Out / installSpirvEntry ---------------
// See `src/notes/software_shaders.md` for the design.  These
// emissions sit alongside the legacy externs above; shaders
// using the new `pub fn shaderMain(io: Io) Out` shape close
// the loop by instantiating `IoT(iface.Ubo)` and re-exporting
// as `Io`.
//
// IoT is a function-returning-a-type because io.zig must NOT
// import the iface module — see codegen comments for why.
//
// Sampler accessor methods (texture0, texture1, ...) have
// target-conditional bodies: on SPIR-V they call the extern
// image-sample op; on CPU they read from `_<name>: TextureRef`
// fields (one per sampler, populated by the caller before
// dispatch).  TextureRef is a tiny pointer-and-dims struct
// defined below; v1 assumes RGBA8 layout.
//
// TextureRef is defined HERE (in the codegen output) rather
// than importing raster.  raster is 7000+ lines including system
// allocators that don't compile on spirv32-vulkan, so
// importing it would break the GPU compile.  The caller
// (dispatcher) constructs TextureRefs from raster.Texture
// bytes itself.
pub const TextureRef = struct {
    pixels: []const u8,
    width: u32,
    height: u32,
    /// True when the GPU side of this binding uses an `*_srgb` texture
    /// view: the sampler then converts RGB sRGB→linear at sample time
    /// (alpha stays linear), matching the hardware's behavior so the
    /// same shader body sees the same values on both targets.
    srgb: bool = false,
    /// Selects bilinear (true, the default — matches a `linear` GPU
    /// sampler) vs nearest filtering.  The dispatcher leaves this at
    /// the default unless the bound sampler is configured `nearest`.
    linear: bool = true,
};

// 256-entry sRGB→linear table, built at comptime (the exact IEC
// 61966-2-1 curve the GPU applies when sampling an `*_srgb` view).
const _srgb_to_linear: [256]f32 = blk: {
    @setEvalBranchQuota(20000);
    var t: [256]f32 = undefined;
    for (&t, 0..) |*v, i| {
        const c: f32 = @as(f32, @floatFromInt(i)) / 255.0;
        v.* = if (c <= 0.04045) c / 12.92 else std.math.pow(f32, (c + 0.055) / 1.055, 2.4);
    }
    break :blk t;
};

// Single RGBA8 texel fetch at integer (x, y), sRGB-aware.  When
// `tex.srgb` is set the RGB channels are converted sRGB→linear via
// the exact IEC curve the GPU applies to an `*_srgb` view; alpha
// stays linear.  No bounds work — callers pass in-range coords.
fn _texelRgba8(tex: TextureRef, x: u32, y: u32) @Vector(4, f32) {
    @setRuntimeSafety(false);
    const idx: usize = (@as(usize, y) * @as(usize, tex.width) + @as(usize, x)) * 4;
    if (tex.srgb) {
        return .{
            _srgb_to_linear[tex.pixels[idx]],
            _srgb_to_linear[tex.pixels[idx + 1]],
            _srgb_to_linear[tex.pixels[idx + 2]],
            @as(f32, @floatFromInt(tex.pixels[idx + 3])) / 255.0,
        };
    }
    return .{
        @as(f32, @floatFromInt(tex.pixels[idx])) / 255.0,
        @as(f32, @floatFromInt(tex.pixels[idx + 1])) / 255.0,
        @as(f32, @floatFromInt(tex.pixels[idx + 2])) / 255.0,
        @as(f32, @floatFromInt(tex.pixels[idx + 3])) / 255.0,
    };
}

// Sample a texture at `uv` with repeat wrap, matching the engine's
// default GPU sampler `address_mode = .repeat` (glTF UVs may tile —
// e.g. the DamagedHelmet's V runs [1, 2]).  `tex.linear` picks
// bilinear (the default, matching a `linear` GPU sampler) vs nearest.
// sRGB→linear runs per texel BEFORE the bilinear blend, exactly as
// the hardware filters (convert at fetch, then lerp in linear space).
// Locals are `uu`/`vv` (not `u`/`v`): `u` collides with the module-
// level UBO extern `pub extern const u: Ubo` emitted above.
fn sampleTextureRgba8(tex: TextureRef, uv: @Vector(2, f32)) @Vector(4, f32) {
    @setRuntimeSafety(false);
    const w_i: i32 = @intCast(tex.width);
    const h_i: i32 = @intCast(tex.height);
    const w_f: f32 = @as(f32, @floatFromInt(tex.width));
    const h_f: f32 = @as(f32, @floatFromInt(tex.height));
    var uu: f32 = uv[0] - @floor(uv[0]);
    var vv: f32 = uv[1] - @floor(uv[1]);
    if (uu < 0) uu = 0;
    if (uu >= 1) uu = 0;
    if (vv < 0) vv = 0;
    if (vv >= 1) vv = 0;
    if (!tex.linear) {
        var x: i32 = @floor(uu * w_f);
        var y: i32 = @floor(vv * h_f);
        if (x >= w_i) x = w_i - 1;
        if (y >= h_i) y = h_i - 1;
        return _texelRgba8(tex, @intCast(x), @intCast(y));
    }
    // Bilinear: the -0.5 half-texel offset puts an integer uv*size on
    // a texel centre; floor gives the lower-left texel and the fracs
    // are the blend weights.  All four texel coords are repeat-wrapped
    // with @mod (positive divisor: @mod(-1, w) = w-1, @mod(w, w) = 0).
    const fx: f32 = uu * w_f - 0.5;
    const fy: f32 = vv * h_f - 0.5;
    const x0i: i32 = @floor(fx);
    const y0i: i32 = @floor(fy);
    const tx: f32 = fx - @as(f32, @floatFromInt(x0i));
    const ty: f32 = fy - @as(f32, @floatFromInt(y0i));
    const x0: u32 = @intCast(@mod(x0i, w_i));
    const y0: u32 = @intCast(@mod(y0i, h_i));
    const x1: u32 = @intCast(@mod(x0i + 1, w_i));
    const y1: u32 = @intCast(@mod(y0i + 1, h_i));
    const c00: @Vector(4, f32) = _texelRgba8(tex, x0, y0);
    const c10: @Vector(4, f32) = _texelRgba8(tex, x1, y0);
    const c01: @Vector(4, f32) = _texelRgba8(tex, x0, y1);
    const c11: @Vector(4, f32) = _texelRgba8(tex, x1, y1);
    const txv: @Vector(4, f32) = @splat(tx);
    const tyv: @Vector(4, f32) = @splat(ty);
    const top: @Vector(4, f32) = c00 + (c10 - c00) * txv;
    const bot: @Vector(4, f32) = c01 + (c11 - c01) * txv;
    return top + (bot - top) * tyv;
}

pub fn IoT(comptime UboType: type) type {
    return struct {
        frag_world_pos: @Vector(3, f32),
        frag_world_normal: @Vector(3, f32),
        u: UboType,

    };
}

pub const Out = struct {
    final_color: @Vector(4, f32),
    frag_depth: f32,
};

// installSpirvEntry: comptime-emit the SPIR-V `entry` wrapper.
// Pattern verified in session 13's design experiment:
//   pub fn shaderMain(io: Io) Out { ... return out; }
//   comptime { _ = installSpirvEntry(shaderMain); }
// On SPIR-V targets this materializes an `export fn entry`
// that reads externs into the body's Io type, calls the body,
// writes the returned Out back to extern outputs.  On CPU
// targets it's a no-op (the dispatcher in `src/raster_shader.zig`
// calls the body directly per pixel).
//
// `body` is anytype so the io module doesn't need to name the
// concrete Io / Out types — those live in the shader source.
pub fn installSpirvEntry(comptime body: anytype) void {
    if (!_builtin.target.cpu.arch.isSpirV()) return;
    const _FnInfo = @typeInfo(@TypeOf(body)).@"fn";
    const _IoT = _FnInfo.param_types[0].?;
    const _OutT = _FnInfo.return_type.?;
    const Wrapper = struct {
        export fn entry() callconv(.{ .spirv_fragment = .{} }) void {
            @setRuntimeSafety(false);
            zm_location(&_Spirv.frag_world_pos, _Spirv._location_frag_world_pos);
            zm_location(&_Spirv.frag_world_normal, _Spirv._location_frag_world_normal);
            zm_location(&_Spirv.final_color, _Spirv._location_final_color);
            zm_binding(&_Spirv.u, 2, _Spirv._binding_u);
            const io: _IoT = .{
                .frag_world_pos = _Spirv.frag_world_pos,
                .frag_world_normal = _Spirv.frag_world_normal,
                .u = .{
                    .base_color = _Spirv.u.base_color,
                    .light_dir = _Spirv.u.light_dir,
                },
            };
            const out: _OutT = body(io);
            _Spirv.final_color = out.final_color;
            _Spirv.frag_depth = out.frag_depth;
        }
    };
    _ = Wrapper;
}

// ---- Imports (kept at the bottom so the top of the file
//             reads as a stage-interface inventory) -------
const zm_mod = @import("shader_builtins");
const zm_location = zm_mod.location;
const zm_binding = zm_mod.binding;
const zm_zsample2d = zm_mod.zsample2d;
const zm_zsample2d_level = zm_mod.zsample2d_level;
const zm_ssboLoad = zm_mod.ssboLoad;
