From d2f749e698f04964c0edf6f57a7e0c59627ad2f7 Mon Sep 17 00:00:00 2001 From: paul Date: Tue, 20 Oct 2026 23:43:13 +0100 Subject: [PATCH 1/5] ae3d.gpu: the quoted shaders written as MSL (gpu.METAL) A third dialect beside Vulkan's and OpenGL's GLSL, from the same trees: emit_msl, emit_vertex_msl and emit_screen_vertex_msl. The expressions and statements are the GLSL emitter's, with a CALL_MSL column in the call table (sample/read for the texture calls, rsqrt, atan2, the prelude's ae_mod and ae_mat3) and struct literals as aggregates. Around them: - Vulkan's conventions: clip z 0..1, the screen's y down, each vertex shader's position y negated as it returns it, so a Metal renderer draws with the Vulkan renderer's matrices and the frame comes out the same way. - Nothing global: each block a struct in a buffer, each texture with its sampler (ae_smp_), each storage buffer a pointer; the entry's first lines name each capture as a reference into its block, so the body reads it where it uses it, as the GLSL does. A helper reading in place is handed what it reads by name, and so is each helper calling it; a helper whose own name would hide one is refused (MSL only). - std140 by hand: each block struct member by member at ae3d.gpu's offsets, padded between (vec3 a packed_float3, scalar arrays float4 arrays read through their lanes, an element struct with a maker for its literals), every offset and size a static_assert. - Varyings at [[user(locnK)]] (an int [[flat]]), attributes at the table's [[attribute(n)]] (a mat4 four float4s), colour outputs four lanes wide. - Bindings: set N's block at buffer(N), storage buffers at 8 + k, textures and samplers at their capture order. Nothing the GLSL dialects write changes (the dialect is set only while MSL is written; names MSL keeps are escaped then alone). tests/test_gpu_msl holds each rule, the offsets to the GLSL block's, and the GLSL and OpenGL's names unchanged by writing MSL; negative controls for each. Co-Authored-By: Claude Opus 5.5 --- src/ae3d/gpu/module.ae | 1051 +++++++++++++++++++++++++++++++++++++++- tests/test_gpu_msl.ae | 252 ++++++++++ 2 files changed, 1292 insertions(+), 11 deletions(-) create mode 100644 tests/test_gpu_msl.ae diff --git a/src/ae3d/gpu/module.ae b/src/ae3d/gpu/module.ae index 0ff61149..0cbfb440 100644 --- a/src/ae3d/gpu/module.ae +++ b/src/ae3d/gpu/module.ae @@ -23,6 +23,9 @@ // capture_field (offsetof); uniforms_* name each value OpenGL sets. // emit_screen_vertex writes the vertex shader a full-screen pass draws // it with. +// - emit_msl, emit_vertex_msl and emit_screen_vertex_msl write the same +// shaders as Metal's MSL (the METAL dialect, the MSL section below), the +// blocks' structs at std140's offsets, each a static_assert; // - cpu_run runs the tree for one pixel over vec values (doubles, // rounded to f32 as each is made, as a GPU holds them), the captures // read from their cells, texture() reading a Texture's ShaderImage. @@ -87,7 +90,7 @@ extern free(p: ptr) exports ( GpuShader, GpuVal, GpuRun, - VULKAN, GL, + VULKAN, GL, METAL, emit_msl, emit_vertex_msl, emit_screen_vertex_msl, fragment, vertex, depth_fragment, cut_fragment, emit_vertex_glsl, screen_vertex, link, linked, varying_location, cpu_vertex_attr, cpu_run_vertex, cpu_varying, ScreenVertex, ScreenVaryings, VERTEX_STAGE, FRAGMENT_STAGE, writes_depth, shader_free, shader_name, shader_tree, shader_hash, emit_glsl, emit_screen_vertex, outputs_count, @@ -922,7 +925,7 @@ fn emit_structs(t: quote.Tree, sb: ptr) { j = 0 while j < list.size(st.fields) { f = list.get_raw(st.fields, j) as *GpuField - ty = glsl_type(f.type) + ty = decl_type(f.type) if ty == "" { refuse(t, quote.body(t), "${st.name}'s ${f.name} is a ${f.type}, which GLSL has no type for") } strbuilder.append(sb, " ${ty} ${glsl_name(f.name)};\n") j = j + 1 @@ -941,7 +944,14 @@ fn glsl_make(t: quote.Tree, n: quote.Node) -> string { return "" } sb = strbuilder.new(64) - strbuilder.append(sb, "${st.name}(") + maker = g_dialect == METAL && g_emitting != null && element_of_type(g_emitting as *GpuShader, st.name) != null + if maker { + strbuilder.append(sb, "ae_make_${st.name}(") + } else if g_dialect == METAL { + strbuilder.append(sb, "${st.name}{") + } else { + strbuilder.append(sb, "${st.name}(") + } j = 0 while j < list.size(st.fields) { f = list.get_raw(st.fields, j) as *GpuField @@ -954,7 +964,11 @@ fn glsl_make(t: quote.Tree, n: quote.Node) -> string { } j = j + 1 } - strbuilder.append(sb, ")") + if g_dialect == METAL && !maker { + strbuilder.append(sb, "}") + } else { + strbuilder.append(sb, ")") + } return strbuilder.finish(sb) } @@ -1001,6 +1015,7 @@ fn glsl_name(n: string) -> string { return "${n}_" } if string.starts_with(n, "gl_") == 1 { return "${n}_" } + if g_dialect == METAL && (msl_reserved(n) || string.starts_with(n, "ae_") == 1) { return "${n}_" } return n } @@ -1105,6 +1120,37 @@ const CALL_GL[] = ["", "", "", "", "", "", "", "($0 * 0.5 + 0.5)", "", "", "", "", "", ""] +// CALL_MSL is Metal's form where it is not Vulkan's GLSL (""): a texture +// is sampled through the sampler beside it (`ae_smp_$0`, ae_smp_ and the +// texture's name), read with .read() where GLSL fetches, and a mat3 of a mat4 is the +// prelude's ae_mat3 (MSL has no such constructor); everything else is +// Vulkan's convention (a clip z of 0..1, a screen whose y runs down). +const CALL_MSL[] = ["float2", "float3", "float4", "$0.sample(ae_smp_$0, $1)", "", + "", "", "", "", "", + "", "", "", "", "", + "ae_frag_coord.xy", "$0.read(uint2(int2($1)), 0)", "$0.read(uint2(int2($1)), uint($2))", "", "", + "", "", "", "", "", + "", "", "", "", "", + "", "", "", "", "", + "float2(float($0.get_width()), float($0.get_height()))", "", "", "", "", + "", "", "$0.sample(ae_smp_$0, $1, level($2))", "$0.read(uint2(int2(float2($1, $2))), uint(int($3)))", "", + "", "", "", "", "", + "", "", "", "", "atan2", + "", "", "float2", "float3", "float4", + "", "", "", "(transpose(ae_mat3($0)) * $1)", "", + "$0.sample(ae_smp_$0, $1)", "", "float2x2", "", "", + "float3", "", "", "", "float4x4", + "float4x4", "", "(ae_mat3($0) * $1)", "$0.read(uint2(int2($1, $2)), 0)", "", + "int(ae_vertex_id)", "", "", + "discard_fragment()", "dfdx", "dfdy", "dfdx", "dfdy", + "dfdx", "dfdy", "", "$0.sample(ae_smp_$0, $1, gradient2d($2, $3))", "", + "", "", "", "", "", + "", "ae_mod", "ae_mod", "", "", + "", "rsqrt", "(float3x3($0, $1, $2) * $3)", "transpose(float4x4($0, $1, $2, $3))", "", + "", "", "", "", "", + "", "", "", + "", "", "", "", "", + "float3"] const CALL_ARGS: int[] = [-1, -1, -1, 2, 1, 2, 2, 2, 1, 1, 3, 3, 3, 1, 1, @@ -1151,6 +1197,7 @@ call_glsl_for(name: string, dialect: int) -> string { i = call_index(name) if i < 0 { return "" } if dialect == GL && CALL_GL[i] != "" { return CALL_GL[i] } + if dialect == METAL && CALL_MSL[i] != "" { return CALL_MSL[i] } return CALL_GLSL[i] } @@ -2307,7 +2354,7 @@ fn glsl_expr(t: quote.Tree, n: quote.Node, outer: int, right: int) -> string { } if k == quote.MAKE { return glsl_make(t, n) } if k == quote.INDEX { - return "${glsl_expr(t, quote.child(n, 0), PREC_POSTFIX, 0)}[${glsl_expr(t, quote.child(n, 1), 0, 0)}]" + return "${glsl_expr(t, quote.child(n, 0), PREC_POSTFIX, 0)}[${glsl_expr(t, quote.child(n, 1), 0, 0)}]${msl_lane_suffix(n)}" } if k == quote.CALL { why = call_refused(n) @@ -2337,16 +2384,24 @@ fn glsl_expr(t: quote.Tree, n: quote.Node, outer: int, right: int) -> string { strbuilder.append(sb, glsl_name(quote.name(n))) strbuilder.append(sb, "(") first = true + callee = helper_named(t, quote.name(n)) j = 0 while j < quote.child_count(n) { // An argument read in place is the helper's to read, not passed. if sub_of(quote.name(n), j) == null { if !first { strbuilder.append(sb, ", ") } first = false - strbuilder.append(sb, glsl_expr(t, quote.child(n, j), 0, 0)) + a = glsl_expr(t, quote.child(n, j), 0, 0) + strbuilder.append(sb, a) + // MSL: a texture goes with its sampler. + if g_dialect == METAL && (callee as ptr) != null && msl_has_sampler(quote.param_type(callee, j)) { + strbuilder.append(sb, ", ae_smp_${a}") + } } j = j + 1 } + // MSL: what it reads in place, handed on by name. + if g_dialect == METAL { strbuilder.append(sb, msl_root_args(quote.name(n), first)) } strbuilder.append(sb, ")") return strbuilder.finish(sb) } @@ -2400,7 +2455,7 @@ fn glsl_block(t: quote.Tree, sb: ptr, blk: quote.Node, depth: int, top: bool) { fn glsl_clause(t: quote.Tree, s: quote.Node) -> string { k = quote.kind(s) if k == quote.LET { - ty = glsl_type(quote.type_of(s)) + ty = decl_type(quote.type_of(s)) if ty == "" { refuse(t, s, "a local of type ${quote.type_of(s)} has no GLSL type") } return "${ty} ${glsl_name(quote.name(s))} = ${glsl_expr(t, quote.child(s, 0), 0, 0)}" } @@ -2444,6 +2499,10 @@ fn glsl_stmt(t: quote.Tree, sb: ptr, s: quote.Node, depth: int, last: bool) { strbuilder.append(sb, "return ${glsl_expr(t, quote.child(s, 0), 0, 0)};\n") return } + if g_dialect == METAL { + msl_return(t, sb, s, depth, last) + return + } if g_vertex_param != "" { // A vertex shader's return: its struct literal's fields, each to // its output (`position` to gl_Position). @@ -2557,24 +2616,33 @@ var g_pixel_param = "" fn emit_helpers(t: quote.Tree, sb: ptr) { subs_free() if g_emitting != null && (g_emitting as *GpuShader).in_place { subs_find(t) } + if g_dialect == METAL { msl_needs_find(t) } i = 0 while i < quote.helper_count(t) { h = quote.helper(t, i) - ret = glsl_type(quote.return_type(h)) + ret = decl_type(quote.return_type(h)) if ret == "" { refuse(t, quote.body(h), "${quote.function_name(h)} returns a ${quote.return_type(h)}, which GLSL has no type for") } - strbuilder.append(sb, "\n${ret} ${glsl_name(quote.function_name(h))}(") + if g_dialect == METAL { + strbuilder.append(sb, "\nstatic ${ret} ${glsl_name(quote.function_name(h))}(") + } else { + strbuilder.append(sb, "\n${ret} ${glsl_name(quote.function_name(h))}(") + } first = true k = 0 while k < quote.param_count(h) { if sub_of(quote.function_name(h), k) == null { if !first { strbuilder.append(sb, ", ") } first = false - pt = glsl_type(quote.param_type(h, k)) + pt = decl_type(quote.param_type(h, k)) if pt == "" { refuse(t, quote.body(h), "${quote.function_name(h)}'s ${quote.param_name(h, k)} is a ${quote.param_type(h, k)}, which GLSL has no type for") } strbuilder.append(sb, "${pt} ${glsl_name(quote.param_name(h, k))}") + if g_dialect == METAL && msl_has_sampler(quote.param_type(h, k)) { + strbuilder.append(sb, ", sampler ae_smp_${glsl_name(quote.param_name(h, k))}") + } } k = k + 1 } + if g_dialect == METAL { strbuilder.append(sb, msl_root_params(quote.function_name(h), first)) } strbuilder.append(sb, ") {\n") g_in_helper = true g_helper = h as ptr @@ -2726,7 +2794,7 @@ fn arg_key(t: quote.Tree, n: quote.Node, h: quote.Tree) -> (string, string) { } if k == quote.INDEX && quote.kind(quote.child(n, 1)) == quote.LIT { bk, bt = arg_key(t, quote.child(n, 0), h) - if string.starts_with(bk, "c:") == 1 { return "${bk}[${quote.lit(quote.child(n, 1))}]", "${bt}[${quote.lit(quote.child(n, 1))}]" } + if string.starts_with(bk, "c:") == 1 { return "${bk}[${quote.lit(quote.child(n, 1))}]", "${bt}[${quote.lit(quote.child(n, 1))}]${msl_lane_suffix(n)}" } return "", "" } if k == quote.LOCAL && (h as ptr) != null { @@ -2874,6 +2942,9 @@ fn emit_ready(s: *GpuShader) -> bool { // unit of its place among the textures), the varyings by name. const VULKAN = 0 const GL = 1 +// Metal's MSL (emit_msl, emit_vertex_msl): the third dialect, written from +// the same trees; see the MSL section below. +const METAL = 2 // The fragment shader's GLSL in `dialect`: the uniforms, the textures, // the parameter as its input, and FragColor. "" when the tree has @@ -3350,6 +3421,964 @@ fn screen_vertex_quote() -> *GpuShader { } } +// ── MSL ──────────────────────────────────────────────────────────────── +// +// Metal's shading language, written from the same trees as the GLSL +// (docs/aether_quote_design.md section 9, item 5), with Vulkan's +// conventions: a stored depth is clip z as it is (0..1), the screen's y runs +// down, and each vertex shader negates its position's y as it returns it, so +// a Metal renderer draws with Vulkan's matrices and viewports and the frame +// comes out the same way up. The expressions and statements are the GLSL +// emitter's, in the METAL dialect (the call table's CALL_MSL column, struct +// literals as aggregates); what differs is around them: +// +// - nothing is global in MSL: each uniform block is a struct handed to the +// entry function in a buffer, each texture with a sampler beside it +// (`ae_smp_name`), each storage buffer a pointer, and the entry's first +// lines name each capture as a reference into its block (`constant Eye& +// eye = ae_set1.eye`), so the body reads them by their GLSL names, where +// it uses them, as the GLSL does; +// - a helper that reads a value in place (read_in_place) is handed what it +// reads by name, after its own parameters, and so is every helper calling +// one (msl_needs_find); +// - a block's structs are laid out as std140 has them, member by member at +// the offsets ae3d.gpu gives them, padded between: a vec3 is a +// packed_float3 (MSL's float3 takes sixteen bytes), an array of scalars, +// vec2s or vec3s is one of float4s (std140's stride), read through its +// first lanes. Every offset and size is a static_assert in the text, so +// the compiler holds the struct to the block the renderer fills; +// - the vertex shader's attributes are its [[stage_in]] struct at +// ATTR_LOCATION's [[attribute(n)]] (a mat4 four float4s); its varyings a +// struct returned, `position` at [[position]], each other at +// [[user(locnK)]], K its location, which the pixel shader's [[stage_in]] +// reads (an int [[flat]]); a pixel shader's outputs [[color(k)]], its +// depth [[depth(any)]]. +// +// The bindings, one rule for every shader (a Metal renderer's to follow): +// the uniform block of Vulkan's set N is [[buffer(N)]] (a pixel shader's own +// Params set 0, a vertex shader's VertexParams vertex_set()); the storage +// buffers [[buffer(8 + k)]] in capture order; the textures [[texture(k)]] +// and their samplers [[sampler(k)]] in capture order (samplers_name); vertex +// buffers from [[buffer(16)]] on, which the renderer's vertex descriptor +// names. Entry functions are ae__vertex and ae__fragment. + +const MSL_STORAGE_BASE = 8 +const MSL_MAX_SET = 8 +const MSL_MAX_SAMPLERS = 16 + +// Words MSL (C++14 and Metal's own) keeps that GLSL does not, given a +// trailing underscore while MSL is written; a name beginning ae_ is MSL's +// emitter's own. +fn msl_reserved(n: string) -> bool { + return n == "constant" || n == "device" || n == "thread" || n == "threadgroup" || n == "threadgroup_imageblock" || + n == "ray_data" || n == "object_data" || n == "kernel" || n == "vertex" || n == "fragment" || n == "visible" || + n == "stage_in" || n == "bool" || n == "char" || n == "int" || n == "uint" || n == "uchar" || n == "ushort" || + n == "ulong" || n == "float" || n == "void" || n == "signed" || n == "auto" || n == "register" || n == "const" || + n == "mutable" || n == "struct" || n == "new" || n == "delete" || n == "operator" || n == "sizeof" || + n == "alignof" || n == "alignas" || n == "decltype" || n == "true" || n == "false" || n == "nullptr" || + n == "if" || n == "else" || n == "for" || n == "while" || n == "do" || n == "switch" || n == "case" || + n == "default" || n == "break" || n == "continue" || n == "return" || n == "try" || n == "catch" || + n == "throw" || n == "private" || n == "protected" || n == "friend" || n == "virtual" || n == "explicit" || + n == "export" || n == "import" || n == "module" || n == "concept" || n == "requires" || n == "noexcept" || + n == "constexpr" || n == "static_assert" || n == "thread_local" || n == "and" || n == "or" || n == "not" || + n == "xor" || n == "bitand" || n == "bitor" || n == "compl" || n == "and_eq" || n == "or_eq" || + n == "xor_eq" || n == "not_eq" || n == "typename" || n == "typeid" || n == "dynamic_cast" || + n == "static_cast" || n == "const_cast" || n == "reinterpret_cast" || n == "wchar_t" || n == "metal" || + n == "sampler" || n == "size_t" || n == "ptrdiff_t" || + // what CALL_MSL calls that GLSL does not: a local of the name would hide it + n == "level" || n == "gradient2d" || n == "atan2" || n == "rsqrt" || n == "dfdx" || n == "dfdy" || + n == "discard_fragment" || n == "float2" || n == "float3" || n == "float4" || n == "float2x2" || + n == "float3x3" || n == "float4x4" || n == "int2" || n == "int4" || n == "uint2" || n == "uint4" || + n == "packed_float3" +} + +// A tree's type as MSL spells it; "" for one it has no word for. +fn msl_type(t: string) -> string { + if t == "float" || t == "f32" { return "float" } + if t == "int" || t == "bool" { return t } + if t == "uint32" { return "uint" } + if t == "vec2" { return "float2" } + if t == "vec3" { return "float3" } + if t == "vec4" { return "float4" } + if t == "mat4" { return "float4x4" } + if t == "mat2" { return "float2x2" } + if t == "Texture" { return "texture2d" } + if t == "TextureMS" { return "texture2d_ms" } + if t == "Texture3D" { return "texture3d" } + if struct_named(t) != null { return t } + if g_emitting != null && element_of_type(g_emitting as *GpuShader, t) != null { return t } + return "" +} + +// The type a local, a helper's parameter or a tree struct's field is +// declared with, in the dialect being written. +fn decl_type(t: string) -> string { + if g_dialect == METAL { return msl_type(t) } + return glsl_type(t) +} + +// A value's type in a uniform block: std140's vec3 is twelve bytes on +// sixteen, a packed_float3 placed at its offset. +fn msl_block_type(t: string) -> string { + if t == "vec3" { return "packed_float3" } + return msl_type(t) +} + +// An array element's type in a uniform block: std140 gives each element +// sixteen bytes at least, so an array of scalars, vec2s or vec3s is one of +// four-lane vectors. +fn msl_element_type(t: string) -> string { + if t == "int" { return "int4" } + if t == "uint32" { return "uint4" } + if t == "mat4" { return "float4x4" } + return "float4" +} + +// Whether a texture of the type `t` goes with a sampler: a multisampled one +// is only read. +fn msl_has_sampler(t: string) -> bool { return t == "Texture" || t == "Texture3D" } + +// The lanes an element of an array of scalars, vec2s or vec3s is read +// through (".x", ".xy", ".xyz"), while MSL is written; "" otherwise. +fn msl_lane_suffix(n: quote.Node) -> string { + if g_dialect != METAL || quote.kind(n) != quote.INDEX { return "" } + at = quote.type_of(quote.child(n, 0)) + if array_count(at) < 1 { return "" } + el = array_element(at) + if el == "float" || el == "f32" || el == "int" || el == "uint32" { return ".x" } + if el == "vec2" { return ".xy" } + if el == "vec3" { return ".xyz" } + return "" +} + +// The @quotable function `name` (its tree name) among `t`'s helpers, or null. +fn helper_named(t: quote.Tree, name: string) -> quote.Tree { + i = 0 + while i < quote.helper_count(t) { + h = quote.helper(t, i) + if quote.function_name(h) == name { return h } + i = i + 1 + } + return null as quote.Tree +} + +// Whether `n` calls the shaderlib function `name`. +fn calls_builtin(n: quote.Node, name: string) -> bool { + if quote.kind(n) == quote.CALL && bare_call(quote.name(n)) == name { return true } + j = 0 + while j < quote.child_count(n) { + if calls_builtin(quote.child(n, j), name) { return true } + j = j + 1 + } + return false +} + +// Whether the tree, its body or any helper, calls `name`. +fn tree_calls(t: quote.Tree, name: string) -> bool { + if calls_builtin(quote.body(t), name) { return true } + i = 0 + while i < quote.helper_count(t) { + if calls_builtin(quote.body(quote.helper(t, i)), name) { return true } + i = i + 1 + } + return false +} + +// Whether `n` reads the local `name`. +fn reads_local(n: quote.Node, name: string) -> bool { + if quote.kind(n) == quote.LOCAL && quote.name(n) == name { return true } + j = 0 + while j < quote.child_count(n) { + if reads_local(quote.child(n, j), name) { return true } + j = j + 1 + } + return false +} + +// ── What a helper reads by name ── + +struct MslNeed { + helper: string + roots: ptr // list of *GpuField: the names, in the order first needed +} + +var g_needs: ptr = null + +fn needs_free() { + if g_needs == null { return } + k = 0 + while k < list.size(g_needs) { + nd = list.get_raw(g_needs, k) as *MslNeed + fields_free(nd.roots) + nd.helper = "" + heap.free(nd) + k = k + 1 + } + list.free(g_needs) + g_needs = null +} + +fn need_of(helper: string) -> *MslNeed { + if g_needs == null { return null } + k = 0 + while k < list.size(g_needs) { + nd = list.get_raw(g_needs, k) as *MslNeed + if nd.helper == helper { return nd } + k = k + 1 + } + return null +} + +fn need_add(nd: *MslNeed, root: string) { + if field_named(nd.roots, root) == null { list.add_raw(nd.roots, field_new(root, "", 0) as ptr) } +} + +// The callees of the helpers in `n`, each's needs added to `nd`. +fn need_callees(t: quote.Tree, n: quote.Node, h: quote.Tree, nd: *MslNeed) { + if quote.kind(n) == quote.HELPER_CALL { + cn = need_of(quote.name(n)) + if cn != null { + k = 0 + while k < list.size(cn.roots) { + need_add(nd, (list.get_raw(cn.roots, k) as *GpuField).name) + k = k + 1 + } + } + } + j = 0 + while j < quote.child_count(n) { + need_callees(t, quote.child(n, j), h, nd) + j = j + 1 + } +} + +// Each helper's names from main's scope (subs_find's values read in place, +// the pixel's position, the vertex's index) and its callees', callees first +// as the tree lists them. +fn msl_needs_find(t: quote.Tree) { + needs_free() + g_needs = list.new() + i = 0 + while i < quote.helper_count(t) { + h = quote.helper(t, i) + nd = heap.new(MslNeed) + nd.helper = quote.function_name(h) + nd.roots = list.new() + if g_subs != null { + k = 0 + while k < list.size(g_subs) { + u = list.get_raw(g_subs, k) as *GpuSub + if u.helper == nd.helper { need_add(nd, sub_root(u.text)) } + k = k + 1 + } + } + if calls_builtin(quote.body(h), "frag_coord") { need_add(nd, "ae_frag_coord") } + if calls_builtin(quote.body(h), "vertex_index") { need_add(nd, "ae_vertex_id") } + need_callees(t, quote.body(h), h, nd) + // Each is a parameter of that name: one of the helper's own, kept, + // or a local of it would be a second. + k = 0 + while k < list.size(nd.roots) { + r = (list.get_raw(nd.roots, k) as *GpuField).name + ri = param_index(h, r) + if (ri >= 0 && sub_of(nd.helper, ri) == null) || (local_named(quote.body(h), r) as ptr) != null { + refuse(t, quote.body(h), "${nd.helper} is handed ${r} by name in MSL (it or a helper it calls reads ${r} in place), but its own ${r} would hide it; name one of them apart") + } + k = k + 1 + } + list.add_raw(g_needs, nd as ptr) + i = i + 1 + } +} + +// The declaration of the name `r` from main's scope as a helper's parameter. +fn msl_root_decl(r: string) -> string { + if r == "ae_frag_coord" { return "float4 ae_frag_coord" } + if r == "ae_vertex_id" { return "uint ae_vertex_id" } + s = g_emitting as *GpuShader + t = s.tree + i = 0 + while i < quote.capture_count(t) { + c = quote.capture(t, i) + if glsl_name(quote.capture_name(c)) == r { + ct = quote.capture_type(c) + if is_texture(ct) { + if msl_has_sampler(ct) { return "${msl_type(ct)} ${r}, sampler ae_smp_${r}" } + return "${msl_type(ct)} ${r}" + } + if is_buffer(ct) { + if ct == "Vec4s" { return "const device float4* ${r}" } + return "const device float* ${r}" + } + if is_struct_capture(s, quote.capture_name(c)) { return "constant ${ct}& ${r}" } + return "constant ${msl_block_type(ct)}& ${r}" + } + i = i + 1 + } + if s.stage == FRAGMENT_STAGE { + if s.links != null { + k = 0 + while k < list.size(s.links) { + f = list.get_raw(s.links, k) as *GpuField + if glsl_name(f.name) == r { return "${msl_type(f.type)} ${r}" } + k = k + 1 + } + } else if glsl_name(quote.param_name(t, 0)) == r { + return "${msl_type(quote.param_type(t, 0))} ${r}" + } + } else { + a = 0 + while a < ATTR_COUNT { + if ATTR_GLSL[a] == r { + rd = first_attr_read(quote.body(t), ATTR_AETHER[a]) + if (rd as ptr) != null { return "${msl_type(quote.type_of(rd))} ${r}" } + } + a = a + 1 + } + } + refuse(t, quote.body(t), "a helper reads ${r} in place, which MSL cannot hand it") + return "float ${r}" +} + +// The parameters a helper is handed by name, after its own (`first`: it has +// none of its own written). +fn msl_root_params(helper: string, first: bool) -> string { + nd = need_of(helper) + if nd == null { return "" } + sb = strbuilder.new(64) + lead = !first + k = 0 + while k < list.size(nd.roots) { + if lead { strbuilder.append(sb, ", ") } + lead = true + strbuilder.append(sb, msl_root_decl((list.get_raw(nd.roots, k) as *GpuField).name)) + k = k + 1 + } + return strbuilder.finish(sb) +} + +// The arguments a call of `helper` hands it by name. +fn msl_root_args(helper: string, first: bool) -> string { + nd = need_of(helper) + if nd == null { return "" } + sb = strbuilder.new(64) + lead = !first + k = 0 + while k < list.size(nd.roots) { + r = (list.get_raw(nd.roots, k) as *GpuField).name + if lead { strbuilder.append(sb, ", ") } + lead = true + strbuilder.append(sb, r) + if string.ends_with(msl_root_decl(r), "ae_smp_${r}") == 1 { strbuilder.append(sb, ", ae_smp_${r}") } + k = k + 1 + } + return strbuilder.finish(sb) +} + +// ── Returns ── + +// A return from the entry function: each value to its output, the struct +// of them returned (a vertex shader's position y negated first, Vulkan's +// clip y to Metal's). +fn msl_return(t: quote.Tree, sb: ptr, s: quote.Node, depth: int, last: bool) { + v = quote.child(s, 0) + if g_vertex_param != "" || g_outputs { + if quote.kind(v) != quote.MAKE { + refuse(t, s, "a shader returns its varyings or outputs as a struct literal") + return + } + j = 0 + while j < quote.child_count(v) { + fv = quote.child(v, j) + if j > 0 { indent(sb, depth) } + value = glsl_expr(t, quote.child(fv, 0), 0, 0) + if g_vertex_param != "" { + strbuilder.append(sb, "ae_out.${msl_varying_name(quote.name(fv))} = ${value};\n") + } else { + strbuilder.append(sb, "ae_out.out_${quote.name(fv)} = ${msl_widened(quote.type_of(fv), value)};\n") + } + j = j + 1 + } + if g_vertex_param != "" { + indent(sb, depth) + strbuilder.append(sb, "ae_out.ae_position.y = -ae_out.ae_position.y;\n") + } + indent(sb, depth) + strbuilder.append(sb, "return ae_out;\n") + return + } + if g_cut { + if !last { + strbuilder.append(sb, "return;\n") + } else { + strbuilder.append(sb, "// kept\n") + } + return + } + if g_depth { + rt = quote.type_of(v) + if rt != "float" && rt != "f32" { + refuse(t, s, "a depth fragment shader returns the depth, a float, not a ${rt}") + return + } + strbuilder.append(sb, "ae_out.ae_depth = ${glsl_expr(t, v, 0, 0)};\n") + } else { + strbuilder.append(sb, "ae_out.FragColor = ${glsl_expr(t, v, 0, 0)};\n") + } + indent(sb, depth) + strbuilder.append(sb, "return ae_out;\n") +} + +// A colour output four lanes wide, as Metal holds an output to the +// attachment's channels (MoltenVK widens them alike): the lanes past the +// value's are 0. +fn msl_widened(t: string, value: string) -> string { + if t == "vec2" { return "float4(${value}, 0.0, 0.0)" } + if t == "vec3" { return "float4(${value}, 0.0)" } + if t == "float" || t == "f32" { return "float4(${value}, 0.0, 0.0, 0.0)" } + return value +} + +// A vertex shader's varying as its output struct's member. +fn msl_varying_name(field: string) -> string { + if field == "position" { return "ae_position" } + if field == g_varying_from { return glsl_name(g_varying_to) } + return glsl_name(field) +} + +// ── Structs laid out as std140 ── + +struct MslStruct { + name: string + body: ptr // strbuilder: the members + asserts: ptr // strbuilder: their static_asserts + at: int // where the next member would go + pads: int +} + +fn mstruct_new(name: string) -> *MslStruct { + ms = heap.new(MslStruct) + ms.name = name + ms.body = strbuilder.new(256) + ms.asserts = strbuilder.new(256) + ms.at = 0 + ms.pads = 0 + return ms +} + +fn mstruct_pad(ms: *MslStruct, to: int) { + if to > ms.at { + strbuilder.append(ms.body, " char ae_pad${ms.pads}[${to - ms.at}];\n") + ms.pads = ms.pads + 1 + ms.at = to + } +} + +// The member `decl` (its type and name, with any [count]) named `name` at +// `at`, `size` bytes; refused if it would overlap the one before. +fn mstruct_member(ms: *MslStruct, decl: string, name: string, at: int, size: int) { + if at < ms.at && g_error == "" { g_error = "MSL: ${ms.name}.${name} at ${at} overlaps the member before it" } + mstruct_pad(ms, at) + strbuilder.append(ms.body, " ${decl};\n") + strbuilder.append(ms.asserts, "static_assert(__builtin_offsetof(${ms.name}, ${name}) == ${at}, \"${ms.name}.${name}: std140\");\n") + ms.at = at + size +} + +// The struct closed at `size` bytes, written to `sb`, and freed. +fn mstruct_end(ms: *MslStruct, size: int, sb: ptr) { + mstruct_pad(ms, size) + body = strbuilder.finish(ms.body) + asserts = strbuilder.finish(ms.asserts) + strbuilder.append(sb, "\nstruct ${ms.name} {\n${body}};\n") + strbuilder.append(sb, "static_assert(sizeof(${ms.name}) == ${size}, \"${ms.name}: std140\");\n${asserts}") + ms.name = "" + heap.free(ms) +} + +// A captured struct's member (or an element's part) as a block member. +fn msl_member_of(ms: *MslStruct, m: *GpuMember) { + nm = glsl_name(m.name) + if m.parts != null { + mstruct_member(ms, "${m.type} ${nm}[${m.count}]", nm, m.placed, m.size * m.count) + } else if m.count > 0 { + stride = round_up(std140_size(m.type), 16) + mstruct_member(ms, "${msl_element_type(m.type)} ${nm}[${m.count}]", nm, m.placed, stride * m.count) + } else { + mstruct_member(ms, "${msl_block_type(m.type)} ${nm}", nm, m.placed, std140_size(m.type)) + } +} + +// The captured structs, and the structs their arrays are made of, each +// before any holding it; an element's struct with a maker, its literals' +// (an aggregate would fill its padding). +fn emit_msl_capture_structs(s: *GpuShader, sb: ptr) { + if s.members == null { return } + t = s.tree + i = 0 + while i < quote.capture_count(t) { + name = quote.capture_name(quote.capture(t, i)) + if is_struct_capture(s, name) { _size = struct_layout(s, name) } + i = i + 1 + } + declared = list.new() + k = 0 + while k < list.size(s.members) { + m = list.get_raw(s.members, k) as *GpuMember + if m.parts != null && m.type != "" && !type_declared(declared, m.type) { + list.add_raw(declared, m as ptr) + ms = mstruct_new(m.type) + j = 0 + while j < list.size(m.parts) { + p = list.get_raw(m.parts, j) as *GpuMember + if p.type != "" { msl_member_of(ms, p) } + j = j + 1 + } + mstruct_end(ms, m.size, sb) + if tree_makes(t, m.type) { emit_msl_maker(sb, m) } + } + k = k + 1 + } + list.free(declared) + i = 0 + while i < quote.capture_count(t) { + c = quote.capture(t, i) + name = quote.capture_name(c) + if is_struct_capture(s, name) { + size = struct_layout(s, name) + ms = mstruct_new(quote.capture_type(c)) + k = 0 + while k < list.size(s.members) { + m = list.get_raw(s.members, k) as *GpuMember + if m.capture == name && m.type != "" { msl_member_of(ms, m) } + k = k + 1 + } + mstruct_end(ms, size, sb) + } + i = i + 1 + } +} + +// Whether the tree, its body or a helper, builds a literal of the struct `st`. +fn tree_makes(t: quote.Tree, st: string) -> bool { + if makes(quote.body(t), st) { return true } + i = 0 + while i < quote.helper_count(t) { + if makes(quote.body(quote.helper(t, i)), st) { return true } + i = i + 1 + } + return false +} + +fn makes(n: quote.Node, st: string) -> bool { + if quote.kind(n) == quote.MAKE && quote.type_of(n) == st { return true } + j = 0 + while j < quote.child_count(n) { + if makes(quote.child(n, j), st) { return true } + j = j + 1 + } + return false +} + +// An element's struct's maker: what a literal of it is (an aggregate would +// fill its padding). +fn emit_msl_maker(sb: ptr, m: *GpuMember) { + strbuilder.append(sb, "static inline ${m.type} ae_make_${m.type}(") + j = 0 + first = true + while j < list.size(m.parts) { + p = list.get_raw(m.parts, j) as *GpuMember + if p.type != "" { + if !first { strbuilder.append(sb, ", ") } + first = false + strbuilder.append(sb, "${msl_type(p.type)} ${glsl_name(p.name)}") + } + j = j + 1 + } + strbuilder.append(sb, ") {\n ${m.type} ae_r;\n") + j = 0 + while j < list.size(m.parts) { + p = list.get_raw(m.parts, j) as *GpuMember + if p.type != "" { strbuilder.append(sb, " ae_r.${glsl_name(p.name)} = ${glsl_name(p.name)};\n") } + j = j + 1 + } + strbuilder.append(sb, " return ae_r;\n}\n") +} + +// The shader's own block (Params, VertexParams): its values and captured +// structs at params_offset's. +fn emit_msl_params(s: *GpuShader, sb: ptr, name: string) { + np = params_count(s) + if np == 0 { return } + ms = mstruct_new(name) + k = 0 + while k < np { + c = param_capture(s, k) + nm = params_name(s, k) + ty = msl_block_type(quote.capture_type(c)) + if is_struct_capture(s, quote.capture_name(c)) { ty = quote.capture_type(c) } + mstruct_member(ms, "${ty} ${nm}", nm, params_offset(s, k), param_size(s, k)) + k = k + 1 + } + mstruct_end(ms, params_size(s), sb) +} + +// The last entry of a layout the shader captures, or -1. +fn last_captured(s: *GpuShader, l: *GpuLayout) -> int { + last = 0 - 1 + k = 0 + while k < list.size(l.entries) { + e = list.get_raw(l.entries, k) as *GpuEntry + if (quote.capture_named(s.tree, e.name) as ptr) != null { last = k } + k = k + 1 + } + return last +} + +// The blocks of its layouts, each set's: the structs it captures at their +// places, the rest padding. +fn emit_msl_layout_blocks(s: *GpuShader, sb: ptr) { + if s.blocks == null { return } + set = 0 + while set < MSL_MAX_SET { + u = block_use(s, set) + if u != null { + l = u.layout as *GpuLayout + _size = layout_size(l) + last = last_captured(s, l) + if last >= 0 { + ms = mstruct_new(l.block) + end = 0 + k = 0 + while k <= last { + e = list.get_raw(l.entries, k) as *GpuEntry + if (quote.capture_named(s.tree, e.name) as ptr) != null { + mstruct_member(ms, "${e.type} ${glsl_name(e.name)}", glsl_name(e.name), e.placed, e.size) + end = e.placed + e.size + } + k = k + 1 + } + mstruct_end(ms, round_up(end, 16), sb) + } + } + set = set + 1 + } +} + +// ── The entry function's arguments and first lines ── + +// The blocks, textures and buffers as the entry's arguments, each after a +// ",\n " (`args`), and the references naming each capture (`lines`). +// `own` is the shader's own block's set (-1 for none). +fn msl_resources(s: *GpuShader, args: ptr, lines: ptr, own: int, own_name: string) { + t = s.tree + if params_count(s) > 0 { + if own >= MSL_MAX_SET { refuse(t, quote.body(t), "MSL: the shader's own block at set ${own}, past ${MSL_MAX_SET - 1}") } + strbuilder.append(args, ",\n constant ${own_name}& ae_params [[buffer(${own})]]") + k = 0 + while k < params_count(s) { + c = param_capture(s, k) + nm = params_name(s, k) + ty = msl_block_type(quote.capture_type(c)) + if is_struct_capture(s, quote.capture_name(c)) { ty = quote.capture_type(c) } + strbuilder.append(lines, " constant ${ty}& ${nm} = ae_params.${nm};\n") + k = k + 1 + } + } + if s.blocks != null { + k = 0 + while k < list.size(s.blocks) { + u = list.get_raw(s.blocks, k) as *GpuBlockUse + if u.set >= MSL_MAX_SET { refuse(t, quote.body(t), "MSL: a layout at set ${u.set}, past ${MSL_MAX_SET - 1}") } + if params_count(s) > 0 && u.set == own { refuse(t, quote.body(t), "MSL: the shader's own block and a layout both at set ${own}") } + k = k + 1 + } + set = 0 + while set < MSL_MAX_SET { + u = block_use(s, set) + if u != null { + l = u.layout as *GpuLayout + last = last_captured(s, l) + if last >= 0 { + strbuilder.append(args, ",\n constant ${l.block}& ae_set${set} [[buffer(${set})]]") + k = 0 + while k <= last { + e = list.get_raw(l.entries, k) as *GpuEntry + if (quote.capture_named(t, e.name) as ptr) != null { + strbuilder.append(lines, " constant ${e.type}& ${glsl_name(e.name)} = ae_set${set}.${glsl_name(e.name)};\n") + } + k = k + 1 + } + } + } + set = set + 1 + } + } + if samplers_count(s) > MSL_MAX_SAMPLERS { + refuse(t, quote.body(t), "MSL: ${samplers_count(s)} textures, past the ${MSL_MAX_SAMPLERS} samplers a Metal stage has") + } + k = 0 + while k < samplers_count(s) { + nm = samplers_name(s, k) + ct = quote.capture_type(quote.capture_named(t, sampler_capture_name(s, k))) + strbuilder.append(args, ",\n ${msl_type(ct)} ${nm} [[texture(${k})]]") + if msl_has_sampler(ct) { strbuilder.append(args, ", sampler ae_smp_${nm} [[sampler(${k})]]") } + k = k + 1 + } + k = 0 + while k < buffers_count(s) { + ty = "float" + if buffers_type(s, k) == "vec4" { ty = "float4" } + strbuilder.append(args, ",\n const device ${ty}* ${buffers_name(s, k)} [[buffer(${MSL_STORAGE_BASE + k})]]") + k = k + 1 + } +} + +fn msl_header(s: *GpuShader, sb: ptr) { + strbuilder.append(sb, "// ${s.name}: written by ae3d.gpu from its quote.\n") + strbuilder.append(sb, "#include \nusing namespace metal;\n") + // What MSL has no word for, where the tree calls it: a mat4's upper + // three columns, and GLSL's mod (x - y * floor(x / y), not fmod). + t = s.tree + if tree_calls(t, "mul_transpose3") || tree_calls(t, "mul_m3") { + strbuilder.append(sb, "static inline float3x3 ae_mat3(float4x4 m) { return float3x3(m[0].xyz, m[1].xyz, m[2].xyz); }\n") + } + if tree_calls(t, "mod") { strbuilder.append(sb, "static inline float ae_mod(float x, float y) { return x - y * floor(x / y); }\n") } + if tree_calls(t, "mod_v2") { strbuilder.append(sb, "static inline float2 ae_mod(float2 x, float y) { return x - y * floor(x / y); }\n") } +} + +// ── The entry points ── + +// The fragment shader as MSL: "" when the tree has something MSL is not +// written for (last_error says what and where). +emit_msl(s: *GpuShader) -> string { + if !emit_ready(s) { return "" } + g_emitting = s as ptr + g_dialect = METAL + text = emit_msl_pixel(s) + g_dialect = VULKAN + needs_free() + g_emitting = null + return text +} + +// The vertex shader `s` as MSL, the varying `from` written as `to` ("" for +// none), as emit_vertex_glsl. +emit_vertex_msl(s: *GpuShader, from: string, to: string) -> string { + if !emit_ready(s) { return "" } + g_emitting = s as ptr + g_dialect = METAL + text = emit_msl_vertex(s, from, to) + g_dialect = VULKAN + needs_free() + g_emitting = null + return text +} + +// The vertex shader a full-screen pass draws the shader `s` with, as MSL. +emit_screen_vertex_msl(s: *GpuShader) -> string { + if !emit_ready(s) { return "" } + t = s.tree + if s.links != null { return emit_vertex_msl(screen_vertex(), "", "") } + if glsl_type(quote.param_type(t, 0)) != "vec2" { + g_error = "${quote.source(t)}: a full-screen pass hands its shader a vec2" + return "" + } + return emit_vertex_msl(screen_vertex(), "uv", quote.param_name(t, 0)) +} + +fn emit_msl_vertex(s: *GpuShader, from: string, to: string) -> string { + t = s.tree + if s.stage != VERTEX_STAGE { + g_error = "${quote.source(t)}: ${s.name} is not a vertex shader" + return "" + } + mk = returned_make(quote.body(t)) + if (mk as ptr) == null { + g_error = "${quote.source(t)}: a vertex shader returns its varyings as a struct literal" + return "" + } + g_vertex_param = quote.param_name(t, 0) + unknown = unknown_attr(quote.body(t)) + if unknown != "" { + g_vertex_param = "" + g_error = "${quote.source(t)}: ${unknown} is not a vertex attribute ae3d.gpu's table has" + return "" + } + g_varying_from = from + g_varying_to = to + entry = "ae_${s.name}" + sb = strbuilder.new(4096) + msl_header(s, sb) + emit_msl_capture_structs(s, sb) + emit_msl_params(s, sb, "ae_VertexParams") + emit_msl_layout_blocks(s, sb) + // The attributes it reads, in the table's order, and the lines naming + // each as the GLSL does. + ins = strbuilder.new(256) + lines = strbuilder.new(1024) + has_in = false + i = 0 + while i < ATTR_COUNT { + rd = first_attr_read(quote.body(t), ATTR_AETHER[i]) + if (rd as ptr) != null { + has_in = true + ty = quote.type_of(rd) + nm = ATTR_GLSL[i] + if ty == "mat4" { + c = 0 + while c < 4 { + strbuilder.append(ins, " float4 ${nm}_${c} [[attribute(${ATTR_LOCATION[i] + c})]];\n") + c = c + 1 + } + strbuilder.append(lines, " float4x4 ${nm} = float4x4(ae_in.${nm}_0, ae_in.${nm}_1, ae_in.${nm}_2, ae_in.${nm}_3);\n") + } else { + strbuilder.append(ins, " ${msl_type(ty)} ${nm} [[attribute(${ATTR_LOCATION[i]})]];\n") + strbuilder.append(lines, " ${msl_type(ty)} ${nm} = ae_in.${nm};\n") + } + } + i = i + 1 + } + ins_text = strbuilder.finish(ins) + if has_in { strbuilder.append(sb, "\nstruct ${entry}_in {\n${ins_text}};\n") } + // Its varyings: position, then each at its location. + strbuilder.append(sb, "\nstruct ${entry}_out {\n float4 ae_position [[position]];\n") + loc = 0 + j = 0 + while j < quote.child_count(mk) { + fv = quote.child(mk, j) + if quote.name(fv) != "position" { + if quote.type_of(fv) == "bool" { refuse(t, fv, "a varying is not a bool in MSL; hand on an int") } + strbuilder.append(sb, " ${msl_type(quote.type_of(fv))} ${msl_varying_name(quote.name(fv))} [[user(locn${loc})]];\n") + loc = loc + 1 + } + j = j + 1 + } + strbuilder.append(sb, "};\n") + structs_of(t, quote.type_of(mk)) + emit_structs(t, sb) + emit_helpers(t, sb) + args = strbuilder.new(512) + if has_in { strbuilder.append(args, "\n ${entry}_in ae_in [[stage_in]]") } + msl_resources(s, args, lines, vertex_set(s), "ae_VertexParams") + if tree_calls(t, "vertex_index") { strbuilder.append(args, ",\n uint ae_vertex_id [[vertex_id]]") } + args_text = strbuilder.finish(args) + if !has_in && string.length(args_text) > 2 { args_text = string.substring(args_text, 1, string.length(args_text)) } + lines_text = strbuilder.finish(lines) + strbuilder.append(sb, "\nvertex ${entry}_out ${entry}_vertex(${args_text}) {\n ${entry}_out ae_out;\n${lines_text}") + glsl_block(t, sb, quote.body(t), 1, true) + strbuilder.append(sb, "}\n") + structs_free() + g_vertex_param = "" + g_varying_from = "" + g_varying_to = "" + text = strbuilder.finish(sb) + if g_error != "" { return "" } + return text +} + +fn emit_msl_pixel(s: *GpuShader) -> string { + t = s.tree + k = 0 + while k < buffers_count(s) { + if place_of(s, quote.capture_name(buffer_capture(s, k))) == null { + g_error = "${quote.source(t)}: ${s.name}: a pixel shader reads a storage buffer only where capture_binding places it" + return "" + } + k = k + 1 + } + entry = "ae_${s.name}" + sb = strbuilder.new(8192) + msl_header(s, sb) + emit_msl_capture_structs(s, sb) + emit_msl_params(s, sb, "ae_Params") + emit_msl_layout_blocks(s, sb) + outs = returned_make(quote.body(t)) + out_type = "" + if (outs as ptr) != null && !s.depth { out_type = quote.type_of(outs) } + structs_of(t, out_type) + emit_structs(t, sb) + // Its inputs: the pixel's position where it is read, and its varyings. + ins = strbuilder.new(256) + lines = strbuilder.new(1024) + has_in = false + if tree_calls(t, "frag_coord") { + has_in = true + strbuilder.append(ins, " float4 ae_frag_coord [[position]];\n") + strbuilder.append(lines, " float4 ae_frag_coord = ae_in.ae_frag_coord;\n") + } + if s.links != null { + k = 0 + while k < list.size(s.links) { + f = list.get_raw(s.links, k) as *GpuField + nm = glsl_name(f.name) + clash = local_named(quote.body(t), nm) + if (clash as ptr) != null { refuse(t, clash, "a local named ${quote.name(clash)} hides the varying of that name") } + flat = "" + if f.type == "int" { flat = " [[flat]]" } + strbuilder.append(ins, " ${msl_type(f.type)} ${nm} [[user(locn${f.location})]]${flat};\n") + strbuilder.append(lines, " ${msl_type(f.type)} ${nm} = ae_in.${nm};\n") + has_in = true + k = k + 1 + } + } else if reads_local(quote.body(t), quote.param_name(t, 0)) { + // A pass's coordinate, where it reads it (one working from + // frag_coord() does not). + nm = glsl_name(quote.param_name(t, 0)) + ty = msl_type(quote.param_type(t, 0)) + strbuilder.append(ins, " ${ty} ${nm} [[user(locn0)]];\n") + strbuilder.append(lines, " ${ty} ${nm} = ae_in.${nm};\n") + has_in = true + } + ins_text = strbuilder.finish(ins) + if has_in { strbuilder.append(sb, "\nstruct ${entry}_in {\n${ins_text}};\n") } + // Its outputs. + ret = "void" + if !s.cut { + ret = "${entry}_frag_out" + strbuilder.append(sb, "\nstruct ${entry}_frag_out {\n") + if s.depth { + strbuilder.append(sb, " float ae_depth [[depth(any)]];\n") + } else if out_type != "" { + j = 0 + while j < quote.child_count(outs) { + fv = quote.child(outs, j) + ot = quote.type_of(fv) + if ot != "vec4" && msl_widened(ot, "") == "" { refuse(t, fv, "a pixel shader's output ${quote.name(fv)} is a ${ot}; MSL writes a float, a vector or a vec4") } + strbuilder.append(sb, " float4 out_${quote.name(fv)} [[color(${j})]];\n") + j = j + 1 + } + } else { + strbuilder.append(sb, " float4 FragColor [[color(0)]];\n") + } + strbuilder.append(sb, "};\n") + } + emit_helpers(t, sb) + // Its varyings are its inputs in main, not in its helpers. + if s.links != null { g_pixel_param = quote.param_name(t, 0) } + args = strbuilder.new(512) + if has_in { strbuilder.append(args, "\n ${entry}_in ae_in [[stage_in]]") } + msl_resources(s, args, lines, 0, "ae_Params") + args_text = strbuilder.finish(args) + if !has_in && string.length(args_text) > 2 { args_text = string.substring(args_text, 1, string.length(args_text)) } + lines_text = strbuilder.finish(lines) + strbuilder.append(sb, "\nfragment ${ret} ${entry}_fragment(${args_text}) {\n") + if !s.cut { strbuilder.append(sb, " ${ret} ae_out;\n") } + strbuilder.append(sb, lines_text) + g_depth = s.depth + g_cut = s.cut + g_outputs = out_type != "" + glsl_block(t, sb, quote.body(t), 1, true) + g_depth = false + g_cut = false + g_outputs = false + g_pixel_param = "" + structs_free() + strbuilder.append(sb, "}\n") + text = strbuilder.finish(sb) + if g_error != "" { return "" } + return text +} + // ── The CPU back end ─────────────────────────────────────────────────── // // Its own lane-wise builtins in doubles, not ae3d.shaderlib's: a CALL diff --git a/tests/test_gpu_msl.ae b/tests/test_gpu_msl.ae new file mode 100644 index 00000000..3d94a2a6 --- /dev/null +++ b/tests/test_gpu_msl.ae @@ -0,0 +1,252 @@ +// ae3d.gpu's MSL (the METAL dialect: emit_msl, emit_vertex_msl, +// emit_screen_vertex_msl), on a pair of its own; no GPU. What is checked here +// is what the MSL is made of, where it departs from the GLSL; that it +// compiles is scripts/msl_check.sh's (Metal's compiler, every warning an +// error), over this pair too when AE3D_MSL_OUT names a directory to write it +// to, and over every quoted shader the renderers draw (src/ae3d/metal, held +// current by tools/generate_shaders.ae --check). +// +// - a captured struct in the shader's own block is laid out as std140 has +// it: a vec3 a packed_float3, an array of floats one of float4s read +// through .x, an array of structs (its element a struct of its own with a +// maker for its literals); each member's offset a static_assert, at the +// offsets the GLSL's block has (uniforms_offset); +// - a helper that reads a capture in place is handed it by name, and so is +// a helper calling it; a texture handed to a helper goes with its +// sampler; a helper whose own name would hide what it is handed is +// refused (MSL only: GLSL reads the capture as a global); +// - the vertex shader: its attributes at the table's [[attribute(n)]] (a +// mat4 four float4s), its varyings at [[user(locnK)]], its position's y +// negated as it returns it; the pixel shader reads an int varying +// [[flat]], writes each output four lanes wide at [[color(k)]]; +// - names MSL keeps (constant, level) given an underscore, in MSL alone; +// - writing MSL leaves the GLSL and OpenGL's uniform names as they were. +import std.os +import std.file +import std.string +import std.quote +import ae3d.gpu +import ae3d.gpu (GpuShader, vertex) +import ae3d.shaderlib (vec2, vec3, vec4, mat4, Texture, texture, rgb, mul, splat3, xyz, float_of) + +extern exit(code: int) + +var g_checks = 0 +var g_failures = 0 + +check(name: string, ok: bool) { + g_checks = g_checks + 1 + if ok { + println(" ok ${name}") + } else { + g_failures = g_failures + 1 + println(" FAIL ${name}") + } +} + +has(text: string, part: string) -> bool { return string.contains(text, part) != 0 } + +// ── The captures ────────────────────────────────────────────────────── + +struct Bulb { + position: vec3 + power: float +} + +struct Lamp { + colour: vec3 + gain: float + level: float + taps: float[4] + bulbs: Bulb[2] +} + +struct MslVertex { + position: vec3 + uv: vec2 + instance_model: mat4 +} + +struct MslVaryings { + position: vec4 + Coord: vec2 + Level: int +} + +struct MslOut { + colour: vec4 + motion: vec2 +} + +// ── The helpers ─────────────────────────────────────────────────────── + +// Read in place: every call hands it the lamp's colour and gain. +@quotable +lamp_tint(c: vec3, gain: float) -> vec3 { return c * gain } + +// Calls one that reads in place: handed the lamp too. +@quotable +lamp_twice(c: vec3, gain: float) -> vec3 { return lamp_tint(c, gain) * 2.0 } + +// Handed two textures, so its own parameter: with its sampler. +@quotable +tap(t: Texture, uv: vec2) -> vec4 { return texture(t, uv) } + +// A bulb, whole: an element of the captured array. +@quotable +bulb_power(b: Bulb) -> float { return b.power } + +// Its own `lamp` would hide the lamp its callee reads in place. +@quotable +shadowed(lamp: float, gain: float) -> float { return lamp + lamp_tint(splat3(1.0), gain).x } + +place(s: *GpuShader) { + _c = gpu.capture_field(s, "lamp", "colour", offsetof(Lamp, colour)) + _g = gpu.capture_field(s, "lamp", "gain", offsetof(Lamp, gain)) + _l = gpu.capture_field(s, "lamp", "level", offsetof(Lamp, level)) + _t = gpu.capture_field(s, "lamp", "taps", offsetof(Lamp, taps)) + _b = gpu.capture_field(s, "lamp", "bulbs", offsetof(Lamp, bulbs)) + _p = gpu.capture_element_field(s, "lamp", "bulbs", sizeof(Bulb), "position", offsetof(Bulb, position)) + _w = gpu.capture_element_field(s, "lamp", "bulbs", sizeof(Bulb), "power", offsetof(Bulb, power)) +} + +msl_vertex(lift: float) -> *GpuShader { + return vertex("msl_vertex") quote | v: MslVertex | { + p = mul(v.instance_model, vec4(v.position, 1.0)) + return MslVaryings { position: vec4(xyz(p), p.w + lift), Coord: v.uv, Level: 2 } + } +} + +msl_pixel(lamp: Lamp, screen: Texture, glow: Texture, device: float) -> *GpuShader { + s = gpu.fragment("msl_pixel") quote | f: MslVaryings | { + constant = lamp.taps[1] + level = float_of(f.Level) + lamp.level * device + lit = lamp_twice(lamp.colour, lamp.gain) + lamp_tint(lamp.colour, lamp.gain) + made = Bulb { position: lamp.bulbs[0].position, power: constant } + c = rgb(tap(screen, f.Coord)) + rgb(tap(glow, f.Coord)) + lit * (level + bulb_power(made) + bulb_power(lamp.bulbs[1])) + return MslOut { colour: vec4(c, constant), motion: f.Coord } + } + place(s) + gpu.read_in_place(s) + return s +} + +hidden_pixel(lamp: Lamp) -> *GpuShader { + s = gpu.fragment("msl_hidden") quote | uv: vec2 | { + return vec4(splat3(shadowed(uv.x, lamp.gain) + shadowed(uv.y, lamp.gain)), 1.0) + } + place(s) + gpu.read_in_place(s) + return s +} + +// The offset of OpenGL's value `name` in the shader's block, -1 for none. +leaf(s: *GpuShader, name: string) -> int { + k = 0 + while k < gpu.uniforms_count(s) { + if gpu.uniforms_name(s, k) == name { return gpu.uniforms_offset(s, k) } + k = k + 1 + } + return 0 - 1 +} + +uniform_names(s: *GpuShader) -> string { + out = "" + k = 0 + while k < gpu.uniforms_count(s) { + out = "${out}${gpu.uniforms_name(s, k)}@${gpu.uniforms_offset(s, k)} " + k = k + 1 + } + return out +} + +write_out(name: string, text: string) { + dir = os.getenv("AE3D_MSL_OUT") + if dir == null { return } + _w = file.write("${dir}/${name}", text) +} + +main() { + lamp = Lamp { gain: 1.0 } + screen = Texture { image: null } + glow = Texture { image: null } + vs = msl_vertex(0.0) + ps = msl_pixel(lamp, screen, glow, 0.5) + check("the pair links", gpu.link(vs, ps)) + + // What the GLSL is before any MSL is written, to hold it to after. + vk_before = gpu.emit_glsl(ps, gpu.VULKAN) + gl_before = gpu.emit_glsl(ps, gpu.GL) + vert_before = gpu.emit_vertex_glsl(vs, gpu.VULKAN, "", "") + names_before = uniform_names(msl_pixel(lamp, screen, glow, 0.5)) + check("the GLSL is written", vk_before != "" && gl_before != "" && vert_before != "") + + frag = gpu.emit_msl(ps) + vert = gpu.emit_vertex_msl(vs, "", "") + // A shader made straight after the MSL was written, its names made then. + names_after = uniform_names(msl_pixel(lamp, screen, glow, 0.5)) + if frag == "" || vert == "" { println(" ${gpu.last_error()}") } + check("the MSL is written", frag != "" && vert != "") + write_out("msl_pixel.frag.metal", frag) + write_out("msl_vertex.vert.metal", vert) + + println("std140 in MSL") + check("a vec3 in a block is a packed_float3", has(frag, " packed_float3 colour;\n")) + check("an array of floats is one of float4s", has(frag, " float4 taps[4];\n")) + check("and is read through its first lane", has(frag, "float constant_ = lamp.taps[1].x;")) + check("an array of structs is one of its element's struct", has(frag, " Bulb bulbs[2];\n") && has(frag, "\nstruct Bulb {\n packed_float3 position;\n")) + check("a literal of the element is its maker's", has(frag, "ae_make_Bulb(lamp.bulbs[0].position, constant_)")) + check("the struct is the shader's own block's, by reference", has(frag, "constant Lamp& lamp = ae_params.lamp;") && + has(frag, "constant ae_Params& ae_params [[buffer(0)]]")) + // Every offset the GLSL's block has is the MSL's, asserted. + base = leaf(ps, "lamp.colour") + check("lamp sits at the block's start", base == 0 && has(frag, "__builtin_offsetof(ae_Params, lamp) == 0,")) + check("gain where the GLSL has it", has(frag, "__builtin_offsetof(Lamp, gain) == ${leaf(ps, "lamp.gain") - base},")) + check("taps where the GLSL has them", has(frag, "__builtin_offsetof(Lamp, taps) == ${leaf(ps, "lamp.taps[0]") - base},")) + bulbs = leaf(ps, "lamp.bulbs[0].position") + check("bulbs where the GLSL has them", has(frag, "__builtin_offsetof(Lamp, bulbs) == ${bulbs - base},")) + check("a bulb's power where the GLSL has it", has(frag, "__builtin_offsetof(Bulb, power) == ${leaf(ps, "lamp.bulbs[0].power") - bulbs},")) + check("a bulb's size the GLSL's stride", has(frag, "sizeof(Bulb) == ${leaf(ps, "lamp.bulbs[1].position") - bulbs},")) + + println("helpers") + check("one reading in place is handed the lamp by name", + has(frag, "lamp_tint(constant Lamp& lamp)") && has(frag, "lamp_tint(lamp)")) + check("and so is one calling it", has(frag, "lamp_twice(constant Lamp& lamp)") && has(frag, "lamp_twice(lamp)")) + // (Its uv is the varying every call hands it, read in place: handed + // on by name too.) + check("a texture goes with its sampler", has(frag, "tap(texture2d t, sampler ae_smp_t, float2 Coord)") && + has(frag, "tap(screen, ae_smp_screen, Coord)") && has(frag, "t.sample(ae_smp_t, Coord)")) + check("and the entry is handed each at its index", + has(frag, "texture2d screen [[texture(0)]], sampler ae_smp_screen [[sampler(0)]]") && + has(frag, "texture2d glow [[texture(1)]], sampler ae_smp_glow [[sampler(1)]]")) + check("helpers are the file's own", has(frag, "\nstatic float3 ")) + hidden = gpu.emit_msl(hidden_pixel(lamp)) + check("a helper's own name hiding what it is handed is refused", hidden == "" && has(gpu.last_error(), "would hide it")) + check("which the GLSL is not", gpu.emit_glsl(hidden_pixel(lamp), gpu.VULKAN) != "") + + println("the stages") + check("a mat4 attribute is four float4s at its locations", + has(vert, "float4 instanceModel_0 [[attribute(3)]];") && has(vert, "float4 instanceModel_3 [[attribute(6)]];") && + has(vert, "float4x4 instanceModel = float4x4(ae_in.instanceModel_0, ae_in.instanceModel_1, ae_in.instanceModel_2, ae_in.instanceModel_3);")) + check("the others at the table's", has(vert, "float3 inPosition [[attribute(0)]];") && has(vert, "float2 inTexCoord [[attribute(1)]];")) + check("the varyings at their locations", has(vert, "float2 Coord [[user(locn0)]];") && has(vert, "int Level [[user(locn1)]];")) + check("the position's y negated as it is returned", + has(vert, " ae_out.ae_position.y = -ae_out.ae_position.y;\n return ae_out;\n")) + check("the pixel shader reads an int flat", has(frag, "int Level [[user(locn1)]] [[flat]];")) + check("its outputs four lanes wide", has(frag, "float4 out_motion [[color(1)]];") && has(frag, "ae_out.out_motion = float4(Coord, 0.0, 0.0);")) + check("names MSL keeps given an underscore", has(frag, "float level_ = float(Level) + lamp.level_ * device_;") && + has(frag, "constant float& device_ = ae_params.device_;")) + + println("the GLSL as it was") + check("Vulkan's", gpu.emit_glsl(ps, gpu.VULKAN) == vk_before) + check("OpenGL's", gpu.emit_glsl(ps, gpu.GL) == gl_before) + check("the vertex shader's", gpu.emit_vertex_glsl(vs, gpu.VULKAN, "", "") == vert_before) + // device, a word MSL keeps, still OpenGL's device. + check("OpenGL's uniform names", names_after == names_before && has(names_before, " device@")) + + if g_failures > 0 { + println("gpu_msl: ${g_failures} of ${g_checks} checks failed") + exit(1) + } + println("gpu_msl: all checks passed (${g_checks})") +} From af9665e32b49da73186e1066a3334b273f19fa87 Mon Sep 17 00:00:00 2001 From: paul Date: Tue, 20 Oct 2026 23:43:14 +0100 Subject: [PATCH 2/5] generate_shaders: the quoted shaders' MSL in src/ae3d/metal, held by --check Every quoted shader's stages written as MSL beside their GLSL, 41 files, which --check compares byte for byte as it does the GLSL (95 generated files now). The GLSL, ae3d.vkscene and ae3d.vkspirv are unchanged. Co-Authored-By: Claude Opus 5.5 --- src/ae3d/metal/bloom_down_quoted.frag.metal | 83 + src/ae3d/metal/bloom_down_quoted.vert.metal | 24 + src/ae3d/metal/bloom_up_quoted.frag.metal | 42 + src/ae3d/metal/bloom_up_quoted.vert.metal | 24 + src/ae3d/metal/cloud_quoted.frag.metal | 383 ++++ src/ae3d/metal/cloud_quoted.vert.metal | 24 + .../metal/color_resolve_quoted.frag.metal | 42 + .../metal/color_resolve_quoted.vert.metal | 24 + src/ae3d/metal/composite_quoted.frag.metal | 135 ++ src/ae3d/metal/composite_quoted.vert.metal | 24 + src/ae3d/metal/crowd_depth_quoted.vert.metal | 98 + src/ae3d/metal/crowd_pull_quoted.vert.metal | 188 ++ src/ae3d/metal/crowd_quoted.vert.metal | 193 ++ src/ae3d/metal/depth_copy_quoted.frag.metal | 20 + src/ae3d/metal/depth_copy_quoted.vert.metal | 24 + .../metal/depth_resolve_quoted.frag.metal | 35 + .../metal/depth_resolve_quoted.vert.metal | 24 + src/ae3d/metal/fxaa_quoted.frag.metal | 70 + src/ae3d/metal/fxaa_quoted.vert.metal | 24 + .../metal/lit_depth_plain_quoted.vert.metal | 187 ++ src/ae3d/metal/lit_depth_quoted.frag.metal | 123 ++ src/ae3d/metal/lit_depth_quoted.vert.metal | 189 ++ src/ae3d/metal/lit_plain_quoted.vert.metal | 263 +++ src/ae3d/metal/lit_quoted.frag.metal | 1589 +++++++++++++++++ src/ae3d/metal/lit_quoted.vert.metal | 267 +++ src/ae3d/metal/sea_quoted.frag.metal | 497 ++++++ src/ae3d/metal/sea_quoted.vert.metal | 166 ++ src/ae3d/metal/sky_capture_quoted.frag.metal | 385 ++++ src/ae3d/metal/sky_capture_quoted.vert.metal | 24 + src/ae3d/metal/sky_filter_quoted.frag.metal | 120 ++ src/ae3d/metal/sky_filter_quoted.vert.metal | 24 + .../metal/sky_irradiance_quoted.frag.metal | 118 ++ .../metal/sky_irradiance_quoted.vert.metal | 24 + src/ae3d/metal/sky_quoted.frag.metal | 228 +++ src/ae3d/metal/sky_quoted.vert.metal | 41 + src/ae3d/metal/ssao_quoted.frag.metal | 114 ++ src/ae3d/metal/ssao_quoted.vert.metal | 24 + src/ae3d/metal/ssr_quoted.frag.metal | 156 ++ src/ae3d/metal/ssr_quoted.vert.metal | 24 + src/ae3d/metal/taa_quoted.frag.metal | 75 + src/ae3d/metal/taa_quoted.vert.metal | 24 + tools/generate_shaders.ae | 33 +- 42 files changed, 6175 insertions(+), 1 deletion(-) create mode 100644 src/ae3d/metal/bloom_down_quoted.frag.metal create mode 100644 src/ae3d/metal/bloom_down_quoted.vert.metal create mode 100644 src/ae3d/metal/bloom_up_quoted.frag.metal create mode 100644 src/ae3d/metal/bloom_up_quoted.vert.metal create mode 100644 src/ae3d/metal/cloud_quoted.frag.metal create mode 100644 src/ae3d/metal/cloud_quoted.vert.metal create mode 100644 src/ae3d/metal/color_resolve_quoted.frag.metal create mode 100644 src/ae3d/metal/color_resolve_quoted.vert.metal create mode 100644 src/ae3d/metal/composite_quoted.frag.metal create mode 100644 src/ae3d/metal/composite_quoted.vert.metal create mode 100644 src/ae3d/metal/crowd_depth_quoted.vert.metal create mode 100644 src/ae3d/metal/crowd_pull_quoted.vert.metal create mode 100644 src/ae3d/metal/crowd_quoted.vert.metal create mode 100644 src/ae3d/metal/depth_copy_quoted.frag.metal create mode 100644 src/ae3d/metal/depth_copy_quoted.vert.metal create mode 100644 src/ae3d/metal/depth_resolve_quoted.frag.metal create mode 100644 src/ae3d/metal/depth_resolve_quoted.vert.metal create mode 100644 src/ae3d/metal/fxaa_quoted.frag.metal create mode 100644 src/ae3d/metal/fxaa_quoted.vert.metal create mode 100644 src/ae3d/metal/lit_depth_plain_quoted.vert.metal create mode 100644 src/ae3d/metal/lit_depth_quoted.frag.metal create mode 100644 src/ae3d/metal/lit_depth_quoted.vert.metal create mode 100644 src/ae3d/metal/lit_plain_quoted.vert.metal create mode 100644 src/ae3d/metal/lit_quoted.frag.metal create mode 100644 src/ae3d/metal/lit_quoted.vert.metal create mode 100644 src/ae3d/metal/sea_quoted.frag.metal create mode 100644 src/ae3d/metal/sea_quoted.vert.metal create mode 100644 src/ae3d/metal/sky_capture_quoted.frag.metal create mode 100644 src/ae3d/metal/sky_capture_quoted.vert.metal create mode 100644 src/ae3d/metal/sky_filter_quoted.frag.metal create mode 100644 src/ae3d/metal/sky_filter_quoted.vert.metal create mode 100644 src/ae3d/metal/sky_irradiance_quoted.frag.metal create mode 100644 src/ae3d/metal/sky_irradiance_quoted.vert.metal create mode 100644 src/ae3d/metal/sky_quoted.frag.metal create mode 100644 src/ae3d/metal/sky_quoted.vert.metal create mode 100644 src/ae3d/metal/ssao_quoted.frag.metal create mode 100644 src/ae3d/metal/ssao_quoted.vert.metal create mode 100644 src/ae3d/metal/ssr_quoted.frag.metal create mode 100644 src/ae3d/metal/ssr_quoted.vert.metal create mode 100644 src/ae3d/metal/taa_quoted.frag.metal create mode 100644 src/ae3d/metal/taa_quoted.vert.metal diff --git a/src/ae3d/metal/bloom_down_quoted.frag.metal b/src/ae3d/metal/bloom_down_quoted.frag.metal new file mode 100644 index 00000000..25e33a92 --- /dev/null +++ b/src/ae3d/metal/bloom_down_quoted.frag.metal @@ -0,0 +1,83 @@ +// bloom_down: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_Params { + float2 texel; + int first; + float exposure; + float threshold; + char ae_pad0[12]; +}; +static_assert(sizeof(ae_Params) == 32, "ae_Params: std140"); +static_assert(__builtin_offsetof(ae_Params, texel) == 0, "ae_Params.texel: std140"); +static_assert(__builtin_offsetof(ae_Params, first) == 8, "ae_Params.first: std140"); +static_assert(__builtin_offsetof(ae_Params, exposure) == 12, "ae_Params.exposure: std140"); +static_assert(__builtin_offsetof(ae_Params, threshold) == 16, "ae_Params.threshold: std140"); + +struct ae_bloom_down_in { + float2 uv [[user(locn0)]]; +}; + +struct ae_bloom_down_frag_out { + float4 FragColor [[color(0)]]; +}; + +static float3 ae3d_shaders_bloom_tap(texture2d screen, sampler ae_smp_screen, float2 uv, float2 texel, float2 offset) { + return screen.sample(ae_smp_screen, (uv + offset * texel)).rgb; +} + +static float ae3d_shaders_bloom_luma(float3 c) { + return dot(c, float3(0.2126, 0.7152, 0.0722)); +} + +static float3 ae3d_shaders_bloom_group(int first, float3 a, float3 b, float3 c, float3 d) { + if (first == 0) { + return (a + b + c + d) * 0.25; + } + float wa = 1.0 / (1.0 + ae3d_shaders_bloom_luma(a)); + float wb = 1.0 / (1.0 + ae3d_shaders_bloom_luma(b)); + float wc = 1.0 / (1.0 + ae3d_shaders_bloom_luma(c)); + float wd = 1.0 / (1.0 + ae3d_shaders_bloom_luma(d)); + return (a * wa + b * wb + c * wc + d * wd) / (wa + wb + wc + wd); +} + +static float3 ae3d_shaders_bloom_past_threshold(float3 c, float exposure, float threshold) { + float light = ae3d_shaders_bloom_luma(c) * exposure; + float knee = max(threshold * 0.5, 0.0001); + float soft = clamp(light - threshold + knee, 0.0, 2.0 * knee); + soft = soft * soft / (4.0 * knee); + float over = max(soft, light - threshold) / max(light, 0.0001); + return c * over; +} + +fragment ae_bloom_down_frag_out ae_bloom_down_fragment( + ae_bloom_down_in ae_in [[stage_in]], + constant ae_Params& ae_params [[buffer(0)]], + texture2d screen [[texture(0)]], sampler ae_smp_screen [[sampler(0)]]) { + ae_bloom_down_frag_out ae_out; + float2 uv = ae_in.uv; + constant float2& texel = ae_params.texel; + constant int& first = ae_params.first; + constant float& exposure = ae_params.exposure; + constant float& threshold = ae_params.threshold; + float3 a = ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(-2.0, -2.0)); + float3 b = ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(0.0, -2.0)); + float3 c = ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(2.0, -2.0)); + float3 d = ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(-1.0, -1.0)); + float3 e = ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(1.0, -1.0)); + float3 f = ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(-2.0, 0.0)); + float3 g = ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(0.0, 0.0)); + float3 h = ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(2.0, 0.0)); + float3 i = ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(-1.0, 1.0)); + float3 j = ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(1.0, 1.0)); + float3 k = ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(-2.0, 2.0)); + float3 l = ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(0.0, 2.0)); + float3 m = ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(2.0, 2.0)); + float3 sum = ae3d_shaders_bloom_group(first, d, e, i, j) * 0.5 + ae3d_shaders_bloom_group(first, a, b, f, g) * 0.125 + ae3d_shaders_bloom_group(first, b, c, g, h) * 0.125 + ae3d_shaders_bloom_group(first, f, g, k, l) * 0.125 + ae3d_shaders_bloom_group(first, g, h, l, m) * 0.125; + if (first != 0) { + sum = ae3d_shaders_bloom_past_threshold(sum, exposure, threshold); + } + ae_out.FragColor = float4(sum, 1.0); + return ae_out; +} diff --git a/src/ae3d/metal/bloom_down_quoted.vert.metal b/src/ae3d/metal/bloom_down_quoted.vert.metal new file mode 100644 index 00000000..4f941c3f --- /dev/null +++ b/src/ae3d/metal/bloom_down_quoted.vert.metal @@ -0,0 +1,24 @@ +// screen: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_screen_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; +}; + +struct ae_screen_out { + float4 ae_position [[position]]; + float2 uv [[user(locn0)]]; +}; + +vertex ae_screen_out ae_screen_vertex( + ae_screen_in ae_in [[stage_in]]) { + ae_screen_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + ae_out.ae_position = float4(float3(inPosition.x, inPosition.y, 0.0), 1.0); + ae_out.uv = inTexCoord; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/bloom_up_quoted.frag.metal b/src/ae3d/metal/bloom_up_quoted.frag.metal new file mode 100644 index 00000000..5c319287 --- /dev/null +++ b/src/ae3d/metal/bloom_up_quoted.frag.metal @@ -0,0 +1,42 @@ +// bloom_up: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_Params { + float2 texel; + float top; + char ae_pad0[4]; +}; +static_assert(sizeof(ae_Params) == 16, "ae_Params: std140"); +static_assert(__builtin_offsetof(ae_Params, texel) == 0, "ae_Params.texel: std140"); +static_assert(__builtin_offsetof(ae_Params, top) == 8, "ae_Params.top: std140"); + +struct ae_bloom_up_in { + float2 uv [[user(locn0)]]; +}; + +struct ae_bloom_up_frag_out { + float4 FragColor [[color(0)]]; +}; + +static float3 ae3d_shaders_bloom_tap(texture2d screen, sampler ae_smp_screen, float2 uv, float2 texel, float2 offset) { + return screen.sample(ae_smp_screen, (uv + offset * texel)).rgb; +} + +fragment ae_bloom_up_frag_out ae_bloom_up_fragment( + ae_bloom_up_in ae_in [[stage_in]], + constant ae_Params& ae_params [[buffer(0)]], + texture2d screen [[texture(0)]], sampler ae_smp_screen [[sampler(0)]], + texture2d level_ [[texture(1)]], sampler ae_smp_level_ [[sampler(1)]]) { + ae_bloom_up_frag_out ae_out; + float2 uv = ae_in.uv; + constant float2& texel = ae_params.texel; + constant float& top = ae_params.top; + float3 tent = ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(0.0, 0.0)) * 4.0 + (ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(-1.0, 0.0)) + ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(1.0, 0.0)) + ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(0.0, -1.0)) + ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(0.0, 1.0))) * 2.0 + ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(-1.0, -1.0)) + ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(1.0, -1.0)) + ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(-1.0, 1.0)) + ae3d_shaders_bloom_tap(screen, ae_smp_screen, uv, texel, float2(1.0, 1.0)); + float3 sum = level_.sample(ae_smp_level_, uv).rgb + tent / 16.0; + if (top > 0.0) { + sum = sum / top; + } + ae_out.FragColor = float4(sum, 1.0); + return ae_out; +} diff --git a/src/ae3d/metal/bloom_up_quoted.vert.metal b/src/ae3d/metal/bloom_up_quoted.vert.metal new file mode 100644 index 00000000..4f941c3f --- /dev/null +++ b/src/ae3d/metal/bloom_up_quoted.vert.metal @@ -0,0 +1,24 @@ +// screen: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_screen_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; +}; + +struct ae_screen_out { + float4 ae_position [[position]]; + float2 uv [[user(locn0)]]; +}; + +vertex ae_screen_out ae_screen_vertex( + ae_screen_in ae_in [[stage_in]]) { + ae_screen_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + ae_out.ae_position = float4(float3(inPosition.x, inPosition.y, 0.0), 1.0); + ae_out.uv = inTexCoord; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/cloud_quoted.frag.metal b/src/ae3d/metal/cloud_quoted.frag.metal new file mode 100644 index 00000000..5a7f2382 --- /dev/null +++ b/src/ae3d/metal/cloud_quoted.frag.metal @@ -0,0 +1,383 @@ +// cloud: written by ae3d.gpu from its quote. +#include +using namespace metal; +static inline float3x3 ae_mat3(float4x4 m) { return float3x3(m[0].xyz, m[1].xyz, m[2].xyz); } + +struct ae_Params { + float2 size; + char ae_pad0[8]; + float4x4 projection; + float4x4 view; + int kind; + char ae_pad1[12]; + packed_float3 sky_sun; + char ae_pad2[4]; + packed_float3 cloud_sun; + char ae_pad3[4]; + packed_float3 cloud_sun_colour; + float turbidity; + packed_float3 flat_colour; + float overcast; + packed_float3 overcast_colour; + int frame; + float cloud_cover; + float cloud_time; + char ae_pad4[8]; + packed_float3 eye; + int history_on; + float4x4 prev_view_projection; +}; +static_assert(sizeof(ae_Params) == 336, "ae_Params: std140"); +static_assert(__builtin_offsetof(ae_Params, size) == 0, "ae_Params.size: std140"); +static_assert(__builtin_offsetof(ae_Params, projection) == 16, "ae_Params.projection: std140"); +static_assert(__builtin_offsetof(ae_Params, view) == 80, "ae_Params.view: std140"); +static_assert(__builtin_offsetof(ae_Params, kind) == 144, "ae_Params.kind: std140"); +static_assert(__builtin_offsetof(ae_Params, sky_sun) == 160, "ae_Params.sky_sun: std140"); +static_assert(__builtin_offsetof(ae_Params, cloud_sun) == 176, "ae_Params.cloud_sun: std140"); +static_assert(__builtin_offsetof(ae_Params, cloud_sun_colour) == 192, "ae_Params.cloud_sun_colour: std140"); +static_assert(__builtin_offsetof(ae_Params, turbidity) == 204, "ae_Params.turbidity: std140"); +static_assert(__builtin_offsetof(ae_Params, flat_colour) == 208, "ae_Params.flat_colour: std140"); +static_assert(__builtin_offsetof(ae_Params, overcast) == 220, "ae_Params.overcast: std140"); +static_assert(__builtin_offsetof(ae_Params, overcast_colour) == 224, "ae_Params.overcast_colour: std140"); +static_assert(__builtin_offsetof(ae_Params, frame) == 236, "ae_Params.frame: std140"); +static_assert(__builtin_offsetof(ae_Params, cloud_cover) == 240, "ae_Params.cloud_cover: std140"); +static_assert(__builtin_offsetof(ae_Params, cloud_time) == 244, "ae_Params.cloud_time: std140"); +static_assert(__builtin_offsetof(ae_Params, eye) == 256, "ae_Params.eye: std140"); +static_assert(__builtin_offsetof(ae_Params, history_on) == 268, "ae_Params.history_on: std140"); +static_assert(__builtin_offsetof(ae_Params, prev_view_projection) == 272, "ae_Params.prev_view_projection: std140"); + +struct CloudSample { + float density; + float height; +}; + +struct ae_cloud_in { + float4 ae_frag_coord [[position]]; +}; + +struct ae_cloud_frag_out { + float4 FragColor [[color(0)]]; +}; + +static float3 ae3d_shaders_skydome_dusk_sky(float3 dir, float3 sun, float3 sun_colour) { + float elevation = sun.y; + float day = smoothstep(-0.14, 0.06, elevation); + float low = 1.0 - smoothstep(-0.05, 0.35, elevation); + float3 zenith_day = float3(0.20, 0.42, 0.85); + float3 zenith_dusk = float3(0.16, 0.20, 0.45); + float3 zenith_night = float3(0.02, 0.03, 0.08); + float3 horizon_day = float3(0.72, 0.82, 0.93); + float3 horizon_dusk = float3(1.0, 0.52, 0.22); + float3 horizon_night = float3(0.06, 0.07, 0.13); + float toward = 0.5 + 0.5 * dot(normalize(float2(dir.x, dir.z) + float2(0.00001)), normalize(float2(sun.x, sun.z) + float2(0.00001))); + float3 horizon = mix(horizon_day, horizon_dusk, low * (0.25 + 0.75 * toward * toward)); + horizon = mix(horizon_night, horizon, day); + float3 zenith = mix(zenith_night, mix(zenith_day, zenith_dusk, low), day); + float up = clamp(dir.y, 0.0, 1.0); + float3 sky_colour = mix(horizon, zenith, pow(up, 0.55)); + if (dir.y < 0.0) { + sky_colour = mix(horizon, horizon * 0.6, clamp(-dir.y * 4.0, 0.0, 1.0)); + } + float cos_angle = dot(dir, sun); + float glow = pow(max(cos_angle, 0.0), 4.0) * 0.16 + pow(max(cos_angle, 0.0), 40.0) * 0.5; + sky_colour = sky_colour + sun_colour * glow * (0.3 + 0.7 * day) * (1.0 + low * 1.6); + return sky_colour; +} + +static float ae3d_shaders_skydome_shown_channel(float x) { + float t = clamp(x * (2.51 * x + 0.03) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0); + return t <= 0.0031308 ? t * 12.92 : 1.055 * pow(t, 1.0 / 2.4) - 0.055; +} + +static float3 ae3d_shaders_skydome_shown_of(float3 c) { + return float3(ae3d_shaders_skydome_shown_channel(c.x), ae3d_shaders_skydome_shown_channel(c.y), ae3d_shaders_skydome_shown_channel(c.z)); +} + +static float ae3d_shaders_skydome_perez(float cos_theta, float gamma, float a, float b, float c, float d, float e) { + return (1.0 + a * exp(b / max(cos_theta, 0.01))) * (1.0 + c * exp(d * gamma) + e * cos(gamma) * cos(gamma)); +} + +static float3 ae3d_shaders_skydome_daylight(float3 dir_in, float3 sun, float turbidity) { + float3 dir = dir_in; + float tt = turbidity; + float theta_s = acos(clamp(sun.y, 0.0, 1.0)); + float below = 1.0; + if (dir.y < 0.0) { + below = mix(1.0, 0.6, clamp(-dir.y * 4.0, 0.0, 1.0)); + dir = normalize(float3(dir.x, 0.0, dir.z)); + } + float cos_theta = dir.y; + float gamma = acos(clamp(dot(dir, sun), -1.0, 1.0)); + float chi = (4.0 / 9.0 - tt / 120.0) * (3.14159265 - 2.0 * theta_s); + float yz_lum = ((4.0453 * tt - 4.9710) * tan(chi) - 0.2155 * tt + 2.4192) * 1000.0; + float t1 = theta_s; + float t2 = theta_s * theta_s; + float t3 = t2 * theta_s; + float xz_c = tt * tt * (0.00166 * t3 - 0.00375 * t2 + 0.00209 * t1) + tt * (-0.02903 * t3 + 0.06377 * t2 - 0.03202 * t1 + 0.00394) + (0.11693 * t3 - 0.21196 * t2 + 0.06052 * t1 + 0.25886); + float yz_c = tt * tt * (0.00275 * t3 - 0.00610 * t2 + 0.00317 * t1) + tt * (-0.04214 * t3 + 0.08970 * t2 - 0.04153 * t1 + 0.00516) + (0.15346 * t3 - 0.26756 * t2 + 0.06670 * t1 + 0.26688); + float lum = yz_lum * ae3d_shaders_skydome_perez(cos_theta, gamma, 0.1787 * tt - 1.4630, -0.3554 * tt + 0.4275, -0.0227 * tt + 5.3251, 0.1206 * tt - 2.5771, -0.0670 * tt + 0.3703) / ae3d_shaders_skydome_perez(1.0, theta_s, 0.1787 * tt - 1.4630, -0.3554 * tt + 0.4275, -0.0227 * tt + 5.3251, 0.1206 * tt - 2.5771, -0.0670 * tt + 0.3703); + float cx = xz_c * ae3d_shaders_skydome_perez(cos_theta, gamma, -0.0193 * tt - 0.2592, -0.0665 * tt + 0.0008, -0.0004 * tt + 0.2125, -0.0641 * tt - 0.8989, -0.0033 * tt + 0.0452) / ae3d_shaders_skydome_perez(1.0, theta_s, -0.0193 * tt - 0.2592, -0.0665 * tt + 0.0008, -0.0004 * tt + 0.2125, -0.0641 * tt - 0.8989, -0.0033 * tt + 0.0452); + float cy = yz_c * ae3d_shaders_skydome_perez(cos_theta, gamma, -0.0167 * tt - 0.2608, -0.0950 * tt + 0.0092, -0.0079 * tt + 0.2102, -0.0441 * tt - 1.6537, -0.0109 * tt + 0.0529) / ae3d_shaders_skydome_perez(1.0, theta_s, -0.0167 * tt - 0.2608, -0.0950 * tt + 0.0092, -0.0079 * tt + 0.2102, -0.0441 * tt - 1.6537, -0.0109 * tt + 0.0529); + float3 xyz_c = float3(cx / cy * lum, lum, (1.0 - cx - cy) / cy * lum); + float3 lin = float3(3.2406 * xyz_c.x - 1.5372 * xyz_c.y - 0.4986 * xyz_c.z, -0.9689 * xyz_c.x + 1.8758 * xyz_c.y + 0.0415 * xyz_c.z, 0.0557 * xyz_c.x - 0.2040 * xyz_c.y + 1.0570 * xyz_c.z); + return max(lin, float3(0.0)) * (2.0e-5 * below); +} + +static float3 ae3d_shaders_skydome_procedural_sky(float3 dir, float3 sun, float3 sun_colour, float turbidity, bool discs) { + float3 dusk = ae3d_shaders_skydome_dusk_sky(dir, sun, sun_colour); + float day_share = smoothstep(0.0, 0.25, sun.y); + float3 sky_colour = dusk; + if (day_share > 0.0) { + sky_colour = mix(dusk, ae3d_shaders_skydome_shown_of(ae3d_shaders_skydome_daylight(dir, sun, turbidity)), day_share); + } + if (discs) { + float cos_angle = dot(dir, sun); + float day = smoothstep(-0.14, 0.06, sun.y); + float disc = smoothstep(0.9993, 0.9997, cos_angle); + sky_colour = sky_colour + sun_colour * disc * (0.6 + 1.4 * day); + } + return sky_colour; +} + +static float3 ae3d_shaders_skydome_night_sky(float3 dir, float3 moon, float night, bool discs) { + if (night <= 0.0 || dir.y <= 0.0) { + return float3(0.0); + } + float3 add = float3(0.0); + float3 cell = floor(dir * 420.0); + float h = fract(sin(dot(cell, float3(12.9898, 78.233, 37.719))) * 43758.5453); + if (h > 0.9935) { + float bright = (h - 0.9935) / 0.0065; + float3 centre = (cell + float3(0.5)) / 420.0; + float near = 1.0 - smoothstep(0.0, 1.2 / 420.0, length(dir - centre)); + add = add + float3(0.85, 0.9, 1.0) * near * (0.25 + 0.75 * bright) * smoothstep(0.0, 0.15, dir.y); + } + if (moon.y > 0.0) { + float c = dot(dir, moon); + if (discs) { + add = add + float3(0.82, 0.86, 0.95) * smoothstep(0.99955, 0.99975, c) * 1.4; + } + add = add + float3(0.30, 0.34, 0.45) * pow(max(c, 0.0), 60.0) * 0.18; + } + return add * night; +} + +static float3 ae3d_shaders_skydome_sky_at(float3 dir, texture2d skybox, sampler ae_smp_skybox, int kind, float3 sky_sun, float3 cloud_sun, float3 cloud_sun_colour, float turbidity, float3 flat_colour, float overcast, float3 overcast_colour, bool discs) { + float theta = atan2(dir.z, dir.x); + float phi = asin(dir.y); + float u = (theta + 3.14159265) / 6.28318531; + float v = (phi + 1.57079633) / 3.14159265; + float3 sky_colour = skybox.sample(ae_smp_skybox, float2(u, v), level(0.0)).rgb; + if (kind == 1) { + float3 hour_sun = normalize(sky_sun); + sky_colour = ae3d_shaders_skydome_procedural_sky(dir, hour_sun, cloud_sun_colour, turbidity, discs); + float night = 1.0 - smoothstep(-0.14, -0.02, hour_sun.y); + sky_colour = sky_colour + ae3d_shaders_skydome_night_sky(dir, normalize(cloud_sun), night, discs); + } + if (kind == 2) { + sky_colour = flat_colour; + } + if (overcast > 0.0) { + float3 broad = sky_colour; + if (kind != 1) { + broad = skybox.sample(ae_smp_skybox, float2(u, v), level(4.0)).rgb; + } + float luma = dot(broad, float3(0.299, 0.587, 0.114)); + sky_colour = mix(sky_colour, overcast_colour * (0.35 + 0.65 * luma), clamp(overcast, 0.0, 1.0)); + } + return sky_colour; +} + +static float ae3d_shaders_skydome_aces_inverse(float y_in) { + float y = clamp(y_in, 0.0, 0.9999); + float a = 2.51 - 2.43 * y; + float b = 0.03 - 0.59 * y; + return (-b + sqrt(b * b + 4.0 * a * 0.14 * y)) / (2.0 * a); +} + +static float ae3d_shaders_skydome_srgb_decode(float c) { + return c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4); +} + +static float3 ae3d_shaders_skydome_display_radiance(float3 shown) { + float3 c = clamp(shown, float3(0.0), float3(1.0)); + return float3(ae3d_shaders_skydome_aces_inverse(ae3d_shaders_skydome_srgb_decode(c.x)), ae3d_shaders_skydome_aces_inverse(ae3d_shaders_skydome_srgb_decode(c.y)), ae3d_shaders_skydome_aces_inverse(ae3d_shaders_skydome_srgb_decode(c.z))); +} + +static float ae3d_shaders_skydome_cloud_phase(float cos_angle, float g) { + float gg = g * g; + return (1.0 - gg) / (4.0 * 3.14159265 * pow(1.0 + gg - 2.0 * g * cos_angle, 1.5)); +} + +static float2 ae3d_shaders_skydome_cloud_weather_uv(float2 p, float t) { + return p / 24000.0 + float2(t * 0.00012, t * 0.00004); +} + +static float ae3d_shaders_skydome_cloud_coverage_from(float2 weather, float cover) { + float threshold = 1.0 - cover * (0.45 + 1.1 * weather.y); + return smoothstep(threshold, threshold + 0.5, weather.x) * 0.9; +} + +static float ae3d_shaders_skydome_remap(float v, float lo, float hi, float new_lo, float new_hi) { + return new_lo + (clamp(v, lo, hi) - lo) / max(hi - lo, 0.00001) * (new_hi - new_lo); +} + +static CloudSample ae3d_shaders_skydome_cloud_density(float3 p, float cover, float t, bool detailed, texture2d cloud_weather, sampler ae_smp_cloud_weather, texture3d cloud_shape, sampler ae_smp_cloud_shape, texture3d cloud_detail, sampler ae_smp_cloud_detail) { + float3 weather = cloud_weather.sample(ae_smp_cloud_weather, ae3d_shaders_skydome_cloud_weather_uv(p.xz, t)).rgb; + float cov = ae3d_shaders_skydome_cloud_coverage_from(float2(weather.x, weather.z), cover); + float height = clamp((p.y - 1400.0) / (2600.0 - 1400.0), 0.0, 1.0); + if (cov <= 0.0) { + return CloudSample{0.0, height}; + } + float kind = smoothstep(0.25, 0.75, weather.y); + float top = mix(0.30, 1.0, kind) * (0.75 + 0.25 * cov); + float profile = smoothstep(0.0, 0.04, height) * (1.0 - smoothstep(top * 0.4, top, height)); + if (profile <= 0.0) { + return CloudSample{0.0, height}; + } + float3 sp = p * (1.0 / 1500.0) + float3(t * 0.004, 0.0, t * 0.0015); + float4 shape = cloud_shape.sample(ae_smp_cloud_shape, sp); + float fbm = shape.y * 0.625 + shape.z * 0.25 + shape.w * 0.125; + float base = ae3d_shaders_skydome_remap(shape.x, fbm - 1.0, 1.0, 0.0, 1.0) * profile; + float d = ae3d_shaders_skydome_remap(base, 1.0 - cov, 1.0, 0.0, 1.0) * cov; + if (d <= 0.0) { + return CloudSample{0.0, height}; + } + float4 fine = cloud_shape.sample(ae_smp_cloud_shape, (sp * 6.0 + float3(0.37, 0.21, 0.63) + float3(t * 0.01, 0.0, 0.0))); + float puffs = fine.y * 0.625 + fine.z * 0.25 + fine.w * 0.125; + float detail = puffs; + if (detailed) { + float3 dp = p * (1.0 / 240.0) + float3(t * 0.012, 0.0, t * 0.005); + float3 curl = cloud_detail.sample(ae_smp_cloud_detail, (dp * 0.25)).rgb * 2.0 - float3(1.0); + dp = dp + curl * (0.25 * (1.0 - height)); + detail = mix(puffs, cloud_detail.sample(ae_smp_cloud_detail, dp).w, 0.6); + } + float erosion = mix(detail, 1.0 - detail, clamp(height * 6.0, 0.0, 1.0)); + d = ae3d_shaders_skydome_remap(d, erosion * 0.45, 1.0, 0.0, 1.0); + return CloudSample{clamp(d, 0.0, 1.0), height}; +} + +static float ae3d_shaders_skydome_cloud_sun_shade(float3 p, float3 sun, float cover, float t, texture2d cloud_weather, sampler ae_smp_cloud_weather, texture3d cloud_shape, sampler ae_smp_cloud_shape, texture3d cloud_detail, sampler ae_smp_cloud_detail) { + float shade = 0.0; + float stride = (2600.0 - 1400.0) * 0.02; + float3 at = p; + for (int k = 0; k < 6; k += 1) { + at = at + sun * stride; + shade = shade + ae3d_shaders_skydome_cloud_density(at, cover, t, k < 2, cloud_weather, ae_smp_cloud_weather, cloud_shape, ae_smp_cloud_shape, cloud_detail, ae_smp_cloud_detail).density * stride; + stride = stride * 1.6; + } + return shade; +} + +static float4 ae3d_shaders_skydome_clouds_along(float3 dir, float3 sky_colour, float cover, float t, float dither, float near_steps, float far_steps, float3 eye, float3 cloud_sun, float3 cloud_sun_colour, texture2d cloud_weather, sampler ae_smp_cloud_weather, texture3d cloud_shape, sampler ae_smp_cloud_shape, texture3d cloud_detail, sampler ae_smp_cloud_detail) { + if (cover <= 0.0 || dir.y <= 0.03) { + return float4(0.0); + } + float t0 = 1400.0 / dir.y; + float t1 = min(2600.0 / dir.y, t0 + 12000.0); + float slant = 1.0 - clamp(dir.y, 0.0, 1.0); + float fine = (t1 - t0) / (near_steps + far_steps * slant * slant); + float coarse = fine * 3.0; + float3 sun = normalize(cloud_sun); + float3 sun_light = cloud_sun_colour * (0.9 / 3.14159265); + float3 sky_radiance = ae3d_shaders_skydome_display_radiance(sky_colour); + float sky_shown = dot(sky_colour, float3(0.299, 0.587, 0.114)); + float sky_bright = dot(sky_radiance, float3(0.299, 0.587, 0.114)); + float3 sky_light = mix(sky_radiance, float3(sky_bright * 1.6), 0.35 * clamp(sky_shown * 2.5, 0.0, 1.0)); + float cos_angle = dot(dir, sun); + float phase0 = mix(ae3d_shaders_skydome_cloud_phase(cos_angle, 0.5), ae3d_shaders_skydome_cloud_phase(cos_angle, -0.25), 0.30) * 12.566; + float phase1 = mix(ae3d_shaders_skydome_cloud_phase(cos_angle, 0.25), ae3d_shaders_skydome_cloud_phase(cos_angle, -0.125), 0.30) * 12.566; + float phase2 = mix(ae3d_shaders_skydome_cloud_phase(cos_angle, 0.125), ae3d_shaders_skydome_cloud_phase(cos_angle, -0.0625), 0.30) * 12.566; + float3 colour = float3(0.0); + float alpha = 0.0; + float ray = t0 + fine * dither; + float dt = coarse; + int empties = 0; + for (int i = 0; i < 160; i += 1) { + if (ray > t1) { + break; + } + float3 p = float3(eye.x, 0.0, eye.z) + dir * ray; + CloudSample s = ae3d_shaders_skydome_cloud_density(p, cover, t, true, cloud_weather, ae_smp_cloud_weather, cloud_shape, ae_smp_cloud_shape, cloud_detail, ae_smp_cloud_detail); + float d = s.density; + if (d > 0.002) { + if (dt > fine) { + ray = ray - (dt - fine); + dt = fine; + empties = 0; + continue; + } + empties = 0; + float shade = ae3d_shaders_skydome_cloud_sun_shade(p, sun, cover, t, cloud_weather, ae_smp_cloud_weather, cloud_shape, ae_smp_cloud_shape, cloud_detail, ae_smp_cloud_detail); + float tau = shade * 0.12; + float scattered = exp(-tau) * phase0 + 0.5 * exp(-tau * 0.25) * phase1 + 0.25 * exp(-tau * 0.0625) * phase2; + float powder = 1.0 - 0.5 * exp(-d * 12.0); + float3 ambient = sky_light * mix(0.62, 1.0, s.height) * (0.7 + 0.3 * exp(-tau * 0.25)); + float3 c = sun_light * scattered * powder + ambient; + float haze = 1.0 - exp(-ray * 0.00005); + c = mix(c, sky_radiance, haze); + float a = (1.0 - exp(-d * dt * 0.12)) * (1.0 - 0.8 * haze); + colour = colour + c * a * (1.0 - alpha); + alpha = alpha + a * (1.0 - alpha); + if (alpha > 0.98) { + break; + } + } else { + empties = empties + 1; + if (empties > 3) { + dt = coarse; + } + } + ray = ray + dt; + } + float horizon = smoothstep(0.04, 0.32, dir.y); + return float4(colour * horizon, alpha * horizon); +} + +static float ae3d_shaders_skydome_cloud_dither(float2 pixel) { + return fract(52.9829189 * fract(dot(pixel, float2(0.06711056, 0.00583715)))); +} + +fragment ae_cloud_frag_out ae_cloud_fragment( + ae_cloud_in ae_in [[stage_in]], + constant ae_Params& ae_params [[buffer(0)]], + texture2d skybox [[texture(0)]], sampler ae_smp_skybox [[sampler(0)]], + texture2d cloud_weather [[texture(1)]], sampler ae_smp_cloud_weather [[sampler(1)]], + texture3d cloud_shape [[texture(2)]], sampler ae_smp_cloud_shape [[sampler(2)]], + texture3d cloud_detail [[texture(3)]], sampler ae_smp_cloud_detail [[sampler(3)]], + texture2d history [[texture(4)]], sampler ae_smp_history [[sampler(4)]]) { + ae_cloud_frag_out ae_out; + float4 ae_frag_coord = ae_in.ae_frag_coord; + constant float2& size = ae_params.size; + constant float4x4& projection = ae_params.projection; + constant float4x4& view = ae_params.view; + constant int& kind = ae_params.kind; + constant packed_float3& sky_sun = ae_params.sky_sun; + constant packed_float3& cloud_sun = ae_params.cloud_sun; + constant packed_float3& cloud_sun_colour = ae_params.cloud_sun_colour; + constant float& turbidity = ae_params.turbidity; + constant packed_float3& flat_colour = ae_params.flat_colour; + constant float& overcast = ae_params.overcast; + constant packed_float3& overcast_colour = ae_params.overcast_colour; + constant int& frame = ae_params.frame; + constant float& cloud_cover = ae_params.cloud_cover; + constant float& cloud_time = ae_params.cloud_time; + constant packed_float3& eye = ae_params.eye; + constant int& history_on = ae_params.history_on; + constant float4x4& prev_view_projection = ae_params.prev_view_projection; + float2 ndc = ae_frag_coord.xy / size * 2.0 - float2(1.0); + float3 toward = float3((ndc.x + projection[2][0]) / projection[0][0], (ndc.y + projection[2][1]) / projection[1][1], -1.0); + float3 dir = normalize((transpose(ae_mat3(view)) * toward)); + float3 sky_colour = ae3d_shaders_skydome_sky_at(dir, skybox, ae_smp_skybox, kind, sky_sun, cloud_sun, cloud_sun_colour, turbidity, flat_colour, overcast, overcast_colour, false); + float2 grain = ae_frag_coord.xy + float2(5.588238 * float((frame % 64))); + float4 clouds = ae3d_shaders_skydome_clouds_along(dir, sky_colour, cloud_cover, cloud_time, ae3d_shaders_skydome_cloud_dither(grain), 16.0, 32.0, eye, cloud_sun, cloud_sun_colour, cloud_weather, ae_smp_cloud_weather, cloud_shape, ae_smp_cloud_shape, cloud_detail, ae_smp_cloud_detail); + if (history_on == 1) { + float4 was = (prev_view_projection * float4(dir, 0.0)); + float2 at = was.xy / max(was.w, 0.00001) * 0.5 + float2(0.5); + if (was.w > 0.0 && at.x >= 0.0 && at.x <= 1.0 && at.y >= 0.0 && at.y <= 1.0) { + clouds = mix(history.sample(ae_smp_history, at), clouds, 0.125); + } + } + ae_out.FragColor = clouds; + return ae_out; +} diff --git a/src/ae3d/metal/cloud_quoted.vert.metal b/src/ae3d/metal/cloud_quoted.vert.metal new file mode 100644 index 00000000..4f941c3f --- /dev/null +++ b/src/ae3d/metal/cloud_quoted.vert.metal @@ -0,0 +1,24 @@ +// screen: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_screen_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; +}; + +struct ae_screen_out { + float4 ae_position [[position]]; + float2 uv [[user(locn0)]]; +}; + +vertex ae_screen_out ae_screen_vertex( + ae_screen_in ae_in [[stage_in]]) { + ae_screen_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + ae_out.ae_position = float4(float3(inPosition.x, inPosition.y, 0.0), 1.0); + ae_out.uv = inTexCoord; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/color_resolve_quoted.frag.metal b/src/ae3d/metal/color_resolve_quoted.frag.metal new file mode 100644 index 00000000..b2649962 --- /dev/null +++ b/src/ae3d/metal/color_resolve_quoted.frag.metal @@ -0,0 +1,42 @@ +// color_resolve: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_Params { + int samples; + float exposure; + char ae_pad0[8]; +}; +static_assert(sizeof(ae_Params) == 16, "ae_Params: std140"); +static_assert(__builtin_offsetof(ae_Params, samples) == 0, "ae_Params.samples: std140"); +static_assert(__builtin_offsetof(ae_Params, exposure) == 4, "ae_Params.exposure: std140"); + +struct ae_color_resolve_in { + float4 ae_frag_coord [[position]]; +}; + +struct ae_color_resolve_frag_out { + float4 FragColor [[color(0)]]; +}; + +fragment ae_color_resolve_frag_out ae_color_resolve_fragment( + ae_color_resolve_in ae_in [[stage_in]], + constant ae_Params& ae_params [[buffer(0)]], + texture2d_ms colour [[texture(0)]]) { + ae_color_resolve_frag_out ae_out; + float4 ae_frag_coord = ae_in.ae_frag_coord; + constant int& samples = ae_params.samples; + constant float& exposure = ae_params.exposure; + float4 sum = float4(float3(0.0, 0.0, 0.0), 0.0); + float weights = 0.0; + int s = 0; + while (s < samples) { + float4 c = colour.read(uint2(int2(ae_frag_coord.xy)), uint(s)); + float w = 1.0 / (1.0 + dot(c.rgb * exposure, float3(0.2126, 0.7152, 0.0722))); + sum = sum + c * w; + weights = weights + w; + s = s + 1; + } + ae_out.FragColor = sum / max(weights, 0.000001); + return ae_out; +} diff --git a/src/ae3d/metal/color_resolve_quoted.vert.metal b/src/ae3d/metal/color_resolve_quoted.vert.metal new file mode 100644 index 00000000..4f941c3f --- /dev/null +++ b/src/ae3d/metal/color_resolve_quoted.vert.metal @@ -0,0 +1,24 @@ +// screen: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_screen_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; +}; + +struct ae_screen_out { + float4 ae_position [[position]]; + float2 uv [[user(locn0)]]; +}; + +vertex ae_screen_out ae_screen_vertex( + ae_screen_in ae_in [[stage_in]]) { + ae_screen_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + ae_out.ae_position = float4(float3(inPosition.x, inPosition.y, 0.0), 1.0); + ae_out.uv = inTexCoord; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/composite_quoted.frag.metal b/src/ae3d/metal/composite_quoted.frag.metal new file mode 100644 index 00000000..30cdea32 --- /dev/null +++ b/src/ae3d/metal/composite_quoted.frag.metal @@ -0,0 +1,135 @@ +// composite: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct Haze { + int count; + char ae_pad0[12]; + float4 column[4]; + float4 shape[4]; +}; +static_assert(sizeof(Haze) == 144, "Haze: std140"); +static_assert(__builtin_offsetof(Haze, count) == 0, "Haze.count: std140"); +static_assert(__builtin_offsetof(Haze, column) == 16, "Haze.column: std140"); +static_assert(__builtin_offsetof(Haze, shape) == 80, "Haze.shape: std140"); + +struct ae_Params { + Haze haze; + int capture_channel; + char ae_pad0[12]; + float4x4 view_projection; + int bloom_on; + float bloom_intensity; + float frame_exposure; + char ae_pad1[4]; +}; +static_assert(sizeof(ae_Params) == 240, "ae_Params: std140"); +static_assert(__builtin_offsetof(ae_Params, haze) == 0, "ae_Params.haze: std140"); +static_assert(__builtin_offsetof(ae_Params, capture_channel) == 144, "ae_Params.capture_channel: std140"); +static_assert(__builtin_offsetof(ae_Params, view_projection) == 160, "ae_Params.view_projection: std140"); +static_assert(__builtin_offsetof(ae_Params, bloom_on) == 224, "ae_Params.bloom_on: std140"); +static_assert(__builtin_offsetof(ae_Params, bloom_intensity) == 228, "ae_Params.bloom_intensity: std140"); +static_assert(__builtin_offsetof(ae_Params, frame_exposure) == 232, "ae_Params.frame_exposure: std140"); + +struct HazePoint { + float2 at; + float w; +}; + +struct ae_composite_in { + float4 ae_frag_coord [[position]]; + float2 uv [[user(locn0)]]; +}; + +struct ae_composite_frag_out { + float4 FragColor [[color(0)]]; +}; + +static HazePoint ae3d_shaders_composite_haze_screen(float4x4 view_projection, float3 p) { + float4 c = (view_projection * float4(p, 1.0)); + return HazePoint{c.xy / max(c.w, 0.0001) * 0.5 + float2(0.5), c.w}; +} + +static float ae3d_shaders_composite_haze_hash(float2 p) { + return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); +} + +static float ae3d_shaders_composite_haze_noise(float2 p) { + float2 i = floor(p); + float2 f0 = fract(p); + float2 f = f0 * f0 * (float2(3.0) - f0 * 2.0); + return mix(mix(ae3d_shaders_composite_haze_hash(i), ae3d_shaders_composite_haze_hash(i + float2(1.0, 0.0)), f.x), mix(ae3d_shaders_composite_haze_hash(i + float2(0.0, 1.0)), ae3d_shaders_composite_haze_hash(i + float2(1.0, 1.0)), f.x), f.y); +} + +static float3 ae3d_shaders_composite_aces(float3 x) { + return clamp(x * (x * 2.51 + float3(0.03)) / (x * (x * 2.43 + float3(0.59)) + float3(0.14)), float3(0.0), float3(1.0)); +} + +static float ae3d_shaders_composite_srgb_encode(float c) { + return c <= 0.0031308 ? c * 12.92 : 1.055 * pow(c, 1.0 / 2.4) - 0.055; +} + +fragment ae_composite_frag_out ae_composite_fragment( + ae_composite_in ae_in [[stage_in]], + constant ae_Params& ae_params [[buffer(0)]], + texture2d screen [[texture(0)]], sampler ae_smp_screen [[sampler(0)]], + texture2d bloom [[texture(1)]], sampler ae_smp_bloom [[sampler(1)]]) { + ae_composite_frag_out ae_out; + float4 ae_frag_coord = ae_in.ae_frag_coord; + float2 uv = ae_in.uv; + constant Haze& haze = ae_params.haze; + constant int& capture_channel = ae_params.capture_channel; + constant float4x4& view_projection = ae_params.view_projection; + constant int& bloom_on = ae_params.bloom_on; + constant float& bloom_intensity = ae_params.bloom_intensity; + constant float& frame_exposure = ae_params.frame_exposure; + float2 at = uv; + if (haze.count > 0 && capture_channel == 0) { + float2 offset = float2(0.0, 0.0); + int k = 0; + while (k < 4) { + int kk = k; + k = k + 1; + if (kk >= haze.count) { + break; + } + float4 column = haze.column[kk]; + float4 shape = haze.shape[kk]; + HazePoint hb = ae3d_shaders_composite_haze_screen(view_projection, column.xyz); + HazePoint ht = ae3d_shaders_composite_haze_screen(view_projection, column.xyz + float3(0.0, shape.x, 0.0)); + if (hb.w <= 0.0 || ht.w <= 0.0) { + continue; + } + float2 b = hb.at; + float2 axis = ht.at - b; + float len2 = dot(axis, axis); + if (len2 < 1e-10) { + continue; + } + float along = dot(uv - b, axis) / len2; + float width = column.w * abs(view_projection[0][0]) / hb.w * 0.5 * (1.0 + 0.6 * along); + float across = abs(uv.x - (b.x + axis.x * along)) / max(width, 1e-6); + if (along < 0.0 || along > 1.0 || across > 1.0) { + continue; + } + float amount = shape.y * smoothstep(0.0, 0.08, along) * (1.0 - along) * (1.0 - across * across); + float2 q = float2(across * 3.0 + float(kk) * 7.1, along * 7.0 - shape.z); + float2 n = float2(ae3d_shaders_composite_haze_noise(q * 2.3), ae3d_shaders_composite_haze_noise(q * 2.3 + float2(17.3, 5.9))) - float2(0.5); + offset = offset + n * amount * width * 0.18; + } + at = at + offset; + } + float3 radiance = screen.sample(ae_smp_screen, at).rgb; + if (capture_channel != 0) { + ae_out.FragColor = float4(radiance, 1.0); + return ae_out; + } + if (bloom_on != 0) { + radiance = radiance + bloom.sample(ae_smp_bloom, at).rgb * bloom_intensity; + } + float3 shown = ae3d_shaders_composite_aces(radiance * frame_exposure); + shown = float3(ae3d_shaders_composite_srgb_encode(shown.x), ae3d_shaders_composite_srgb_encode(shown.y), ae3d_shaders_composite_srgb_encode(shown.z)); + float noise = fract(sin(dot(ae_frag_coord.xy, float2(12.9898, 78.233))) * 43758.5453) - 0.5; + ae_out.FragColor = float4(shown + float3(noise / 255.0), 1.0); + return ae_out; +} diff --git a/src/ae3d/metal/composite_quoted.vert.metal b/src/ae3d/metal/composite_quoted.vert.metal new file mode 100644 index 00000000..4f941c3f --- /dev/null +++ b/src/ae3d/metal/composite_quoted.vert.metal @@ -0,0 +1,24 @@ +// screen: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_screen_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; +}; + +struct ae_screen_out { + float4 ae_position [[position]]; + float2 uv [[user(locn0)]]; +}; + +vertex ae_screen_out ae_screen_vertex( + ae_screen_in ae_in [[stage_in]]) { + ae_screen_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + ae_out.ae_position = float4(float3(inPosition.x, inPosition.y, 0.0), 1.0); + ae_out.uv = inTexCoord; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/crowd_depth_quoted.vert.metal b/src/ae3d/metal/crowd_depth_quoted.vert.metal new file mode 100644 index 00000000..a6015500 --- /dev/null +++ b/src/ae3d/metal/crowd_depth_quoted.vert.metal @@ -0,0 +1,98 @@ +// crowd_depth_vertex: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct Throng { + float4x4 model; + int frames; + char ae_pad0[12]; +}; +static_assert(sizeof(Throng) == 80, "Throng: std140"); +static_assert(__builtin_offsetof(Throng, model) == 0, "Throng.model: std140"); +static_assert(__builtin_offsetof(Throng, frames) == 64, "Throng.frames: std140"); + +struct Shadow { + float4x4 light_space; + float4 reach; +}; +static_assert(sizeof(Shadow) == 80, "Shadow: std140"); +static_assert(__builtin_offsetof(Shadow, light_space) == 0, "Shadow.light_space: std140"); +static_assert(__builtin_offsetof(Shadow, reach) == 64, "Shadow.reach: std140"); + +struct ae_VertexParams { + Throng throng; + Shadow shadow; +}; +static_assert(sizeof(ae_VertexParams) == 160, "ae_VertexParams: std140"); +static_assert(__builtin_offsetof(ae_VertexParams, throng) == 0, "ae_VertexParams.throng: std140"); +static_assert(__builtin_offsetof(ae_VertexParams, shadow) == 80, "ae_VertexParams.shadow: std140"); + +struct ae_crowd_depth_vertex_in { + float3 inPosition [[attribute(0)]]; + float4 instanceModel_0 [[attribute(3)]]; + float4 instanceModel_1 [[attribute(4)]]; + float4 instanceModel_2 [[attribute(5)]]; + float4 instanceModel_3 [[attribute(6)]]; + float4 inJoints [[attribute(8)]]; + float4 inWeights [[attribute(9)]]; + float instancePhase [[attribute(11)]]; +}; + +struct ae_crowd_depth_vertex_out { + float4 ae_position [[position]]; + float3 BindPos [[user(locn0)]]; + float ClipLimb [[user(locn1)]]; +}; + +static float4x4 ae3d_shaders_throng_bone_at(texture2d bank, sampler ae_smp_bank, int bone, int frame) { + int x = bone * 4; + return float4x4(bank.read(uint2(int2((x + 0), frame)), 0), bank.read(uint2(int2((x + 1), frame)), 0), bank.read(uint2(int2((x + 2), frame)), 0), bank.read(uint2(int2((x + 3), frame)), 0)); +} + +static float4x4 ae3d_shaders_throng_skin_at(texture2d bank, sampler ae_smp_bank, float4 joints, float4 weights, int frame) { + return ((((weights.x * ae3d_shaders_throng_bone_at(bank, ae_smp_bank, int(joints.x), frame)) + (weights.y * ae3d_shaders_throng_bone_at(bank, ae_smp_bank, int(joints.y), frame))) + (weights.z * ae3d_shaders_throng_bone_at(bank, ae_smp_bank, int(joints.z), frame))) + (weights.w * ae3d_shaders_throng_bone_at(bank, ae_smp_bank, int(joints.w), frame))); +} + +vertex ae_crowd_depth_vertex_out ae_crowd_depth_vertex_vertex( + ae_crowd_depth_vertex_in ae_in [[stage_in]], + constant ae_VertexParams& ae_params [[buffer(3)]], + texture2d pose_bank [[texture(0)]], sampler ae_smp_pose_bank [[sampler(0)]]) { + ae_crowd_depth_vertex_out ae_out; + float3 inPosition = ae_in.inPosition; + float4x4 instanceModel = float4x4(ae_in.instanceModel_0, ae_in.instanceModel_1, ae_in.instanceModel_2, ae_in.instanceModel_3); + float4 inJoints = ae_in.inJoints; + float4 inWeights = ae_in.inWeights; + float instancePhase = ae_in.instancePhase; + constant Throng& throng = ae_params.throng; + constant Shadow& shadow = ae_params.shadow; + float4x4 model_matrix = (throng.model * instanceModel); + if (shadow.reach.w > 0.0 && distance(model_matrix[3].xyz, shadow.reach.xyz) > shadow.reach.w) { + ae_out.ae_position = float4(float3(2.0, 2.0, 2.0), 1.0); + ae_out.BindPos = inPosition; + ae_out.ClipLimb = 1.0; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; + } + float fpos = instancePhase * float(throng.frames); + int frame0 = int(floor(fpos)); + float blend = fpos - float(frame0); + if (frame0 < 0) { + frame0 = 0; + blend = 0.0; + } + if (frame0 >= throng.frames) { + frame0 = throng.frames - 1; + blend = 0.0; + } + int frame1 = frame0 + 1; + if (frame1 >= throng.frames) { + frame1 = 0; + } + float4x4 skin = ((ae3d_shaders_throng_skin_at(pose_bank, ae_smp_pose_bank, inJoints, inWeights, frame0) * (1.0 - blend)) + (ae3d_shaders_throng_skin_at(pose_bank, ae_smp_pose_bank, inJoints, inWeights, frame1) * blend)); + float4 posed = (skin * float4(inPosition, 1.0)); + ae_out.ae_position = ((shadow.light_space * model_matrix) * posed); + ae_out.BindPos = inPosition; + ae_out.ClipLimb = 1.0; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/crowd_pull_quoted.vert.metal b/src/ae3d/metal/crowd_pull_quoted.vert.metal new file mode 100644 index 00000000..4d67eed5 --- /dev/null +++ b/src/ae3d/metal/crowd_pull_quoted.vert.metal @@ -0,0 +1,188 @@ +// crowd_pull_vertex: written by ae3d.gpu from its quote. +#include +using namespace metal; +static inline float3x3 ae_mat3(float4x4 m) { return float3x3(m[0].xyz, m[1].xyz, m[2].xyz); } + +struct Throng { + float4x4 model; + int frames; + int impostor; + int columns; + int rows; + float width; + float height; + float travel; + float phase_step; + int pull_bits; + char ae_pad0[12]; +}; +static_assert(sizeof(Throng) == 112, "Throng: std140"); +static_assert(__builtin_offsetof(Throng, model) == 0, "Throng.model: std140"); +static_assert(__builtin_offsetof(Throng, frames) == 64, "Throng.frames: std140"); +static_assert(__builtin_offsetof(Throng, impostor) == 68, "Throng.impostor: std140"); +static_assert(__builtin_offsetof(Throng, columns) == 72, "Throng.columns: std140"); +static_assert(__builtin_offsetof(Throng, rows) == 76, "Throng.rows: std140"); +static_assert(__builtin_offsetof(Throng, width) == 80, "Throng.width: std140"); +static_assert(__builtin_offsetof(Throng, height) == 84, "Throng.height: std140"); +static_assert(__builtin_offsetof(Throng, travel) == 88, "Throng.travel: std140"); +static_assert(__builtin_offsetof(Throng, phase_step) == 92, "Throng.phase_step: std140"); +static_assert(__builtin_offsetof(Throng, pull_bits) == 96, "Throng.pull_bits: std140"); + +struct Eye { + float4x4 view_projection; + float4x4 prev_view_projection; + packed_float3 position; + char ae_pad0[4]; +}; +static_assert(sizeof(Eye) == 144, "Eye: std140"); +static_assert(__builtin_offsetof(Eye, view_projection) == 0, "Eye.view_projection: std140"); +static_assert(__builtin_offsetof(Eye, prev_view_projection) == 64, "Eye.prev_view_projection: std140"); +static_assert(__builtin_offsetof(Eye, position) == 128, "Eye.position: std140"); + +struct Shadow { + float4x4 light_space; +}; +static_assert(sizeof(Shadow) == 64, "Shadow: std140"); +static_assert(__builtin_offsetof(Shadow, light_space) == 0, "Shadow.light_space: std140"); + +struct ae_VertexParams { + Throng throng; + Eye viewer; + Shadow shadow; +}; +static_assert(sizeof(ae_VertexParams) == 320, "ae_VertexParams: std140"); +static_assert(__builtin_offsetof(ae_VertexParams, throng) == 0, "ae_VertexParams.throng: std140"); +static_assert(__builtin_offsetof(ae_VertexParams, viewer) == 112, "ae_VertexParams.viewer: std140"); +static_assert(__builtin_offsetof(ae_VertexParams, shadow) == 256, "ae_VertexParams.shadow: std140"); + +struct ae_crowd_pull_vertex_out { + float4 ae_position [[position]]; + float2 fragTexCoord [[user(locn0)]]; + float3 Normal [[user(locn1)]]; + float3 FragPos [[user(locn2)]]; + float3 InstanceColor [[user(locn3)]]; + float4 FragPosLightSpace [[user(locn4)]]; + float Occlusion [[user(locn5)]]; + float4 ClipNow [[user(locn6)]]; + float4 ClipPrev [[user(locn7)]]; + float3 BindPos [[user(locn8)]]; + float ClipLimb [[user(locn9)]]; + float2 MaskUV [[user(locn10)]]; + float ParticleAlpha [[user(locn11)]]; + float3 FlipNext [[user(locn12)]]; +}; + +static float4x4 ae3d_shaders_throng_bone_at(texture2d bank, sampler ae_smp_bank, int bone, int frame) { + int x = bone * 4; + return float4x4(bank.read(uint2(int2((x + 0), frame)), 0), bank.read(uint2(int2((x + 1), frame)), 0), bank.read(uint2(int2((x + 2), frame)), 0), bank.read(uint2(int2((x + 3), frame)), 0)); +} + +static float4x4 ae3d_shaders_throng_skin_at(texture2d bank, sampler ae_smp_bank, float4 joints, float4 weights, int frame) { + return ((((weights.x * ae3d_shaders_throng_bone_at(bank, ae_smp_bank, int(joints.x), frame)) + (weights.y * ae3d_shaders_throng_bone_at(bank, ae_smp_bank, int(joints.y), frame))) + (weights.z * ae3d_shaders_throng_bone_at(bank, ae_smp_bank, int(joints.z), frame))) + (weights.w * ae3d_shaders_throng_bone_at(bank, ae_smp_bank, int(joints.w), frame))); +} + +static float4x4 ae3d_shaders_throng_skin_at_phase(texture2d bank, sampler ae_smp_bank, float4 joints, float4 weights, float phase, int frames) { + float fpos = fract(phase) * float(frames); + int frame0 = int(floor(fpos)); + float blend = fpos - float(frame0); + if (frame0 < 0) { + frame0 = 0; + blend = 0.0; + } + if (frame0 >= frames) { + frame0 = frames - 1; + blend = 0.0; + } + int frame1 = frame0 + 1; + if (frame1 >= frames) { + frame1 = 0; + } + return ((ae3d_shaders_throng_skin_at(bank, ae_smp_bank, joints, weights, frame0) * (1.0 - blend)) + (ae3d_shaders_throng_skin_at(bank, ae_smp_bank, joints, weights, frame1) * blend)); +} + +vertex ae_crowd_pull_vertex_out ae_crowd_pull_vertex_vertex( + constant ae_VertexParams& ae_params [[buffer(4)]], + texture2d pose_bank [[texture(0)]], sampler ae_smp_pose_bank [[sampler(0)]], + const device float* pulled_vertex [[buffer(8)]], + const device float4* pulled_skin [[buffer(9)]], + const device float* pulled_figure [[buffer(10)]], + uint ae_vertex_id [[vertex_id]]) { + ae_crowd_pull_vertex_out ae_out; + constant Throng& throng = ae_params.throng; + constant Eye& viewer = ae_params.viewer; + constant Shadow& shadow = ae_params.shadow; + uint bits = uint(throng.pull_bits); + uint vi = uint(int(ae_vertex_id)) & (uint(1) << bits) - uint(1); + uint f = (uint(int(ae_vertex_id)) >> bits) * uint(20); + uint at = vi * uint(9); + float3 position = float3(pulled_vertex[at], pulled_vertex[(at + uint(1))], pulled_vertex[(at + uint(2))]); + float2 uv = float2(pulled_vertex[(at + uint(3))], pulled_vertex[(at + uint(4))]); + float3 normal = float3(pulled_vertex[(at + uint(5))], pulled_vertex[(at + uint(6))], pulled_vertex[(at + uint(7))]); + float occlusion = pulled_vertex[(at + uint(8))]; + float4 joints = pulled_skin[(vi * uint(3))]; + float4 weights = pulled_skin[(vi * uint(3) + uint(1))]; + float4x4 instance_model = float4x4(pulled_figure[f], pulled_figure[(f + uint(1))], pulled_figure[(f + uint(2))], pulled_figure[(f + uint(3))], pulled_figure[(f + uint(4))], pulled_figure[(f + uint(5))], pulled_figure[(f + uint(6))], pulled_figure[(f + uint(7))], pulled_figure[(f + uint(8))], pulled_figure[(f + uint(9))], pulled_figure[(f + uint(10))], pulled_figure[(f + uint(11))], pulled_figure[(f + uint(12))], pulled_figure[(f + uint(13))], pulled_figure[(f + uint(14))], pulled_figure[(f + uint(15))]); + float3 instance_color = float3(pulled_figure[(f + uint(16))], pulled_figure[(f + uint(17))], pulled_figure[(f + uint(18))]); + float instance_phase = pulled_figure[(f + uint(19))]; + float4x4 model_matrix = (throng.model * instance_model); + float3 walking = normalize(float3(model_matrix[0][0], 0.0, model_matrix[0][2])); + if (throng.impostor != 0) { + float3 origin = model_matrix[3].xyz; + float3 facing = normalize(float3(model_matrix[0][0], 0.0, model_matrix[0][2])); + float3 to_eye = viewer.position - origin; + float3 level_ = normalize(float3(to_eye.x, 0.0, to_eye.z)); + float angle = atan2(dot(level_, float3(-facing.z, 0.0, facing.x)), dot(level_, facing)); + float turn = fract(angle / 6.28318530718 + 1.0); + int column = int(floor(turn * float(throng.columns) + 0.5)) % throng.columns; + int row = int(floor(fract(instance_phase) * float(throng.rows))) % throng.rows; + float3 right = normalize(cross(float3(0.0, 1.0, 0.0), level_)); + float3 world = origin + right * (position.x * throng.width) + float3(0.0, position.y * throng.height, 0.0); + float2 cell = float2((float(column) + uv.x) / float(throng.columns), 1.0 - (float(row) + (1.0 - uv.y)) / float(throng.rows)); + float4 quad_light = (shadow.light_space * float4(world, 1.0)); + float4 quad_now = (viewer.view_projection * float4(world, 1.0)); + float4 quad_prev = (viewer.prev_view_projection * float4(world - walking * throng.travel, 1.0)); + ae_out.ae_position = quad_now; + ae_out.fragTexCoord = cell; + ae_out.Normal = facing; + ae_out.FragPos = world; + ae_out.InstanceColor = instance_color; + ae_out.FragPosLightSpace = quad_light; + ae_out.Occlusion = occlusion; + ae_out.ClipNow = quad_now; + ae_out.ClipPrev = quad_prev; + ae_out.BindPos = position; + ae_out.ClipLimb = 1.0; + ae_out.MaskUV = uv; + ae_out.ParticleAlpha = 1.0; + ae_out.FlipNext = float3(0.0, 0.0, -1.0); + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; + } + float4x4 skin = ae3d_shaders_throng_skin_at_phase(pose_bank, ae_smp_pose_bank, joints, weights, instance_phase, throng.frames); + float4 posed = (skin * float4(position, 1.0)); + float3 posed_normal = (ae_mat3(skin) * normal); + float3 frag_pos = (model_matrix * posed).xyz; + float3 normal_out = normalize((ae_mat3(model_matrix) * posed_normal)); + float4 light = (shadow.light_space * float4(frag_pos, 1.0)); + float4 now = ((viewer.view_projection * model_matrix) * posed); + float4 posed_prev = (ae3d_shaders_throng_skin_at_phase(pose_bank, ae_smp_pose_bank, joints, weights, instance_phase - throng.phase_step, throng.frames) * float4(position, 1.0)); + float4 world_prev = (model_matrix * posed_prev); + float4 stepped = float4(world_prev.xyz - walking * throng.travel, world_prev.w); + float4 prev = (viewer.prev_view_projection * stepped); + ae_out.ae_position = now; + ae_out.fragTexCoord = uv; + ae_out.Normal = normal_out; + ae_out.FragPos = frag_pos; + ae_out.InstanceColor = instance_color; + ae_out.FragPosLightSpace = light; + ae_out.Occlusion = occlusion; + ae_out.ClipNow = now; + ae_out.ClipPrev = prev; + ae_out.BindPos = position; + ae_out.ClipLimb = 1.0; + ae_out.MaskUV = uv; + ae_out.ParticleAlpha = 1.0; + ae_out.FlipNext = float3(0.0, 0.0, -1.0); + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/crowd_quoted.vert.metal b/src/ae3d/metal/crowd_quoted.vert.metal new file mode 100644 index 00000000..727791f0 --- /dev/null +++ b/src/ae3d/metal/crowd_quoted.vert.metal @@ -0,0 +1,193 @@ +// crowd_vertex: written by ae3d.gpu from its quote. +#include +using namespace metal; +static inline float3x3 ae_mat3(float4x4 m) { return float3x3(m[0].xyz, m[1].xyz, m[2].xyz); } + +struct Throng { + float4x4 model; + int frames; + int impostor; + int columns; + int rows; + float width; + float height; + float travel; + float phase_step; +}; +static_assert(sizeof(Throng) == 96, "Throng: std140"); +static_assert(__builtin_offsetof(Throng, model) == 0, "Throng.model: std140"); +static_assert(__builtin_offsetof(Throng, frames) == 64, "Throng.frames: std140"); +static_assert(__builtin_offsetof(Throng, impostor) == 68, "Throng.impostor: std140"); +static_assert(__builtin_offsetof(Throng, columns) == 72, "Throng.columns: std140"); +static_assert(__builtin_offsetof(Throng, rows) == 76, "Throng.rows: std140"); +static_assert(__builtin_offsetof(Throng, width) == 80, "Throng.width: std140"); +static_assert(__builtin_offsetof(Throng, height) == 84, "Throng.height: std140"); +static_assert(__builtin_offsetof(Throng, travel) == 88, "Throng.travel: std140"); +static_assert(__builtin_offsetof(Throng, phase_step) == 92, "Throng.phase_step: std140"); + +struct Eye { + float4x4 view_projection; + float4x4 prev_view_projection; + packed_float3 position; + char ae_pad0[4]; +}; +static_assert(sizeof(Eye) == 144, "Eye: std140"); +static_assert(__builtin_offsetof(Eye, view_projection) == 0, "Eye.view_projection: std140"); +static_assert(__builtin_offsetof(Eye, prev_view_projection) == 64, "Eye.prev_view_projection: std140"); +static_assert(__builtin_offsetof(Eye, position) == 128, "Eye.position: std140"); + +struct Shadow { + float4x4 light_space; +}; +static_assert(sizeof(Shadow) == 64, "Shadow: std140"); +static_assert(__builtin_offsetof(Shadow, light_space) == 0, "Shadow.light_space: std140"); + +struct ae_VertexParams { + Throng throng; + Eye viewer; + Shadow shadow; +}; +static_assert(sizeof(ae_VertexParams) == 304, "ae_VertexParams: std140"); +static_assert(__builtin_offsetof(ae_VertexParams, throng) == 0, "ae_VertexParams.throng: std140"); +static_assert(__builtin_offsetof(ae_VertexParams, viewer) == 96, "ae_VertexParams.viewer: std140"); +static_assert(__builtin_offsetof(ae_VertexParams, shadow) == 240, "ae_VertexParams.shadow: std140"); + +struct ae_crowd_vertex_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; + float3 inNormal [[attribute(2)]]; + float4 instanceModel_0 [[attribute(3)]]; + float4 instanceModel_1 [[attribute(4)]]; + float4 instanceModel_2 [[attribute(5)]]; + float4 instanceModel_3 [[attribute(6)]]; + float3 instanceColor [[attribute(7)]]; + float4 inJoints [[attribute(8)]]; + float4 inWeights [[attribute(9)]]; + float inOcclusion [[attribute(10)]]; + float instancePhase [[attribute(11)]]; +}; + +struct ae_crowd_vertex_out { + float4 ae_position [[position]]; + float2 fragTexCoord [[user(locn0)]]; + float3 Normal [[user(locn1)]]; + float3 FragPos [[user(locn2)]]; + float3 InstanceColor [[user(locn3)]]; + float4 FragPosLightSpace [[user(locn4)]]; + float Occlusion [[user(locn5)]]; + float4 ClipNow [[user(locn6)]]; + float4 ClipPrev [[user(locn7)]]; + float3 BindPos [[user(locn8)]]; + float ClipLimb [[user(locn9)]]; + float2 MaskUV [[user(locn10)]]; + float ParticleAlpha [[user(locn11)]]; + float3 FlipNext [[user(locn12)]]; +}; + +static float4x4 ae3d_shaders_throng_bone_at(texture2d bank, sampler ae_smp_bank, int bone, int frame) { + int x = bone * 4; + return float4x4(bank.read(uint2(int2((x + 0), frame)), 0), bank.read(uint2(int2((x + 1), frame)), 0), bank.read(uint2(int2((x + 2), frame)), 0), bank.read(uint2(int2((x + 3), frame)), 0)); +} + +static float4x4 ae3d_shaders_throng_skin_at(texture2d bank, sampler ae_smp_bank, float4 joints, float4 weights, int frame) { + return ((((weights.x * ae3d_shaders_throng_bone_at(bank, ae_smp_bank, int(joints.x), frame)) + (weights.y * ae3d_shaders_throng_bone_at(bank, ae_smp_bank, int(joints.y), frame))) + (weights.z * ae3d_shaders_throng_bone_at(bank, ae_smp_bank, int(joints.z), frame))) + (weights.w * ae3d_shaders_throng_bone_at(bank, ae_smp_bank, int(joints.w), frame))); +} + +static float4x4 ae3d_shaders_throng_skin_at_phase(texture2d bank, sampler ae_smp_bank, float4 joints, float4 weights, float phase, int frames) { + float fpos = fract(phase) * float(frames); + int frame0 = int(floor(fpos)); + float blend = fpos - float(frame0); + if (frame0 < 0) { + frame0 = 0; + blend = 0.0; + } + if (frame0 >= frames) { + frame0 = frames - 1; + blend = 0.0; + } + int frame1 = frame0 + 1; + if (frame1 >= frames) { + frame1 = 0; + } + return ((ae3d_shaders_throng_skin_at(bank, ae_smp_bank, joints, weights, frame0) * (1.0 - blend)) + (ae3d_shaders_throng_skin_at(bank, ae_smp_bank, joints, weights, frame1) * blend)); +} + +vertex ae_crowd_vertex_out ae_crowd_vertex_vertex( + ae_crowd_vertex_in ae_in [[stage_in]], + constant ae_VertexParams& ae_params [[buffer(3)]], + texture2d pose_bank [[texture(0)]], sampler ae_smp_pose_bank [[sampler(0)]]) { + ae_crowd_vertex_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + float3 inNormal = ae_in.inNormal; + float4x4 instanceModel = float4x4(ae_in.instanceModel_0, ae_in.instanceModel_1, ae_in.instanceModel_2, ae_in.instanceModel_3); + float3 instanceColor = ae_in.instanceColor; + float4 inJoints = ae_in.inJoints; + float4 inWeights = ae_in.inWeights; + float inOcclusion = ae_in.inOcclusion; + float instancePhase = ae_in.instancePhase; + constant Throng& throng = ae_params.throng; + constant Eye& viewer = ae_params.viewer; + constant Shadow& shadow = ae_params.shadow; + float4x4 model_matrix = (throng.model * instanceModel); + float3 walking = normalize(float3(model_matrix[0][0], 0.0, model_matrix[0][2])); + if (throng.impostor != 0) { + float3 origin = model_matrix[3].xyz; + float3 facing = normalize(float3(model_matrix[0][0], 0.0, model_matrix[0][2])); + float3 to_eye = viewer.position - origin; + float3 level_ = normalize(float3(to_eye.x, 0.0, to_eye.z)); + float angle = atan2(dot(level_, float3(-facing.z, 0.0, facing.x)), dot(level_, facing)); + float turn = fract(angle / 6.28318530718 + 1.0); + int column = int(floor(turn * float(throng.columns) + 0.5)) % throng.columns; + int row = int(floor(fract(instancePhase) * float(throng.rows))) % throng.rows; + float3 right = normalize(cross(float3(0.0, 1.0, 0.0), level_)); + float3 world = origin + right * (inPosition.x * throng.width) + float3(0.0, inPosition.y * throng.height, 0.0); + float2 cell = float2((float(column) + inTexCoord.x) / float(throng.columns), 1.0 - (float(row) + (1.0 - inTexCoord.y)) / float(throng.rows)); + float4 quad_light = (shadow.light_space * float4(world, 1.0)); + float4 quad_now = (viewer.view_projection * float4(world, 1.0)); + float4 quad_prev = (viewer.prev_view_projection * float4(world - walking * throng.travel, 1.0)); + ae_out.ae_position = quad_now; + ae_out.fragTexCoord = cell; + ae_out.Normal = facing; + ae_out.FragPos = world; + ae_out.InstanceColor = instanceColor; + ae_out.FragPosLightSpace = quad_light; + ae_out.Occlusion = inOcclusion; + ae_out.ClipNow = quad_now; + ae_out.ClipPrev = quad_prev; + ae_out.BindPos = inPosition; + ae_out.ClipLimb = 1.0; + ae_out.MaskUV = inTexCoord; + ae_out.ParticleAlpha = 1.0; + ae_out.FlipNext = float3(0.0, 0.0, -1.0); + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; + } + float4x4 skin = ae3d_shaders_throng_skin_at_phase(pose_bank, ae_smp_pose_bank, inJoints, inWeights, instancePhase, throng.frames); + float4 posed = (skin * float4(inPosition, 1.0)); + float3 posed_normal = (ae_mat3(skin) * inNormal); + float3 frag_pos = (model_matrix * posed).xyz; + float3 normal_out = normalize((ae_mat3(model_matrix) * posed_normal)); + float4 light = (shadow.light_space * float4(frag_pos, 1.0)); + float4 now = ((viewer.view_projection * model_matrix) * posed); + float4 posed_prev = (ae3d_shaders_throng_skin_at_phase(pose_bank, ae_smp_pose_bank, inJoints, inWeights, instancePhase - throng.phase_step, throng.frames) * float4(inPosition, 1.0)); + float4 world_prev = (model_matrix * posed_prev); + float4 stepped = float4(world_prev.xyz - walking * throng.travel, world_prev.w); + float4 prev = (viewer.prev_view_projection * stepped); + ae_out.ae_position = now; + ae_out.fragTexCoord = inTexCoord; + ae_out.Normal = normal_out; + ae_out.FragPos = frag_pos; + ae_out.InstanceColor = instanceColor; + ae_out.FragPosLightSpace = light; + ae_out.Occlusion = inOcclusion; + ae_out.ClipNow = now; + ae_out.ClipPrev = prev; + ae_out.BindPos = inPosition; + ae_out.ClipLimb = 1.0; + ae_out.MaskUV = inTexCoord; + ae_out.ParticleAlpha = 1.0; + ae_out.FlipNext = float3(0.0, 0.0, -1.0); + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/depth_copy_quoted.frag.metal b/src/ae3d/metal/depth_copy_quoted.frag.metal new file mode 100644 index 00000000..6e28a2f5 --- /dev/null +++ b/src/ae3d/metal/depth_copy_quoted.frag.metal @@ -0,0 +1,20 @@ +// depth_copy: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_depth_copy_in { + float4 ae_frag_coord [[position]]; +}; + +struct ae_depth_copy_frag_out { + float ae_depth [[depth(any)]]; +}; + +fragment ae_depth_copy_frag_out ae_depth_copy_fragment( + ae_depth_copy_in ae_in [[stage_in]], + texture2d depth [[texture(0)]], sampler ae_smp_depth [[sampler(0)]]) { + ae_depth_copy_frag_out ae_out; + float4 ae_frag_coord = ae_in.ae_frag_coord; + ae_out.ae_depth = depth.read(uint2(int2(ae_frag_coord.xy)), 0).x; + return ae_out; +} diff --git a/src/ae3d/metal/depth_copy_quoted.vert.metal b/src/ae3d/metal/depth_copy_quoted.vert.metal new file mode 100644 index 00000000..4f941c3f --- /dev/null +++ b/src/ae3d/metal/depth_copy_quoted.vert.metal @@ -0,0 +1,24 @@ +// screen: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_screen_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; +}; + +struct ae_screen_out { + float4 ae_position [[position]]; + float2 uv [[user(locn0)]]; +}; + +vertex ae_screen_out ae_screen_vertex( + ae_screen_in ae_in [[stage_in]]) { + ae_screen_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + ae_out.ae_position = float4(float3(inPosition.x, inPosition.y, 0.0), 1.0); + ae_out.uv = inTexCoord; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/depth_resolve_quoted.frag.metal b/src/ae3d/metal/depth_resolve_quoted.frag.metal new file mode 100644 index 00000000..4d296b7c --- /dev/null +++ b/src/ae3d/metal/depth_resolve_quoted.frag.metal @@ -0,0 +1,35 @@ +// depth_resolve: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_Params { + int samples; + char ae_pad0[12]; +}; +static_assert(sizeof(ae_Params) == 16, "ae_Params: std140"); +static_assert(__builtin_offsetof(ae_Params, samples) == 0, "ae_Params.samples: std140"); + +struct ae_depth_resolve_in { + float4 ae_frag_coord [[position]]; +}; + +struct ae_depth_resolve_frag_out { + float ae_depth [[depth(any)]]; +}; + +fragment ae_depth_resolve_frag_out ae_depth_resolve_fragment( + ae_depth_resolve_in ae_in [[stage_in]], + constant ae_Params& ae_params [[buffer(0)]], + texture2d_ms depth [[texture(0)]]) { + ae_depth_resolve_frag_out ae_out; + float4 ae_frag_coord = ae_in.ae_frag_coord; + constant int& samples = ae_params.samples; + float nearest = 1.0; + int s = 0; + while (s < samples) { + nearest = min(nearest, depth.read(uint2(int2(ae_frag_coord.xy)), uint(s)).x); + s = s + 1; + } + ae_out.ae_depth = nearest; + return ae_out; +} diff --git a/src/ae3d/metal/depth_resolve_quoted.vert.metal b/src/ae3d/metal/depth_resolve_quoted.vert.metal new file mode 100644 index 00000000..4f941c3f --- /dev/null +++ b/src/ae3d/metal/depth_resolve_quoted.vert.metal @@ -0,0 +1,24 @@ +// screen: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_screen_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; +}; + +struct ae_screen_out { + float4 ae_position [[position]]; + float2 uv [[user(locn0)]]; +}; + +vertex ae_screen_out ae_screen_vertex( + ae_screen_in ae_in [[stage_in]]) { + ae_screen_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + ae_out.ae_position = float4(float3(inPosition.x, inPosition.y, 0.0), 1.0); + ae_out.uv = inTexCoord; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/fxaa_quoted.frag.metal b/src/ae3d/metal/fxaa_quoted.frag.metal new file mode 100644 index 00000000..4904b51e --- /dev/null +++ b/src/ae3d/metal/fxaa_quoted.frag.metal @@ -0,0 +1,70 @@ +// fxaa: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_Params { + float2 texel; + float edge_min; + float edge; + float subpixel; + char ae_pad0[12]; +}; +static_assert(sizeof(ae_Params) == 32, "ae_Params: std140"); +static_assert(__builtin_offsetof(ae_Params, texel) == 0, "ae_Params.texel: std140"); +static_assert(__builtin_offsetof(ae_Params, edge_min) == 8, "ae_Params.edge_min: std140"); +static_assert(__builtin_offsetof(ae_Params, edge) == 12, "ae_Params.edge: std140"); +static_assert(__builtin_offsetof(ae_Params, subpixel) == 16, "ae_Params.subpixel: std140"); + +struct ae_fxaa_in { + float2 uv [[user(locn0)]]; +}; + +struct ae_fxaa_frag_out { + float4 FragColor [[color(0)]]; +}; + +static float ae3d_shaders_fxaa_luma(float3 c) { + return dot(c, float3(0.299, 0.587, 0.114)); +} + +fragment ae_fxaa_frag_out ae_fxaa_fragment( + ae_fxaa_in ae_in [[stage_in]], + constant ae_Params& ae_params [[buffer(0)]], + texture2d screen [[texture(0)]], sampler ae_smp_screen [[sampler(0)]]) { + ae_fxaa_frag_out ae_out; + float2 uv = ae_in.uv; + constant float2& texel = ae_params.texel; + constant float& edge_min = ae_params.edge_min; + constant float& edge = ae_params.edge; + constant float& subpixel = ae_params.subpixel; + float3 c = screen.sample(ae_smp_screen, uv).rgb; + float3 n = screen.sample(ae_smp_screen, (uv + float2(0.0, -1.0) * texel)).rgb; + float3 s = screen.sample(ae_smp_screen, (uv + float2(0.0, 1.0) * texel)).rgb; + float3 e = screen.sample(ae_smp_screen, (uv + float2(1.0, 0.0) * texel)).rgb; + float3 w = screen.sample(ae_smp_screen, (uv + float2(-1.0, 0.0) * texel)).rgb; + float lc = ae3d_shaders_fxaa_luma(c); + float ln = ae3d_shaders_fxaa_luma(n); + float ls = ae3d_shaders_fxaa_luma(s); + float le = ae3d_shaders_fxaa_luma(e); + float lw = ae3d_shaders_fxaa_luma(w); + float lo = min(lc, min(min(ln, ls), min(le, lw))); + float hi = max(lc, max(max(ln, ls), max(le, lw))); + float range = hi - lo; + if (range < max(edge_min, hi * edge)) { + ae_out.FragColor = float4(c, 1.0); + return ae_out; + } + float avg = (ln + ls + le + lw) * 0.25; + float blend = smoothstep(0.0, 1.0, clamp(abs(avg - lc) / range, 0.0, 1.0)); + blend = blend * blend * subpixel; + float horiz = abs(-2.0 * lw + lc) + abs(-2.0 * lc + le) * 2.0 + abs(-2.0 * le + lc); + float vert = abs(-2.0 * ln + lc) + abs(-2.0 * lc + ls) * 2.0 + abs(-2.0 * ls + lc); + bool along = horiz >= vert; + float g1 = (along ? ls : le) - lc; + float g2 = (along ? ln : lw) - lc; + float dir = abs(g1) >= abs(g2) ? sign(g1) : sign(g2); + float step = blend * dir; + float2 off = along ? float2(0.0, step * texel.y) : float2(step * texel.x, 0.0); + ae_out.FragColor = float4(screen.sample(ae_smp_screen, (uv + off)).rgb, 1.0); + return ae_out; +} diff --git a/src/ae3d/metal/fxaa_quoted.vert.metal b/src/ae3d/metal/fxaa_quoted.vert.metal new file mode 100644 index 00000000..4f941c3f --- /dev/null +++ b/src/ae3d/metal/fxaa_quoted.vert.metal @@ -0,0 +1,24 @@ +// screen: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_screen_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; +}; + +struct ae_screen_out { + float4 ae_position [[position]]; + float2 uv [[user(locn0)]]; +}; + +vertex ae_screen_out ae_screen_vertex( + ae_screen_in ae_in [[stage_in]]) { + ae_screen_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + ae_out.ae_position = float4(float3(inPosition.x, inPosition.y, 0.0), 1.0); + ae_out.uv = inTexCoord; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/lit_depth_plain_quoted.vert.metal b/src/ae3d/metal/lit_depth_plain_quoted.vert.metal new file mode 100644 index 00000000..3b0b2c20 --- /dev/null +++ b/src/ae3d/metal/lit_depth_plain_quoted.vert.metal @@ -0,0 +1,187 @@ +// lit_depth_plain_vertex: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct Draw { + float4x4 model; + float4x4 prev_model; + float4x4 light_space; + int instanced; + int instance_colour; + int points; + int billboard; + int flip_columns; + int flip_rows; + float lift; + int skinned; + int impostor; + char ae_pad0[12]; +}; +static_assert(sizeof(Draw) == 240, "Draw: std140"); +static_assert(__builtin_offsetof(Draw, model) == 0, "Draw.model: std140"); +static_assert(__builtin_offsetof(Draw, prev_model) == 64, "Draw.prev_model: std140"); +static_assert(__builtin_offsetof(Draw, light_space) == 128, "Draw.light_space: std140"); +static_assert(__builtin_offsetof(Draw, instanced) == 192, "Draw.instanced: std140"); +static_assert(__builtin_offsetof(Draw, instance_colour) == 196, "Draw.instance_colour: std140"); +static_assert(__builtin_offsetof(Draw, points) == 200, "Draw.points: std140"); +static_assert(__builtin_offsetof(Draw, billboard) == 204, "Draw.billboard: std140"); +static_assert(__builtin_offsetof(Draw, flip_columns) == 208, "Draw.flip_columns: std140"); +static_assert(__builtin_offsetof(Draw, flip_rows) == 212, "Draw.flip_rows: std140"); +static_assert(__builtin_offsetof(Draw, lift) == 216, "Draw.lift: std140"); +static_assert(__builtin_offsetof(Draw, skinned) == 220, "Draw.skinned: std140"); +static_assert(__builtin_offsetof(Draw, impostor) == 224, "Draw.impostor: std140"); + +struct Cut { + int on; + char ae_pad0[12]; + float4 plane; + int detached; + float noise; + float noise_scale; + int joints0; + int joints1; + int joints2; + char ae_pad1[8]; +}; +static_assert(sizeof(Cut) == 64, "Cut: std140"); +static_assert(__builtin_offsetof(Cut, on) == 0, "Cut.on: std140"); +static_assert(__builtin_offsetof(Cut, plane) == 16, "Cut.plane: std140"); +static_assert(__builtin_offsetof(Cut, detached) == 32, "Cut.detached: std140"); +static_assert(__builtin_offsetof(Cut, noise) == 36, "Cut.noise: std140"); +static_assert(__builtin_offsetof(Cut, noise_scale) == 40, "Cut.noise_scale: std140"); +static_assert(__builtin_offsetof(Cut, joints0) == 44, "Cut.joints0: std140"); +static_assert(__builtin_offsetof(Cut, joints1) == 48, "Cut.joints1: std140"); +static_assert(__builtin_offsetof(Cut, joints2) == 52, "Cut.joints2: std140"); + +struct Eye { + float4x4 view_projection; + float4x4 prev_view_projection; + float4x4 inv_view_projection; + packed_float3 position; + float far; + float2 jitter; + float2 screen_size; + int depth_on; + char ae_pad0[12]; + float4 depth_plane; + int history; + int capture; + float ray_reach; + int ray_frame; +}; +static_assert(sizeof(Eye) == 272, "Eye: std140"); +static_assert(__builtin_offsetof(Eye, view_projection) == 0, "Eye.view_projection: std140"); +static_assert(__builtin_offsetof(Eye, prev_view_projection) == 64, "Eye.prev_view_projection: std140"); +static_assert(__builtin_offsetof(Eye, inv_view_projection) == 128, "Eye.inv_view_projection: std140"); +static_assert(__builtin_offsetof(Eye, position) == 192, "Eye.position: std140"); +static_assert(__builtin_offsetof(Eye, far) == 204, "Eye.far: std140"); +static_assert(__builtin_offsetof(Eye, jitter) == 208, "Eye.jitter: std140"); +static_assert(__builtin_offsetof(Eye, screen_size) == 216, "Eye.screen_size: std140"); +static_assert(__builtin_offsetof(Eye, depth_on) == 224, "Eye.depth_on: std140"); +static_assert(__builtin_offsetof(Eye, depth_plane) == 240, "Eye.depth_plane: std140"); +static_assert(__builtin_offsetof(Eye, history) == 256, "Eye.history: std140"); +static_assert(__builtin_offsetof(Eye, capture) == 260, "Eye.capture: std140"); +static_assert(__builtin_offsetof(Eye, ray_reach) == 264, "Eye.ray_reach: std140"); +static_assert(__builtin_offsetof(Eye, ray_frame) == 268, "Eye.ray_frame: std140"); + +struct Skin { + float4x4 bones[96]; + float4 prev_rows[288]; +}; +static_assert(sizeof(Skin) == 10752, "Skin: std140"); +static_assert(__builtin_offsetof(Skin, bones) == 0, "Skin.bones: std140"); +static_assert(__builtin_offsetof(Skin, prev_rows) == 6144, "Skin.prev_rows: std140"); + +struct DrawBlock { + Draw draw; + char ae_pad0[256]; + Cut cut; + char ae_pad1[1744]; + Skin skin; +}; +static_assert(sizeof(DrawBlock) == 13056, "DrawBlock: std140"); +static_assert(__builtin_offsetof(DrawBlock, draw) == 0, "DrawBlock.draw: std140"); +static_assert(__builtin_offsetof(DrawBlock, cut) == 496, "DrawBlock.cut: std140"); +static_assert(__builtin_offsetof(DrawBlock, skin) == 2304, "DrawBlock.skin: std140"); + +struct CameraBlock { + Eye eye; +}; +static_assert(sizeof(CameraBlock) == 272, "CameraBlock: std140"); +static_assert(__builtin_offsetof(CameraBlock, eye) == 0, "CameraBlock.eye: std140"); + +struct ae_lit_depth_plain_vertex_in { + float3 inPosition [[attribute(0)]]; + float4 instanceModel_0 [[attribute(3)]]; + float4 instanceModel_1 [[attribute(4)]]; + float4 instanceModel_2 [[attribute(5)]]; + float4 instanceModel_3 [[attribute(6)]]; +}; + +struct ae_lit_depth_plain_vertex_out { + float4 ae_position [[position]]; + float3 BindPos [[user(locn0)]]; + float ClipLimb [[user(locn1)]]; +}; + +static float ae3d_shaders_lit_clip_joint_on(float joint, constant Cut& cut) { + int j = int((joint + 0.5)); + int word = j < 32 ? cut.joints0 : (j < 64 ? cut.joints1 : cut.joints2); + return float((word >> (j & 31) & 1)); +} + +static float ae3d_shaders_lit_clip_limb_of(float4 joints, float4 weights, constant Draw& draw, constant Cut& cut) { + if (draw.skinned == 0 || (cut.joints0 | cut.joints1 | cut.joints2) == 0) { + return 1.0; + } + return weights.x * ae3d_shaders_lit_clip_joint_on(joints.x, cut) + weights.y * ae3d_shaders_lit_clip_joint_on(joints.y, cut) + weights.z * ae3d_shaders_lit_clip_joint_on(joints.z, cut) + weights.w * ae3d_shaders_lit_clip_joint_on(joints.w, cut); +} + +vertex ae_lit_depth_plain_vertex_out ae_lit_depth_plain_vertex_vertex( + ae_lit_depth_plain_vertex_in ae_in [[stage_in]], + constant DrawBlock& ae_set0 [[buffer(0)]], + constant CameraBlock& ae_set1 [[buffer(1)]]) { + ae_lit_depth_plain_vertex_out ae_out; + float3 inPosition = ae_in.inPosition; + float4x4 instanceModel = float4x4(ae_in.instanceModel_0, ae_in.instanceModel_1, ae_in.instanceModel_2, ae_in.instanceModel_3); + constant Draw& draw = ae_set0.draw; + constant Cut& cut = ae_set0.cut; + constant Skin& skin = ae_set0.skin; + constant Eye& eye = ae_set1.eye; + float clip_limb = ae3d_shaders_lit_clip_limb_of(float4(0.0), float4(0.0), draw, cut); + float4x4 model_matrix0 = draw.instanced != 0 ? (draw.model * instanceModel) : draw.model; + float4x4 model_matrix = model_matrix0; + if (draw.instanced != 0 && draw.points != 0) { + float4 point = instanceModel[0]; + model_matrix = float4x4(draw.model[0] * point.w, draw.model[1] * point.w, draw.model[2] * point.w, float4(point.xyz, 1.0)); + if (draw.billboard > 0) { + float3 to_eye = eye.position - point.xyz; + float3 up0 = float3(0.0, 1.0, 0.0); + float3 forward = float3(0.0); + if (draw.billboard == 1) { + forward = normalize(float3(to_eye.x, 0.0, to_eye.z)); + } else { + forward = normalize(to_eye); + } + float3 right = normalize(cross(up0, forward)); + float3 up = cross(forward, right); + float sx = length(draw.model[0].xyz) * point.w; + float sy = length(draw.model[1].xyz) * point.w; + float sz = length(draw.model[2].xyz) * point.w; + model_matrix = float4x4(float4(right * sx, 0.0), float4(up * sy, 0.0), float4(forward * sz, 0.0), float4(point.xyz + up * (sy * draw.lift), 1.0)); + } + } + float4 posed0 = float4(inPosition, 1.0); + float4 posed = posed0; + if (draw.skinned != 0) { + float4 weights = float4(0.0); + float4 joints = float4(0.0); + float4x4 skin_now = ((((weights.x * skin.bones[int(joints.x)]) + (weights.y * skin.bones[int(joints.y)])) + (weights.z * skin.bones[int(joints.z)])) + (weights.w * skin.bones[int(joints.w)])); + posed = (skin_now * posed0); + } + ae_out.ae_position = ((draw.light_space * model_matrix) * posed); + ae_out.BindPos = inPosition; + ae_out.ClipLimb = clip_limb; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/lit_depth_quoted.frag.metal b/src/ae3d/metal/lit_depth_quoted.frag.metal new file mode 100644 index 00000000..1dd2ddd2 --- /dev/null +++ b/src/ae3d/metal/lit_depth_quoted.frag.metal @@ -0,0 +1,123 @@ +// lit_depth: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct Cut { + int on; + char ae_pad0[12]; + float4 plane; + int detached; + float noise; + float noise_scale; + int joints0; + int joints1; + int joints2; + char ae_pad1[8]; +}; +static_assert(sizeof(Cut) == 64, "Cut: std140"); +static_assert(__builtin_offsetof(Cut, on) == 0, "Cut.on: std140"); +static_assert(__builtin_offsetof(Cut, plane) == 16, "Cut.plane: std140"); +static_assert(__builtin_offsetof(Cut, detached) == 32, "Cut.detached: std140"); +static_assert(__builtin_offsetof(Cut, noise) == 36, "Cut.noise: std140"); +static_assert(__builtin_offsetof(Cut, noise_scale) == 40, "Cut.noise_scale: std140"); +static_assert(__builtin_offsetof(Cut, joints0) == 44, "Cut.joints0: std140"); +static_assert(__builtin_offsetof(Cut, joints1) == 48, "Cut.joints1: std140"); +static_assert(__builtin_offsetof(Cut, joints2) == 52, "Cut.joints2: std140"); + +struct Wounds { + int count; + char ae_pad0[12]; + float4 data[96]; + float4 layers[4]; + float core; + char ae_pad1[12]; +}; +static_assert(sizeof(Wounds) == 1632, "Wounds: std140"); +static_assert(__builtin_offsetof(Wounds, count) == 0, "Wounds.count: std140"); +static_assert(__builtin_offsetof(Wounds, data) == 16, "Wounds.data: std140"); +static_assert(__builtin_offsetof(Wounds, layers) == 1552, "Wounds.layers: std140"); +static_assert(__builtin_offsetof(Wounds, core) == 1616, "Wounds.core: std140"); + +struct DrawBlock { + char ae_pad0[496]; + Cut cut; + Wounds wounds; +}; +static_assert(sizeof(DrawBlock) == 2192, "DrawBlock: std140"); +static_assert(__builtin_offsetof(DrawBlock, cut) == 496, "DrawBlock.cut: std140"); +static_assert(__builtin_offsetof(DrawBlock, wounds) == 560, "DrawBlock.wounds: std140"); + +struct ae_lit_depth_in { + float3 BindPos [[user(locn0)]]; + float ClipLimb [[user(locn1)]]; +}; + +static float ae3d_shaders_lit_clip_hash(float3 p_in) { + float3 p0 = fract(p_in * 0.3183099 + float3(0.71, 0.113, 0.419)); + float3 p = p0 * 17.0; + return fract(p.x * p.y * p.z * (p.x + p.y + p.z)); +} + +static float ae3d_shaders_lit_clip_value_noise(float3 p) { + float3 i = floor(p); + float3 f0 = fract(p); + float3 f = f0 * f0 * (float3(3.0) - f0 * 2.0); + return mix(mix(mix(ae3d_shaders_lit_clip_hash(i + float3(0.0, 0.0, 0.0)), ae3d_shaders_lit_clip_hash(i + float3(1.0, 0.0, 0.0)), f.x), mix(ae3d_shaders_lit_clip_hash(i + float3(0.0, 1.0, 0.0)), ae3d_shaders_lit_clip_hash(i + float3(1.0, 1.0, 0.0)), f.x), f.y), mix(mix(ae3d_shaders_lit_clip_hash(i + float3(0.0, 0.0, 1.0)), ae3d_shaders_lit_clip_hash(i + float3(1.0, 0.0, 1.0)), f.x), mix(ae3d_shaders_lit_clip_hash(i + float3(0.0, 1.0, 1.0)), ae3d_shaders_lit_clip_hash(i + float3(1.0, 1.0, 1.0)), f.x), f.y), f.z); +} + +static bool ae3d_shaders_lit_clipped_away(float3 bind_pos, constant Cut& cut, float ClipLimb) { + if (cut.on == 0) { + return false; + } + bool on_limb = ClipLimb >= 0.5; + if (cut.detached != 0 && !on_limb) { + return true; + } + if (!on_limb) { + return false; + } + float side0 = dot(cut.plane.xyz, bind_pos) - cut.plane.w; + float side = side0; + if (cut.noise > 0.0) { + side = side0 + cut.noise * (ae3d_shaders_lit_clip_value_noise(bind_pos * cut.noise_scale) * 2.0 - 1.0); + } + return side > 0.0; +} + +static float3 ae3d_shaders_lit_wound_unturn(float4 q, float3 v) { + float3 u = -q.xyz; + float3 t = cross(u, v) * 2.0; + return v + t * q.w + cross(u, t); +} + +static float ae3d_shaders_lit_wound_depth(float3 p, constant Wounds& wounds) { + float best = 2.0; + for (int i = 0; i < 32; i += 1) { + if (i >= wounds.count) { + break; + } + if (wounds.data[i * 3].w > 0.5) { + continue; + } + float3 r = max(wounds.data[i * 3 + 1].xyz, float3(0.0001)); + float3 local = ae3d_shaders_lit_wound_unturn(wounds.data[i * 3 + 2], p - wounds.data[i * 3].xyz) / r; + best = min(best, length(local)); + } + return best; +} + +fragment void ae_lit_depth_fragment( + ae_lit_depth_in ae_in [[stage_in]], + constant DrawBlock& ae_set0 [[buffer(0)]]) { + float3 BindPos = ae_in.BindPos; + float ClipLimb = ae_in.ClipLimb; + constant Cut& cut = ae_set0.cut; + constant Wounds& wounds = ae_set0.wounds; + if (ae3d_shaders_lit_clipped_away(BindPos, cut, ClipLimb)) { + discard_fragment(); + } + if (wounds.count > 0 && ae3d_shaders_lit_wound_depth(BindPos, wounds) < wounds.core) { + discard_fragment(); + } + // kept +} diff --git a/src/ae3d/metal/lit_depth_quoted.vert.metal b/src/ae3d/metal/lit_depth_quoted.vert.metal new file mode 100644 index 00000000..244993ef --- /dev/null +++ b/src/ae3d/metal/lit_depth_quoted.vert.metal @@ -0,0 +1,189 @@ +// lit_depth_vertex: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct Draw { + float4x4 model; + float4x4 prev_model; + float4x4 light_space; + int instanced; + int instance_colour; + int points; + int billboard; + int flip_columns; + int flip_rows; + float lift; + int skinned; + int impostor; + char ae_pad0[12]; +}; +static_assert(sizeof(Draw) == 240, "Draw: std140"); +static_assert(__builtin_offsetof(Draw, model) == 0, "Draw.model: std140"); +static_assert(__builtin_offsetof(Draw, prev_model) == 64, "Draw.prev_model: std140"); +static_assert(__builtin_offsetof(Draw, light_space) == 128, "Draw.light_space: std140"); +static_assert(__builtin_offsetof(Draw, instanced) == 192, "Draw.instanced: std140"); +static_assert(__builtin_offsetof(Draw, instance_colour) == 196, "Draw.instance_colour: std140"); +static_assert(__builtin_offsetof(Draw, points) == 200, "Draw.points: std140"); +static_assert(__builtin_offsetof(Draw, billboard) == 204, "Draw.billboard: std140"); +static_assert(__builtin_offsetof(Draw, flip_columns) == 208, "Draw.flip_columns: std140"); +static_assert(__builtin_offsetof(Draw, flip_rows) == 212, "Draw.flip_rows: std140"); +static_assert(__builtin_offsetof(Draw, lift) == 216, "Draw.lift: std140"); +static_assert(__builtin_offsetof(Draw, skinned) == 220, "Draw.skinned: std140"); +static_assert(__builtin_offsetof(Draw, impostor) == 224, "Draw.impostor: std140"); + +struct Cut { + int on; + char ae_pad0[12]; + float4 plane; + int detached; + float noise; + float noise_scale; + int joints0; + int joints1; + int joints2; + char ae_pad1[8]; +}; +static_assert(sizeof(Cut) == 64, "Cut: std140"); +static_assert(__builtin_offsetof(Cut, on) == 0, "Cut.on: std140"); +static_assert(__builtin_offsetof(Cut, plane) == 16, "Cut.plane: std140"); +static_assert(__builtin_offsetof(Cut, detached) == 32, "Cut.detached: std140"); +static_assert(__builtin_offsetof(Cut, noise) == 36, "Cut.noise: std140"); +static_assert(__builtin_offsetof(Cut, noise_scale) == 40, "Cut.noise_scale: std140"); +static_assert(__builtin_offsetof(Cut, joints0) == 44, "Cut.joints0: std140"); +static_assert(__builtin_offsetof(Cut, joints1) == 48, "Cut.joints1: std140"); +static_assert(__builtin_offsetof(Cut, joints2) == 52, "Cut.joints2: std140"); + +struct Eye { + float4x4 view_projection; + float4x4 prev_view_projection; + float4x4 inv_view_projection; + packed_float3 position; + float far; + float2 jitter; + float2 screen_size; + int depth_on; + char ae_pad0[12]; + float4 depth_plane; + int history; + int capture; + float ray_reach; + int ray_frame; +}; +static_assert(sizeof(Eye) == 272, "Eye: std140"); +static_assert(__builtin_offsetof(Eye, view_projection) == 0, "Eye.view_projection: std140"); +static_assert(__builtin_offsetof(Eye, prev_view_projection) == 64, "Eye.prev_view_projection: std140"); +static_assert(__builtin_offsetof(Eye, inv_view_projection) == 128, "Eye.inv_view_projection: std140"); +static_assert(__builtin_offsetof(Eye, position) == 192, "Eye.position: std140"); +static_assert(__builtin_offsetof(Eye, far) == 204, "Eye.far: std140"); +static_assert(__builtin_offsetof(Eye, jitter) == 208, "Eye.jitter: std140"); +static_assert(__builtin_offsetof(Eye, screen_size) == 216, "Eye.screen_size: std140"); +static_assert(__builtin_offsetof(Eye, depth_on) == 224, "Eye.depth_on: std140"); +static_assert(__builtin_offsetof(Eye, depth_plane) == 240, "Eye.depth_plane: std140"); +static_assert(__builtin_offsetof(Eye, history) == 256, "Eye.history: std140"); +static_assert(__builtin_offsetof(Eye, capture) == 260, "Eye.capture: std140"); +static_assert(__builtin_offsetof(Eye, ray_reach) == 264, "Eye.ray_reach: std140"); +static_assert(__builtin_offsetof(Eye, ray_frame) == 268, "Eye.ray_frame: std140"); + +struct Skin { + float4x4 bones[96]; + float4 prev_rows[288]; +}; +static_assert(sizeof(Skin) == 10752, "Skin: std140"); +static_assert(__builtin_offsetof(Skin, bones) == 0, "Skin.bones: std140"); +static_assert(__builtin_offsetof(Skin, prev_rows) == 6144, "Skin.prev_rows: std140"); + +struct DrawBlock { + Draw draw; + char ae_pad0[256]; + Cut cut; + char ae_pad1[1744]; + Skin skin; +}; +static_assert(sizeof(DrawBlock) == 13056, "DrawBlock: std140"); +static_assert(__builtin_offsetof(DrawBlock, draw) == 0, "DrawBlock.draw: std140"); +static_assert(__builtin_offsetof(DrawBlock, cut) == 496, "DrawBlock.cut: std140"); +static_assert(__builtin_offsetof(DrawBlock, skin) == 2304, "DrawBlock.skin: std140"); + +struct CameraBlock { + Eye eye; +}; +static_assert(sizeof(CameraBlock) == 272, "CameraBlock: std140"); +static_assert(__builtin_offsetof(CameraBlock, eye) == 0, "CameraBlock.eye: std140"); + +struct ae_lit_depth_vertex_in { + float3 inPosition [[attribute(0)]]; + float4 instanceModel_0 [[attribute(3)]]; + float4 instanceModel_1 [[attribute(4)]]; + float4 instanceModel_2 [[attribute(5)]]; + float4 instanceModel_3 [[attribute(6)]]; + float4 inJoints [[attribute(8)]]; + float4 inWeights [[attribute(9)]]; +}; + +struct ae_lit_depth_vertex_out { + float4 ae_position [[position]]; + float3 BindPos [[user(locn0)]]; + float ClipLimb [[user(locn1)]]; +}; + +static float ae3d_shaders_lit_clip_joint_on(float joint, constant Cut& cut) { + int j = int((joint + 0.5)); + int word = j < 32 ? cut.joints0 : (j < 64 ? cut.joints1 : cut.joints2); + return float((word >> (j & 31) & 1)); +} + +static float ae3d_shaders_lit_clip_limb_of(float4 joints, float4 weights, constant Draw& draw, constant Cut& cut) { + if (draw.skinned == 0 || (cut.joints0 | cut.joints1 | cut.joints2) == 0) { + return 1.0; + } + return weights.x * ae3d_shaders_lit_clip_joint_on(joints.x, cut) + weights.y * ae3d_shaders_lit_clip_joint_on(joints.y, cut) + weights.z * ae3d_shaders_lit_clip_joint_on(joints.z, cut) + weights.w * ae3d_shaders_lit_clip_joint_on(joints.w, cut); +} + +vertex ae_lit_depth_vertex_out ae_lit_depth_vertex_vertex( + ae_lit_depth_vertex_in ae_in [[stage_in]], + constant DrawBlock& ae_set0 [[buffer(0)]], + constant CameraBlock& ae_set1 [[buffer(1)]]) { + ae_lit_depth_vertex_out ae_out; + float3 inPosition = ae_in.inPosition; + float4x4 instanceModel = float4x4(ae_in.instanceModel_0, ae_in.instanceModel_1, ae_in.instanceModel_2, ae_in.instanceModel_3); + float4 inJoints = ae_in.inJoints; + float4 inWeights = ae_in.inWeights; + constant Draw& draw = ae_set0.draw; + constant Cut& cut = ae_set0.cut; + constant Skin& skin = ae_set0.skin; + constant Eye& eye = ae_set1.eye; + float clip_limb = ae3d_shaders_lit_clip_limb_of(inJoints, inWeights, draw, cut); + float4x4 model_matrix0 = draw.instanced != 0 ? (draw.model * instanceModel) : draw.model; + float4x4 model_matrix = model_matrix0; + if (draw.instanced != 0 && draw.points != 0) { + float4 point = instanceModel[0]; + model_matrix = float4x4(draw.model[0] * point.w, draw.model[1] * point.w, draw.model[2] * point.w, float4(point.xyz, 1.0)); + if (draw.billboard > 0) { + float3 to_eye = eye.position - point.xyz; + float3 up0 = float3(0.0, 1.0, 0.0); + float3 forward = float3(0.0); + if (draw.billboard == 1) { + forward = normalize(float3(to_eye.x, 0.0, to_eye.z)); + } else { + forward = normalize(to_eye); + } + float3 right = normalize(cross(up0, forward)); + float3 up = cross(forward, right); + float sx = length(draw.model[0].xyz) * point.w; + float sy = length(draw.model[1].xyz) * point.w; + float sz = length(draw.model[2].xyz) * point.w; + model_matrix = float4x4(float4(right * sx, 0.0), float4(up * sy, 0.0), float4(forward * sz, 0.0), float4(point.xyz + up * (sy * draw.lift), 1.0)); + } + } + float4 posed0 = float4(inPosition, 1.0); + float4 posed = posed0; + if (draw.skinned != 0) { + float4x4 skin_now = ((((inWeights.x * skin.bones[int(inJoints.x)]) + (inWeights.y * skin.bones[int(inJoints.y)])) + (inWeights.z * skin.bones[int(inJoints.z)])) + (inWeights.w * skin.bones[int(inJoints.w)])); + posed = (skin_now * posed0); + } + ae_out.ae_position = ((draw.light_space * model_matrix) * posed); + ae_out.BindPos = inPosition; + ae_out.ClipLimb = clip_limb; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/lit_plain_quoted.vert.metal b/src/ae3d/metal/lit_plain_quoted.vert.metal new file mode 100644 index 00000000..08829131 --- /dev/null +++ b/src/ae3d/metal/lit_plain_quoted.vert.metal @@ -0,0 +1,263 @@ +// lit_plain_vertex: written by ae3d.gpu from its quote. +#include +using namespace metal; +static inline float3x3 ae_mat3(float4x4 m) { return float3x3(m[0].xyz, m[1].xyz, m[2].xyz); } + +struct Draw { + float4x4 model; + float4x4 prev_model; + float4x4 light_space; + int instanced; + int instance_colour; + int points; + int billboard; + int flip_columns; + int flip_rows; + float lift; + int skinned; + int impostor; + char ae_pad0[12]; +}; +static_assert(sizeof(Draw) == 240, "Draw: std140"); +static_assert(__builtin_offsetof(Draw, model) == 0, "Draw.model: std140"); +static_assert(__builtin_offsetof(Draw, prev_model) == 64, "Draw.prev_model: std140"); +static_assert(__builtin_offsetof(Draw, light_space) == 128, "Draw.light_space: std140"); +static_assert(__builtin_offsetof(Draw, instanced) == 192, "Draw.instanced: std140"); +static_assert(__builtin_offsetof(Draw, instance_colour) == 196, "Draw.instance_colour: std140"); +static_assert(__builtin_offsetof(Draw, points) == 200, "Draw.points: std140"); +static_assert(__builtin_offsetof(Draw, billboard) == 204, "Draw.billboard: std140"); +static_assert(__builtin_offsetof(Draw, flip_columns) == 208, "Draw.flip_columns: std140"); +static_assert(__builtin_offsetof(Draw, flip_rows) == 212, "Draw.flip_rows: std140"); +static_assert(__builtin_offsetof(Draw, lift) == 216, "Draw.lift: std140"); +static_assert(__builtin_offsetof(Draw, skinned) == 220, "Draw.skinned: std140"); +static_assert(__builtin_offsetof(Draw, impostor) == 224, "Draw.impostor: std140"); + +struct Cut { + int on; + char ae_pad0[12]; + float4 plane; + int detached; + float noise; + float noise_scale; + int joints0; + int joints1; + int joints2; + char ae_pad1[8]; +}; +static_assert(sizeof(Cut) == 64, "Cut: std140"); +static_assert(__builtin_offsetof(Cut, on) == 0, "Cut.on: std140"); +static_assert(__builtin_offsetof(Cut, plane) == 16, "Cut.plane: std140"); +static_assert(__builtin_offsetof(Cut, detached) == 32, "Cut.detached: std140"); +static_assert(__builtin_offsetof(Cut, noise) == 36, "Cut.noise: std140"); +static_assert(__builtin_offsetof(Cut, noise_scale) == 40, "Cut.noise_scale: std140"); +static_assert(__builtin_offsetof(Cut, joints0) == 44, "Cut.joints0: std140"); +static_assert(__builtin_offsetof(Cut, joints1) == 48, "Cut.joints1: std140"); +static_assert(__builtin_offsetof(Cut, joints2) == 52, "Cut.joints2: std140"); + +struct Eye { + float4x4 view_projection; + float4x4 prev_view_projection; + float4x4 inv_view_projection; + packed_float3 position; + float far; + float2 jitter; + float2 screen_size; + int depth_on; + char ae_pad0[12]; + float4 depth_plane; + int history; + int capture; + float ray_reach; + int ray_frame; +}; +static_assert(sizeof(Eye) == 272, "Eye: std140"); +static_assert(__builtin_offsetof(Eye, view_projection) == 0, "Eye.view_projection: std140"); +static_assert(__builtin_offsetof(Eye, prev_view_projection) == 64, "Eye.prev_view_projection: std140"); +static_assert(__builtin_offsetof(Eye, inv_view_projection) == 128, "Eye.inv_view_projection: std140"); +static_assert(__builtin_offsetof(Eye, position) == 192, "Eye.position: std140"); +static_assert(__builtin_offsetof(Eye, far) == 204, "Eye.far: std140"); +static_assert(__builtin_offsetof(Eye, jitter) == 208, "Eye.jitter: std140"); +static_assert(__builtin_offsetof(Eye, screen_size) == 216, "Eye.screen_size: std140"); +static_assert(__builtin_offsetof(Eye, depth_on) == 224, "Eye.depth_on: std140"); +static_assert(__builtin_offsetof(Eye, depth_plane) == 240, "Eye.depth_plane: std140"); +static_assert(__builtin_offsetof(Eye, history) == 256, "Eye.history: std140"); +static_assert(__builtin_offsetof(Eye, capture) == 260, "Eye.capture: std140"); +static_assert(__builtin_offsetof(Eye, ray_reach) == 264, "Eye.ray_reach: std140"); +static_assert(__builtin_offsetof(Eye, ray_frame) == 268, "Eye.ray_frame: std140"); + +struct Skin { + float4x4 bones[96]; + float4 prev_rows[288]; +}; +static_assert(sizeof(Skin) == 10752, "Skin: std140"); +static_assert(__builtin_offsetof(Skin, bones) == 0, "Skin.bones: std140"); +static_assert(__builtin_offsetof(Skin, prev_rows) == 6144, "Skin.prev_rows: std140"); + +struct DrawBlock { + Draw draw; + char ae_pad0[256]; + Cut cut; + char ae_pad1[1744]; + Skin skin; +}; +static_assert(sizeof(DrawBlock) == 13056, "DrawBlock: std140"); +static_assert(__builtin_offsetof(DrawBlock, draw) == 0, "DrawBlock.draw: std140"); +static_assert(__builtin_offsetof(DrawBlock, cut) == 496, "DrawBlock.cut: std140"); +static_assert(__builtin_offsetof(DrawBlock, skin) == 2304, "DrawBlock.skin: std140"); + +struct CameraBlock { + Eye eye; +}; +static_assert(sizeof(CameraBlock) == 272, "CameraBlock: std140"); +static_assert(__builtin_offsetof(CameraBlock, eye) == 0, "CameraBlock.eye: std140"); + +struct ae_lit_plain_vertex_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; + float3 inNormal [[attribute(2)]]; + float4 instanceModel_0 [[attribute(3)]]; + float4 instanceModel_1 [[attribute(4)]]; + float4 instanceModel_2 [[attribute(5)]]; + float4 instanceModel_3 [[attribute(6)]]; + float3 instanceColor [[attribute(7)]]; + float inOcclusion [[attribute(10)]]; + float4 instanceExtras [[attribute(13)]]; +}; + +struct ae_lit_plain_vertex_out { + float4 ae_position [[position]]; + float2 fragTexCoord [[user(locn0)]]; + float3 Normal [[user(locn1)]]; + float3 FragPos [[user(locn2)]]; + float3 InstanceColor [[user(locn3)]]; + float4 FragPosLightSpace [[user(locn4)]]; + float Occlusion [[user(locn5)]]; + float4 ClipNow [[user(locn6)]]; + float4 ClipPrev [[user(locn7)]]; + float3 BindPos [[user(locn8)]]; + float ClipLimb [[user(locn9)]]; + float2 MaskUV [[user(locn10)]]; + float ParticleAlpha [[user(locn11)]]; + float3 FlipNext [[user(locn12)]]; +}; + +static float ae3d_shaders_lit_clip_joint_on(float joint, constant Cut& cut) { + int j = int((joint + 0.5)); + int word = j < 32 ? cut.joints0 : (j < 64 ? cut.joints1 : cut.joints2); + return float((word >> (j & 31) & 1)); +} + +static float ae3d_shaders_lit_clip_limb_of(float4 joints, float4 weights, constant Draw& draw, constant Cut& cut) { + if (draw.skinned == 0 || (cut.joints0 | cut.joints1 | cut.joints2) == 0) { + return 1.0; + } + return weights.x * ae3d_shaders_lit_clip_joint_on(joints.x, cut) + weights.y * ae3d_shaders_lit_clip_joint_on(joints.y, cut) + weights.z * ae3d_shaders_lit_clip_joint_on(joints.z, cut) + weights.w * ae3d_shaders_lit_clip_joint_on(joints.w, cut); +} + +static float4x4 ae3d_shaders_lit_prev_bone(float joint, constant Skin& skin) { + int j = int((joint + 0.5)) * 3; + return transpose(float4x4(skin.prev_rows[j], skin.prev_rows[j + 1], skin.prev_rows[j + 2], float4(float3(0.0, 0.0, 0.0), 1.0))); +} + +vertex ae_lit_plain_vertex_out ae_lit_plain_vertex_vertex( + ae_lit_plain_vertex_in ae_in [[stage_in]], + constant DrawBlock& ae_set0 [[buffer(0)]], + constant CameraBlock& ae_set1 [[buffer(1)]]) { + ae_lit_plain_vertex_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + float3 inNormal = ae_in.inNormal; + float4x4 instanceModel = float4x4(ae_in.instanceModel_0, ae_in.instanceModel_1, ae_in.instanceModel_2, ae_in.instanceModel_3); + float3 instanceColor = ae_in.instanceColor; + float inOcclusion = ae_in.inOcclusion; + float4 instanceExtras = ae_in.instanceExtras; + constant Draw& draw = ae_set0.draw; + constant Cut& cut = ae_set0.cut; + constant Skin& skin = ae_set0.skin; + constant Eye& eye = ae_set1.eye; + float clip_limb = ae3d_shaders_lit_clip_limb_of(float4(0.0), float4(0.0), draw, cut); + float2 mask_uv = float2(-1.0).x < -0.5 ? inTexCoord : float2(-1.0); + float4x4 model_matrix0 = draw.instanced != 0 ? (draw.model * instanceModel) : draw.model; + float4x4 model_matrix = model_matrix0; + float particle_alpha = 1.0; + float3 flip_next = float3(0.0, 0.0, -1.0); + float2 tex_coord = inTexCoord; + if (draw.instanced != 0 && draw.points != 0) { + float4 point = instanceModel[0]; + particle_alpha = instanceExtras.x; + if (draw.flip_columns > 0 && draw.flip_rows > 0) { + int frames = draw.flip_columns * draw.flip_rows; + float at = clamp(instanceExtras.y, 0.0, 1.0) * float((frames - 1)); + int frame = min(int(at), frames - 1); + int next = min(frame + 1, frames - 1); + float column = float((frame - frame / draw.flip_columns * draw.flip_columns)); + float row = float((frame / draw.flip_columns)); + tex_coord = float2((column + inTexCoord.x) / float(draw.flip_columns), 1.0 - (row + 1.0 - inTexCoord.y) / float(draw.flip_rows)); + float next_column = float((next - next / draw.flip_columns * draw.flip_columns)); + float next_row = float((next / draw.flip_columns)); + flip_next = float3((next_column + inTexCoord.x) / float(draw.flip_columns), 1.0 - (next_row + 1.0 - inTexCoord.y) / float(draw.flip_rows), at - float(frame)); + } + model_matrix = float4x4(draw.model[0] * point.w, draw.model[1] * point.w, draw.model[2] * point.w, float4(point.xyz, 1.0)); + if (draw.billboard > 0) { + float3 to_eye = eye.position - point.xyz; + float3 up0 = float3(0.0, 1.0, 0.0); + float3 forward = float3(0.0); + if (draw.billboard == 1) { + forward = normalize(float3(to_eye.x, 0.0, to_eye.z)); + } else { + forward = normalize(to_eye); + } + float3 right0 = normalize(cross(up0, forward)); + float3 up1 = cross(forward, right0); + float cn = cos(instanceExtras.z); + float sn = sin(instanceExtras.z); + float3 turned = right0 * cn + up1 * sn; + float3 up = up1 * cn - right0 * sn; + float3 right = turned * max(instanceExtras.w, 1.0); + float sx = length(draw.model[0].xyz) * point.w; + float sy = length(draw.model[1].xyz) * point.w; + float sz = length(draw.model[2].xyz) * point.w; + model_matrix = float4x4(float4(right * sx, 0.0), float4(up * sy, 0.0), float4(forward * sz, 0.0), float4(point.xyz + up * (sy * draw.lift), 1.0)); + } + } + float4 posed0 = float4(inPosition, 1.0); + float4 posed = posed0; + float4 posed_prev = posed0; + float3 posed_normal = inNormal; + if (draw.skinned != 0) { + float4 weights = float4(0.0); + float4 joints = float4(0.0); + float4x4 skin_now = ((((weights.x * skin.bones[int(joints.x)]) + (weights.y * skin.bones[int(joints.y)])) + (weights.z * skin.bones[int(joints.z)])) + (weights.w * skin.bones[int(joints.w)])); + float4x4 skin_prev = ((((weights.x * ae3d_shaders_lit_prev_bone(joints.x, skin)) + (weights.y * ae3d_shaders_lit_prev_bone(joints.y, skin))) + (weights.z * ae3d_shaders_lit_prev_bone(joints.z, skin))) + (weights.w * ae3d_shaders_lit_prev_bone(joints.w, skin))); + posed_prev = (skin_prev * posed0); + posed = (skin_now * posed0); + posed_normal = (ae_mat3(skin_now) * inNormal); + } + float4x4 prev_model_matrix0 = draw.instanced != 0 ? (draw.prev_model * instanceModel) : draw.prev_model; + float4x4 prev_model_matrix = prev_model_matrix0; + if (draw.instanced != 0 && draw.points != 0) { + prev_model_matrix = model_matrix; + } + float3 frag_pos = (model_matrix * posed).xyz; + float3 normal_out = normalize((ae_mat3(model_matrix) * posed_normal)); + float3 instance_colour = draw.instanced != 0 && draw.instance_colour != 0 ? instanceColor : float3(1.0, 1.0, 1.0); + float4 light_space = (draw.light_space * float4(frag_pos, 1.0)); + float4 clip_now = ((eye.view_projection * model_matrix) * posed); + float4 clip_prev = ((eye.prev_view_projection * prev_model_matrix) * posed_prev); + ae_out.ae_position = clip_now; + ae_out.fragTexCoord = tex_coord; + ae_out.Normal = normal_out; + ae_out.FragPos = frag_pos; + ae_out.InstanceColor = instance_colour; + ae_out.FragPosLightSpace = light_space; + ae_out.Occlusion = inOcclusion; + ae_out.ClipNow = clip_now; + ae_out.ClipPrev = clip_prev; + ae_out.BindPos = inPosition; + ae_out.ClipLimb = clip_limb; + ae_out.MaskUV = mask_uv; + ae_out.ParticleAlpha = particle_alpha; + ae_out.FlipNext = flip_next; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/lit_quoted.frag.metal b/src/ae3d/metal/lit_quoted.frag.metal new file mode 100644 index 00000000..915da417 --- /dev/null +++ b/src/ae3d/metal/lit_quoted.frag.metal @@ -0,0 +1,1589 @@ +// lit: written by ae3d.gpu from its quote. +#include +using namespace metal; +static inline float ae_mod(float x, float y) { return x - y * floor(x / y); } +static inline float2 ae_mod(float2 x, float y) { return x - y * floor(x / y); } + +struct WorldLight { + packed_float3 position; + char ae_pad0[4]; + packed_float3 colour; + float intensity; + float temperature; + int kind; + char ae_pad1[8]; + packed_float3 direction; + float constant_; + float linear; + float quadratic; + float cos_outer; + float cos_inner; +}; +static_assert(sizeof(WorldLight) == 80, "WorldLight: std140"); +static_assert(__builtin_offsetof(WorldLight, position) == 0, "WorldLight.position: std140"); +static_assert(__builtin_offsetof(WorldLight, colour) == 16, "WorldLight.colour: std140"); +static_assert(__builtin_offsetof(WorldLight, intensity) == 28, "WorldLight.intensity: std140"); +static_assert(__builtin_offsetof(WorldLight, temperature) == 32, "WorldLight.temperature: std140"); +static_assert(__builtin_offsetof(WorldLight, kind) == 36, "WorldLight.kind: std140"); +static_assert(__builtin_offsetof(WorldLight, direction) == 48, "WorldLight.direction: std140"); +static_assert(__builtin_offsetof(WorldLight, constant_) == 60, "WorldLight.constant_: std140"); +static_assert(__builtin_offsetof(WorldLight, linear) == 64, "WorldLight.linear: std140"); +static_assert(__builtin_offsetof(WorldLight, quadratic) == 68, "WorldLight.quadratic: std140"); +static_assert(__builtin_offsetof(WorldLight, cos_outer) == 72, "WorldLight.cos_outer: std140"); +static_assert(__builtin_offsetof(WorldLight, cos_inner) == 76, "WorldLight.cos_inner: std140"); +static inline WorldLight ae_make_WorldLight(float3 position, float3 colour, float intensity, float temperature, int kind, float3 direction, float constant_, float linear, float quadratic, float cos_outer, float cos_inner) { + WorldLight ae_r; + ae_r.position = position; + ae_r.colour = colour; + ae_r.intensity = intensity; + ae_r.temperature = temperature; + ae_r.kind = kind; + ae_r.direction = direction; + ae_r.constant_ = constant_; + ae_r.linear = linear; + ae_r.quadratic = quadratic; + ae_r.cos_outer = cos_outer; + ae_r.cos_inner = cos_inner; + return ae_r; +} + +struct Cut { + int on; + char ae_pad0[12]; + float4 plane; + int detached; + float noise; + float noise_scale; + int joints0; + int joints1; + int joints2; + char ae_pad1[8]; +}; +static_assert(sizeof(Cut) == 64, "Cut: std140"); +static_assert(__builtin_offsetof(Cut, on) == 0, "Cut.on: std140"); +static_assert(__builtin_offsetof(Cut, plane) == 16, "Cut.plane: std140"); +static_assert(__builtin_offsetof(Cut, detached) == 32, "Cut.detached: std140"); +static_assert(__builtin_offsetof(Cut, noise) == 36, "Cut.noise: std140"); +static_assert(__builtin_offsetof(Cut, noise_scale) == 40, "Cut.noise_scale: std140"); +static_assert(__builtin_offsetof(Cut, joints0) == 44, "Cut.joints0: std140"); +static_assert(__builtin_offsetof(Cut, joints1) == 48, "Cut.joints1: std140"); +static_assert(__builtin_offsetof(Cut, joints2) == 52, "Cut.joints2: std140"); + +struct Wounds { + int count; + char ae_pad0[12]; + float4 data[96]; + float4 layers[4]; + float core; + char ae_pad1[12]; +}; +static_assert(sizeof(Wounds) == 1632, "Wounds: std140"); +static_assert(__builtin_offsetof(Wounds, count) == 0, "Wounds.count: std140"); +static_assert(__builtin_offsetof(Wounds, data) == 16, "Wounds.data: std140"); +static_assert(__builtin_offsetof(Wounds, layers) == 1552, "Wounds.layers: std140"); +static_assert(__builtin_offsetof(Wounds, core) == 1616, "Wounds.core: std140"); + +struct Eye { + float4x4 view_projection; + float4x4 prev_view_projection; + float4x4 inv_view_projection; + packed_float3 position; + float far; + float2 jitter; + float2 screen_size; + int depth_on; + char ae_pad0[12]; + float4 depth_plane; + int history; + int capture; + float ray_reach; + int ray_frame; +}; +static_assert(sizeof(Eye) == 272, "Eye: std140"); +static_assert(__builtin_offsetof(Eye, view_projection) == 0, "Eye.view_projection: std140"); +static_assert(__builtin_offsetof(Eye, prev_view_projection) == 64, "Eye.prev_view_projection: std140"); +static_assert(__builtin_offsetof(Eye, inv_view_projection) == 128, "Eye.inv_view_projection: std140"); +static_assert(__builtin_offsetof(Eye, position) == 192, "Eye.position: std140"); +static_assert(__builtin_offsetof(Eye, far) == 204, "Eye.far: std140"); +static_assert(__builtin_offsetof(Eye, jitter) == 208, "Eye.jitter: std140"); +static_assert(__builtin_offsetof(Eye, screen_size) == 216, "Eye.screen_size: std140"); +static_assert(__builtin_offsetof(Eye, depth_on) == 224, "Eye.depth_on: std140"); +static_assert(__builtin_offsetof(Eye, depth_plane) == 240, "Eye.depth_plane: std140"); +static_assert(__builtin_offsetof(Eye, history) == 256, "Eye.history: std140"); +static_assert(__builtin_offsetof(Eye, capture) == 260, "Eye.capture: std140"); +static_assert(__builtin_offsetof(Eye, ray_reach) == 264, "Eye.ray_reach: std140"); +static_assert(__builtin_offsetof(Eye, ray_frame) == 268, "Eye.ray_frame: std140"); + +struct DrawMaterial { + packed_float3 diffuse; + char ae_pad0[4]; + packed_float3 specular; + float metallic; + float roughness; + float exposure; + float alpha; + float cutout; + float normal_strength; + float tile_breakup; + float tile_rotation; + float variation; + float variation_scale; + float detail_scale; + float detail_strength; + float detail_fade; + float fire; + float triplanar; + float soft; + float glitter; + float glitter_grains; + float glitter_strength; + float embers; + float ember_cover; + float ember_glow; + int blend; + float reflectivity; + int normal_map; +}; +static_assert(sizeof(DrawMaterial) == 128, "DrawMaterial: std140"); +static_assert(__builtin_offsetof(DrawMaterial, diffuse) == 0, "DrawMaterial.diffuse: std140"); +static_assert(__builtin_offsetof(DrawMaterial, specular) == 16, "DrawMaterial.specular: std140"); +static_assert(__builtin_offsetof(DrawMaterial, metallic) == 28, "DrawMaterial.metallic: std140"); +static_assert(__builtin_offsetof(DrawMaterial, roughness) == 32, "DrawMaterial.roughness: std140"); +static_assert(__builtin_offsetof(DrawMaterial, exposure) == 36, "DrawMaterial.exposure: std140"); +static_assert(__builtin_offsetof(DrawMaterial, alpha) == 40, "DrawMaterial.alpha: std140"); +static_assert(__builtin_offsetof(DrawMaterial, cutout) == 44, "DrawMaterial.cutout: std140"); +static_assert(__builtin_offsetof(DrawMaterial, normal_strength) == 48, "DrawMaterial.normal_strength: std140"); +static_assert(__builtin_offsetof(DrawMaterial, tile_breakup) == 52, "DrawMaterial.tile_breakup: std140"); +static_assert(__builtin_offsetof(DrawMaterial, tile_rotation) == 56, "DrawMaterial.tile_rotation: std140"); +static_assert(__builtin_offsetof(DrawMaterial, variation) == 60, "DrawMaterial.variation: std140"); +static_assert(__builtin_offsetof(DrawMaterial, variation_scale) == 64, "DrawMaterial.variation_scale: std140"); +static_assert(__builtin_offsetof(DrawMaterial, detail_scale) == 68, "DrawMaterial.detail_scale: std140"); +static_assert(__builtin_offsetof(DrawMaterial, detail_strength) == 72, "DrawMaterial.detail_strength: std140"); +static_assert(__builtin_offsetof(DrawMaterial, detail_fade) == 76, "DrawMaterial.detail_fade: std140"); +static_assert(__builtin_offsetof(DrawMaterial, fire) == 80, "DrawMaterial.fire: std140"); +static_assert(__builtin_offsetof(DrawMaterial, triplanar) == 84, "DrawMaterial.triplanar: std140"); +static_assert(__builtin_offsetof(DrawMaterial, soft) == 88, "DrawMaterial.soft: std140"); +static_assert(__builtin_offsetof(DrawMaterial, glitter) == 92, "DrawMaterial.glitter: std140"); +static_assert(__builtin_offsetof(DrawMaterial, glitter_grains) == 96, "DrawMaterial.glitter_grains: std140"); +static_assert(__builtin_offsetof(DrawMaterial, glitter_strength) == 100, "DrawMaterial.glitter_strength: std140"); +static_assert(__builtin_offsetof(DrawMaterial, embers) == 104, "DrawMaterial.embers: std140"); +static_assert(__builtin_offsetof(DrawMaterial, ember_cover) == 108, "DrawMaterial.ember_cover: std140"); +static_assert(__builtin_offsetof(DrawMaterial, ember_glow) == 112, "DrawMaterial.ember_glow: std140"); +static_assert(__builtin_offsetof(DrawMaterial, blend) == 116, "DrawMaterial.blend: std140"); +static_assert(__builtin_offsetof(DrawMaterial, reflectivity) == 120, "DrawMaterial.reflectivity: std140"); +static_assert(__builtin_offsetof(DrawMaterial, normal_map) == 124, "DrawMaterial.normal_map: std140"); + +struct Draw { + float4x4 model; + float4x4 prev_model; + float4x4 light_space; + int instanced; + int instance_colour; + int points; + int billboard; + int flip_columns; + int flip_rows; + float lift; + int skinned; + int impostor; + char ae_pad0[12]; +}; +static_assert(sizeof(Draw) == 240, "Draw: std140"); +static_assert(__builtin_offsetof(Draw, model) == 0, "Draw.model: std140"); +static_assert(__builtin_offsetof(Draw, prev_model) == 64, "Draw.prev_model: std140"); +static_assert(__builtin_offsetof(Draw, light_space) == 128, "Draw.light_space: std140"); +static_assert(__builtin_offsetof(Draw, instanced) == 192, "Draw.instanced: std140"); +static_assert(__builtin_offsetof(Draw, instance_colour) == 196, "Draw.instance_colour: std140"); +static_assert(__builtin_offsetof(Draw, points) == 200, "Draw.points: std140"); +static_assert(__builtin_offsetof(Draw, billboard) == 204, "Draw.billboard: std140"); +static_assert(__builtin_offsetof(Draw, flip_columns) == 208, "Draw.flip_columns: std140"); +static_assert(__builtin_offsetof(Draw, flip_rows) == 212, "Draw.flip_rows: std140"); +static_assert(__builtin_offsetof(Draw, lift) == 216, "Draw.lift: std140"); +static_assert(__builtin_offsetof(Draw, skinned) == 220, "Draw.skinned: std140"); +static_assert(__builtin_offsetof(Draw, impostor) == 224, "Draw.impostor: std140"); + +struct WorldState { + WorldLight lights[16]; + int light_count; + char ae_pad0[12]; + packed_float3 shadow_direction; + float cloud_cover; + float cloud_time; + char ae_pad1[12]; + packed_float3 cloud_sun; + float time; + float wetness; + float normal_strength; + float occlusion_strength; + int gi_off; + int rays; + float sun_angle; + float ray_occlusion; + float ray_occlusion_strength; + float ray_lamp_radius; + char ae_pad2[12]; +}; +static_assert(sizeof(WorldState) == 1392, "WorldState: std140"); +static_assert(__builtin_offsetof(WorldState, lights) == 0, "WorldState.lights: std140"); +static_assert(__builtin_offsetof(WorldState, light_count) == 1280, "WorldState.light_count: std140"); +static_assert(__builtin_offsetof(WorldState, shadow_direction) == 1296, "WorldState.shadow_direction: std140"); +static_assert(__builtin_offsetof(WorldState, cloud_cover) == 1308, "WorldState.cloud_cover: std140"); +static_assert(__builtin_offsetof(WorldState, cloud_time) == 1312, "WorldState.cloud_time: std140"); +static_assert(__builtin_offsetof(WorldState, cloud_sun) == 1328, "WorldState.cloud_sun: std140"); +static_assert(__builtin_offsetof(WorldState, time) == 1340, "WorldState.time: std140"); +static_assert(__builtin_offsetof(WorldState, wetness) == 1344, "WorldState.wetness: std140"); +static_assert(__builtin_offsetof(WorldState, normal_strength) == 1348, "WorldState.normal_strength: std140"); +static_assert(__builtin_offsetof(WorldState, occlusion_strength) == 1352, "WorldState.occlusion_strength: std140"); +static_assert(__builtin_offsetof(WorldState, gi_off) == 1356, "WorldState.gi_off: std140"); +static_assert(__builtin_offsetof(WorldState, rays) == 1360, "WorldState.rays: std140"); +static_assert(__builtin_offsetof(WorldState, sun_angle) == 1364, "WorldState.sun_angle: std140"); +static_assert(__builtin_offsetof(WorldState, ray_occlusion) == 1368, "WorldState.ray_occlusion: std140"); +static_assert(__builtin_offsetof(WorldState, ray_occlusion_strength) == 1372, "WorldState.ray_occlusion_strength: std140"); +static_assert(__builtin_offsetof(WorldState, ray_lamp_radius) == 1376, "WorldState.ray_lamp_radius: std140"); + +struct Damage { + int on; + char ae_pad0[12]; + float4 colours[4]; + float4 rect; + float clamp; + char ae_pad1[12]; +}; +static_assert(sizeof(Damage) == 112, "Damage: std140"); +static_assert(__builtin_offsetof(Damage, on) == 0, "Damage.on: std140"); +static_assert(__builtin_offsetof(Damage, colours) == 16, "Damage.colours: std140"); +static_assert(__builtin_offsetof(Damage, rect) == 80, "Damage.rect: std140"); +static_assert(__builtin_offsetof(Damage, clamp) == 96, "Damage.clamp: std140"); + +struct Surface { + int shadows; + float shadow_intensity; + float shadow_softness; + int clearcoat; + float clearcoat_roughness; + float clearcoat_intensity; + int sheen; + char ae_pad0[4]; + packed_float3 sheen_colour; + float sheen_roughness; + int transmission; + float transmission_factor; + int multiple_scattering; + int energy_conservation; + int ibl; + float ibl_intensity; + int volumetric; + float volumetric_intensity; + int volumetric_steps; + float volumetric_scattering; + int perlin; + float noise_scale; + int noise_octaves; + float noise_intensity; + int caustics; + float caustics_intensity; + float caustics_scale; + float caustics_speed; + float caustics_water_level; + float caustics_depth; +}; +static_assert(sizeof(Surface) == 128, "Surface: std140"); +static_assert(__builtin_offsetof(Surface, shadows) == 0, "Surface.shadows: std140"); +static_assert(__builtin_offsetof(Surface, shadow_intensity) == 4, "Surface.shadow_intensity: std140"); +static_assert(__builtin_offsetof(Surface, shadow_softness) == 8, "Surface.shadow_softness: std140"); +static_assert(__builtin_offsetof(Surface, clearcoat) == 12, "Surface.clearcoat: std140"); +static_assert(__builtin_offsetof(Surface, clearcoat_roughness) == 16, "Surface.clearcoat_roughness: std140"); +static_assert(__builtin_offsetof(Surface, clearcoat_intensity) == 20, "Surface.clearcoat_intensity: std140"); +static_assert(__builtin_offsetof(Surface, sheen) == 24, "Surface.sheen: std140"); +static_assert(__builtin_offsetof(Surface, sheen_colour) == 32, "Surface.sheen_colour: std140"); +static_assert(__builtin_offsetof(Surface, sheen_roughness) == 44, "Surface.sheen_roughness: std140"); +static_assert(__builtin_offsetof(Surface, transmission) == 48, "Surface.transmission: std140"); +static_assert(__builtin_offsetof(Surface, transmission_factor) == 52, "Surface.transmission_factor: std140"); +static_assert(__builtin_offsetof(Surface, multiple_scattering) == 56, "Surface.multiple_scattering: std140"); +static_assert(__builtin_offsetof(Surface, energy_conservation) == 60, "Surface.energy_conservation: std140"); +static_assert(__builtin_offsetof(Surface, ibl) == 64, "Surface.ibl: std140"); +static_assert(__builtin_offsetof(Surface, ibl_intensity) == 68, "Surface.ibl_intensity: std140"); +static_assert(__builtin_offsetof(Surface, volumetric) == 72, "Surface.volumetric: std140"); +static_assert(__builtin_offsetof(Surface, volumetric_intensity) == 76, "Surface.volumetric_intensity: std140"); +static_assert(__builtin_offsetof(Surface, volumetric_steps) == 80, "Surface.volumetric_steps: std140"); +static_assert(__builtin_offsetof(Surface, volumetric_scattering) == 84, "Surface.volumetric_scattering: std140"); +static_assert(__builtin_offsetof(Surface, perlin) == 88, "Surface.perlin: std140"); +static_assert(__builtin_offsetof(Surface, noise_scale) == 92, "Surface.noise_scale: std140"); +static_assert(__builtin_offsetof(Surface, noise_octaves) == 96, "Surface.noise_octaves: std140"); +static_assert(__builtin_offsetof(Surface, noise_intensity) == 100, "Surface.noise_intensity: std140"); +static_assert(__builtin_offsetof(Surface, caustics) == 104, "Surface.caustics: std140"); +static_assert(__builtin_offsetof(Surface, caustics_intensity) == 108, "Surface.caustics_intensity: std140"); +static_assert(__builtin_offsetof(Surface, caustics_scale) == 112, "Surface.caustics_scale: std140"); +static_assert(__builtin_offsetof(Surface, caustics_speed) == 116, "Surface.caustics_speed: std140"); +static_assert(__builtin_offsetof(Surface, caustics_water_level) == 120, "Surface.caustics_water_level: std140"); +static_assert(__builtin_offsetof(Surface, caustics_depth) == 124, "Surface.caustics_depth: std140"); + +struct ShadowCascades { + int on; + char ae_pad0[12]; + float4x4 matrices[4]; + float4 splits; + float4 texel_world; + int count; + char ae_pad1[12]; +}; +static_assert(sizeof(ShadowCascades) == 320, "ShadowCascades: std140"); +static_assert(__builtin_offsetof(ShadowCascades, on) == 0, "ShadowCascades.on: std140"); +static_assert(__builtin_offsetof(ShadowCascades, matrices) == 16, "ShadowCascades.matrices: std140"); +static_assert(__builtin_offsetof(ShadowCascades, splits) == 272, "ShadowCascades.splits: std140"); +static_assert(__builtin_offsetof(ShadowCascades, texel_world) == 288, "ShadowCascades.texel_world: std140"); +static_assert(__builtin_offsetof(ShadowCascades, count) == 304, "ShadowCascades.count: std140"); + +struct Grid { + float4 dims; + float4 depth; + float4x4 view_projection; + int lamp_base; + int key_lamp; + char ae_pad0[8]; +}; +static_assert(sizeof(Grid) == 112, "Grid: std140"); +static_assert(__builtin_offsetof(Grid, dims) == 0, "Grid.dims: std140"); +static_assert(__builtin_offsetof(Grid, depth) == 16, "Grid.depth: std140"); +static_assert(__builtin_offsetof(Grid, view_projection) == 32, "Grid.view_projection: std140"); +static_assert(__builtin_offsetof(Grid, lamp_base) == 96, "Grid.lamp_base: std140"); +static_assert(__builtin_offsetof(Grid, key_lamp) == 100, "Grid.key_lamp: std140"); + +struct Weather { + int fog; + float fog_start; + float fog_end; + char ae_pad0[4]; + packed_float3 fog_radiance; + float fog_intensity; +}; +static_assert(sizeof(Weather) == 32, "Weather: std140"); +static_assert(__builtin_offsetof(Weather, fog) == 0, "Weather.fog: std140"); +static_assert(__builtin_offsetof(Weather, fog_start) == 4, "Weather.fog_start: std140"); +static_assert(__builtin_offsetof(Weather, fog_end) == 8, "Weather.fog_end: std140"); +static_assert(__builtin_offsetof(Weather, fog_radiance) == 16, "Weather.fog_radiance: std140"); +static_assert(__builtin_offsetof(Weather, fog_intensity) == 28, "Weather.fog_intensity: std140"); + +struct DrawBlock { + Draw draw; + DrawMaterial material; + Surface shading; + Cut cut; + Wounds wounds; + Damage damage; +}; +static_assert(sizeof(DrawBlock) == 2304, "DrawBlock: std140"); +static_assert(__builtin_offsetof(DrawBlock, draw) == 0, "DrawBlock.draw: std140"); +static_assert(__builtin_offsetof(DrawBlock, material) == 240, "DrawBlock.material: std140"); +static_assert(__builtin_offsetof(DrawBlock, shading) == 368, "DrawBlock.shading: std140"); +static_assert(__builtin_offsetof(DrawBlock, cut) == 496, "DrawBlock.cut: std140"); +static_assert(__builtin_offsetof(DrawBlock, wounds) == 560, "DrawBlock.wounds: std140"); +static_assert(__builtin_offsetof(DrawBlock, damage) == 2192, "DrawBlock.damage: std140"); + +struct CameraBlock { + Eye eye; + ShadowCascades cascades; + Grid grid; +}; +static_assert(sizeof(CameraBlock) == 704, "CameraBlock: std140"); +static_assert(__builtin_offsetof(CameraBlock, eye) == 0, "CameraBlock.eye: std140"); +static_assert(__builtin_offsetof(CameraBlock, cascades) == 272, "CameraBlock.cascades: std140"); +static_assert(__builtin_offsetof(CameraBlock, grid) == 592, "CameraBlock.grid: std140"); + +struct WorldBlock { + WorldState world; + Weather weather; +}; +static_assert(sizeof(WorldBlock) == 1424, "WorldBlock: std140"); +static_assert(__builtin_offsetof(WorldBlock, world) == 0, "WorldBlock.world: std140"); +static_assert(__builtin_offsetof(WorldBlock, weather) == 1392, "WorldBlock.weather: std140"); + +struct Tri { + float3 weights; + float2 x; + float2 y; + float2 z; +}; + +struct Hex { + float2 uv0; + float2 uv1; + float2 uv2; + float2x2 turn0; + float2x2 turn1; + float2x2 turn2; + float2 dx; + float2 dy; + float3 weights; +}; + +struct HexColour { + float4 colour; + float3 weights; +}; + +struct MappedNormal { + float3 normal; + float variance; +}; + +struct Frame { + float3 tangent; + float3 bitangent; + float3 normal; +}; + +struct DirectLight { + float3 lit; + float3 back_fill; +}; + +struct ClusterLamp { + WorldLight light; + float reach; + int slot; +}; + +struct ae_lit_in { + float4 ae_frag_coord [[position]]; + float2 fragTexCoord [[user(locn0)]]; + float3 Normal [[user(locn1)]]; + float3 FragPos [[user(locn2)]]; + float3 InstanceColor [[user(locn3)]]; + float Occlusion [[user(locn5)]]; + float4 ClipNow [[user(locn6)]]; + float4 ClipPrev [[user(locn7)]]; + float3 BindPos [[user(locn8)]]; + float ClipLimb [[user(locn9)]]; + float2 MaskUV [[user(locn10)]]; + float ParticleAlpha [[user(locn11)]]; + float3 FlipNext [[user(locn12)]]; +}; + +struct ae_lit_frag_out { + float4 out_colour [[color(0)]]; + float4 out_velocity [[color(1)]]; +}; + +static float ae3d_shaders_lit_clip_hash(float3 p_in) { + float3 p0 = fract(p_in * 0.3183099 + float3(0.71, 0.113, 0.419)); + float3 p = p0 * 17.0; + return fract(p.x * p.y * p.z * (p.x + p.y + p.z)); +} + +static float ae3d_shaders_lit_clip_value_noise(float3 p) { + float3 i = floor(p); + float3 f0 = fract(p); + float3 f = f0 * f0 * (float3(3.0) - f0 * 2.0); + return mix(mix(mix(ae3d_shaders_lit_clip_hash(i + float3(0.0, 0.0, 0.0)), ae3d_shaders_lit_clip_hash(i + float3(1.0, 0.0, 0.0)), f.x), mix(ae3d_shaders_lit_clip_hash(i + float3(0.0, 1.0, 0.0)), ae3d_shaders_lit_clip_hash(i + float3(1.0, 1.0, 0.0)), f.x), f.y), mix(mix(ae3d_shaders_lit_clip_hash(i + float3(0.0, 0.0, 1.0)), ae3d_shaders_lit_clip_hash(i + float3(1.0, 0.0, 1.0)), f.x), mix(ae3d_shaders_lit_clip_hash(i + float3(0.0, 1.0, 1.0)), ae3d_shaders_lit_clip_hash(i + float3(1.0, 1.0, 1.0)), f.x), f.y), f.z); +} + +static bool ae3d_shaders_lit_clipped_away(float3 bind_pos, constant Cut& cut, float ClipLimb) { + if (cut.on == 0) { + return false; + } + bool on_limb = ClipLimb >= 0.5; + if (cut.detached != 0 && !on_limb) { + return true; + } + if (!on_limb) { + return false; + } + float side0 = dot(cut.plane.xyz, bind_pos) - cut.plane.w; + float side = side0; + if (cut.noise > 0.0) { + side = side0 + cut.noise * (ae3d_shaders_lit_clip_value_noise(bind_pos * cut.noise_scale) * 2.0 - 1.0); + } + return side > 0.0; +} + +static float3 ae3d_shaders_lit_wound_unturn(float4 q, float3 v) { + float3 u = -q.xyz; + float3 t = cross(u, v) * 2.0; + return v + t * q.w + cross(u, t); +} + +static float ae3d_shaders_lit_wound_depth(float3 p, constant Wounds& wounds) { + float best = 2.0; + for (int i = 0; i < 32; i += 1) { + if (i >= wounds.count) { + break; + } + if (wounds.data[i * 3].w > 0.5) { + continue; + } + float3 r = max(wounds.data[i * 3 + 1].xyz, float3(0.0001)); + float3 local = ae3d_shaders_lit_wound_unturn(wounds.data[i * 3 + 2], p - wounds.data[i * 3].xyz) / r; + best = min(best, length(local)); + } + return best; +} + +static float2 ae3d_shaders_lit_velocity(float4 now, float4 prev, float2 nudge) { + if (now.w <= 0.0 || prev.w <= 0.0) { + return float2(0.0); + } + float2 uv_now = now.xy / now.w * 0.5 + float2(0.5) - nudge; + float2 uv_prev = prev.xy / prev.w * 0.5 + float2(0.5); + return uv_now - uv_prev; +} + +static Tri ae3d_shaders_lit_tri_setup(float3 n, float3 position, float metres) { + float3 w = pow(abs(n), float3(4.0)); + float3 q = position / metres; + return Tri{w / max(w.x + w.y + w.z, 1e-6), q.zy, q.xz, q.xy}; +} + +static float4 ae3d_shaders_lit_tri_texture(texture2d tex, sampler ae_smp_tex, Tri tri) { + return tex.sample(ae_smp_tex, tri.x) * tri.weights.x + tex.sample(ae_smp_tex, tri.y) * tri.weights.y + tex.sample(ae_smp_tex, tri.z) * tri.weights.z; +} + +static float2x2 ae3d_shaders_lit_hex_rotation(int cx, int cy, float strength) { + float angle0 = abs(float((cx * cy))) + abs(float((cx + cy))) + 3.14159265; + float angle1 = ae_mod(angle0, 6.28318531); + float angle2 = angle1; + if (angle1 > 3.14159265) { + angle2 = angle1 - 6.28318531; + } + float angle = angle2 * strength; + float c = cos(angle); + float s = sin(angle); + return float2x2(c, -s, s, c); +} + +static float2 ae3d_shaders_lit_hex_offset(int cx, int cy) { + float2 q = float2(float(cx), float(cy)); + return fract(sin(float2(dot(q, float2(127.1, 311.7)), dot(q, float2(269.5, 183.3)))) * 43758.5453); +} + +static Hex ae3d_shaders_lit_hex_setup(float2 uv, float rotation) { + float2 dx = dfdx(uv); + float2 dy = dfdy(uv); + float2 skewed = (float2x2(1.0, -0.57735027, 0.0, 1.15470054) * (uv * 3.46410162)); + int bx = int(floor(skewed.x)); + int by = int(floor(skewed.y)); + float2 t = fract(skewed); + float tz = 1.0 - t.x - t.y; + float s = step(0.0, -tz); + float s2 = 2.0 * s - 1.0; + float3 weights = float3(-tz * s2, s - t.y * s2, s - t.x * s2); + int si = int(s); + int c0x = bx + si; + int c0y = by + si; + int c1x = bx + si; + int c1y = by + (1 - si); + int c2x = bx + (1 - si); + int c2y = by + si; + float2x2 turn0 = ae3d_shaders_lit_hex_rotation(c0x, c0y, rotation); + float2 middle0 = (float2x2(1.0, 0.0, 0.5, 0.86602540) * float2(float(c0x), float(c0y))) / 3.46410162; + float2 uv0 = (turn0 * (uv - middle0)) + middle0 + ae3d_shaders_lit_hex_offset(c0x, c0y); + float2x2 turn1 = ae3d_shaders_lit_hex_rotation(c1x, c1y, rotation); + float2 middle1 = (float2x2(1.0, 0.0, 0.5, 0.86602540) * float2(float(c1x), float(c1y))) / 3.46410162; + float2 uv1 = (turn1 * (uv - middle1)) + middle1 + ae3d_shaders_lit_hex_offset(c1x, c1y); + float2x2 turn2 = ae3d_shaders_lit_hex_rotation(c2x, c2y, rotation); + float2 middle2 = (float2x2(1.0, 0.0, 0.5, 0.86602540) * float2(float(c2x), float(c2y))) / 3.46410162; + float2 uv2 = (turn2 * (uv - middle2)) + middle2 + ae3d_shaders_lit_hex_offset(c2x, c2y); + return Hex{uv0, uv1, uv2, turn0, turn1, turn2, dx, dy, weights}; +} + +static HexColour ae3d_shaders_lit_hex_texture(texture2d tex, sampler ae_smp_tex, Hex hx) { + float4 c0 = tex.sample(ae_smp_tex, hx.uv0, gradient2d((hx.turn0 * hx.dx), (hx.turn0 * hx.dy))); + float4 c1 = tex.sample(ae_smp_tex, hx.uv1, gradient2d((hx.turn1 * hx.dx), (hx.turn1 * hx.dy))); + float4 c2 = tex.sample(ae_smp_tex, hx.uv2, gradient2d((hx.turn2 * hx.dx), (hx.turn2 * hx.dy))); + float3 luma = float3(0.299, 0.587, 0.114); + float3 bright = mix(float3(1.0), float3(dot(c0.rgb, luma), dot(c1.rgb, luma), dot(c2.rgb, luma)), 0.6); + float3 w0 = bright * pow(hx.weights, float3(7.0)); + float3 w = w0 / max(w0.x + w0.y + w0.z, 1e-6); + return HexColour{c0 * w.x + c1 * w.y + c2 * w.z, w}; +} + +static float3 ae3d_shaders_lit_black_body(float kelvin) { + float t = max(kelvin, 500.0) / 100.0; + float g = t <= 6.6 ? 0.0 : clamp((99.4708025861 * log(t) - 161.1195681661) / 255.0, 0.0, 1.0); + float b = t <= 19.0 ? 0.0 : clamp((138.5177312231 * log(t - 10.0) - 305.0447927307) / 255.0, 0.0, 1.0); + return pow(float3(1.0, g, b), float3(2.2)); +} + +static float ae3d_shaders_lit_soft_fade(constant DrawMaterial& material, constant Eye& eye, texture2d scene_depth, sampler ae_smp_scene_depth, float3 FragPos, float4 ae_frag_coord) { + if (material.soft <= 0.0 || eye.depth_on != 1) { + return 1.0; + } + float2 suv = ae_frag_coord.xy / eye.screen_size; + float d = scene_depth.sample(ae_smp_scene_depth, suv).x; + if (d >= 0.99999) { + return 1.0; + } + float2 sp = suv * 2.0 - float2(1.0); + float4 clip = float4(float3(sp.x, sp.y, d), 1.0); + float4 world = (eye.inv_view_projection * clip); + float3 behind = world.xyz / world.w; + float gap = distance(eye.position, behind) - distance(eye.position, FragPos); + return clamp(gap / material.soft, 0.0, 1.0); +} + +static float ae3d_shaders_lit_toksvig(float len, float strength) { + float l = clamp(len, 0.001, 1.0); + return min((1.0 - l) / l * strength * strength, 1.0); +} + +static MappedNormal ae3d_shaders_lit_tri_normal(float3 n, float strength, Tri tri, texture2d normal_map, sampler ae_smp_normal_map) { + float3 tx0 = normal_map.sample(ae_smp_normal_map, tri.x).xyz * 2.0 - float3(1.0); + float3 ty0 = normal_map.sample(ae_smp_normal_map, tri.y).xyz * 2.0 - float3(1.0); + float3 tz0 = normal_map.sample(ae_smp_normal_map, tri.z).xyz * 2.0 - float3(1.0); + float variance = ae3d_shaders_lit_toksvig(length(tx0) * tri.weights.x + length(ty0) * tri.weights.y + length(tz0) * tri.weights.z, strength); + float2 txy = tx0.xy * strength; + float2 tyy = ty0.xy * strength; + float2 tzy = tz0.xy * strength; + float3 tx = float3(txy + n.zy, abs(tx0.z) * n.x); + float3 ty = float3(tyy + n.xz, abs(ty0.z) * n.y); + float3 tz = float3(tzy + n.xy, abs(tz0.z) * n.z); + return MappedNormal{normalize(tx.zyx * tri.weights.x + ty.xzy * tri.weights.y + tz * tri.weights.z), variance}; +} + +static float3 ae3d_shaders_lit_hex_normal(Hex hx, float3 weights, texture2d normal_map, sampler ae_smp_normal_map) { + float3 s0 = normal_map.sample(ae_smp_normal_map, hx.uv0, gradient2d((hx.turn0 * hx.dx), (hx.turn0 * hx.dy))).xyz * 2.0 - float3(1.0); + float2 t0 = (transpose(hx.turn0) * s0.xy); + float3 n0 = float3(0.0) + float3(t0, s0.z) * weights.x; + float3 s1 = normal_map.sample(ae_smp_normal_map, hx.uv1, gradient2d((hx.turn1 * hx.dx), (hx.turn1 * hx.dy))).xyz * 2.0 - float3(1.0); + float2 t1 = (transpose(hx.turn1) * s1.xy); + float3 n1 = n0 + float3(t1, s1.z) * weights.y; + float3 s2 = normal_map.sample(ae_smp_normal_map, hx.uv2, gradient2d((hx.turn2 * hx.dx), (hx.turn2 * hx.dy))).xyz * 2.0 - float3(1.0); + float2 t2 = (transpose(hx.turn2) * s2.xy); + return n1 + float3(t2, s2.z) * weights.z; +} + +static Frame ae3d_shaders_lit_cotangent_frame(float3 normal, float3 position, float2 uv) { + float3 dpx = dfdx(position); + float3 dpy = dfdy(position); + float2 duvx = dfdx(uv); + float2 duvy = dfdy(uv); + float3 perp_y = cross(dpy, normal); + float3 perp_x = cross(normal, dpx); + float3 tangent = perp_y * duvx.x + perp_x * duvy.x; + float3 bitangent = perp_y * duvx.y + perp_x * duvy.y; + float handed = dot(dpx, perp_y) < 0.0 ? -1.0 : 1.0; + float scale = handed * rsqrt(max(dot(tangent, tangent), dot(bitangent, bitangent))); + return Frame{tangent * scale, bitangent * scale, normal}; +} + +static MappedNormal ae3d_shaders_lit_mapped_normal(float3 normal, float3 position, float2 uv, int tri_on, Tri tri, int hex_on, Hex hx, float3 weights, float detail_weight, constant WorldState& world, constant DrawMaterial& material, texture2d normal_map, sampler ae_smp_normal_map) { + float strength = world.normal_strength * material.normal_strength; + if (tri_on != 0) { + return ae3d_shaders_lit_tri_normal(normal, strength, tri, normal_map, ae_smp_normal_map); + } + float3 sampled0 = hex_on != 0 ? ae3d_shaders_lit_hex_normal(hx, weights, normal_map, ae_smp_normal_map) : normal_map.sample(ae_smp_normal_map, uv).xyz * 2.0 - float3(1.0); + float variance = ae3d_shaders_lit_toksvig(length(sampled0), strength); + float3 sampled1 = sampled0; + if (detail_weight > 0.0) { + float3 fine = normal_map.sample(ae_smp_normal_map, (uv * material.detail_scale)).xyz * 2.0 - float3(1.0); + sampled1 = float3(sampled0.xy + fine.xy * detail_weight, sampled0.z); + } + float2 sxy = sampled1.xy * strength; + float3 sampled = float3(sxy, sampled1.z); + Frame fr = ae3d_shaders_lit_cotangent_frame(normal, position, uv); + return MappedNormal{normalize((float3x3(fr.tangent, fr.bitangent, fr.normal) * sampled)), variance}; +} + +static float ae3d_shaders_lit_splat_cover(float3 p, constant Wounds& wounds) { + float most = 0.0; + for (int i = 0; i < 32; i += 1) { + if (i >= wounds.count) { + break; + } + if (wounds.data[i * 3].w < 0.5) { + continue; + } + float3 r = max(wounds.data[i * 3 + 1].xyz, float3(0.0001)); + float3 local = ae3d_shaders_lit_wound_unturn(wounds.data[i * 3 + 2], p - wounds.data[i * 3].xyz) / r; + float edge = 1.0 + 0.35 * (ae3d_shaders_lit_clip_value_noise(local * 3.0 + wounds.data[i * 3].xyz * 7.0) * 2.0 - 1.0); + float cover = wounds.data[i * 3 + 1].w * (1.0 - smoothstep(0.8 * edge, edge, length(local))); + most = max(most, cover); + } + return most; +} + +static float ae3d_shaders_lit_fade(float t) { + return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); +} + +static int ae3d_shaders_lit_lattice_hash(int x, int y, int z) { + int n0 = x + y * 57 + z * 113; + int n = n0 << 13 ^ n0; + return abs(n * (n * n * 15731 + 789221) + 1376312589) & 255; +} + +static float3 ae3d_shaders_lit_get_gradient(int hashed) { + int h = hashed & 15; + float u = h < 8 ? 1.0 : -1.0; + float v = (h & 1) == 0 ? 1.0 : -1.0; + float w = (h & 2) == 0 ? 1.0 : -1.0; + if (h < 4) { + return float3(u, v, 0.0); + } else if (h < 8) { + return float3(u, 0.0, w); + } else if (h < 12) { + return float3(0.0, v, w); + } else { + return float3(u, v, w); + } +} + +static float ae3d_shaders_lit_lerp(float t, float a, float b) { + return a + t * (b - a); +} + +static float ae3d_shaders_lit_perlin_noise3d(float3 p) { + int ix = int(floor(p.x)); + int iy = int(floor(p.y)); + int iz = int(floor(p.z)); + float3 f = p - float3(float(ix), float(iy), float(iz)); + float3 u = float3(ae3d_shaders_lit_fade(f.x), ae3d_shaders_lit_fade(f.y), ae3d_shaders_lit_fade(f.z)); + int n000 = ae3d_shaders_lit_lattice_hash(ix, iy, iz); + int n001 = ae3d_shaders_lit_lattice_hash(ix, iy, iz + 1); + int n010 = ae3d_shaders_lit_lattice_hash(ix, iy + 1, iz); + int n011 = ae3d_shaders_lit_lattice_hash(ix, iy + 1, iz + 1); + int n100 = ae3d_shaders_lit_lattice_hash(ix + 1, iy, iz); + int n101 = ae3d_shaders_lit_lattice_hash(ix + 1, iy, iz + 1); + int n110 = ae3d_shaders_lit_lattice_hash(ix + 1, iy + 1, iz); + int n111 = ae3d_shaders_lit_lattice_hash(ix + 1, iy + 1, iz + 1); + float d000 = dot(ae3d_shaders_lit_get_gradient(n000), f); + float d001 = dot(ae3d_shaders_lit_get_gradient(n001), f - float3(0.0, 0.0, 1.0)); + float d010 = dot(ae3d_shaders_lit_get_gradient(n010), f - float3(0.0, 1.0, 0.0)); + float d011 = dot(ae3d_shaders_lit_get_gradient(n011), f - float3(0.0, 1.0, 1.0)); + float d100 = dot(ae3d_shaders_lit_get_gradient(n100), f - float3(1.0, 0.0, 0.0)); + float d101 = dot(ae3d_shaders_lit_get_gradient(n101), f - float3(1.0, 0.0, 1.0)); + float d110 = dot(ae3d_shaders_lit_get_gradient(n110), f - float3(1.0, 1.0, 0.0)); + float d111 = dot(ae3d_shaders_lit_get_gradient(n111), f - float3(1.0, 1.0, 1.0)); + return ae3d_shaders_lit_lerp(u.z, ae3d_shaders_lit_lerp(u.y, ae3d_shaders_lit_lerp(u.x, d000, d100), ae3d_shaders_lit_lerp(u.x, d010, d110)), ae3d_shaders_lit_lerp(u.y, ae3d_shaders_lit_lerp(u.x, d001, d101), ae3d_shaders_lit_lerp(u.x, d011, d111))); +} + +static float ae3d_shaders_lit_turbulence(float3 p, int octaves) { + float value = 0.0; + float amplitude = 1.0; + float frequency = 1.0; + float most = 0.0; + for (int i = 0; i < octaves && i < 8; i += 1) { + value = value + ae3d_shaders_lit_perlin_noise3d(p * frequency) * amplitude; + most = most + amplitude; + amplitude = amplitude * 0.5; + frequency = frequency * 2.0; + } + return value / most; +} + +static float ae3d_shaders_lit_cascade_lit(int c, float3 surface, float slope, constant ShadowCascades& cascades, float3 FragPos, texture2d shadow_map, sampler ae_smp_shadow_map, constant Surface& shading) { + float4 light_space = (cascades.matrices[c] * float4(FragPos + surface * cascades.texel_world[c] * (1.0 + slope), 1.0)); + float3 projected = light_space.xyz / light_space.w; + float2 pxy = projected.xy * 0.5 + float2(0.5); + float pz = projected.z; + if (pz > 1.0) { + return 1.0; + } + if (pxy.x < 0.0 || pxy.x > 1.0 || pxy.y < 0.0 || pxy.y > 1.0) { + return 1.0; + } + float2 texel = float2(1.0) / float2(float(shadow_map.get_width()), float(shadow_map.get_height())); + float radius = max(shading.shadow_softness, 1.0); + float2 tile = float2(float((c % 2)), float((c / 2))) * 0.5; + float2 centre = tile + pxy * 0.5; + float2 lo = tile + texel * (radius + 0.5); + float2 hi = tile + float2(0.5) - texel * (radius + 0.5); + float bias = radius / (float2(float(shadow_map.get_width()), float(shadow_map.get_height())).x * 0.5); + float lit = 0.0; + for (int sx = -1; sx <= 1; sx += 1) { + for (int sy = -1; sy <= 1; sy += 1) { + float2 at = clamp(centre + float2(float(sx), float(sy)) * texel * radius, lo, hi); + float closest = shadow_map.sample(ae_smp_shadow_map, at).x; + lit = lit + (pz - bias > closest ? 0.0 : 1.0); + } + } + return lit / 9.0; +} + +static float ae3d_shaders_lit_shadow_factor(float3 Normal, constant WorldState& world, constant Eye& eye, float3 FragPos, constant ShadowCascades& cascades, constant Surface& shading, texture2d shadow_map, sampler ae_smp_shadow_map) { + float3 surface = normalize(Normal); + float3 to_light = normalize(-world.shadow_direction); + float facing = max(dot(surface, to_light), 0.0); + float slope = min(sqrt(1.0 - facing * facing) / max(facing, 0.02), 32.0); + float depth = dot(eye.depth_plane.xyz, FragPos) + eye.depth_plane.w; + int c = -1; + for (int i = 0; i < 4; i += 1) { + if (i < cascades.count && depth <= cascades.splits[i]) { + c = i; + break; + } + } + if (c < 0) { + return 1.0; + } + float lit = ae3d_shaders_lit_cascade_lit(c, surface, slope, cascades, FragPos, shadow_map, ae_smp_shadow_map, shading); + if (c + 1 < cascades.count) { + float start = c == 0 ? 0.0 : cascades.splits[(c - 1)]; + float band = (cascades.splits[c] - start) * 0.1; + float into = (cascades.splits[c] - depth) / max(band, 0.0001); + if (into < 1.0) { + lit = mix(ae3d_shaders_lit_cascade_lit(c + 1, surface, slope, cascades, FragPos, shadow_map, ae_smp_shadow_map, shading), lit, into); + } + } + return mix(shading.shadow_intensity, 1.0, lit); +} + +static float ae3d_shaders_lit_lamp_distance(float depth, float near, float far) { + return near * far / (far - depth * (far - near)); +} + +static float ae3d_shaders_lit_lamp_shadow(int slot, float3 surface, constant Grid& grid, const device float4* cluster_lights, float3 FragPos, texture2d lamp_shadow_map, sampler ae_smp_lamp_shadow_map, constant Surface& shading) { + int base = grid.lamp_base + slot * 26; + float4 head = cluster_lights[base]; + float4 more = cluster_lights[(base + 1)]; + float3 v = FragPos - head.xyz; + float far = head.w; + float strength = more.x; + float near = more.y; + float3 a = abs(v); + int face = 0; + float along = 0.0; + if (a.x >= a.y && a.x >= a.z) { + face = v.x > 0.0 ? 0 : 1; + along = a.x; + } else if (a.y >= a.z) { + face = v.y > 0.0 ? 2 : 3; + along = a.y; + } else { + face = v.z > 0.0 ? 4 : 5; + along = a.z; + } + if (strength <= 0.0 || along >= far || along <= near) { + return 1.0; + } + float3 to_lamp = -v / max(length(v), 0.0001); + float facing = max(dot(surface, to_lamp), 0.0); + float slope = min(sqrt(1.0 - facing * facing) / max(facing, 0.02), 32.0); + float texel_world = 2.0 * along / 512.0; + int m = base + 2 + face * 4; + float4x4 face_matrix = float4x4(cluster_lights[m], cluster_lights[(m + 1)], cluster_lights[(m + 2)], cluster_lights[(m + 3)]); + float4 light_space = (face_matrix * float4(FragPos + surface * texel_world * (1.0 + slope), 1.0)); + float3 projected = light_space.xyz / light_space.w; + float2 pxy = clamp(projected.xy * 0.5 + float2(0.5), float2(0.0), float2(1.0)); + float mine = ae3d_shaders_lit_lamp_distance(projected.z, near, far); + int tile = slot * 6 + face; + float share = 1.0 / 8.0; + float2 origin = float2(float((tile % 8)), float((tile / 8))) * share; + float2 texel = float2(1.0) / float2(float(lamp_shadow_map.get_width()), float(lamp_shadow_map.get_height())); + float radius = max(shading.shadow_softness, 1.0); + float2 centre = origin + pxy * share; + float2 lo = origin + texel * (radius + 0.5); + float2 hi = origin + float2(share) - texel * (radius + 0.5); + float bias = texel_world * 1.5; + float lit = 0.0; + for (int sx = -1; sx <= 1; sx += 1) { + for (int sy = -1; sy <= 1; sy += 1) { + float2 at = clamp(centre + float2(float(sx), float(sy)) * texel * radius, lo, hi); + float closest = ae3d_shaders_lit_lamp_distance(lamp_shadow_map.sample(ae_smp_lamp_shadow_map, at).x, near, far); + lit = lit + (mine - bias > closest ? 0.0 : 1.0); + } + } + return mix(1.0, mix(shading.shadow_intensity, 1.0, lit / 9.0), strength); +} + +static float ae3d_shaders_lit_cloud_coverage_from(float2 weather, float cover) { + float threshold = 1.0 - cover * (0.45 + 1.1 * weather.y); + return smoothstep(threshold, threshold + 0.5, weather.x) * 0.9; +} + +static float ae3d_shaders_lit_cloud_hash(float3 p) { + uint vx = uint(int(floor(p.x))); + uint vy = uint(int(floor(p.y))); + uint vz = uint(int(floor(p.z))); + vx = vx * 0x5F356495; + vy = vy * uint(-482951495); + vz = vz * uint(-1496170881); + uint n5 = (vx ^ vy ^ vz) * 0x5F356495; + n5 = n5 ^ n5 >> 16u; + n5 = n5 * 0x7FEB352D; + n5 = n5 ^ n5 >> 15u; + n5 = n5 * uint(-2073254261); + n5 = n5 ^ n5 >> 16u; + return float(n5) * (1.0 / 4294967296.0); +} + +static float ae3d_shaders_lit_cloud_noise2(float2 x, float period, float seed) { + float2 i = floor(x); + float2 f0 = fract(x); + float2 f = f0 * f0 * (float2(3.0) - f0 * 2.0); + float2 i0 = ae_mod(i, period); + float2 i1 = ae_mod(i + float2(1.0), period); + float a = ae3d_shaders_lit_cloud_hash(float3(i0.x, i0.y, seed)); + float b = ae3d_shaders_lit_cloud_hash(float3(i1.x, i0.y, seed)); + float c = ae3d_shaders_lit_cloud_hash(float3(i0.x, i1.y, seed)); + float d = ae3d_shaders_lit_cloud_hash(float3(i1.x, i1.y, seed)); + return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); +} + +static float2 ae3d_shaders_lit_cloud_weather_at(float2 uv) { + float shape = 0.0; + float amp = 0.5; + float freq = 6.0; + for (int o = 0; o < 5; o += 1) { + shape = shape + amp * ae3d_shaders_lit_cloud_noise2(uv * freq, freq, float((41 + o * 17))); + amp = amp * 0.5; + freq = freq * 2.0; + } + float shaped = clamp((shape - 0.3) / 0.4, 0.0, 1.0); + float bank = ae3d_shaders_lit_cloud_noise2(uv * 3.0, 3.0, 8.0); + return float2(shaped, bank); +} + +static float2 ae3d_shaders_lit_cloud_weather_uv(float2 at, float t) { + return at / 24000.0 + float2(t * 0.00012, t * 0.00004); +} + +static float ae3d_shaders_lit_cloud_coverage(float2 at, float cover, float t) { + return ae3d_shaders_lit_cloud_coverage_from(ae3d_shaders_lit_cloud_weather_at(ae3d_shaders_lit_cloud_weather_uv(at, t)), cover); +} + +static float ae3d_shaders_lit_cloud_shadow(float3 world_pos, constant WorldState& world) { + if (world.cloud_cover <= 0.0 || world.cloud_sun.y <= 0.05) { + return 1.0; + } + float3 sun = normalize(world.cloud_sun); + float up = (1400.0 + (2600.0 - 1400.0) * 0.35 - world_pos.y) / max(sun.y, 0.05); + float2 at = world_pos.xz + sun.xz * up; + return 1.0 - 0.65 * ae3d_shaders_lit_cloud_coverage(at, world.cloud_cover, world.cloud_time); +} + +static float3 ae3d_shaders_lit_kelvin_to_rgb(float kelvin_in) { + float kelvin = clamp(kelvin_in, 1000.0, 12000.0); + if (kelvin < 3000.0) { + return mix(float3(1.0, 0.4, 0.0), float3(1.0, 0.7, 0.3), (kelvin - 1000.0) / 2000.0); + } else if (kelvin < 6500.0) { + return mix(float3(1.0, 0.7, 0.3), float3(1.0, 1.0, 1.0), (kelvin - 3000.0) / 3500.0); + } else { + return mix(float3(1.0, 1.0, 1.0), float3(0.7, 0.8, 1.0), (kelvin - 6500.0) / 5500.0); + } +} + +static float ae3d_shaders_lit_distribution_ggx(float3 n, float3 h, float rough) { + float a = rough * rough; + float a2 = a * a; + float ndoth = max(dot(n, h), 0.0); + float ndoth2 = ndoth * ndoth; + float num = a2; + float denom0 = ndoth2 * (a2 - 1.0) + 1.0; + float denom = 3.14159265359 * denom0 * denom0; + float result = num / denom; + return min(result, 10.0); +} + +static float ae3d_shaders_lit_geometry_schlick_ggx(float ndotv, float rough) { + float r = rough + 1.0; + float k = r * r / 8.0; + float num = ndotv; + float denom = ndotv * (1.0 - k) + k; + return num / denom; +} + +static float ae3d_shaders_lit_geometry_smith(float3 n, float3 v, float3 l, float rough) { + float ndotv = max(dot(n, v), 0.0); + float ndotl = max(dot(n, l), 0.0); + float ggx2 = ae3d_shaders_lit_geometry_schlick_ggx(ndotv, rough); + float ggx1 = ae3d_shaders_lit_geometry_schlick_ggx(ndotl, rough); + return ggx1 * ggx2; +} + +static float3 ae3d_shaders_lit_fresnel_schlick(float cos_theta, float3 f0) { + float inv = clamp(1.0 - cos_theta, 0.0, 1.0); + float inv2 = inv * inv; + float inv5 = inv2 * inv2 * inv; + return f0 + (float3(1.0) - f0) * inv5; +} + +static float3 ae3d_shaders_lit_calculate_clearcoat(float3 n, float3 v, float3 l, float3 h, float3 base_colour, constant Surface& shading) { + if (shading.clearcoat == 0) { + return float3(0.0); + } + float ndf = ae3d_shaders_lit_distribution_ggx(n, h, shading.clearcoat_roughness); + float g = ae3d_shaders_lit_geometry_smith(n, v, l, shading.clearcoat_roughness); + float3 f = ae3d_shaders_lit_fresnel_schlick(max(dot(h, v), 0.0), float3(0.04)); + float3 specular = f * (ndf * g) / (4.0 * max(dot(n, v), 0.0) * max(dot(n, l), 0.0) + 0.001); + return specular * shading.clearcoat_intensity; +} + +static float3 ae3d_shaders_lit_calculate_sheen(float3 n, float3 v, float3 l, float3 h, constant Surface& shading) { + if (shading.sheen == 0) { + return float3(0.0); + } + float ndoth = max(dot(n, h), 0.0); + float d = (2.0 + shading.sheen_roughness) * pow(ndoth, shading.sheen_roughness) / (2.0 * 3.14159265359); + return shading.sheen_colour * d * 0.25; +} + +static float3 ae3d_shaders_lit_calculate_transmission(float3 n, float3 v, float3 l, float3 base_colour, constant Surface& shading) { + if (shading.transmission == 0) { + return float3(0.0); + } + float ndotv = max(dot(n, v), 0.0); + float ndotl = max(dot(n, l), 0.0); + float f0 = 0.04; + float fresnel = f0 + (1.0 - f0) * pow(1.0 - ndotv, 5.0); + float transmission = (1.0 - fresnel) * shading.transmission_factor; + float3 transmitted = base_colour * transmission * ndotl; + float3 scattering = base_colour * transmission * 0.1; + return transmitted + scattering; +} + +static float3 ae3d_shaders_lit_compensate_energy_loss(float3 colour, float ndotv, float rough, constant Surface& shading) { + if (shading.multiple_scattering == 0) { + return colour; + } + float compensation = 1.0 + rough * (1.0 - ndotv) * 0.2; + return colour * compensation; +} + +static float3 ae3d_shaders_lit_apply_energy_conservation(float3 diffuse, float3 specular, float3 clearcoat, float3 sheen, constant Surface& shading) { + if (shading.energy_conservation == 0) { + return diffuse + specular + clearcoat + sheen; + } + float3 total = diffuse + specular + clearcoat + sheen; + float most = max(max(total.x, total.y), total.z); + if (most > 1.0) { + return total / most; + } + return total; +} + +static DirectLight ae3d_shaders_lit_direct_light(WorldLight l, float3 norm, float3 view_dir, float3 albedo, float3 f0, float ndotv, float adjusted_roughness, float wet_mirror, float3 FragPos, constant DrawMaterial& material, constant Surface& shading) { + float3 adjusted_colour = l.colour * ae3d_shaders_lit_kelvin_to_rgb(l.temperature); + float3 light_dir = float3(0.0); + float attenuation = 1.0; + if (l.kind == 1) { + light_dir = normalize(l.direction); + } else { + float3 light_vec = l.position - FragPos; + float dist = length(light_vec); + light_dir = light_vec / dist; + attenuation = 1.0 / (l.constant_ + l.linear * dist + l.quadratic * dist * dist); + if (l.kind == 2) { + float on_axis = dot(-light_dir, normalize(l.direction)); + attenuation = attenuation * smoothstep(l.cos_outer, l.cos_inner, on_axis); + } + } + float3 halfway = normalize(light_dir + view_dir); + float3 radiance = adjusted_colour * l.intensity * attenuation; + float ndotl_raw = dot(norm, light_dir); + float hdotv = clamp(dot(halfway, view_dir), 0.001, 1.0); + float ndotl = max(ndotl_raw, 0.0); + float ndf = ae3d_shaders_lit_distribution_ggx(norm, halfway, adjusted_roughness); + float g = ae3d_shaders_lit_geometry_smith(norm, view_dir, light_dir, adjusted_roughness); + float3 f = ae3d_shaders_lit_fresnel_schlick(hdotv, f0); + float3 ks = f; + float3 kd0 = float3(1.0) - ks; + float3 kd = kd0 * (1.0 - material.metallic); + float3 numerator = f * (ndf * g); + float denominator = 4.0 * ndotv * ndotl + 0.0001; + float3 specular0 = numerator / denominator; + float view_attenuation = pow(ndotv, 0.6); + float mirror = max(material.reflectivity, wet_mirror); + float3 specular1 = specular0; + if (mirror > 0.001) { + float grazing = 1.0 - ndotv; + specular1 = specular0 * (mirror * (0.5 + grazing * grazing * 4.0)); + } else { + specular1 = specular0 * (view_attenuation * 0.5); + } + float3 clearcoat = ae3d_shaders_lit_calculate_clearcoat(norm, view_dir, light_dir, halfway, albedo, shading); + float3 sheen = ae3d_shaders_lit_calculate_sheen(norm, view_dir, light_dir, halfway, shading); + float3 transmission = ae3d_shaders_lit_calculate_transmission(norm, view_dir, light_dir, albedo, shading); + float3 specular = ae3d_shaders_lit_compensate_energy_loss(specular1, ndotv, material.roughness, shading); + float hemisphere = max(ndotl_raw, 0.0); + float fill = max(-ndotl_raw * 0.3, 0.0); + float3 back_fill = adjusted_colour * fill * albedo * 0.2 * attenuation; + float3 lit = ae3d_shaders_lit_apply_energy_conservation(kd * albedo / 3.14159265359 * radiance * hemisphere, specular * radiance * hemisphere, clearcoat * radiance * hemisphere, sheen * radiance * hemisphere, shading) + transmission; + return DirectLight{lit, back_fill}; +} + +static float3 ae3d_shaders_lit_glitter(float3 n, float3 v, WorldLight l_in, float3 FragPos, constant DrawMaterial& material) { + float3 grid = FragPos * material.glitter_grains; + float footprint = max(length(fwidth(grid)), 1e-4); + float level_ = max(ceil(log2(footprint)), 0.0); + if (level_ > 7.0) { + return float3(0.0); + } + float scale = exp2(level_); + float3 cell = floor(grid / scale); + float3 h = fract(sin(float3(dot(cell, float3(127.1, 311.7, 74.7)), dot(cell, float3(269.5, 183.3, 246.1)), dot(cell, float3(113.5, 271.9, 124.6)))) * 43758.5453); + float chance = min(material.glitter * scale * scale, 1.0); + if (h.x > chance) { + return float3(0.0); + } + float3 l = normalize(l_in.direction); + float lit = max(dot(n, l), 0.0); + if (lit <= 0.0) { + return float3(0.0); + } + float3 k = fract(h * 13.731 + h.yzx * 7.17); + float3 facet = normalize(n * 0.6 + (k * 2.0 - float3(1.0))); + float flash = pow(max(dot(facet, normalize(l + v)), 0.0), 900.0); + return l_in.colour * ae3d_shaders_lit_kelvin_to_rgb(l_in.temperature) * l_in.intensity * flash * lit * material.glitter_strength / (scale * scale); +} + +static int ae3d_shaders_lit_cluster_of(float3 p, constant Grid& grid, constant Eye& eye) { + float4 clip = (grid.view_projection * float4(p, 1.0)); + float w = clip.w != 0.0 ? clip.w : 1.0; + float2 ndc = clip.xy / w; + float depth = dot(eye.depth_plane.xyz, p) + eye.depth_plane.w; + float3 dims = grid.dims.xyz + float3(0.5); + int dx = int(dims.x); + int dy = int(dims.y); + int dz = int(dims.z); + int x = clamp(int(floor((ndc.x * 0.5 + 0.5) * float(dx))), 0, dx - 1); + int y = clamp(int(floor((ndc.y * 0.5 + 0.5) * float(dy))), 0, dy - 1); + int z = 0; + if (depth > grid.depth.x) { + z = clamp(int(floor(log(depth) * grid.depth.z + grid.depth.w)), 0, dz - 1); + } + return (z * dy + y) * dx + x; +} + +static ClusterLamp ae3d_shaders_lit_clustered_light(int i, const device float4* cluster_lights) { + float4 a = cluster_lights[(i * 5)]; + float4 b = cluster_lights[(i * 5 + 1)]; + float4 c = cluster_lights[(i * 5 + 2)]; + float4 d = cluster_lights[(i * 5 + 3)]; + float4 e = cluster_lights[(i * 5 + 4)]; + return ClusterLamp{ae_make_WorldLight(a.xyz, b.rgb, b.w, d.w, int((c.w + 0.5)), c.xyz, d.x, d.y, d.z, e.x, e.y), a.w, int(floor(e.z + 0.5))}; +} + +static float ae3d_shaders_lit_screen_occlusion(constant Eye& eye, float4 ClipPrev, texture2d occlusion_map, sampler ae_smp_occlusion_map) { + if (eye.history == 0 || ClipPrev.w <= 0.0) { + return 1.0; + } + float2 uv = ClipPrev.xy / ClipPrev.w * 0.5 + float2(0.5); + if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) { + return 1.0; + } + float2 found = occlusion_map.sample(ae_smp_occlusion_map, uv).xy; + if (abs(found.y - ClipPrev.w) > 0.05 * ClipPrev.w) { + return 1.0; + } + return found.x; +} + +static float2 ae3d_shaders_lit_oct_encode(float3 d_in) { + float3 d = d_in / (abs(d_in.x) + abs(d_in.y) + abs(d_in.z)); + float2 p = d.xz; + if (d.y < 0.0) { + p = (float2(1.0) - abs(p.yx)) * float2(p.x >= 0.0 ? 1.0 : -1.0, p.y >= 0.0 ? 1.0 : -1.0); + } + return p; +} + +static float2 ae3d_shaders_lit_oct_uv(float3 d, float size) { + return ((ae3d_shaders_lit_oct_encode(d) * 0.5 + float2(0.5)) * (size - 2.0) + float2(1.0)) / size; +} + +static float3 ae3d_shaders_lit_sky_irradiance(float3 n, texture2d sky_irradiance, sampler ae_smp_sky_irradiance) { + return sky_irradiance.sample(ae_smp_sky_irradiance, ae3d_shaders_lit_oct_uv(n, 16.0)).rgb; +} + +static float3 ae3d_shaders_lit_sky_reflection(float3 r, float rough, texture2d sky_light, sampler ae_smp_sky_light) { + float level_ = clamp(rough, 0.0, 1.0) * (6.0 - 1.0); + return sky_light.sample(ae_smp_sky_light, ae3d_shaders_lit_oct_uv(r, 256.0), level(level_)).rgb; +} + +static float3 ae3d_shaders_lit_sky_lit(float3 n, float3 v, float3 albedo, float3 f0, float rough, float metal_, float ndotv, constant WorldState& world, constant Surface& shading, texture2d sky_irradiance, sampler ae_smp_sky_irradiance, texture2d sky_light, sampler ae_smp_sky_light, texture2d brdf_lut, sampler ae_smp_brdf_lut) { + if (world.gi_off != 0) { + return float3(0.0); + } + float3 f = f0 + (max(float3(1.0 - rough), f0) - f0) * pow(1.0 - ndotv, 5.0); + float3 kd = (float3(1.0) - f) * (1.0 - metal_); + float3 irradiance = ae3d_shaders_lit_sky_irradiance(n, sky_irradiance, ae_smp_sky_irradiance); + float3 diffuse = kd * albedo * irradiance / 3.14159265; + if (shading.ibl == 0) { + return diffuse; + } + float2 ab = brdf_lut.sample(ae_smp_brdf_lut, float2(ndotv, rough)).xy; + float3 specular = ae3d_shaders_lit_sky_reflection(reflect(-v, n), rough, sky_light, ae_smp_sky_light) * (f0 * ab.x + float3(ab.y)); + return diffuse + specular * shading.ibl_intensity; +} + +static float3 ae3d_shaders_lit_calculate_volumetric_lighting(float3 world_pos, float3 light_pos, float3 view_pos, constant Surface& shading, constant WorldState& world) { + if (shading.volumetric == 0) { + return float3(0.0); + } + float3 to_surface = world_pos - view_pos; + float ray_length = length(to_surface); + if (ray_length < 0.001) { + return float3(0.0); + } + float3 ray_dir = to_surface / ray_length; + int steps = clamp(shading.volumetric_steps, 8, 32); + float3 tint = world.lights[0].colour * ae3d_shaders_lit_kelvin_to_rgb(world.lights[0].temperature); + float scatter = 0.0; + float step_size = ray_length / float(steps); + for (int i = 0; i < steps && i < 32; i += 1) { + float3 sample_pos = view_pos + ray_dir * (step_size * (float(i) + 0.5)); + float d = length(light_pos - sample_pos); + scatter = scatter + step_size / (1.0 + d * d * shading.volumetric_scattering); + } + return tint * scatter * shading.volumetric_intensity; +} + +static float ae3d_shaders_lit_caustic_web(float2 uv, float t, constant Surface& shading) { + float2 drift = float2(0.7, 0.3) * shading.caustics_speed * t; + float a = ae3d_shaders_lit_perlin_noise3d(float3(uv + drift, t * shading.caustics_speed * 0.5)); + float b = ae3d_shaders_lit_perlin_noise3d(float3(uv * 1.7 - drift * 0.8, t * shading.caustics_speed * 0.4 + 5.3)); + return pow(clamp(1.0 - abs(a - b) * 3.0, 0.0, 1.0), 8.0); +} + +static float3 ae3d_shaders_lit_caustic_light(float3 world_pos, float3 norm, constant Surface& shading, constant WorldState& world) { + if (shading.caustics == 0) { + return float3(0.0); + } + float depth = shading.caustics_water_level - world_pos.y; + if (depth <= 0.0) { + return float3(0.0); + } + float2 uv = world_pos.xz * shading.caustics_scale; + float t = world.time; + float2 fringe = float2(0.006, 0.004); + float red = ae3d_shaders_lit_caustic_web(uv + fringe, t, shading) + 0.5 * ae3d_shaders_lit_caustic_web(uv * 2.3 + fringe * 2.0 + float2(11.0), t, shading); + float green = ae3d_shaders_lit_caustic_web(uv, t, shading) + 0.5 * ae3d_shaders_lit_caustic_web(uv * 2.3 + float2(11.0), t, shading); + float blue = ae3d_shaders_lit_caustic_web(uv - fringe, t, shading) + 0.5 * ae3d_shaders_lit_caustic_web(uv * 2.3 - fringe * 2.0 + float2(11.0), t, shading); + float3 web = float3(red, green, blue); + float facing = clamp(norm.y, 0.0, 1.0); + float attenuation = exp(-depth / max(shading.caustics_depth, 0.001)); + float3 key = world.lights[0].colour * ae3d_shaders_lit_kelvin_to_rgb(world.lights[0].temperature); + return key * world.lights[0].intensity * web * facing * attenuation * shading.caustics_intensity; +} + +static float ae3d_shaders_lit_ember_hash(float3 c) { + return fract(sin(dot(c, float3(41.3, 289.1, 157.7))) * 43758.5453); +} + +static float ae3d_shaders_lit_ember_noise(float3 p) { + float3 i = floor(p); + float3 f0 = p - i; + float3 f = f0 * f0 * (float3(3.0) - f0 * 2.0); + float a = mix(mix(ae3d_shaders_lit_ember_hash(i), ae3d_shaders_lit_ember_hash(i + float3(1.0, 0.0, 0.0)), f.x), mix(ae3d_shaders_lit_ember_hash(i + float3(0.0, 1.0, 0.0)), ae3d_shaders_lit_ember_hash(i + float3(1.0, 1.0, 0.0)), f.x), f.y); + float b = mix(mix(ae3d_shaders_lit_ember_hash(i + float3(0.0, 0.0, 1.0)), ae3d_shaders_lit_ember_hash(i + float3(1.0, 0.0, 1.0)), f.x), mix(ae3d_shaders_lit_ember_hash(i + float3(0.0, 1.0, 1.0)), ae3d_shaders_lit_ember_hash(i + float3(1.0, 1.0, 1.0)), f.x), f.y); + return mix(a, b, f.z); +} + +static float3 ae3d_shaders_lit_ember_light(float3 FragPos, constant WorldState& world, constant DrawMaterial& material) { + float3 p = FragPos * 20.0; + float3 i = floor(p); + float3 f = p - i; + float f1 = 8.0; + float f2 = 8.0; + float3 nearest = i; + for (int z = -1; z <= 1; z += 1) { + for (int y = -1; y <= 1; y += 1) { + for (int x = -1; x <= 1; x += 1) { + float3 o = float3(float(x), float(y), float(z)); + float3 c = i + o; + float3 h = fract(sin(float3(dot(c, float3(127.1, 311.7, 74.7)), dot(c, float3(269.5, 183.3, 246.1)), dot(c, float3(113.5, 271.9, 124.6)))) * 43758.5453); + float d = length(o + h - f); + if (d < f1) { + f2 = f1; + f1 = d; + nearest = c; + } else if (d < f2) { + f2 = d; + } + } + } + } + float crack = 1.0 - smoothstep(0.0, 0.07, f2 - f1); + float hn = ae3d_shaders_lit_ember_hash(nearest); + float hot = ae3d_shaders_lit_ember_noise(nearest * 0.2) * 0.75 + hn * 0.25; + float breath = 0.75 + 0.25 * sin(world.time * (0.9 + hn * 1.6) + hn * 37.0); + float heat = crack * 0.3; + if (hot < material.ember_cover) { + heat = mix(0.55, 1.0, crack) * breath * (0.8 + 0.2 * ae3d_shaders_lit_ember_noise(p * 0.7)); + } + float h2 = heat * heat; + return ae3d_shaders_lit_black_body(heat * material.embers) * (h2 * h2) * material.ember_glow; +} + +fragment ae_lit_frag_out ae_lit_fragment( + ae_lit_in ae_in [[stage_in]], + constant DrawBlock& ae_set0 [[buffer(0)]], + constant CameraBlock& ae_set1 [[buffer(1)]], + constant WorldBlock& ae_set2 [[buffer(2)]], + texture2d colour_map [[texture(0)]], sampler ae_smp_colour_map [[sampler(0)]], + texture2d scene_depth [[texture(1)]], sampler ae_smp_scene_depth [[sampler(1)]], + texture2d normal_map [[texture(2)]], sampler ae_smp_normal_map [[sampler(2)]], + texture2d damage_mask [[texture(3)]], sampler ae_smp_damage_mask [[sampler(3)]], + texture2d shadow_map [[texture(4)]], sampler ae_smp_shadow_map [[sampler(4)]], + texture2d lamp_shadow_map [[texture(5)]], sampler ae_smp_lamp_shadow_map [[sampler(5)]], + texture2d occlusion_map [[texture(6)]], sampler ae_smp_occlusion_map [[sampler(6)]], + texture2d sky_irradiance [[texture(7)]], sampler ae_smp_sky_irradiance [[sampler(7)]], + texture2d sky_light [[texture(8)]], sampler ae_smp_sky_light [[sampler(8)]], + texture2d brdf_lut [[texture(9)]], sampler ae_smp_brdf_lut [[sampler(9)]], + const device float4* cluster_lights [[buffer(8)]], + const device float* cluster_words [[buffer(9)]]) { + ae_lit_frag_out ae_out; + float4 ae_frag_coord = ae_in.ae_frag_coord; + float2 fragTexCoord = ae_in.fragTexCoord; + float3 Normal = ae_in.Normal; + float3 FragPos = ae_in.FragPos; + float3 InstanceColor = ae_in.InstanceColor; + float Occlusion = ae_in.Occlusion; + float4 ClipNow = ae_in.ClipNow; + float4 ClipPrev = ae_in.ClipPrev; + float3 BindPos = ae_in.BindPos; + float ClipLimb = ae_in.ClipLimb; + float2 MaskUV = ae_in.MaskUV; + float ParticleAlpha = ae_in.ParticleAlpha; + float3 FlipNext = ae_in.FlipNext; + constant Draw& draw = ae_set0.draw; + constant DrawMaterial& material = ae_set0.material; + constant Surface& shading = ae_set0.shading; + constant Cut& cut = ae_set0.cut; + constant Wounds& wounds = ae_set0.wounds; + constant Damage& damage = ae_set0.damage; + constant Eye& eye = ae_set1.eye; + constant ShadowCascades& cascades = ae_set1.cascades; + constant Grid& grid = ae_set1.grid; + constant WorldState& world = ae_set2.world; + constant Weather& weather = ae_set2.weather; + if (ae3d_shaders_lit_clipped_away(BindPos, cut, ClipLimb)) { + discard_fragment(); + } + float wound_at = wounds.count > 0 ? ae3d_shaders_lit_wound_depth(BindPos, wounds) : 2.0; + if (wound_at < wounds.core) { + discard_fragment(); + } + float2 moved = ae3d_shaders_lit_velocity(ClipNow, ClipPrev, eye.jitter); + int tri_on = 0; + int hex_on = 0; + Tri tri = Tri{float3(0.0, 1.0, 0.0), float2(0.0), float2(0.0), float2(0.0)}; + Hex hx = Hex{float2(0.0), float2(0.0), float2(0.0), float2x2(1.0, 0.0, 0.0, 1.0), float2x2(1.0, 0.0, 0.0, 1.0), float2x2(1.0, 0.0, 0.0, 1.0), float2(0.0), float2(0.0), float3(1.0, 0.0, 0.0)}; + float3 hex_weights = float3(1.0, 0.0, 0.0); + float4 tex_colour = float4(0.0); + if (material.triplanar > 0.0) { + tri = ae3d_shaders_lit_tri_setup(normalize(Normal), FragPos, material.triplanar); + tri_on = 1; + tex_colour = ae3d_shaders_lit_tri_texture(colour_map, ae_smp_colour_map, tri); + } else if (material.tile_breakup > 0.0) { + hx = ae3d_shaders_lit_hex_setup(fragTexCoord, material.tile_rotation); + hex_on = 1; + HexColour hc = ae3d_shaders_lit_hex_texture(colour_map, ae_smp_colour_map, hx); + tex_colour = hc.colour; + hex_weights = hc.weights; + } else { + tex_colour = colour_map.sample(ae_smp_colour_map, fragTexCoord); + if (FlipNext.z >= 0.0) { + tex_colour = mix(tex_colour, colour_map.sample(ae_smp_colour_map, FlipNext.xy), FlipNext.z); + } + } + if (tex_colour.w < material.cutout) { + discard_fragment(); + } + float detail_weight = 0.0; + if (material.detail_scale > 0.0 && tri_on == 0) { + float away = length(FragPos - eye.position); + detail_weight = material.detail_strength * (1.0 - smoothstep(material.detail_fade * 0.5, material.detail_fade, away)); + } + if (material.fire > 0.0) { + float heat = clamp(tex_colour.x * InstanceColor.x, 0.0, 1.0); + float3 glowing = ae3d_shaders_lit_black_body(heat * material.fire) * (heat * heat * heat * heat) * max(material.exposure * 0.1, 1.0); + ae_out.out_colour = float4(glowing * material.diffuse, tex_colour.w * material.alpha * ParticleAlpha * ae3d_shaders_lit_soft_fade(material, eye, scene_depth, ae_smp_scene_depth, FragPos, ae_frag_coord)); + ae_out.out_velocity = float4(moved, 0.0, 0.0); + return ae_out; + } + if (material.exposure > 10.0) { + float glow_alpha = material.blend != 0 ? tex_colour.w * material.alpha * ParticleAlpha * ae3d_shaders_lit_soft_fade(material, eye, scene_depth, ae_smp_scene_depth, FragPos, ae_frag_coord) : 1.0; + ae_out.out_colour = float4(material.diffuse * tex_colour.rgb * InstanceColor * (material.exposure * 0.1), glow_alpha); + ae_out.out_velocity = float4(moved, 0.0, 0.0); + return ae_out; + } + float3 norm = normalize(Normal); + float normal_variance = 0.0; + if (draw.impostor != 0) { + if (tex_colour.w < 0.5) { + discard_fragment(); + } + float3 facing = normalize(Normal); + float3 baked = normal_map.sample(ae_smp_normal_map, fragTexCoord).xyz * 2.0 - float3(1.0); + norm = normalize(facing * baked.x + float3(0.0, baked.y, 0.0) + float3(-facing.z, 0.0, facing.x) * baked.z); + } else if (material.normal_map != 0) { + MappedNormal mn = ae3d_shaders_lit_mapped_normal(norm, FragPos, fragTexCoord, tri_on, tri, hex_on, hx, hex_weights, detail_weight, world, material, normal_map, ae_smp_normal_map); + norm = mn.normal; + normal_variance = mn.variance; + } + if (eye.capture == 1) { + ae_out.out_colour = float4(material.diffuse * tex_colour.rgb * InstanceColor, tex_colour.w); + ae_out.out_velocity = float4(moved, 0.0, 0.0); + return ae_out; + } + if (eye.capture == 2) { + ae_out.out_colour = float4(norm * 0.5 + float3(0.5), 1.0); + ae_out.out_velocity = float4(moved, 0.0, 0.0); + return ae_out; + } + if (eye.capture == 3) { + float2 shown = moved * eye.screen_size / 200.0 + float2(0.5); + ae_out.out_colour = float4(float3(shown.x, shown.y, 0.0), 1.0); + ae_out.out_velocity = float4(moved, 0.0, 0.0); + return ae_out; + } + float3 view_dir = normalize(eye.position - FragPos); + float3 albedo0 = material.diffuse * tex_colour.rgb * InstanceColor; + float3 albedo1 = albedo0; + if (wounds.count > 0) { + albedo1 = mix(albedo0, wounds.layers[0].rgb, ae3d_shaders_lit_splat_cover(BindPos, wounds)); + } + float2 mask_at = damage.rect.xy + clamp(MaskUV, float2(damage.clamp), float2(1.0 - damage.clamp)) * float2(damage.rect.z, damage.rect.w); + float4 damaged = damage_mask.sample(ae_smp_damage_mask, mask_at); + float3 albedo2 = albedo1; + if (damage.on != 0) { + for (int k = 0; k < 4; k += 1) { + albedo2 = mix(albedo2, damage.colours[k].rgb, clamp(damaged[k] * damage.colours[k].w, 0.0, 1.0)); + } + } + float3 albedo3 = albedo2; + if (wound_at < 1.0) { + float3 layer = wounds.layers[0].rgb; + for (int j = 1; j < 4; j += 1) { + if (wound_at < wounds.layers[j].w) { + layer = wounds.layers[j].rgb; + } + } + albedo3 = mix(albedo2, layer, smoothstep(1.0, 0.94, wound_at)); + } + float3 albedo4 = albedo3; + if (detail_weight > 0.0) { + float3 fine = colour_map.sample(ae_smp_colour_map, (fragTexCoord * material.detail_scale)).rgb; + float3 mean = max(colour_map.sample(ae_smp_colour_map, fragTexCoord, level(16.0)).rgb, float3(0.02)); + albedo4 = albedo3 * mix(float3(1.0), clamp(fine / mean, float3(0.0), float3(2.0)), detail_weight); + } + float3 albedo5 = albedo4; + if (material.variation > 0.0) { + float3 cell = FragPos / max(material.variation_scale, 0.01); + float v0 = ae3d_shaders_lit_clip_value_noise(cell) * 0.65 + ae3d_shaders_lit_clip_value_noise(cell * 2.7 + float3(13.1, 7.3, 2.9)) * 0.35; + float v1 = smoothstep(0.25, 0.75, v0); + albedo5 = albedo4 * (1.0 + (v1 * 2.0 - 1.0) * material.variation); + } + float3 albedo6 = albedo5; + if (shading.perlin != 0) { + float3 noise_coord = FragPos * shading.noise_scale; + float noise_value = ae3d_shaders_lit_turbulence(noise_coord, shading.noise_octaves); + albedo6 = mix(albedo5, albedo5 * (1.0 + noise_value * 0.3), shading.noise_intensity); + } + float3 f0a = float3(0.04) * material.specular; + float3 f0 = f0a; + if (material.metallic > 0.5) { + float3 metal_f0 = albedo6; + if (albedo6.x > albedo6.y && albedo6.x > albedo6.z) { + metal_f0 = mix(float3(0.95, 0.64, 0.54), albedo6, 0.7); + } else if (albedo6.y > albedo6.x && albedo6.y > albedo6.z) { + metal_f0 = mix(float3(0.70, 0.78, 0.74), albedo6, 0.7); + } else if (albedo6.z > albedo6.x && albedo6.z > albedo6.y) { + metal_f0 = mix(float3(0.66, 0.73, 0.80), albedo6, 0.7); + } else { + metal_f0 = mix(float3(0.95, 0.93, 0.88), albedo6, 0.7); + } + f0 = mix(f0a, metal_f0, material.metallic); + } else { + f0 = mix(f0a, albedo6, material.metallic); + } + float ndotv = clamp(dot(norm, view_dir), 0.001, 1.0); + float adjusted0 = max(sqrt(material.roughness * material.roughness + normal_variance), 0.08); + float adjusted = adjusted0; + float wet0 = world.wetness * clamp(norm.y, 0.0, 1.0); + float wet = wet0 * wet0; + float wet_mirror = 0.0; + float3 albedo = albedo6; + if (wet > 0.001) { + albedo = albedo6 * (1.0 - 0.35 * wet); + adjusted = max(mix(adjusted0, 0.05, wet), 0.08); + wet_mirror = wet * 0.7; + } else { + wet_mirror = 0.0; + } + float shaded0 = 1.0; + if (cascades.on != 0 && shading.shadows != 0) { + shaded0 = ae3d_shaders_lit_shadow_factor(Normal, world, eye, FragPos, cascades, shading, shadow_map, ae_smp_shadow_map); + } + float shaded = shaded0; + if (grid.key_lamp >= 0 && grid.lamp_base >= 0 && shading.shadows != 0) { + shaded = ae3d_shaders_lit_lamp_shadow(grid.key_lamp, normalize(Normal), grid, cluster_lights, FragPos, lamp_shadow_map, ae_smp_lamp_shadow_map, shading); + } + float sunlit = ae3d_shaders_lit_cloud_shadow(FragPos, world); + float3 lo = float3(0.0); + for (int i = 0; i < 16; i += 1) { + if (i >= world.light_count) { + break; + } + if (world.lights[i].kind != 1) { + float3 gap = world.lights[i].position - FragPos; + if (dot(gap, gap) * world.lights[i].quadratic > 64.0) { + continue; + } + if (world.lights[i].kind == 2 && dot(normalize(-gap), normalize(world.lights[i].direction)) < world.lights[i].cos_outer) { + continue; + } + } + DirectLight dl = ae3d_shaders_lit_direct_light(world.lights[i], norm, view_dir, albedo, f0, ndotv, adjusted, wet_mirror, FragPos, material, shading); + lo = lo + (i == 0 ? dl.lit * sunlit * shaded : dl.lit) + dl.back_fill; + } + if (material.glitter > 0.0 && world.light_count > 0 && world.lights[0].kind == 1) { + lo = lo + ae3d_shaders_lit_glitter(norm, view_dir, world.lights[0], FragPos, material) * sunlit * shaded; + } + if (grid.dims.w > 0.5) { + int cell_at = ae3d_shaders_lit_cluster_of(FragPos, grid, eye); + int first = int((cluster_words[(cell_at * 2)] + 0.5)); + int count = int((cluster_words[(cell_at * 2 + 1)] + 0.5)); + for (int k2 = 0; k2 < count; k2 += 1) { + ClusterLamp cl = ae3d_shaders_lit_clustered_light(int((cluster_words[(first + k2)] + 0.5)), cluster_lights); + WorldLight lamp = cl.light; + float3 lgap = lamp.position - FragPos; + float far2 = dot(lgap, lgap); + if (far2 >= cl.reach * cl.reach) { + continue; + } + if (lamp.kind == 2 && dot(normalize(-lgap), normalize(lamp.direction)) < lamp.cos_outer) { + continue; + } + float fade_out = 1.0 - smoothstep(0.75 * cl.reach, cl.reach, sqrt(far2)); + DirectLight ldl = ae3d_shaders_lit_direct_light(lamp, norm, view_dir, albedo, f0, ndotv, adjusted, wet_mirror, FragPos, material, shading); + float3 lit = ldl.lit * fade_out; + lo = lo + ldl.back_fill * fade_out; + float shade = 1.0; + if (cl.slot >= 0 && grid.lamp_base >= 0 && shading.shadows != 0 && dot(lit, float3(0.333)) > 0.002) { + shade = ae3d_shaders_lit_lamp_shadow(cl.slot, normalize(Normal), grid, cluster_lights, FragPos, lamp_shadow_map, ae_smp_lamp_shadow_map, shading); + } + lo = lo + lit * shade; + } + } + float shut = mix(1.0, Occlusion, world.occlusion_strength) * ae3d_shaders_lit_screen_occlusion(eye, ClipPrev, occlusion_map, ae_smp_occlusion_map); + float3 colour0 = ae3d_shaders_lit_sky_lit(norm, view_dir, albedo, f0, adjusted, material.metallic, ndotv, world, shading, sky_irradiance, ae_smp_sky_irradiance, sky_light, ae_smp_sky_light, brdf_lut, ae_smp_brdf_lut) * shut + lo; + float distance_to_camera = length(FragPos - eye.position); + float3 volumetric = ae3d_shaders_lit_calculate_volumetric_lighting(FragPos, world.lights[0].position, eye.position, shading, world); + float3 colour1 = colour0 + volumetric; + float3 colour2 = colour1 + ae3d_shaders_lit_caustic_light(FragPos, norm, shading, world) * albedo; + float3 colour3 = colour2 * material.exposure; + float3 colour4 = colour3; + if (material.embers > 0.0) { + colour4 = colour3 + ae3d_shaders_lit_ember_light(FragPos, world, material); + } + float3 colour = colour4; + if (weather.fog != 0) { + float haze = smoothstep(weather.fog_start, weather.fog_end, distance_to_camera) * weather.fog_intensity; + colour = mix(colour4, weather.fog_radiance, clamp(haze, 0.0, 1.0)); + } + float final0 = tex_colour.w * material.alpha; + float final1 = final0; + if (material.alpha >= 0.99) { + final1 = 1.0; + } + float final_alpha = final1; + if (material.blend != 0) { + final_alpha = tex_colour.w * material.alpha * ParticleAlpha * ae3d_shaders_lit_soft_fade(material, eye, scene_depth, ae_smp_scene_depth, FragPos, ae_frag_coord); + } + ae_out.out_colour = float4(colour, final_alpha); + ae_out.out_velocity = float4(moved, 0.0, 0.0); + return ae_out; +} diff --git a/src/ae3d/metal/lit_quoted.vert.metal b/src/ae3d/metal/lit_quoted.vert.metal new file mode 100644 index 00000000..4a6eadcc --- /dev/null +++ b/src/ae3d/metal/lit_quoted.vert.metal @@ -0,0 +1,267 @@ +// lit_vertex: written by ae3d.gpu from its quote. +#include +using namespace metal; +static inline float3x3 ae_mat3(float4x4 m) { return float3x3(m[0].xyz, m[1].xyz, m[2].xyz); } + +struct Draw { + float4x4 model; + float4x4 prev_model; + float4x4 light_space; + int instanced; + int instance_colour; + int points; + int billboard; + int flip_columns; + int flip_rows; + float lift; + int skinned; + int impostor; + char ae_pad0[12]; +}; +static_assert(sizeof(Draw) == 240, "Draw: std140"); +static_assert(__builtin_offsetof(Draw, model) == 0, "Draw.model: std140"); +static_assert(__builtin_offsetof(Draw, prev_model) == 64, "Draw.prev_model: std140"); +static_assert(__builtin_offsetof(Draw, light_space) == 128, "Draw.light_space: std140"); +static_assert(__builtin_offsetof(Draw, instanced) == 192, "Draw.instanced: std140"); +static_assert(__builtin_offsetof(Draw, instance_colour) == 196, "Draw.instance_colour: std140"); +static_assert(__builtin_offsetof(Draw, points) == 200, "Draw.points: std140"); +static_assert(__builtin_offsetof(Draw, billboard) == 204, "Draw.billboard: std140"); +static_assert(__builtin_offsetof(Draw, flip_columns) == 208, "Draw.flip_columns: std140"); +static_assert(__builtin_offsetof(Draw, flip_rows) == 212, "Draw.flip_rows: std140"); +static_assert(__builtin_offsetof(Draw, lift) == 216, "Draw.lift: std140"); +static_assert(__builtin_offsetof(Draw, skinned) == 220, "Draw.skinned: std140"); +static_assert(__builtin_offsetof(Draw, impostor) == 224, "Draw.impostor: std140"); + +struct Cut { + int on; + char ae_pad0[12]; + float4 plane; + int detached; + float noise; + float noise_scale; + int joints0; + int joints1; + int joints2; + char ae_pad1[8]; +}; +static_assert(sizeof(Cut) == 64, "Cut: std140"); +static_assert(__builtin_offsetof(Cut, on) == 0, "Cut.on: std140"); +static_assert(__builtin_offsetof(Cut, plane) == 16, "Cut.plane: std140"); +static_assert(__builtin_offsetof(Cut, detached) == 32, "Cut.detached: std140"); +static_assert(__builtin_offsetof(Cut, noise) == 36, "Cut.noise: std140"); +static_assert(__builtin_offsetof(Cut, noise_scale) == 40, "Cut.noise_scale: std140"); +static_assert(__builtin_offsetof(Cut, joints0) == 44, "Cut.joints0: std140"); +static_assert(__builtin_offsetof(Cut, joints1) == 48, "Cut.joints1: std140"); +static_assert(__builtin_offsetof(Cut, joints2) == 52, "Cut.joints2: std140"); + +struct Eye { + float4x4 view_projection; + float4x4 prev_view_projection; + float4x4 inv_view_projection; + packed_float3 position; + float far; + float2 jitter; + float2 screen_size; + int depth_on; + char ae_pad0[12]; + float4 depth_plane; + int history; + int capture; + float ray_reach; + int ray_frame; +}; +static_assert(sizeof(Eye) == 272, "Eye: std140"); +static_assert(__builtin_offsetof(Eye, view_projection) == 0, "Eye.view_projection: std140"); +static_assert(__builtin_offsetof(Eye, prev_view_projection) == 64, "Eye.prev_view_projection: std140"); +static_assert(__builtin_offsetof(Eye, inv_view_projection) == 128, "Eye.inv_view_projection: std140"); +static_assert(__builtin_offsetof(Eye, position) == 192, "Eye.position: std140"); +static_assert(__builtin_offsetof(Eye, far) == 204, "Eye.far: std140"); +static_assert(__builtin_offsetof(Eye, jitter) == 208, "Eye.jitter: std140"); +static_assert(__builtin_offsetof(Eye, screen_size) == 216, "Eye.screen_size: std140"); +static_assert(__builtin_offsetof(Eye, depth_on) == 224, "Eye.depth_on: std140"); +static_assert(__builtin_offsetof(Eye, depth_plane) == 240, "Eye.depth_plane: std140"); +static_assert(__builtin_offsetof(Eye, history) == 256, "Eye.history: std140"); +static_assert(__builtin_offsetof(Eye, capture) == 260, "Eye.capture: std140"); +static_assert(__builtin_offsetof(Eye, ray_reach) == 264, "Eye.ray_reach: std140"); +static_assert(__builtin_offsetof(Eye, ray_frame) == 268, "Eye.ray_frame: std140"); + +struct Skin { + float4x4 bones[96]; + float4 prev_rows[288]; +}; +static_assert(sizeof(Skin) == 10752, "Skin: std140"); +static_assert(__builtin_offsetof(Skin, bones) == 0, "Skin.bones: std140"); +static_assert(__builtin_offsetof(Skin, prev_rows) == 6144, "Skin.prev_rows: std140"); + +struct DrawBlock { + Draw draw; + char ae_pad0[256]; + Cut cut; + char ae_pad1[1744]; + Skin skin; +}; +static_assert(sizeof(DrawBlock) == 13056, "DrawBlock: std140"); +static_assert(__builtin_offsetof(DrawBlock, draw) == 0, "DrawBlock.draw: std140"); +static_assert(__builtin_offsetof(DrawBlock, cut) == 496, "DrawBlock.cut: std140"); +static_assert(__builtin_offsetof(DrawBlock, skin) == 2304, "DrawBlock.skin: std140"); + +struct CameraBlock { + Eye eye; +}; +static_assert(sizeof(CameraBlock) == 272, "CameraBlock: std140"); +static_assert(__builtin_offsetof(CameraBlock, eye) == 0, "CameraBlock.eye: std140"); + +struct ae_lit_vertex_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; + float3 inNormal [[attribute(2)]]; + float4 instanceModel_0 [[attribute(3)]]; + float4 instanceModel_1 [[attribute(4)]]; + float4 instanceModel_2 [[attribute(5)]]; + float4 instanceModel_3 [[attribute(6)]]; + float3 instanceColor [[attribute(7)]]; + float4 inJoints [[attribute(8)]]; + float4 inWeights [[attribute(9)]]; + float inOcclusion [[attribute(10)]]; + float2 inMaskUV [[attribute(12)]]; + float4 instanceExtras [[attribute(13)]]; +}; + +struct ae_lit_vertex_out { + float4 ae_position [[position]]; + float2 fragTexCoord [[user(locn0)]]; + float3 Normal [[user(locn1)]]; + float3 FragPos [[user(locn2)]]; + float3 InstanceColor [[user(locn3)]]; + float4 FragPosLightSpace [[user(locn4)]]; + float Occlusion [[user(locn5)]]; + float4 ClipNow [[user(locn6)]]; + float4 ClipPrev [[user(locn7)]]; + float3 BindPos [[user(locn8)]]; + float ClipLimb [[user(locn9)]]; + float2 MaskUV [[user(locn10)]]; + float ParticleAlpha [[user(locn11)]]; + float3 FlipNext [[user(locn12)]]; +}; + +static float ae3d_shaders_lit_clip_joint_on(float joint, constant Cut& cut) { + int j = int((joint + 0.5)); + int word = j < 32 ? cut.joints0 : (j < 64 ? cut.joints1 : cut.joints2); + return float((word >> (j & 31) & 1)); +} + +static float ae3d_shaders_lit_clip_limb_of(float4 joints, float4 weights, constant Draw& draw, constant Cut& cut) { + if (draw.skinned == 0 || (cut.joints0 | cut.joints1 | cut.joints2) == 0) { + return 1.0; + } + return weights.x * ae3d_shaders_lit_clip_joint_on(joints.x, cut) + weights.y * ae3d_shaders_lit_clip_joint_on(joints.y, cut) + weights.z * ae3d_shaders_lit_clip_joint_on(joints.z, cut) + weights.w * ae3d_shaders_lit_clip_joint_on(joints.w, cut); +} + +static float4x4 ae3d_shaders_lit_prev_bone(float joint, constant Skin& skin) { + int j = int((joint + 0.5)) * 3; + return transpose(float4x4(skin.prev_rows[j], skin.prev_rows[j + 1], skin.prev_rows[j + 2], float4(float3(0.0, 0.0, 0.0), 1.0))); +} + +vertex ae_lit_vertex_out ae_lit_vertex_vertex( + ae_lit_vertex_in ae_in [[stage_in]], + constant DrawBlock& ae_set0 [[buffer(0)]], + constant CameraBlock& ae_set1 [[buffer(1)]]) { + ae_lit_vertex_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + float3 inNormal = ae_in.inNormal; + float4x4 instanceModel = float4x4(ae_in.instanceModel_0, ae_in.instanceModel_1, ae_in.instanceModel_2, ae_in.instanceModel_3); + float3 instanceColor = ae_in.instanceColor; + float4 inJoints = ae_in.inJoints; + float4 inWeights = ae_in.inWeights; + float inOcclusion = ae_in.inOcclusion; + float2 inMaskUV = ae_in.inMaskUV; + float4 instanceExtras = ae_in.instanceExtras; + constant Draw& draw = ae_set0.draw; + constant Cut& cut = ae_set0.cut; + constant Skin& skin = ae_set0.skin; + constant Eye& eye = ae_set1.eye; + float clip_limb = ae3d_shaders_lit_clip_limb_of(inJoints, inWeights, draw, cut); + float2 mask_uv = inMaskUV.x < -0.5 ? inTexCoord : inMaskUV; + float4x4 model_matrix0 = draw.instanced != 0 ? (draw.model * instanceModel) : draw.model; + float4x4 model_matrix = model_matrix0; + float particle_alpha = 1.0; + float3 flip_next = float3(0.0, 0.0, -1.0); + float2 tex_coord = inTexCoord; + if (draw.instanced != 0 && draw.points != 0) { + float4 point = instanceModel[0]; + particle_alpha = instanceExtras.x; + if (draw.flip_columns > 0 && draw.flip_rows > 0) { + int frames = draw.flip_columns * draw.flip_rows; + float at = clamp(instanceExtras.y, 0.0, 1.0) * float((frames - 1)); + int frame = min(int(at), frames - 1); + int next = min(frame + 1, frames - 1); + float column = float((frame - frame / draw.flip_columns * draw.flip_columns)); + float row = float((frame / draw.flip_columns)); + tex_coord = float2((column + inTexCoord.x) / float(draw.flip_columns), 1.0 - (row + 1.0 - inTexCoord.y) / float(draw.flip_rows)); + float next_column = float((next - next / draw.flip_columns * draw.flip_columns)); + float next_row = float((next / draw.flip_columns)); + flip_next = float3((next_column + inTexCoord.x) / float(draw.flip_columns), 1.0 - (next_row + 1.0 - inTexCoord.y) / float(draw.flip_rows), at - float(frame)); + } + model_matrix = float4x4(draw.model[0] * point.w, draw.model[1] * point.w, draw.model[2] * point.w, float4(point.xyz, 1.0)); + if (draw.billboard > 0) { + float3 to_eye = eye.position - point.xyz; + float3 up0 = float3(0.0, 1.0, 0.0); + float3 forward = float3(0.0); + if (draw.billboard == 1) { + forward = normalize(float3(to_eye.x, 0.0, to_eye.z)); + } else { + forward = normalize(to_eye); + } + float3 right0 = normalize(cross(up0, forward)); + float3 up1 = cross(forward, right0); + float cn = cos(instanceExtras.z); + float sn = sin(instanceExtras.z); + float3 turned = right0 * cn + up1 * sn; + float3 up = up1 * cn - right0 * sn; + float3 right = turned * max(instanceExtras.w, 1.0); + float sx = length(draw.model[0].xyz) * point.w; + float sy = length(draw.model[1].xyz) * point.w; + float sz = length(draw.model[2].xyz) * point.w; + model_matrix = float4x4(float4(right * sx, 0.0), float4(up * sy, 0.0), float4(forward * sz, 0.0), float4(point.xyz + up * (sy * draw.lift), 1.0)); + } + } + float4 posed0 = float4(inPosition, 1.0); + float4 posed = posed0; + float4 posed_prev = posed0; + float3 posed_normal = inNormal; + if (draw.skinned != 0) { + float4x4 skin_now = ((((inWeights.x * skin.bones[int(inJoints.x)]) + (inWeights.y * skin.bones[int(inJoints.y)])) + (inWeights.z * skin.bones[int(inJoints.z)])) + (inWeights.w * skin.bones[int(inJoints.w)])); + float4x4 skin_prev = ((((inWeights.x * ae3d_shaders_lit_prev_bone(inJoints.x, skin)) + (inWeights.y * ae3d_shaders_lit_prev_bone(inJoints.y, skin))) + (inWeights.z * ae3d_shaders_lit_prev_bone(inJoints.z, skin))) + (inWeights.w * ae3d_shaders_lit_prev_bone(inJoints.w, skin))); + posed_prev = (skin_prev * posed0); + posed = (skin_now * posed0); + posed_normal = (ae_mat3(skin_now) * inNormal); + } + float4x4 prev_model_matrix0 = draw.instanced != 0 ? (draw.prev_model * instanceModel) : draw.prev_model; + float4x4 prev_model_matrix = prev_model_matrix0; + if (draw.instanced != 0 && draw.points != 0) { + prev_model_matrix = model_matrix; + } + float3 frag_pos = (model_matrix * posed).xyz; + float3 normal_out = normalize((ae_mat3(model_matrix) * posed_normal)); + float3 instance_colour = draw.instanced != 0 && draw.instance_colour != 0 ? instanceColor : float3(1.0, 1.0, 1.0); + float4 light_space = (draw.light_space * float4(frag_pos, 1.0)); + float4 clip_now = ((eye.view_projection * model_matrix) * posed); + float4 clip_prev = ((eye.prev_view_projection * prev_model_matrix) * posed_prev); + ae_out.ae_position = clip_now; + ae_out.fragTexCoord = tex_coord; + ae_out.Normal = normal_out; + ae_out.FragPos = frag_pos; + ae_out.InstanceColor = instance_colour; + ae_out.FragPosLightSpace = light_space; + ae_out.Occlusion = inOcclusion; + ae_out.ClipNow = clip_now; + ae_out.ClipPrev = clip_prev; + ae_out.BindPos = inPosition; + ae_out.ClipLimb = clip_limb; + ae_out.MaskUV = mask_uv; + ae_out.ParticleAlpha = particle_alpha; + ae_out.FlipNext = flip_next; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/sea_quoted.frag.metal b/src/ae3d/metal/sea_quoted.frag.metal new file mode 100644 index 00000000..95eeaee7 --- /dev/null +++ b/src/ae3d/metal/sea_quoted.frag.metal @@ -0,0 +1,497 @@ +// sea: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct KeyLight { + packed_float3 position; + char ae_pad0[4]; + packed_float3 direction; + char ae_pad1[4]; + packed_float3 colour; + float intensity; +}; +static_assert(sizeof(KeyLight) == 48, "KeyLight: std140"); +static_assert(__builtin_offsetof(KeyLight, position) == 0, "KeyLight.position: std140"); +static_assert(__builtin_offsetof(KeyLight, direction) == 16, "KeyLight.direction: std140"); +static_assert(__builtin_offsetof(KeyLight, colour) == 32, "KeyLight.colour: std140"); +static_assert(__builtin_offsetof(KeyLight, intensity) == 44, "KeyLight.intensity: std140"); + +struct Eye { + float4x4 inv_view_projection; + packed_float3 position; + float far; + float2 jitter; + float2 screen_size; + int depth_on; + char ae_pad0[12]; +}; +static_assert(sizeof(Eye) == 112, "Eye: std140"); +static_assert(__builtin_offsetof(Eye, inv_view_projection) == 0, "Eye.inv_view_projection: std140"); +static_assert(__builtin_offsetof(Eye, position) == 64, "Eye.position: std140"); +static_assert(__builtin_offsetof(Eye, far) == 76, "Eye.far: std140"); +static_assert(__builtin_offsetof(Eye, jitter) == 80, "Eye.jitter: std140"); +static_assert(__builtin_offsetof(Eye, screen_size) == 88, "Eye.screen_size: std140"); +static_assert(__builtin_offsetof(Eye, depth_on) == 96, "Eye.depth_on: std140"); + +struct Water { + float time; + float speed; + float height; + char ae_pad0[4]; + float4 directions[4]; + float4 amplitudes[4]; + float4 frequencies[4]; + float4 speeds[4]; + float4 phases[4]; + packed_float3 colour; + float opacity; + int foam; + float foam_intensity; + int caustics; + float caustics_intensity; + float caustics_scale; + int reflection; + float reflection_intensity; + int distortion; + float distortion_intensity; + int normal_mapping; + float normal_intensity; + float depth_fade; + float shore_foam; + char ae_pad1[12]; + packed_float3 sky_colour; + char ae_pad2[4]; + packed_float3 horizon_colour; + int sky_texture; + int sky_capture; + int ripples; + char ae_pad3[8]; + packed_float3 ripple_area; + char ae_pad4[4]; +}; +static_assert(sizeof(Water) == 480, "Water: std140"); +static_assert(__builtin_offsetof(Water, time) == 0, "Water.time: std140"); +static_assert(__builtin_offsetof(Water, speed) == 4, "Water.speed: std140"); +static_assert(__builtin_offsetof(Water, height) == 8, "Water.height: std140"); +static_assert(__builtin_offsetof(Water, directions) == 16, "Water.directions: std140"); +static_assert(__builtin_offsetof(Water, amplitudes) == 80, "Water.amplitudes: std140"); +static_assert(__builtin_offsetof(Water, frequencies) == 144, "Water.frequencies: std140"); +static_assert(__builtin_offsetof(Water, speeds) == 208, "Water.speeds: std140"); +static_assert(__builtin_offsetof(Water, phases) == 272, "Water.phases: std140"); +static_assert(__builtin_offsetof(Water, colour) == 336, "Water.colour: std140"); +static_assert(__builtin_offsetof(Water, opacity) == 348, "Water.opacity: std140"); +static_assert(__builtin_offsetof(Water, foam) == 352, "Water.foam: std140"); +static_assert(__builtin_offsetof(Water, foam_intensity) == 356, "Water.foam_intensity: std140"); +static_assert(__builtin_offsetof(Water, caustics) == 360, "Water.caustics: std140"); +static_assert(__builtin_offsetof(Water, caustics_intensity) == 364, "Water.caustics_intensity: std140"); +static_assert(__builtin_offsetof(Water, caustics_scale) == 368, "Water.caustics_scale: std140"); +static_assert(__builtin_offsetof(Water, reflection) == 372, "Water.reflection: std140"); +static_assert(__builtin_offsetof(Water, reflection_intensity) == 376, "Water.reflection_intensity: std140"); +static_assert(__builtin_offsetof(Water, distortion) == 380, "Water.distortion: std140"); +static_assert(__builtin_offsetof(Water, distortion_intensity) == 384, "Water.distortion_intensity: std140"); +static_assert(__builtin_offsetof(Water, normal_mapping) == 388, "Water.normal_mapping: std140"); +static_assert(__builtin_offsetof(Water, normal_intensity) == 392, "Water.normal_intensity: std140"); +static_assert(__builtin_offsetof(Water, depth_fade) == 396, "Water.depth_fade: std140"); +static_assert(__builtin_offsetof(Water, shore_foam) == 400, "Water.shore_foam: std140"); +static_assert(__builtin_offsetof(Water, sky_colour) == 416, "Water.sky_colour: std140"); +static_assert(__builtin_offsetof(Water, horizon_colour) == 432, "Water.horizon_colour: std140"); +static_assert(__builtin_offsetof(Water, sky_texture) == 444, "Water.sky_texture: std140"); +static_assert(__builtin_offsetof(Water, sky_capture) == 448, "Water.sky_capture: std140"); +static_assert(__builtin_offsetof(Water, ripples) == 452, "Water.ripples: std140"); +static_assert(__builtin_offsetof(Water, ripple_area) == 464, "Water.ripple_area: std140"); + +struct Weather { + int fog; + float fog_start; + float fog_end; + char ae_pad0[4]; + packed_float3 fog_radiance; + float fog_intensity; + float overcast; + char ae_pad1[12]; + packed_float3 overcast_colour; + char ae_pad2[4]; +}; +static_assert(sizeof(Weather) == 64, "Weather: std140"); +static_assert(__builtin_offsetof(Weather, fog) == 0, "Weather.fog: std140"); +static_assert(__builtin_offsetof(Weather, fog_start) == 4, "Weather.fog_start: std140"); +static_assert(__builtin_offsetof(Weather, fog_end) == 8, "Weather.fog_end: std140"); +static_assert(__builtin_offsetof(Weather, fog_radiance) == 16, "Weather.fog_radiance: std140"); +static_assert(__builtin_offsetof(Weather, fog_intensity) == 28, "Weather.fog_intensity: std140"); +static_assert(__builtin_offsetof(Weather, overcast) == 32, "Weather.overcast: std140"); +static_assert(__builtin_offsetof(Weather, overcast_colour) == 48, "Weather.overcast_colour: std140"); + +struct ae_Params { + KeyLight key; + Eye eye; + Water water; + Weather weather; +}; +static_assert(sizeof(ae_Params) == 704, "ae_Params: std140"); +static_assert(__builtin_offsetof(ae_Params, key) == 0, "ae_Params.key: std140"); +static_assert(__builtin_offsetof(ae_Params, eye) == 48, "ae_Params.eye: std140"); +static_assert(__builtin_offsetof(ae_Params, water) == 160, "ae_Params.water: std140"); +static_assert(__builtin_offsetof(ae_Params, weather) == 640, "ae_Params.weather: std140"); + +struct ae_sea_in { + float4 ae_frag_coord [[position]]; + float3 world [[user(locn2)]]; + float4 clip_now [[user(locn3)]]; + float4 clip_prev [[user(locn4)]]; + float eye_below [[user(locn5)]]; +}; + +struct ae_sea_frag_out { + float4 out_colour [[color(0)]]; + float4 out_velocity [[color(1)]]; +}; + +static float ae3d_shaders_sea_sea_hash(float2 p) { + float3 p3_0 = fract(float3(p.x, p.y, p.x) * 0.1031); + float3 p3 = p3_0 + float3(dot(p3_0, float3(p3_0.y, p3_0.z, p3_0.x) + float3(33.33))); + return fract((p3.x + p3.y) * p3.z); +} + +static float ae3d_shaders_sea_sea_noise(float2 st) { + float2 i = floor(st); + float2 f = fract(st); + float a = ae3d_shaders_sea_sea_hash(i); + float b = ae3d_shaders_sea_sea_hash(i + float2(1.0, 0.0)); + float c = ae3d_shaders_sea_sea_hash(i + float2(0.0, 1.0)); + float d = ae3d_shaders_sea_sea_hash(i + float2(1.0, 1.0)); + float2 u = f * f * f * (f * (f * 6.0 - float2(15.0)) + float2(10.0)); + return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y; +} + +static float3 ae3d_shaders_sea_sea_noised(float2 st) { + float2 i = floor(st); + float2 f = fract(st); + float a = ae3d_shaders_sea_sea_hash(i); + float b = ae3d_shaders_sea_sea_hash(i + float2(1.0, 0.0)); + float c = ae3d_shaders_sea_sea_hash(i + float2(0.0, 1.0)); + float d = ae3d_shaders_sea_sea_hash(i + float2(1.0, 1.0)); + float2 u = f * f * f * (f * (f * 6.0 - float2(15.0)) + float2(10.0)); + float2 du = f * 30.0 * f * (f * (f - float2(2.0)) + float2(1.0)); + float k1 = b - a; + float k2 = c - a; + float k3 = a - b - c + d; + return float3(a + k1 * u.x + k2 * u.y + k3 * u.x * u.y, du * float2(k1 + k3 * u.y, k2 + k3 * u.x)); +} + +static float ae3d_shaders_skydome_aces_inverse(float y_in) { + float y = clamp(y_in, 0.0, 0.9999); + float a = 2.51 - 2.43 * y; + float b = 0.03 - 0.59 * y; + return (-b + sqrt(b * b + 4.0 * a * 0.14 * y)) / (2.0 * a); +} + +static float ae3d_shaders_skydome_srgb_decode(float c) { + return c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4); +} + +static float3 ae3d_shaders_skydome_display_radiance(float3 shown) { + float3 c = clamp(shown, float3(0.0), float3(1.0)); + return float3(ae3d_shaders_skydome_aces_inverse(ae3d_shaders_skydome_srgb_decode(c.x)), ae3d_shaders_skydome_aces_inverse(ae3d_shaders_skydome_srgb_decode(c.y)), ae3d_shaders_skydome_aces_inverse(ae3d_shaders_skydome_srgb_decode(c.z))); +} + +static float3 ae3d_shaders_sea_overcast_sky(float3 shown, float overcast, float3 overcast_colour) { + if (overcast <= 0.0) { + return shown; + } + float luma = dot(shown, float3(0.299, 0.587, 0.114)); + return mix(shown, overcast_colour * (0.35 + 0.65 * luma), clamp(overcast, 0.0, 1.0)); +} + +static float2 ae3d_shaders_sea_sea_oct_uv(float3 d_in, float size) { + float3 d = d_in / (abs(d_in.x) + abs(d_in.y) + abs(d_in.z)); + float2 p = d.xz; + if (d.y < 0.0) { + p = (float2(1.0) - abs(p.yx)) * float2(p.x >= 0.0 ? 1.0 : -1.0, p.y >= 0.0 ? 1.0 : -1.0); + } + return ((p * 0.5 + float2(0.5)) * (size - 2.0) + float2(1.0)) / size; +} + +static float ae3d_shaders_sea_sea_fbm(float2 st_in) { + float2 st = st_in; + float value = 0.0; + float amplitude = 0.5; + float2x2 rotation = float2x2(cos(0.5), sin(0.5), -sin(0.5), cos(0.5)); + for (int i = 0; i < 6; i += 1) { + value = value + amplitude * ae3d_shaders_sea_sea_noise(st); + st = (rotation * st) * 2.31; + amplitude = amplitude * 0.53; + } + return value; +} + +static float ae3d_shaders_sea_sea_ridged(float2 st) { + float n = ae3d_shaders_sea_sea_fbm(st); + return 1.0 - abs(n * 2.0 - 1.0); +} + +static float3 ae3d_shaders_sea_reflected_sky(float3 ray_in, int sky_capture, texture2d sky_light, sampler ae_smp_sky_light, int sky_texture, texture2d sky_image, sampler ae_smp_sky_image, float3 sky_colour, float3 horizon_colour, float overcast, float3 overcast_colour) { + float3 ray = float3(ray_in.x, abs(ray_in.y), ray_in.z); + if (sky_capture == 1) { + return sky_light.sample(ae_smp_sky_light, ae3d_shaders_sea_sea_oct_uv(normalize(ray), 256.0), level(0.5)).rgb; + } + if (sky_texture == 1) { + float theta = atan2(ray.z, ray.x); + float phi = asin(clamp(ray.y, -1.0, 1.0)); + float3 shown = sky_image.sample(ae_smp_sky_image, float2((theta + 3.14159265) / 6.28318531, (phi + 1.57079633) / 3.14159265), level(0.0)).rgb; + return ae3d_shaders_skydome_display_radiance(ae3d_shaders_sea_overcast_sky(shown, overcast, overcast_colour)); + } + float3 zenith = ae3d_shaders_skydome_display_radiance(ae3d_shaders_sea_overcast_sky(sky_colour, overcast, overcast_colour)); + float3 rim = ae3d_shaders_skydome_display_radiance(ae3d_shaders_sea_overcast_sky(horizon_colour, overcast, overcast_colour)); + return mix(rim, zenith, smoothstep(0.0, 0.35, ray.y)); +} + +static float ae3d_shaders_sea_sea_perlin(float2 p, int octaves) { + float value = 0.0; + float amplitude = 0.5; + float frequency = 1.0; + for (int i = 0; i < octaves; i += 1) { + value = value + amplitude * ae3d_shaders_sea_sea_noise(p * frequency); + amplitude = amplitude * 0.5; + frequency = frequency * 2.0; + } + return value; +} + +static float ae3d_shaders_sea_sea_caustics(float3 world, float time, int on, float scale, float intensity, float3 eye) { + if (on == 0) { + return 0.0; + } + float2 coord = world.xz * scale; + float2 warp1 = float2(ae3d_shaders_sea_sea_perlin(coord * 0.5 + float2(time * 0.02), 2), ae3d_shaders_sea_sea_perlin(coord * 0.5 + float2(time * 0.025) + float2(5.2, 1.3), 2)); + float caustic1 = ae3d_shaders_sea_sea_perlin(coord + warp1 * 0.3 + float2(time * 0.015), 3); + float2 warp2 = float2(ae3d_shaders_sea_sea_perlin(coord * 0.8 - float2(time * 0.018), 2), ae3d_shaders_sea_sea_perlin(coord * 0.8 - float2(time * 0.022) + float2(3.7, 2.1), 2)); + float caustic2 = ae3d_shaders_sea_sea_perlin(coord * 1.3 + warp2 * 0.25 - float2(time * 0.012), 3); + float caustic3 = ae3d_shaders_sea_sea_perlin(coord * 2.0 + float2(time * 0.03), 2); + float combined = caustic1 * 0.5 + caustic2 * 0.3 + caustic3 * 0.2; + combined = pow(abs(combined), 2.5) * 1.3; + combined = smoothstep(0.2, 0.8, combined); + float attenuation = 1.0 / (1.0 + length(world.xz - eye.xz) * 0.00005); + return combined * intensity * attenuation; +} + +static float2 ae3d_shaders_skydome_velocity(float4 now, float4 prev, float2 nudge) { + if (now.w <= 0.0 || prev.w <= 0.0) { + return float2(0.0); + } + float2 uv_now = now.xy / now.w * 0.5 + float2(0.5) - nudge; + float2 uv_prev = prev.xy / prev.w * 0.5 + float2(0.5); + return uv_now - uv_prev; +} + +fragment ae_sea_frag_out ae_sea_fragment( + ae_sea_in ae_in [[stage_in]], + constant ae_Params& ae_params [[buffer(0)]], + texture2d ripple_mask [[texture(0)]], sampler ae_smp_ripple_mask [[sampler(0)]], + texture2d sky_irradiance [[texture(1)]], sampler ae_smp_sky_irradiance [[sampler(1)]], + texture2d sky_light_map [[texture(2)]], sampler ae_smp_sky_light_map [[sampler(2)]], + texture2d sky_image [[texture(3)]], sampler ae_smp_sky_image [[sampler(3)]], + texture2d scene_depth [[texture(4)]], sampler ae_smp_scene_depth [[sampler(4)]]) { + ae_sea_frag_out ae_out; + float4 ae_frag_coord = ae_in.ae_frag_coord; + float3 world = ae_in.world; + float4 clip_now = ae_in.clip_now; + float4 clip_prev = ae_in.clip_prev; + float eye_below = ae_in.eye_below; + constant KeyLight& key = ae_params.key; + constant Eye& eye = ae_params.eye; + constant Water& water = ae_params.water; + constant Weather& weather = ae_params.weather; + float3 light_dir = float3(0.0); + if (key.direction.x != 0.0 || key.direction.y != 0.0 || key.direction.z != 0.0) { + light_dir = normalize(key.direction); + } else { + light_dir = normalize(key.position - world); + } + float3 view_dir = normalize(eye.position - world); + float distance_from_camera = length(eye.position - world); + float far = smoothstep(0.0, eye.far * 0.35, distance_from_camera); + float2 p = world.xz; + float t = water.time; + float dhx = 0.0; + float dhz = 0.0; + float sx = 0.0; + float sz = 0.0; + float slope_max = 0.0; + float speed_mul = max(water.speed, 0.001); + float field_height = 0.0; + for (int i = 0; i < 4; i += 1) { + float2 td = normalize(water.directions[i].xyz.xz); + float tk = water.frequencies[i].x; + float ta = water.amplitudes[i].x * water.height * 1.5; + float t_lambda = 6.28318531 / max(tk, 0.000001); + float t_fade = 1.0 - smoothstep(t_lambda * 40.0, t_lambda * 160.0, distance_from_camera); + float t_phase = dot(td, p) * tk + t * water.speeds[i].x * speed_mul + water.phases[i].x + ae3d_shaders_sea_sea_noise(p * tk * 0.2 + float2(float(i) * 4.1, 0.0)) * 1.2; + float tc = cos(t_phase) * ta * tk * t_fade; + field_height = field_height + ta * sin(t_phase) * t_fade; + sx = sx + td.x * tc; + sz = sz + td.y * tc; + slope_max = slope_max + ta * tk * t_fade; + } + dhx = sx; + dhz = sz; + float2 wind = normalize(water.directions[0].xyz.xz); + float k0 = water.frequencies[3].x; + float sets = 0.55 + 0.9 * ae3d_shaders_sea_sea_noise(p * k0 * 0.03 + float2(t * 0.004, 0.0)); + float2 warp = float2(ae3d_shaders_sea_sea_noise(p * k0 * 0.12 + float2(3.1, 7.7)), ae3d_shaders_sea_sea_noise(p * k0 * 0.12 + float2(9.2, 1.3))) * 2.0 - float2(1.0); + for (int j = 1; j <= 4; j += 1) { + float wk = k0 * pow(1.65, float(j)); + float turn = sin(float(j) * 2.3 + float((j * j)) * 0.61) * 0.5; + float2 wd = float2(wind.x * cos(turn) - wind.y * sin(turn), wind.x * sin(turn) + wind.y * cos(turn)); + float wa = 0.028 / wk; + float w_lambda = 6.28318531 / wk; + float w_fade = (1.0 - smoothstep(w_lambda * 40.0, w_lambda * 160.0, distance_from_camera)) * sets; + float w_bend = (warp.x * wd.y - warp.y * wd.x) * 2.4; + float w_phase = dot(wd, p) * wk + t * sqrt(9.81 * wk) * speed_mul + float(j) * 1.7 + w_bend; + float wc = cos(w_phase) * wa * wk * w_fade; + field_height = field_height + wa * sin(w_phase) * w_fade; + dhx = dhx + wd.x * wc; + dhz = dhz + wd.y * wc; + slope_max = slope_max + wa * wk * w_fade; + } + float kc = k0 * pow(1.65, 5.0); + for (int m = 0; m < 3; m += 1) { + float ck = kc * pow(2.1, float(m)); + float c_lambda = 6.28318531 / ck; + float c_fade = (1.0 - smoothstep(c_lambda * 40.0, c_lambda * 160.0, distance_from_camera)) * sets; + if (c_fade <= 0.0) { + continue; + } + float ca = 0.16 / ck; + float2 st = p * (ck / 6.28318531) - wind * (t * sqrt(9.81 * ck) * speed_mul / 6.28318531) + float2(float(m) * 17.3, float(m) * 5.9); + float3 cn = ae3d_shaders_sea_sea_noised(st); + field_height = field_height + ca * (cn.x - 0.5) * c_fade; + float c_slope = ca * ck / 6.28318531 * c_fade; + dhx = dhx + cn.y * c_slope; + dhz = dhz + cn.z * c_slope; + slope_max = slope_max + c_slope * 1.5; + } + float3 norm = normalize(float3(-dhx, 1.0, -dhz)); + float3 swell = normalize(float3(-sx, 1.0, -sz)); + float steep = clamp(length(float2(dhx, dhz)) / max(slope_max * 0.5, 0.0001), 0.0, 1.0); + if (water.normal_mapping != 0) { + norm = normalize(mix(float3(0.0, 1.0, 0.0), norm, water.normal_intensity)); + swell = normalize(mix(float3(0.0, 1.0, 0.0), swell, water.normal_intensity)); + } + if (water.ripples == 1) { + float2 ruv = (p - float2(water.ripple_area.x, water.ripple_area.y)) * water.ripple_area.z; + if (ruv.x > 0.0 && ruv.y > 0.0 && ruv.x < 1.0 && ruv.y < 1.0) { + float2 slope = (ripple_mask.sample(ae_smp_ripple_mask, ruv).xy * 2.0 - float2(1.0)) * 0.6; + float3 tilt = float3(-slope.x, 0.0, -slope.y); + norm = normalize(norm + tilt); + swell = normalize(swell + tilt); + } + } + float ripple_freq = water.frequencies[3].x * 9.0 / 6.28318531; + float ripple_time = water.time * water.speeds[3].x * max(water.speed, 0.001) * 0.35; + float bend = water.distortion != 0 ? clamp(water.distortion_intensity, 0.0, 1.0) * 0.6 : 0.12; + bend = bend * (1.0 - 0.7 * far); + float3 mirror_norm = normalize(mix(swell, norm, bend)); + float3 half_dir = normalize(light_dir + view_dir); + float n_dot_v = max(dot(norm, view_dir), 0.001); + float n_dot_l = max(dot(norm, light_dir), 0.0); + float n_dot_h = max(dot(norm, half_dir), 0.0); + float v_dot_h = max(dot(view_dir, half_dir), 0.0); + float fresnel = 0.02 + 0.98 * pow(1.0 - max(dot(mirror_norm, view_dir), 0.001), 5.0); + float mirror = water.reflection != 0 ? clamp(water.reflection_intensity, 0.0, 1.0) : 0.0; + float3 sun = key.colour * key.intensity; + float3 sky_lit = ae3d_shaders_skydome_display_radiance(ae3d_shaders_sea_overcast_sky(water.sky_colour, weather.overcast, weather.overcast_colour)); + if (water.sky_capture == 1) { + sky_lit = sky_irradiance.sample(ae_smp_sky_irradiance, ae3d_shaders_sea_sea_oct_uv(float3(0.0, 1.0, 0.0), 16.0)).rgb / 3.14159265; + } + float roughness = mix(0.07, 0.22, far); + float alpha_r = roughness * roughness; + float alpha2 = alpha_r * alpha_r; + float denom = n_dot_h * n_dot_h * (alpha2 - 1.0) + 1.0; + float ggx_d = alpha2 / (3.14159265 * denom * denom); + float ggx_k = (roughness + 1.0) * (roughness + 1.0) / 8.0; + float ggx_g = n_dot_v / (n_dot_v * (1.0 - ggx_k) + ggx_k) * (n_dot_l / (n_dot_l * (1.0 - ggx_k) + ggx_k)); + float ggx_fs = 0.02 + 0.98 * pow(1.0 - v_dot_h, 5.0); + float3 glitter = sun * (ggx_d * ggx_g * ggx_fs) / (4.0 * n_dot_l * n_dot_v + 0.0001) * n_dot_l; + float3 base = water.colour; + if (base.x == 0.0 && base.y == 0.0 && base.z == 0.0) { + base = float3(0.02, 0.08, 0.14); + } + float3 body = base * (sky_lit * 0.7 + sun * (0.12 + 0.30 * n_dot_l)); + float tallest = (water.amplitudes[0].x + water.amplitudes[1].x + water.amplitudes[2].x + water.amplitudes[3].x) * water.height; + float crest = clamp(field_height / max(tallest, 0.001), 0.0, 1.0); + float through = pow(clamp(dot(view_dir, -light_dir), 0.0, 1.0), 3.0) * (0.3 + 0.7 * crest) + crest * 0.25 * n_dot_l; + float3 scatter = (base * 2.5 + float3(0.0, 0.06, 0.04)) * sun * 0.30 * through; + float foam = 0.0; + if (water.foam != 0 && tallest > 0.0) { + float torn = ae3d_shaders_sea_sea_ridged(p * ripple_freq * 0.08 + float2(water.time * 0.02)); + float breaking = smoothstep(0.45, 0.9, steep) * (0.4 + 0.6 * crest); + foam = breaking * (0.35 + 0.65 * smoothstep(0.35, 0.8, torn)); + foam = foam * (0.55 + 0.45 * ae3d_shaders_sea_sea_noise(p * ripple_freq * 1.5 + float2(ripple_time * 0.5))); + foam = foam * (clamp(water.foam_intensity, 0.0, 2.0) * (1.0 - 0.7 * far)); + foam = clamp(foam, 0.0, 1.0); + } + float3 sky_hit = ae3d_shaders_sea_reflected_sky(reflect(-view_dir, mirror_norm), water.sky_capture, sky_light_map, ae_smp_sky_light_map, water.sky_texture, sky_image, ae_smp_sky_image, water.sky_colour, water.horizon_colour, weather.overcast, weather.overcast_colour); + float3 final_colour = mix(body + scatter, sky_hit, fresnel * mirror) + glitter * (1.0 - 0.6 * foam); + float3 foam_lit = float3(0.85) * (sun * 0.5 * max(light_dir.y, 0.0) + sky_lit * 0.6); + final_colour = mix(final_colour, foam_lit, foam); + float caustic = ae3d_shaders_sea_sea_caustics(world, water.time, water.caustics, water.caustics_scale, water.caustics_intensity, eye.position); + final_colour = final_colour + sun * caustic * 0.1; + if (weather.fog != 0) { + float haze = smoothstep(weather.fog_start, weather.fog_end, distance_from_camera) * weather.fog_intensity; + final_colour = mix(final_colour, weather.fog_radiance, clamp(haze, 0.0, 1.0)); + } + float below = 1000.0; + if (eye.depth_on == 1) { + float2 suv = ae_frag_coord.xy / eye.screen_size; + float dd = scene_depth.sample(ae_smp_scene_depth, suv).x; + if (dd < 0.99999) { + float2 ndc = suv * 2.0 - float2(1.0); + float4 clip = float4(float3(ndc.x, ndc.y, dd), 1.0); + float4 at = (eye.inv_view_projection * clip); + float3 ground = at.xyz / at.w; + below = max(distance(eye.position, ground) - distance(eye.position, world), 0.0); + } + } + float clear = 1.0 - exp(-below / max(water.depth_fade, 0.01)); + if (eye.depth_on == 1 && below < 999.0) { + float wash = 1.0 - smoothstep(0.0, max(water.shore_foam, 0.01), below); + float lace = 0.5 + 0.5 * ae3d_shaders_sea_sea_noise(p * ripple_freq * 0.8 + float2(ripple_time * 0.3)); + float shore_foam = wash * wash * lace * 0.9; + float3 shore_lit = float3(0.9) * (sun * 0.5 * max(light_dir.y, 0.0) + sky_lit * 0.6); + final_colour = mix(final_colour, shore_lit, clamp(shore_foam, 0.0, 1.0)); + foam = max(foam, shore_foam); + } + float alpha = water.opacity; + if (alpha <= 0.01) { + alpha = 0.85; + } + alpha = mix(alpha, 1.0, fresnel * mirror); + alpha = alpha * clear; + alpha = mix(alpha, 1.0, foam); + float underwater_depth = max(0.0, eye_below); + if (underwater_depth > 0.0) { + float3 sky_through = ae3d_shaders_skydome_display_radiance(ae3d_shaders_sea_overcast_sky(water.sky_colour, weather.overcast, weather.overcast_colour)); + float3 through_sky = mix(sky_through, water.colour * sky_through, 0.35); + float3 under = normalize(mix(float3(0.0, 1.0, 0.0), norm, 0.35)); + float overhead = clamp(dot(under, -view_dir), 0.0, 1.0); + float window = smoothstep(0.15, 0.75, overhead); + float3 underside = mix(water.colour * sky_through * 0.55, through_sky, window); + float deep = clamp(underwater_depth * 0.0025, 0.0, 0.5); + final_colour = mix(underside, water.colour * sky_through * 0.5, deep); + float3 refracted = normalize(reflect(-view_dir, under)); + float glint = pow(max(0.0, dot(refracted, light_dir)), 48.0); + final_colour = final_colour + key.colour * glint * 0.8 * window; + final_colour = final_colour + float3(caustic * 0.35) * window; + float underwater_fog = clamp(distance_from_camera * 0.0002, 0.0, 0.85); + float3 murk = water.colour * sky_through * 0.4; + if (weather.fog != 0) { + underwater_fog = smoothstep(weather.fog_start, weather.fog_end, distance_from_camera) * weather.fog_intensity; + murk = weather.fog_radiance; + } + final_colour = mix(final_colour, murk, underwater_fog); + } + alpha = clamp(alpha, 0.001, 0.98); + ae_out.out_colour = float4(final_colour, alpha); + ae_out.out_velocity = float4(ae3d_shaders_skydome_velocity(clip_now, clip_prev, eye.jitter), 0.0, 0.0); + return ae_out; +} diff --git a/src/ae3d/metal/sea_quoted.vert.metal b/src/ae3d/metal/sea_quoted.vert.metal new file mode 100644 index 00000000..b172f9fa --- /dev/null +++ b/src/ae3d/metal/sea_quoted.vert.metal @@ -0,0 +1,166 @@ +// sea_vertex: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct Water { + float4x4 model; + float time; + float speed; + float height; + float randomness; + float4 directions[4]; + float4 amplitudes[4]; + float4 frequencies[4]; + float4 speeds[4]; + float4 phases[4]; + float4 steepness[4]; +}; +static_assert(sizeof(Water) == 464, "Water: std140"); +static_assert(__builtin_offsetof(Water, model) == 0, "Water.model: std140"); +static_assert(__builtin_offsetof(Water, time) == 64, "Water.time: std140"); +static_assert(__builtin_offsetof(Water, speed) == 68, "Water.speed: std140"); +static_assert(__builtin_offsetof(Water, height) == 72, "Water.height: std140"); +static_assert(__builtin_offsetof(Water, randomness) == 76, "Water.randomness: std140"); +static_assert(__builtin_offsetof(Water, directions) == 80, "Water.directions: std140"); +static_assert(__builtin_offsetof(Water, amplitudes) == 144, "Water.amplitudes: std140"); +static_assert(__builtin_offsetof(Water, frequencies) == 208, "Water.frequencies: std140"); +static_assert(__builtin_offsetof(Water, speeds) == 272, "Water.speeds: std140"); +static_assert(__builtin_offsetof(Water, phases) == 336, "Water.phases: std140"); +static_assert(__builtin_offsetof(Water, steepness) == 400, "Water.steepness: std140"); + +struct Eye { + float4x4 view_projection; + float4x4 prev_view_projection; + packed_float3 position; + char ae_pad0[4]; +}; +static_assert(sizeof(Eye) == 144, "Eye: std140"); +static_assert(__builtin_offsetof(Eye, view_projection) == 0, "Eye.view_projection: std140"); +static_assert(__builtin_offsetof(Eye, prev_view_projection) == 64, "Eye.prev_view_projection: std140"); +static_assert(__builtin_offsetof(Eye, position) == 128, "Eye.position: std140"); + +struct ae_VertexParams { + Water surface; + Eye viewer; +}; +static_assert(sizeof(ae_VertexParams) == 608, "ae_VertexParams: std140"); +static_assert(__builtin_offsetof(ae_VertexParams, surface) == 0, "ae_VertexParams.surface: std140"); +static_assert(__builtin_offsetof(ae_VertexParams, viewer) == 464, "ae_VertexParams.viewer: std140"); + +struct ae_sea_vertex_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; +}; + +struct ae_sea_vertex_out { + float4 ae_position [[position]]; + float2 uv [[user(locn0)]]; + float3 normal [[user(locn1)]]; + float3 world [[user(locn2)]]; + float4 clip_now [[user(locn3)]]; + float4 clip_prev [[user(locn4)]]; + float eye_below [[user(locn5)]]; +}; + +static float ae3d_shaders_sea_swell_hash(float2 p) { + return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); +} + +static float ae3d_shaders_sea_swell_noise(float2 p_in, int octaves) { + float2 p = p_in; + float value = 0.0; + float amplitude = 0.5; + for (int i = 0; i < octaves; i += 1) { + float2 i_p = floor(p); + float2 f_p0 = fract(p); + float2 f_p = f_p0 * f_p0 * (float2(3.0) - f_p0 * 2.0); + float a = ae3d_shaders_sea_swell_hash(i_p); + float b = ae3d_shaders_sea_swell_hash(i_p + float2(1.0, 0.0)); + float c = ae3d_shaders_sea_swell_hash(i_p + float2(0.0, 1.0)); + float d = ae3d_shaders_sea_swell_hash(i_p + float2(1.0, 1.0)); + value = value + amplitude * mix(mix(a, b, f_p.x), mix(c, d, f_p.x), f_p.y); + amplitude = amplitude * 0.5; + p = p * 2.0; + } + return value; +} + +static float3 ae3d_shaders_sea_gerstner(float3 position, float3 direction, float amplitude, float frequency, float speed, float phase, float steepness, float time, float speed_multiplier) { + float2 d = normalize(direction.xz); + float effective_speed = speed * speed_multiplier; + if (speed_multiplier <= 0.001) { + effective_speed = speed; + } + float wave = dot(d, position.xz) * frequency + time * effective_speed + phase; + float c = cos(wave); + float s = sin(wave); + float k = 1.2; + float sharpened_s = pow((s + 1.0) * 0.5, k) * 2.0 - 1.0; + float q = steepness / (frequency * amplitude * 6.0 + 0.01); + return float3(q * amplitude * d.x * c, amplitude * sharpened_s, q * amplitude * d.y * c); +} + +static float3 ae3d_shaders_sea_gerstner_normal(float3 position, float3 direction, float amplitude, float frequency, float speed, float phase, float steepness, float time, float speed_multiplier) { + float2 d = normalize(direction.xz); + float effective_speed = speed * speed_multiplier; + if (speed_multiplier <= 0.001) { + effective_speed = speed; + } + float wave = dot(d, position.xz) * frequency + time * effective_speed + phase; + float c = cos(wave); + float s = sin(wave); + float k = 1.2; + float sharpened_derivative = k * pow((s + 1.0) * 0.5, k - 1.0); + float q = steepness / (frequency * amplitude * 6.0 + 0.01); + float wa = frequency * amplitude; + return float3(-d.x * wa * c * sharpened_derivative, 1.0 - q * wa * s, -d.y * wa * c * sharpened_derivative); +} + +vertex ae_sea_vertex_out ae_sea_vertex_vertex( + ae_sea_vertex_in ae_in [[stage_in]], + constant ae_VertexParams& ae_params [[buffer(1)]]) { + ae_sea_vertex_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + constant Water& surface = ae_params.surface; + constant Eye& viewer = ae_params.viewer; + float3 world0 = (surface.model * float4(inPosition, 1.0)).xyz; + float3 total = float3(0.0); + float3 total_normal = float3(0.0, 1.0, 0.0); + for (int i = 0; i < 4; i += 1) { + float random_factor = 1.0; + if (surface.randomness > 0.001) { + float random_noise = ae3d_shaders_sea_swell_noise(world0.xz * 0.01 + float2(surface.time * 0.1) + float2(float(i)), 2); + random_factor = mix(1.0, 0.5 + random_noise, surface.randomness); + } + float adjusted = surface.amplitudes[i].x * surface.height * random_factor; + float3 wave_disp = ae3d_shaders_sea_gerstner(world0, surface.directions[i].xyz, adjusted, surface.frequencies[i].x, surface.speeds[i].x, surface.phases[i].x, surface.steepness[i].x, surface.time, surface.speed); + float3 wave_normal = ae3d_shaders_sea_gerstner_normal(world0, surface.directions[i].xyz, adjusted, surface.frequencies[i].x, surface.speeds[i].x, surface.phases[i].x, surface.steepness[i].x, surface.time, surface.speed); + total = total + wave_disp; + total_normal = total_normal + wave_normal; + } + float3 lifted = float3(total.x, total.y * 1.5, total.z); + float3 displaced = world0 + lifted; + float3 normal_out = normalize(total_normal); + float4 now = (viewer.view_projection * float4(displaced, 1.0)); + float4 prev = (viewer.prev_view_projection * float4(displaced, 1.0)); + float3 eye_at = float3(viewer.position.x, 0.0, viewer.position.z); + float over = 0.0; + for (int j = 0; j < 4; j += 1) { + float eye_factor = 1.0; + if (surface.randomness > 0.001) { + float eye_noise = ae3d_shaders_sea_swell_noise(eye_at.xz * 0.01 + float2(surface.time * 0.1) + float2(float(j)), 2); + eye_factor = mix(1.0, 0.5 + eye_noise, surface.randomness); + } + over = over + ae3d_shaders_sea_gerstner(eye_at, surface.directions[j].xyz, surface.amplitudes[j].x * surface.height * eye_factor, surface.frequencies[j].x, surface.speeds[j].x, surface.phases[j].x, surface.steepness[j].x, surface.time, surface.speed).y; + } + ae_out.ae_position = now; + ae_out.uv = inTexCoord; + ae_out.normal = normal_out; + ae_out.world = displaced; + ae_out.clip_now = now; + ae_out.clip_prev = prev; + ae_out.eye_below = surface.model[3][1] + over * 1.5 - viewer.position.y; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/sky_capture_quoted.frag.metal b/src/ae3d/metal/sky_capture_quoted.frag.metal new file mode 100644 index 00000000..c140d881 --- /dev/null +++ b/src/ae3d/metal/sky_capture_quoted.frag.metal @@ -0,0 +1,385 @@ +// sky_capture: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_Params { + int kind; + char ae_pad0[12]; + packed_float3 sky_sun; + char ae_pad1[4]; + packed_float3 cloud_sun; + char ae_pad2[4]; + packed_float3 cloud_sun_colour; + float turbidity; + packed_float3 flat_colour; + float overcast; + packed_float3 overcast_colour; + float cloud_cover; + float cloud_time; + char ae_pad3[12]; + packed_float3 eye; + char ae_pad4[4]; +}; +static_assert(sizeof(ae_Params) == 128, "ae_Params: std140"); +static_assert(__builtin_offsetof(ae_Params, kind) == 0, "ae_Params.kind: std140"); +static_assert(__builtin_offsetof(ae_Params, sky_sun) == 16, "ae_Params.sky_sun: std140"); +static_assert(__builtin_offsetof(ae_Params, cloud_sun) == 32, "ae_Params.cloud_sun: std140"); +static_assert(__builtin_offsetof(ae_Params, cloud_sun_colour) == 48, "ae_Params.cloud_sun_colour: std140"); +static_assert(__builtin_offsetof(ae_Params, turbidity) == 60, "ae_Params.turbidity: std140"); +static_assert(__builtin_offsetof(ae_Params, flat_colour) == 64, "ae_Params.flat_colour: std140"); +static_assert(__builtin_offsetof(ae_Params, overcast) == 76, "ae_Params.overcast: std140"); +static_assert(__builtin_offsetof(ae_Params, overcast_colour) == 80, "ae_Params.overcast_colour: std140"); +static_assert(__builtin_offsetof(ae_Params, cloud_cover) == 92, "ae_Params.cloud_cover: std140"); +static_assert(__builtin_offsetof(ae_Params, cloud_time) == 96, "ae_Params.cloud_time: std140"); +static_assert(__builtin_offsetof(ae_Params, eye) == 112, "ae_Params.eye: std140"); + +struct CloudSample { + float density; + float height; +}; + +struct ae_sky_capture_in { + float4 ae_frag_coord [[position]]; +}; + +struct ae_sky_capture_frag_out { + float4 FragColor [[color(0)]]; +}; + +static float3 ae3d_shaders_skydome_texel_decode(float2 p) { + float3 d = float3(p.x, 1.0 - abs(p.x) - abs(p.y), p.y); + if (d.y < 0.0) { + float2 folded = (float2(1.0) - abs(d.zx)) * float2(d.x >= 0.0 ? 1.0 : -1.0, d.z >= 0.0 ? 1.0 : -1.0); + d = float3(folded.x, d.y, folded.y); + } + return normalize(d); +} + +static float3 ae3d_shaders_skydome_texel_direction(float2 uv, float size) { + float2 p = (uv * size - float2(1.0)) / (size - 2.0) * 2.0 - float2(1.0); + float px = p.x; + float py = p.y; + if (px > 1.0) { + px = 2.0 - px; + py = -py; + } + if (px < -1.0) { + px = -2.0 - px; + py = -py; + } + if (py > 1.0) { + py = 2.0 - py; + px = -px; + } + if (py < -1.0) { + py = -2.0 - py; + px = -px; + } + return ae3d_shaders_skydome_texel_decode(float2(px, py)); +} + +static float3 ae3d_shaders_skydome_dusk_sky(float3 dir, float3 sun, float3 sun_colour) { + float elevation = sun.y; + float day = smoothstep(-0.14, 0.06, elevation); + float low = 1.0 - smoothstep(-0.05, 0.35, elevation); + float3 zenith_day = float3(0.20, 0.42, 0.85); + float3 zenith_dusk = float3(0.16, 0.20, 0.45); + float3 zenith_night = float3(0.02, 0.03, 0.08); + float3 horizon_day = float3(0.72, 0.82, 0.93); + float3 horizon_dusk = float3(1.0, 0.52, 0.22); + float3 horizon_night = float3(0.06, 0.07, 0.13); + float toward = 0.5 + 0.5 * dot(normalize(float2(dir.x, dir.z) + float2(0.00001)), normalize(float2(sun.x, sun.z) + float2(0.00001))); + float3 horizon = mix(horizon_day, horizon_dusk, low * (0.25 + 0.75 * toward * toward)); + horizon = mix(horizon_night, horizon, day); + float3 zenith = mix(zenith_night, mix(zenith_day, zenith_dusk, low), day); + float up = clamp(dir.y, 0.0, 1.0); + float3 sky_colour = mix(horizon, zenith, pow(up, 0.55)); + if (dir.y < 0.0) { + sky_colour = mix(horizon, horizon * 0.6, clamp(-dir.y * 4.0, 0.0, 1.0)); + } + float cos_angle = dot(dir, sun); + float glow = pow(max(cos_angle, 0.0), 4.0) * 0.16 + pow(max(cos_angle, 0.0), 40.0) * 0.5; + sky_colour = sky_colour + sun_colour * glow * (0.3 + 0.7 * day) * (1.0 + low * 1.6); + return sky_colour; +} + +static float ae3d_shaders_skydome_shown_channel(float x) { + float t = clamp(x * (2.51 * x + 0.03) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0); + return t <= 0.0031308 ? t * 12.92 : 1.055 * pow(t, 1.0 / 2.4) - 0.055; +} + +static float3 ae3d_shaders_skydome_shown_of(float3 c) { + return float3(ae3d_shaders_skydome_shown_channel(c.x), ae3d_shaders_skydome_shown_channel(c.y), ae3d_shaders_skydome_shown_channel(c.z)); +} + +static float ae3d_shaders_skydome_perez(float cos_theta, float gamma, float a, float b, float c, float d, float e) { + return (1.0 + a * exp(b / max(cos_theta, 0.01))) * (1.0 + c * exp(d * gamma) + e * cos(gamma) * cos(gamma)); +} + +static float3 ae3d_shaders_skydome_daylight(float3 dir_in, float3 sun, float turbidity) { + float3 dir = dir_in; + float tt = turbidity; + float theta_s = acos(clamp(sun.y, 0.0, 1.0)); + float below = 1.0; + if (dir.y < 0.0) { + below = mix(1.0, 0.6, clamp(-dir.y * 4.0, 0.0, 1.0)); + dir = normalize(float3(dir.x, 0.0, dir.z)); + } + float cos_theta = dir.y; + float gamma = acos(clamp(dot(dir, sun), -1.0, 1.0)); + float chi = (4.0 / 9.0 - tt / 120.0) * (3.14159265 - 2.0 * theta_s); + float yz_lum = ((4.0453 * tt - 4.9710) * tan(chi) - 0.2155 * tt + 2.4192) * 1000.0; + float t1 = theta_s; + float t2 = theta_s * theta_s; + float t3 = t2 * theta_s; + float xz_c = tt * tt * (0.00166 * t3 - 0.00375 * t2 + 0.00209 * t1) + tt * (-0.02903 * t3 + 0.06377 * t2 - 0.03202 * t1 + 0.00394) + (0.11693 * t3 - 0.21196 * t2 + 0.06052 * t1 + 0.25886); + float yz_c = tt * tt * (0.00275 * t3 - 0.00610 * t2 + 0.00317 * t1) + tt * (-0.04214 * t3 + 0.08970 * t2 - 0.04153 * t1 + 0.00516) + (0.15346 * t3 - 0.26756 * t2 + 0.06670 * t1 + 0.26688); + float lum = yz_lum * ae3d_shaders_skydome_perez(cos_theta, gamma, 0.1787 * tt - 1.4630, -0.3554 * tt + 0.4275, -0.0227 * tt + 5.3251, 0.1206 * tt - 2.5771, -0.0670 * tt + 0.3703) / ae3d_shaders_skydome_perez(1.0, theta_s, 0.1787 * tt - 1.4630, -0.3554 * tt + 0.4275, -0.0227 * tt + 5.3251, 0.1206 * tt - 2.5771, -0.0670 * tt + 0.3703); + float cx = xz_c * ae3d_shaders_skydome_perez(cos_theta, gamma, -0.0193 * tt - 0.2592, -0.0665 * tt + 0.0008, -0.0004 * tt + 0.2125, -0.0641 * tt - 0.8989, -0.0033 * tt + 0.0452) / ae3d_shaders_skydome_perez(1.0, theta_s, -0.0193 * tt - 0.2592, -0.0665 * tt + 0.0008, -0.0004 * tt + 0.2125, -0.0641 * tt - 0.8989, -0.0033 * tt + 0.0452); + float cy = yz_c * ae3d_shaders_skydome_perez(cos_theta, gamma, -0.0167 * tt - 0.2608, -0.0950 * tt + 0.0092, -0.0079 * tt + 0.2102, -0.0441 * tt - 1.6537, -0.0109 * tt + 0.0529) / ae3d_shaders_skydome_perez(1.0, theta_s, -0.0167 * tt - 0.2608, -0.0950 * tt + 0.0092, -0.0079 * tt + 0.2102, -0.0441 * tt - 1.6537, -0.0109 * tt + 0.0529); + float3 xyz_c = float3(cx / cy * lum, lum, (1.0 - cx - cy) / cy * lum); + float3 lin = float3(3.2406 * xyz_c.x - 1.5372 * xyz_c.y - 0.4986 * xyz_c.z, -0.9689 * xyz_c.x + 1.8758 * xyz_c.y + 0.0415 * xyz_c.z, 0.0557 * xyz_c.x - 0.2040 * xyz_c.y + 1.0570 * xyz_c.z); + return max(lin, float3(0.0)) * (2.0e-5 * below); +} + +static float3 ae3d_shaders_skydome_procedural_sky(float3 dir, float3 sun, float3 sun_colour, float turbidity, bool discs) { + float3 dusk = ae3d_shaders_skydome_dusk_sky(dir, sun, sun_colour); + float day_share = smoothstep(0.0, 0.25, sun.y); + float3 sky_colour = dusk; + if (day_share > 0.0) { + sky_colour = mix(dusk, ae3d_shaders_skydome_shown_of(ae3d_shaders_skydome_daylight(dir, sun, turbidity)), day_share); + } + if (discs) { + float cos_angle = dot(dir, sun); + float day = smoothstep(-0.14, 0.06, sun.y); + float disc = smoothstep(0.9993, 0.9997, cos_angle); + sky_colour = sky_colour + sun_colour * disc * (0.6 + 1.4 * day); + } + return sky_colour; +} + +static float3 ae3d_shaders_skydome_night_sky(float3 dir, float3 moon, float night, bool discs) { + if (night <= 0.0 || dir.y <= 0.0) { + return float3(0.0); + } + float3 add = float3(0.0); + float3 cell = floor(dir * 420.0); + float h = fract(sin(dot(cell, float3(12.9898, 78.233, 37.719))) * 43758.5453); + if (h > 0.9935) { + float bright = (h - 0.9935) / 0.0065; + float3 centre = (cell + float3(0.5)) / 420.0; + float near = 1.0 - smoothstep(0.0, 1.2 / 420.0, length(dir - centre)); + add = add + float3(0.85, 0.9, 1.0) * near * (0.25 + 0.75 * bright) * smoothstep(0.0, 0.15, dir.y); + } + if (moon.y > 0.0) { + float c = dot(dir, moon); + if (discs) { + add = add + float3(0.82, 0.86, 0.95) * smoothstep(0.99955, 0.99975, c) * 1.4; + } + add = add + float3(0.30, 0.34, 0.45) * pow(max(c, 0.0), 60.0) * 0.18; + } + return add * night; +} + +static float3 ae3d_shaders_skydome_sky_at(float3 dir, texture2d skybox, sampler ae_smp_skybox, int kind, float3 sky_sun, float3 cloud_sun, float3 cloud_sun_colour, float turbidity, float3 flat_colour, float overcast, float3 overcast_colour, bool discs) { + float theta = atan2(dir.z, dir.x); + float phi = asin(dir.y); + float u = (theta + 3.14159265) / 6.28318531; + float v = (phi + 1.57079633) / 3.14159265; + float3 sky_colour = skybox.sample(ae_smp_skybox, float2(u, v), level(0.0)).rgb; + if (kind == 1) { + float3 hour_sun = normalize(sky_sun); + sky_colour = ae3d_shaders_skydome_procedural_sky(dir, hour_sun, cloud_sun_colour, turbidity, discs); + float night = 1.0 - smoothstep(-0.14, -0.02, hour_sun.y); + sky_colour = sky_colour + ae3d_shaders_skydome_night_sky(dir, normalize(cloud_sun), night, discs); + } + if (kind == 2) { + sky_colour = flat_colour; + } + if (overcast > 0.0) { + float3 broad = sky_colour; + if (kind != 1) { + broad = skybox.sample(ae_smp_skybox, float2(u, v), level(4.0)).rgb; + } + float luma = dot(broad, float3(0.299, 0.587, 0.114)); + sky_colour = mix(sky_colour, overcast_colour * (0.35 + 0.65 * luma), clamp(overcast, 0.0, 1.0)); + } + return sky_colour; +} + +static float ae3d_shaders_skydome_aces_inverse(float y_in) { + float y = clamp(y_in, 0.0, 0.9999); + float a = 2.51 - 2.43 * y; + float b = 0.03 - 0.59 * y; + return (-b + sqrt(b * b + 4.0 * a * 0.14 * y)) / (2.0 * a); +} + +static float ae3d_shaders_skydome_srgb_decode(float c) { + return c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4); +} + +static float3 ae3d_shaders_skydome_display_radiance(float3 shown) { + float3 c = clamp(shown, float3(0.0), float3(1.0)); + return float3(ae3d_shaders_skydome_aces_inverse(ae3d_shaders_skydome_srgb_decode(c.x)), ae3d_shaders_skydome_aces_inverse(ae3d_shaders_skydome_srgb_decode(c.y)), ae3d_shaders_skydome_aces_inverse(ae3d_shaders_skydome_srgb_decode(c.z))); +} + +static float ae3d_shaders_skydome_cloud_phase(float cos_angle, float g) { + float gg = g * g; + return (1.0 - gg) / (4.0 * 3.14159265 * pow(1.0 + gg - 2.0 * g * cos_angle, 1.5)); +} + +static float2 ae3d_shaders_skydome_cloud_weather_uv(float2 p, float t) { + return p / 24000.0 + float2(t * 0.00012, t * 0.00004); +} + +static float ae3d_shaders_skydome_cloud_coverage_from(float2 weather, float cover) { + float threshold = 1.0 - cover * (0.45 + 1.1 * weather.y); + return smoothstep(threshold, threshold + 0.5, weather.x) * 0.9; +} + +static float ae3d_shaders_skydome_remap(float v, float lo, float hi, float new_lo, float new_hi) { + return new_lo + (clamp(v, lo, hi) - lo) / max(hi - lo, 0.00001) * (new_hi - new_lo); +} + +static CloudSample ae3d_shaders_skydome_cloud_density(float3 p, float cover, float t, bool detailed, texture2d cloud_weather, sampler ae_smp_cloud_weather, texture3d cloud_shape, sampler ae_smp_cloud_shape, texture3d cloud_detail, sampler ae_smp_cloud_detail) { + float3 weather = cloud_weather.sample(ae_smp_cloud_weather, ae3d_shaders_skydome_cloud_weather_uv(p.xz, t)).rgb; + float cov = ae3d_shaders_skydome_cloud_coverage_from(float2(weather.x, weather.z), cover); + float height = clamp((p.y - 1400.0) / (2600.0 - 1400.0), 0.0, 1.0); + if (cov <= 0.0) { + return CloudSample{0.0, height}; + } + float kind = smoothstep(0.25, 0.75, weather.y); + float top = mix(0.30, 1.0, kind) * (0.75 + 0.25 * cov); + float profile = smoothstep(0.0, 0.04, height) * (1.0 - smoothstep(top * 0.4, top, height)); + if (profile <= 0.0) { + return CloudSample{0.0, height}; + } + float3 sp = p * (1.0 / 1500.0) + float3(t * 0.004, 0.0, t * 0.0015); + float4 shape = cloud_shape.sample(ae_smp_cloud_shape, sp); + float fbm = shape.y * 0.625 + shape.z * 0.25 + shape.w * 0.125; + float base = ae3d_shaders_skydome_remap(shape.x, fbm - 1.0, 1.0, 0.0, 1.0) * profile; + float d = ae3d_shaders_skydome_remap(base, 1.0 - cov, 1.0, 0.0, 1.0) * cov; + if (d <= 0.0) { + return CloudSample{0.0, height}; + } + float4 fine = cloud_shape.sample(ae_smp_cloud_shape, (sp * 6.0 + float3(0.37, 0.21, 0.63) + float3(t * 0.01, 0.0, 0.0))); + float puffs = fine.y * 0.625 + fine.z * 0.25 + fine.w * 0.125; + float detail = puffs; + if (detailed) { + float3 dp = p * (1.0 / 240.0) + float3(t * 0.012, 0.0, t * 0.005); + float3 curl = cloud_detail.sample(ae_smp_cloud_detail, (dp * 0.25)).rgb * 2.0 - float3(1.0); + dp = dp + curl * (0.25 * (1.0 - height)); + detail = mix(puffs, cloud_detail.sample(ae_smp_cloud_detail, dp).w, 0.6); + } + float erosion = mix(detail, 1.0 - detail, clamp(height * 6.0, 0.0, 1.0)); + d = ae3d_shaders_skydome_remap(d, erosion * 0.45, 1.0, 0.0, 1.0); + return CloudSample{clamp(d, 0.0, 1.0), height}; +} + +static float ae3d_shaders_skydome_cloud_sun_shade(float3 p, float3 sun, float cover, float t, texture2d cloud_weather, sampler ae_smp_cloud_weather, texture3d cloud_shape, sampler ae_smp_cloud_shape, texture3d cloud_detail, sampler ae_smp_cloud_detail) { + float shade = 0.0; + float stride = (2600.0 - 1400.0) * 0.02; + float3 at = p; + for (int k = 0; k < 6; k += 1) { + at = at + sun * stride; + shade = shade + ae3d_shaders_skydome_cloud_density(at, cover, t, k < 2, cloud_weather, ae_smp_cloud_weather, cloud_shape, ae_smp_cloud_shape, cloud_detail, ae_smp_cloud_detail).density * stride; + stride = stride * 1.6; + } + return shade; +} + +static float4 ae3d_shaders_skydome_clouds_along(float3 dir, float3 sky_colour, float cover, float t, float dither, float near_steps, float far_steps, float3 eye, float3 cloud_sun, float3 cloud_sun_colour, texture2d cloud_weather, sampler ae_smp_cloud_weather, texture3d cloud_shape, sampler ae_smp_cloud_shape, texture3d cloud_detail, sampler ae_smp_cloud_detail) { + if (cover <= 0.0 || dir.y <= 0.03) { + return float4(0.0); + } + float t0 = 1400.0 / dir.y; + float t1 = min(2600.0 / dir.y, t0 + 12000.0); + float slant = 1.0 - clamp(dir.y, 0.0, 1.0); + float fine = (t1 - t0) / (near_steps + far_steps * slant * slant); + float coarse = fine * 3.0; + float3 sun = normalize(cloud_sun); + float3 sun_light = cloud_sun_colour * (0.9 / 3.14159265); + float3 sky_radiance = ae3d_shaders_skydome_display_radiance(sky_colour); + float sky_shown = dot(sky_colour, float3(0.299, 0.587, 0.114)); + float sky_bright = dot(sky_radiance, float3(0.299, 0.587, 0.114)); + float3 sky_light = mix(sky_radiance, float3(sky_bright * 1.6), 0.35 * clamp(sky_shown * 2.5, 0.0, 1.0)); + float cos_angle = dot(dir, sun); + float phase0 = mix(ae3d_shaders_skydome_cloud_phase(cos_angle, 0.5), ae3d_shaders_skydome_cloud_phase(cos_angle, -0.25), 0.30) * 12.566; + float phase1 = mix(ae3d_shaders_skydome_cloud_phase(cos_angle, 0.25), ae3d_shaders_skydome_cloud_phase(cos_angle, -0.125), 0.30) * 12.566; + float phase2 = mix(ae3d_shaders_skydome_cloud_phase(cos_angle, 0.125), ae3d_shaders_skydome_cloud_phase(cos_angle, -0.0625), 0.30) * 12.566; + float3 colour = float3(0.0); + float alpha = 0.0; + float ray = t0 + fine * dither; + float dt = coarse; + int empties = 0; + for (int i = 0; i < 160; i += 1) { + if (ray > t1) { + break; + } + float3 p = float3(eye.x, 0.0, eye.z) + dir * ray; + CloudSample s = ae3d_shaders_skydome_cloud_density(p, cover, t, true, cloud_weather, ae_smp_cloud_weather, cloud_shape, ae_smp_cloud_shape, cloud_detail, ae_smp_cloud_detail); + float d = s.density; + if (d > 0.002) { + if (dt > fine) { + ray = ray - (dt - fine); + dt = fine; + empties = 0; + continue; + } + empties = 0; + float shade = ae3d_shaders_skydome_cloud_sun_shade(p, sun, cover, t, cloud_weather, ae_smp_cloud_weather, cloud_shape, ae_smp_cloud_shape, cloud_detail, ae_smp_cloud_detail); + float tau = shade * 0.12; + float scattered = exp(-tau) * phase0 + 0.5 * exp(-tau * 0.25) * phase1 + 0.25 * exp(-tau * 0.0625) * phase2; + float powder = 1.0 - 0.5 * exp(-d * 12.0); + float3 ambient = sky_light * mix(0.62, 1.0, s.height) * (0.7 + 0.3 * exp(-tau * 0.25)); + float3 c = sun_light * scattered * powder + ambient; + float haze = 1.0 - exp(-ray * 0.00005); + c = mix(c, sky_radiance, haze); + float a = (1.0 - exp(-d * dt * 0.12)) * (1.0 - 0.8 * haze); + colour = colour + c * a * (1.0 - alpha); + alpha = alpha + a * (1.0 - alpha); + if (alpha > 0.98) { + break; + } + } else { + empties = empties + 1; + if (empties > 3) { + dt = coarse; + } + } + ray = ray + dt; + } + float horizon = smoothstep(0.04, 0.32, dir.y); + return float4(colour * horizon, alpha * horizon); +} + +static float ae3d_shaders_skydome_cloud_dither(float2 pixel) { + return fract(52.9829189 * fract(dot(pixel, float2(0.06711056, 0.00583715)))); +} + +fragment ae_sky_capture_frag_out ae_sky_capture_fragment( + ae_sky_capture_in ae_in [[stage_in]], + constant ae_Params& ae_params [[buffer(0)]], + texture2d skybox [[texture(0)]], sampler ae_smp_skybox [[sampler(0)]], + texture2d cloud_weather [[texture(1)]], sampler ae_smp_cloud_weather [[sampler(1)]], + texture3d cloud_shape [[texture(2)]], sampler ae_smp_cloud_shape [[sampler(2)]], + texture3d cloud_detail [[texture(3)]], sampler ae_smp_cloud_detail [[sampler(3)]]) { + ae_sky_capture_frag_out ae_out; + float4 ae_frag_coord = ae_in.ae_frag_coord; + constant int& kind = ae_params.kind; + constant packed_float3& sky_sun = ae_params.sky_sun; + constant packed_float3& cloud_sun = ae_params.cloud_sun; + constant packed_float3& cloud_sun_colour = ae_params.cloud_sun_colour; + constant float& turbidity = ae_params.turbidity; + constant packed_float3& flat_colour = ae_params.flat_colour; + constant float& overcast = ae_params.overcast; + constant packed_float3& overcast_colour = ae_params.overcast_colour; + constant float& cloud_cover = ae_params.cloud_cover; + constant float& cloud_time = ae_params.cloud_time; + constant packed_float3& eye = ae_params.eye; + float3 dir = ae3d_shaders_skydome_texel_direction(ae_frag_coord.xy / 256.0, 256.0); + float3 sky_colour = ae3d_shaders_skydome_sky_at(dir, skybox, ae_smp_skybox, kind, sky_sun, cloud_sun, cloud_sun_colour, turbidity, flat_colour, overcast, overcast_colour, false); + float4 clouds = ae3d_shaders_skydome_clouds_along(dir, sky_colour, cloud_cover, cloud_time, ae3d_shaders_skydome_cloud_dither(ae_frag_coord.xy), 32.0, 64.0, eye, cloud_sun, cloud_sun_colour, cloud_weather, ae_smp_cloud_weather, cloud_shape, ae_smp_cloud_shape, cloud_detail, ae_smp_cloud_detail); + ae_out.FragColor = float4(ae3d_shaders_skydome_display_radiance(sky_colour) * (1.0 - clouds.w) + clouds.rgb, 1.0); + return ae_out; +} diff --git a/src/ae3d/metal/sky_capture_quoted.vert.metal b/src/ae3d/metal/sky_capture_quoted.vert.metal new file mode 100644 index 00000000..4f941c3f --- /dev/null +++ b/src/ae3d/metal/sky_capture_quoted.vert.metal @@ -0,0 +1,24 @@ +// screen: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_screen_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; +}; + +struct ae_screen_out { + float4 ae_position [[position]]; + float2 uv [[user(locn0)]]; +}; + +vertex ae_screen_out ae_screen_vertex( + ae_screen_in ae_in [[stage_in]]) { + ae_screen_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + ae_out.ae_position = float4(float3(inPosition.x, inPosition.y, 0.0), 1.0); + ae_out.uv = inTexCoord; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/sky_filter_quoted.frag.metal b/src/ae3d/metal/sky_filter_quoted.frag.metal new file mode 100644 index 00000000..f26bd706 --- /dev/null +++ b/src/ae3d/metal/sky_filter_quoted.frag.metal @@ -0,0 +1,120 @@ +// sky_filter: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_Params { + float level_size; + float roughness; + char ae_pad0[8]; +}; +static_assert(sizeof(ae_Params) == 16, "ae_Params: std140"); +static_assert(__builtin_offsetof(ae_Params, level_size) == 0, "ae_Params.level_size: std140"); +static_assert(__builtin_offsetof(ae_Params, roughness) == 4, "ae_Params.roughness: std140"); + +struct ae_sky_filter_in { + float4 ae_frag_coord [[position]]; +}; + +struct ae_sky_filter_frag_out { + float4 FragColor [[color(0)]]; +}; + +static float3 ae3d_shaders_skyprobe_oct_decode(float2 p) { + float3 d = float3(p.x, 1.0 - abs(p.x) - abs(p.y), p.y); + if (d.y < 0.0) { + d = float3((1.0 - abs(d.z)) * (d.x >= 0.0 ? 1.0 : -1.0), d.y, (1.0 - abs(d.x)) * (d.z >= 0.0 ? 1.0 : -1.0)); + } + return normalize(d); +} + +static float3 ae3d_shaders_skyprobe_oct_texel_direction(float2 uv, float size) { + float px = (uv.x * size - 1.0) / (size - 2.0) * 2.0 - 1.0; + float py = (uv.y * size - 1.0) / (size - 2.0) * 2.0 - 1.0; + if (px > 1.0) { + px = 2.0 - px; + py = -py; + } + if (px < -1.0) { + px = -2.0 - px; + py = -py; + } + if (py > 1.0) { + py = 2.0 - py; + px = -px; + } + if (py < -1.0) { + py = -2.0 - py; + px = -px; + } + return ae3d_shaders_skyprobe_oct_decode(float2(px, py)); +} + +static float2 ae3d_shaders_skyprobe_oct_encode(float3 d) { + float3 e = d / (abs(d.x) + abs(d.y) + abs(d.z)); + float2 p = float2(e.x, e.z); + if (e.y < 0.0) { + p = float2((1.0 - abs(p.y)) * (p.x >= 0.0 ? 1.0 : -1.0), (1.0 - abs(p.x)) * (p.y >= 0.0 ? 1.0 : -1.0)); + } + return p; +} + +static float2 ae3d_shaders_skyprobe_oct_uv(float3 d, float size) { + float2 e = ae3d_shaders_skyprobe_oct_encode(d); + return float2(((e.x * 0.5 + 0.5) * (size - 2.0) + 1.0) / size, ((e.y * 0.5 + 0.5) * (size - 2.0) + 1.0) / size); +} + +static float ae3d_shaders_skyprobe_radical_inverse(float i) { + uint bits = uint(i); + bits = bits << 16u | bits >> 16u; + bits = (bits & 0x55555555u) << 1u | (bits & 0xAAAAAAAAu) >> 1u; + bits = (bits & 0x33333333u) << 2u | (bits & 0xCCCCCCCCu) >> 2u; + bits = (bits & 0x0F0F0F0Fu) << 4u | (bits & 0xF0F0F0F0u) >> 4u; + bits = (bits & 0x00FF00FFu) << 8u | (bits & 0xFF00FF00u) >> 8u; + return float(bits) * 2.3283064365386963e-10; +} + +fragment ae_sky_filter_frag_out ae_sky_filter_fragment( + ae_sky_filter_in ae_in [[stage_in]], + constant ae_Params& ae_params [[buffer(0)]], + texture2d capture [[texture(0)]], sampler ae_smp_capture [[sampler(0)]]) { + ae_sky_filter_frag_out ae_out; + float4 ae_frag_coord = ae_in.ae_frag_coord; + constant float& level_size = ae_params.level_size; + constant float& roughness = ae_params.roughness; + float2 fc = ae_frag_coord.xy; + float3 n = ae3d_shaders_skyprobe_oct_texel_direction(float2(fc.x / level_size, fc.y / level_size), 256.0); + if (roughness <= 0.0) { + ae_out.FragColor = float4(capture.sample(ae_smp_capture, ae3d_shaders_skyprobe_oct_uv(n, 256.0), level(0.0)).rgb, 1.0); + return ae_out; + } + float a = roughness * roughness; + float3 up = abs(n.y) < 0.999 ? float3(0.0, 1.0, 0.0) : float3(1.0, 0.0, 0.0); + float3 t = normalize(cross(up, n)); + float3 b = cross(n, t); + float texel = 4.0 * 3.14159265 / ((256.0 - 2.0) * (256.0 - 2.0)); + float3 sum = float3(0.0, 0.0, 0.0); + float weight = 0.0; + float i = 0.0; + while (i < 48.0) { + float xi_x = (i + 0.5) / 48.0; + float xi_y = ae3d_shaders_skyprobe_radical_inverse(i); + i = i + 1.0; + float phi = 6.28318531 * xi_y; + float cos_theta = sqrt((1.0 - xi_x) / (1.0 + (a * a - 1.0) * xi_x)); + float sin_theta = sqrt(1.0 - cos_theta * cos_theta); + float3 h = t * (sin_theta * cos(phi)) + b * (sin_theta * sin(phi)) + n * cos_theta; + float3 l = h * (2.0 * dot(n, h)) - n; + float n_dot_l = dot(n, l); + if (n_dot_l <= 0.0) { + continue; + } + float d = cos_theta * cos_theta * (a * a - 1.0) + 1.0; + float pdf = a * a / (3.14159265 * d * d) / 4.0; + float sample_angle = 1.0 / (48.0 * pdf + 0.000001); + float lod = max(0.5 * log2(sample_angle / texel) + 1.0, 0.0); + sum = sum + capture.sample(ae_smp_capture, ae3d_shaders_skyprobe_oct_uv(l, 256.0), level(lod)).rgb * n_dot_l; + weight = weight + n_dot_l; + } + ae_out.FragColor = float4(sum / max(weight, 0.000001), 1.0); + return ae_out; +} diff --git a/src/ae3d/metal/sky_filter_quoted.vert.metal b/src/ae3d/metal/sky_filter_quoted.vert.metal new file mode 100644 index 00000000..736f9b4a --- /dev/null +++ b/src/ae3d/metal/sky_filter_quoted.vert.metal @@ -0,0 +1,24 @@ +// screen: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_screen_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; +}; + +struct ae_screen_out { + float4 ae_position [[position]]; + float2 uv_in [[user(locn0)]]; +}; + +vertex ae_screen_out ae_screen_vertex( + ae_screen_in ae_in [[stage_in]]) { + ae_screen_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + ae_out.ae_position = float4(float3(inPosition.x, inPosition.y, 0.0), 1.0); + ae_out.uv_in = inTexCoord; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/sky_irradiance_quoted.frag.metal b/src/ae3d/metal/sky_irradiance_quoted.frag.metal new file mode 100644 index 00000000..14382986 --- /dev/null +++ b/src/ae3d/metal/sky_irradiance_quoted.frag.metal @@ -0,0 +1,118 @@ +// sky_irradiance: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_Params { + packed_float3 ground_albedo; + char ae_pad0[4]; + packed_float3 ground_sun; + char ae_pad1[4]; +}; +static_assert(sizeof(ae_Params) == 32, "ae_Params: std140"); +static_assert(__builtin_offsetof(ae_Params, ground_albedo) == 0, "ae_Params.ground_albedo: std140"); +static_assert(__builtin_offsetof(ae_Params, ground_sun) == 16, "ae_Params.ground_sun: std140"); + +struct ae_sky_irradiance_in { + float4 ae_frag_coord [[position]]; +}; + +struct ae_sky_irradiance_frag_out { + float4 FragColor [[color(0)]]; +}; + +static float3 ae3d_shaders_skyprobe_oct_decode(float2 p) { + float3 d = float3(p.x, 1.0 - abs(p.x) - abs(p.y), p.y); + if (d.y < 0.0) { + d = float3((1.0 - abs(d.z)) * (d.x >= 0.0 ? 1.0 : -1.0), d.y, (1.0 - abs(d.x)) * (d.z >= 0.0 ? 1.0 : -1.0)); + } + return normalize(d); +} + +static float3 ae3d_shaders_skyprobe_oct_texel_direction(float2 uv, float size) { + float px = (uv.x * size - 1.0) / (size - 2.0) * 2.0 - 1.0; + float py = (uv.y * size - 1.0) / (size - 2.0) * 2.0 - 1.0; + if (px > 1.0) { + px = 2.0 - px; + py = -py; + } + if (px < -1.0) { + px = -2.0 - px; + py = -py; + } + if (py > 1.0) { + py = 2.0 - py; + px = -px; + } + if (py < -1.0) { + py = -2.0 - py; + px = -px; + } + return ae3d_shaders_skyprobe_oct_decode(float2(px, py)); +} + +static float3 ae3d_shaders_skyprobe_capture_q(float x, float y) { + float u = (x + 0.5) / 16.0; + float v = (y + 0.5) / 16.0; + float px = clamp((u * 256.0 - 1.0) / (256.0 - 2.0) * 2.0 - 1.0, -1.0, 1.0); + float py = clamp((v * 256.0 - 1.0) / (256.0 - 2.0) * 2.0 - 1.0, -1.0, 1.0); + float3 q = float3(px, 1.0 - abs(px) - abs(py), py); + if (q.y < 0.0) { + q = float3((1.0 - abs(q.z)) * (q.x >= 0.0 ? 1.0 : -1.0), q.y, (1.0 - abs(q.x)) * (q.z >= 0.0 ? 1.0 : -1.0)); + } + return q; +} + +fragment ae_sky_irradiance_frag_out ae_sky_irradiance_fragment( + ae_sky_irradiance_in ae_in [[stage_in]], + constant ae_Params& ae_params [[buffer(0)]], + texture2d capture [[texture(0)]], sampler ae_smp_capture [[sampler(0)]]) { + ae_sky_irradiance_frag_out ae_out; + float4 ae_frag_coord = ae_in.ae_frag_coord; + constant packed_float3& ground_albedo = ae_params.ground_albedo; + constant packed_float3& ground_sun = ae_params.ground_sun; + float2 fc = ae_frag_coord.xy; + float3 n = ae3d_shaders_skyprobe_oct_texel_direction(float2(fc.x / 16.0, fc.y / 16.0), 16.0); + bool grounded = ground_albedo.x + ground_albedo.y + ground_albedo.z > 0.0; + float3 ground = float3(0.0, 0.0, 0.0); + if (grounded) { + float3 up = float3(0.0, 0.0, 0.0); + float whole = 0.0; + float y = 0.0; + while (y < 16.0) { + float x = 0.0; + while (x < 16.0) { + float3 q = ae3d_shaders_skyprobe_capture_q(x, y); + float len = length(q); + float angle = 1.0 / (len * len * len); + float3 l = q / len; + if (l.y > 0.0) { + up = up + capture.read(uint2(int2(float2(x, y))), uint(int(4.0))).rgb * l.y * angle; + } + whole = whole + angle; + x = x + 1.0; + } + y = y + 1.0; + } + up = up * (4.0 * 3.14159265 / whole); + ground = ground_albedo * (up + ground_sun) / 3.14159265; + } + float3 sum = float3(0.0, 0.0, 0.0); + float total = 0.0; + float y = 0.0; + while (y < 16.0) { + float x = 0.0; + while (x < 16.0) { + float3 q = ae3d_shaders_skyprobe_capture_q(x, y); + float len = length(q); + float angle = 1.0 / (len * len * len); + float3 l = q / len; + float3 seen = grounded && l.y < 0.0 ? ground : capture.read(uint2(int2(float2(x, y))), uint(int(4.0))).rgb; + sum = sum + seen * max(dot(n, l), 0.0) * angle; + total = total + angle; + x = x + 1.0; + } + y = y + 1.0; + } + ae_out.FragColor = float4(sum * (4.0 * 3.14159265 / total), 1.0); + return ae_out; +} diff --git a/src/ae3d/metal/sky_irradiance_quoted.vert.metal b/src/ae3d/metal/sky_irradiance_quoted.vert.metal new file mode 100644 index 00000000..736f9b4a --- /dev/null +++ b/src/ae3d/metal/sky_irradiance_quoted.vert.metal @@ -0,0 +1,24 @@ +// screen: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_screen_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; +}; + +struct ae_screen_out { + float4 ae_position [[position]]; + float2 uv_in [[user(locn0)]]; +}; + +vertex ae_screen_out ae_screen_vertex( + ae_screen_in ae_in [[stage_in]]) { + ae_screen_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + ae_out.ae_position = float4(float3(inPosition.x, inPosition.y, 0.0), 1.0); + ae_out.uv_in = inTexCoord; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/sky_quoted.frag.metal b/src/ae3d/metal/sky_quoted.frag.metal new file mode 100644 index 00000000..aa55c32c --- /dev/null +++ b/src/ae3d/metal/sky_quoted.frag.metal @@ -0,0 +1,228 @@ +// sky: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_Params { + int kind; + char ae_pad0[12]; + packed_float3 sky_sun; + char ae_pad1[4]; + packed_float3 cloud_sun; + char ae_pad2[4]; + packed_float3 cloud_sun_colour; + float turbidity; + packed_float3 flat_colour; + float overcast; + packed_float3 overcast_colour; + float cloud_cover; + float2 screen_size; + char ae_pad3[8]; +}; +static_assert(sizeof(ae_Params) == 112, "ae_Params: std140"); +static_assert(__builtin_offsetof(ae_Params, kind) == 0, "ae_Params.kind: std140"); +static_assert(__builtin_offsetof(ae_Params, sky_sun) == 16, "ae_Params.sky_sun: std140"); +static_assert(__builtin_offsetof(ae_Params, cloud_sun) == 32, "ae_Params.cloud_sun: std140"); +static_assert(__builtin_offsetof(ae_Params, cloud_sun_colour) == 48, "ae_Params.cloud_sun_colour: std140"); +static_assert(__builtin_offsetof(ae_Params, turbidity) == 60, "ae_Params.turbidity: std140"); +static_assert(__builtin_offsetof(ae_Params, flat_colour) == 64, "ae_Params.flat_colour: std140"); +static_assert(__builtin_offsetof(ae_Params, overcast) == 76, "ae_Params.overcast: std140"); +static_assert(__builtin_offsetof(ae_Params, overcast_colour) == 80, "ae_Params.overcast_colour: std140"); +static_assert(__builtin_offsetof(ae_Params, cloud_cover) == 92, "ae_Params.cloud_cover: std140"); +static_assert(__builtin_offsetof(ae_Params, screen_size) == 96, "ae_Params.screen_size: std140"); + +struct ae_sky_in { + float4 ae_frag_coord [[position]]; + float3 tex_coords [[user(locn0)]]; + float4 clip_now [[user(locn1)]]; + float4 clip_prev [[user(locn2)]]; +}; + +struct ae_sky_frag_out { + float4 out_colour [[color(0)]]; + float4 out_velocity [[color(1)]]; +}; + +static float3 ae3d_shaders_skydome_dusk_sky(float3 dir, float3 sun, float3 sun_colour) { + float elevation = sun.y; + float day = smoothstep(-0.14, 0.06, elevation); + float low = 1.0 - smoothstep(-0.05, 0.35, elevation); + float3 zenith_day = float3(0.20, 0.42, 0.85); + float3 zenith_dusk = float3(0.16, 0.20, 0.45); + float3 zenith_night = float3(0.02, 0.03, 0.08); + float3 horizon_day = float3(0.72, 0.82, 0.93); + float3 horizon_dusk = float3(1.0, 0.52, 0.22); + float3 horizon_night = float3(0.06, 0.07, 0.13); + float toward = 0.5 + 0.5 * dot(normalize(float2(dir.x, dir.z) + float2(0.00001)), normalize(float2(sun.x, sun.z) + float2(0.00001))); + float3 horizon = mix(horizon_day, horizon_dusk, low * (0.25 + 0.75 * toward * toward)); + horizon = mix(horizon_night, horizon, day); + float3 zenith = mix(zenith_night, mix(zenith_day, zenith_dusk, low), day); + float up = clamp(dir.y, 0.0, 1.0); + float3 sky_colour = mix(horizon, zenith, pow(up, 0.55)); + if (dir.y < 0.0) { + sky_colour = mix(horizon, horizon * 0.6, clamp(-dir.y * 4.0, 0.0, 1.0)); + } + float cos_angle = dot(dir, sun); + float glow = pow(max(cos_angle, 0.0), 4.0) * 0.16 + pow(max(cos_angle, 0.0), 40.0) * 0.5; + sky_colour = sky_colour + sun_colour * glow * (0.3 + 0.7 * day) * (1.0 + low * 1.6); + return sky_colour; +} + +static float ae3d_shaders_skydome_shown_channel(float x) { + float t = clamp(x * (2.51 * x + 0.03) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0); + return t <= 0.0031308 ? t * 12.92 : 1.055 * pow(t, 1.0 / 2.4) - 0.055; +} + +static float3 ae3d_shaders_skydome_shown_of(float3 c) { + return float3(ae3d_shaders_skydome_shown_channel(c.x), ae3d_shaders_skydome_shown_channel(c.y), ae3d_shaders_skydome_shown_channel(c.z)); +} + +static float ae3d_shaders_skydome_perez(float cos_theta, float gamma, float a, float b, float c, float d, float e) { + return (1.0 + a * exp(b / max(cos_theta, 0.01))) * (1.0 + c * exp(d * gamma) + e * cos(gamma) * cos(gamma)); +} + +static float3 ae3d_shaders_skydome_daylight(float3 dir_in, float3 sun, float turbidity) { + float3 dir = dir_in; + float tt = turbidity; + float theta_s = acos(clamp(sun.y, 0.0, 1.0)); + float below = 1.0; + if (dir.y < 0.0) { + below = mix(1.0, 0.6, clamp(-dir.y * 4.0, 0.0, 1.0)); + dir = normalize(float3(dir.x, 0.0, dir.z)); + } + float cos_theta = dir.y; + float gamma = acos(clamp(dot(dir, sun), -1.0, 1.0)); + float chi = (4.0 / 9.0 - tt / 120.0) * (3.14159265 - 2.0 * theta_s); + float yz_lum = ((4.0453 * tt - 4.9710) * tan(chi) - 0.2155 * tt + 2.4192) * 1000.0; + float t1 = theta_s; + float t2 = theta_s * theta_s; + float t3 = t2 * theta_s; + float xz_c = tt * tt * (0.00166 * t3 - 0.00375 * t2 + 0.00209 * t1) + tt * (-0.02903 * t3 + 0.06377 * t2 - 0.03202 * t1 + 0.00394) + (0.11693 * t3 - 0.21196 * t2 + 0.06052 * t1 + 0.25886); + float yz_c = tt * tt * (0.00275 * t3 - 0.00610 * t2 + 0.00317 * t1) + tt * (-0.04214 * t3 + 0.08970 * t2 - 0.04153 * t1 + 0.00516) + (0.15346 * t3 - 0.26756 * t2 + 0.06670 * t1 + 0.26688); + float lum = yz_lum * ae3d_shaders_skydome_perez(cos_theta, gamma, 0.1787 * tt - 1.4630, -0.3554 * tt + 0.4275, -0.0227 * tt + 5.3251, 0.1206 * tt - 2.5771, -0.0670 * tt + 0.3703) / ae3d_shaders_skydome_perez(1.0, theta_s, 0.1787 * tt - 1.4630, -0.3554 * tt + 0.4275, -0.0227 * tt + 5.3251, 0.1206 * tt - 2.5771, -0.0670 * tt + 0.3703); + float cx = xz_c * ae3d_shaders_skydome_perez(cos_theta, gamma, -0.0193 * tt - 0.2592, -0.0665 * tt + 0.0008, -0.0004 * tt + 0.2125, -0.0641 * tt - 0.8989, -0.0033 * tt + 0.0452) / ae3d_shaders_skydome_perez(1.0, theta_s, -0.0193 * tt - 0.2592, -0.0665 * tt + 0.0008, -0.0004 * tt + 0.2125, -0.0641 * tt - 0.8989, -0.0033 * tt + 0.0452); + float cy = yz_c * ae3d_shaders_skydome_perez(cos_theta, gamma, -0.0167 * tt - 0.2608, -0.0950 * tt + 0.0092, -0.0079 * tt + 0.2102, -0.0441 * tt - 1.6537, -0.0109 * tt + 0.0529) / ae3d_shaders_skydome_perez(1.0, theta_s, -0.0167 * tt - 0.2608, -0.0950 * tt + 0.0092, -0.0079 * tt + 0.2102, -0.0441 * tt - 1.6537, -0.0109 * tt + 0.0529); + float3 xyz_c = float3(cx / cy * lum, lum, (1.0 - cx - cy) / cy * lum); + float3 lin = float3(3.2406 * xyz_c.x - 1.5372 * xyz_c.y - 0.4986 * xyz_c.z, -0.9689 * xyz_c.x + 1.8758 * xyz_c.y + 0.0415 * xyz_c.z, 0.0557 * xyz_c.x - 0.2040 * xyz_c.y + 1.0570 * xyz_c.z); + return max(lin, float3(0.0)) * (2.0e-5 * below); +} + +static float3 ae3d_shaders_skydome_procedural_sky(float3 dir, float3 sun, float3 sun_colour, float turbidity, bool discs) { + float3 dusk = ae3d_shaders_skydome_dusk_sky(dir, sun, sun_colour); + float day_share = smoothstep(0.0, 0.25, sun.y); + float3 sky_colour = dusk; + if (day_share > 0.0) { + sky_colour = mix(dusk, ae3d_shaders_skydome_shown_of(ae3d_shaders_skydome_daylight(dir, sun, turbidity)), day_share); + } + if (discs) { + float cos_angle = dot(dir, sun); + float day = smoothstep(-0.14, 0.06, sun.y); + float disc = smoothstep(0.9993, 0.9997, cos_angle); + sky_colour = sky_colour + sun_colour * disc * (0.6 + 1.4 * day); + } + return sky_colour; +} + +static float3 ae3d_shaders_skydome_night_sky(float3 dir, float3 moon, float night, bool discs) { + if (night <= 0.0 || dir.y <= 0.0) { + return float3(0.0); + } + float3 add = float3(0.0); + float3 cell = floor(dir * 420.0); + float h = fract(sin(dot(cell, float3(12.9898, 78.233, 37.719))) * 43758.5453); + if (h > 0.9935) { + float bright = (h - 0.9935) / 0.0065; + float3 centre = (cell + float3(0.5)) / 420.0; + float near = 1.0 - smoothstep(0.0, 1.2 / 420.0, length(dir - centre)); + add = add + float3(0.85, 0.9, 1.0) * near * (0.25 + 0.75 * bright) * smoothstep(0.0, 0.15, dir.y); + } + if (moon.y > 0.0) { + float c = dot(dir, moon); + if (discs) { + add = add + float3(0.82, 0.86, 0.95) * smoothstep(0.99955, 0.99975, c) * 1.4; + } + add = add + float3(0.30, 0.34, 0.45) * pow(max(c, 0.0), 60.0) * 0.18; + } + return add * night; +} + +static float3 ae3d_shaders_skydome_sky_at(float3 dir, texture2d skybox, sampler ae_smp_skybox, int kind, float3 sky_sun, float3 cloud_sun, float3 cloud_sun_colour, float turbidity, float3 flat_colour, float overcast, float3 overcast_colour, bool discs) { + float theta = atan2(dir.z, dir.x); + float phi = asin(dir.y); + float u = (theta + 3.14159265) / 6.28318531; + float v = (phi + 1.57079633) / 3.14159265; + float3 sky_colour = skybox.sample(ae_smp_skybox, float2(u, v), level(0.0)).rgb; + if (kind == 1) { + float3 hour_sun = normalize(sky_sun); + sky_colour = ae3d_shaders_skydome_procedural_sky(dir, hour_sun, cloud_sun_colour, turbidity, discs); + float night = 1.0 - smoothstep(-0.14, -0.02, hour_sun.y); + sky_colour = sky_colour + ae3d_shaders_skydome_night_sky(dir, normalize(cloud_sun), night, discs); + } + if (kind == 2) { + sky_colour = flat_colour; + } + if (overcast > 0.0) { + float3 broad = sky_colour; + if (kind != 1) { + broad = skybox.sample(ae_smp_skybox, float2(u, v), level(4.0)).rgb; + } + float luma = dot(broad, float3(0.299, 0.587, 0.114)); + sky_colour = mix(sky_colour, overcast_colour * (0.35 + 0.65 * luma), clamp(overcast, 0.0, 1.0)); + } + return sky_colour; +} + +static float ae3d_shaders_skydome_aces_inverse(float y_in) { + float y = clamp(y_in, 0.0, 0.9999); + float a = 2.51 - 2.43 * y; + float b = 0.03 - 0.59 * y; + return (-b + sqrt(b * b + 4.0 * a * 0.14 * y)) / (2.0 * a); +} + +static float ae3d_shaders_skydome_srgb_decode(float c) { + return c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4); +} + +static float3 ae3d_shaders_skydome_display_radiance(float3 shown) { + float3 c = clamp(shown, float3(0.0), float3(1.0)); + return float3(ae3d_shaders_skydome_aces_inverse(ae3d_shaders_skydome_srgb_decode(c.x)), ae3d_shaders_skydome_aces_inverse(ae3d_shaders_skydome_srgb_decode(c.y)), ae3d_shaders_skydome_aces_inverse(ae3d_shaders_skydome_srgb_decode(c.z))); +} + +static float2 ae3d_shaders_skydome_velocity(float4 now, float4 prev, float2 nudge) { + if (now.w <= 0.0 || prev.w <= 0.0) { + return float2(0.0); + } + float2 uv_now = now.xy / now.w * 0.5 + float2(0.5) - nudge; + float2 uv_prev = prev.xy / prev.w * 0.5 + float2(0.5); + return uv_now - uv_prev; +} + +fragment ae_sky_frag_out ae_sky_fragment( + ae_sky_in ae_in [[stage_in]], + constant ae_Params& ae_params [[buffer(0)]], + texture2d skybox [[texture(0)]], sampler ae_smp_skybox [[sampler(0)]], + texture2d cloud_buffer [[texture(1)]], sampler ae_smp_cloud_buffer [[sampler(1)]]) { + ae_sky_frag_out ae_out; + float4 ae_frag_coord = ae_in.ae_frag_coord; + float3 tex_coords = ae_in.tex_coords; + float4 clip_now = ae_in.clip_now; + float4 clip_prev = ae_in.clip_prev; + constant int& kind = ae_params.kind; + constant packed_float3& sky_sun = ae_params.sky_sun; + constant packed_float3& cloud_sun = ae_params.cloud_sun; + constant packed_float3& cloud_sun_colour = ae_params.cloud_sun_colour; + constant float& turbidity = ae_params.turbidity; + constant packed_float3& flat_colour = ae_params.flat_colour; + constant float& overcast = ae_params.overcast; + constant packed_float3& overcast_colour = ae_params.overcast_colour; + constant float& cloud_cover = ae_params.cloud_cover; + constant float2& screen_size = ae_params.screen_size; + float3 dir = normalize(tex_coords); + float3 sky_colour = ae3d_shaders_skydome_sky_at(dir, skybox, ae_smp_skybox, kind, sky_sun, cloud_sun, cloud_sun_colour, turbidity, flat_colour, overcast, overcast_colour, true); + float4 clouds = float4(0.0); + if (cloud_cover > 0.0) { + clouds = cloud_buffer.sample(ae_smp_cloud_buffer, (ae_frag_coord.xy / screen_size)); + } + ae_out.out_colour = float4(ae3d_shaders_skydome_display_radiance(sky_colour) * (1.0 - clouds.w) + clouds.rgb, 1.0); + ae_out.out_velocity = float4(ae3d_shaders_skydome_velocity(clip_now, clip_prev, float2(0.0)), 0.0, 0.0); + return ae_out; +} diff --git a/src/ae3d/metal/sky_quoted.vert.metal b/src/ae3d/metal/sky_quoted.vert.metal new file mode 100644 index 00000000..866a9bac --- /dev/null +++ b/src/ae3d/metal/sky_quoted.vert.metal @@ -0,0 +1,41 @@ +// sky_vertex: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_VertexParams { + float4x4 projection; + float4x4 view; + float4x4 prev_view_projection; +}; +static_assert(sizeof(ae_VertexParams) == 192, "ae_VertexParams: std140"); +static_assert(__builtin_offsetof(ae_VertexParams, projection) == 0, "ae_VertexParams.projection: std140"); +static_assert(__builtin_offsetof(ae_VertexParams, view) == 64, "ae_VertexParams.view: std140"); +static_assert(__builtin_offsetof(ae_VertexParams, prev_view_projection) == 128, "ae_VertexParams.prev_view_projection: std140"); + +struct ae_sky_vertex_in { + float3 inPosition [[attribute(0)]]; +}; + +struct ae_sky_vertex_out { + float4 ae_position [[position]]; + float3 tex_coords [[user(locn0)]]; + float4 clip_now [[user(locn1)]]; + float4 clip_prev [[user(locn2)]]; +}; + +vertex ae_sky_vertex_out ae_sky_vertex_vertex( + ae_sky_vertex_in ae_in [[stage_in]], + constant ae_VertexParams& ae_params [[buffer(1)]]) { + ae_sky_vertex_out ae_out; + float3 inPosition = ae_in.inPosition; + constant float4x4& projection = ae_params.projection; + constant float4x4& view = ae_params.view; + constant float4x4& prev_view_projection = ae_params.prev_view_projection; + float4 pos = ((projection * view) * float4(inPosition, 1.0)); + ae_out.ae_position = float4(float3(pos.x, pos.y, pos.w), pos.w); + ae_out.tex_coords = inPosition; + ae_out.clip_now = pos; + ae_out.clip_prev = (prev_view_projection * float4(inPosition, 0.0)); + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/ssao_quoted.frag.metal b/src/ae3d/metal/ssao_quoted.frag.metal new file mode 100644 index 00000000..51f190a2 --- /dev/null +++ b/src/ae3d/metal/ssao_quoted.frag.metal @@ -0,0 +1,114 @@ +// ssao: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_Params { + float2 screen_size; + char ae_pad0[8]; + float4x4 inv_view_projection; + float intensity; + char ae_pad1[12]; + packed_float3 view_pos; + float radius; + float4x4 view_projection; +}; +static_assert(sizeof(ae_Params) == 176, "ae_Params: std140"); +static_assert(__builtin_offsetof(ae_Params, screen_size) == 0, "ae_Params.screen_size: std140"); +static_assert(__builtin_offsetof(ae_Params, inv_view_projection) == 16, "ae_Params.inv_view_projection: std140"); +static_assert(__builtin_offsetof(ae_Params, intensity) == 80, "ae_Params.intensity: std140"); +static_assert(__builtin_offsetof(ae_Params, view_pos) == 96, "ae_Params.view_pos: std140"); +static_assert(__builtin_offsetof(ae_Params, radius) == 108, "ae_Params.radius: std140"); +static_assert(__builtin_offsetof(ae_Params, view_projection) == 112, "ae_Params.view_projection: std140"); + +struct ae_ssao_in { + float4 ae_frag_coord [[position]]; +}; + +struct ae_ssao_frag_out { + float4 FragColor [[color(0)]]; +}; + +static float3 ae3d_shaders_ssao_world_at(float4x4 inv_view_projection, float2 uv, float depth) { + float4 world = (inv_view_projection * float4(float3(uv.x * 2.0 - 1.0, uv.y * 2.0 - 1.0, depth), 1.0)); + return world.xyz / world.w; +} + +static float ae3d_shaders_ssao_dither(float2 pixel) { + return fract(52.9829189 * fract(dot(pixel, float2(0.06711056, 0.00583715)))); +} + +fragment ae_ssao_frag_out ae_ssao_fragment( + ae_ssao_in ae_in [[stage_in]], + constant ae_Params& ae_params [[buffer(0)]], + texture2d depth [[texture(0)]], sampler ae_smp_depth [[sampler(0)]]) { + ae_ssao_frag_out ae_out; + float4 ae_frag_coord = ae_in.ae_frag_coord; + constant float2& screen_size = ae_params.screen_size; + constant float4x4& inv_view_projection = ae_params.inv_view_projection; + constant float& intensity = ae_params.intensity; + constant packed_float3& view_pos = ae_params.view_pos; + constant float& radius = ae_params.radius; + constant float4x4& view_projection = ae_params.view_projection; + float2 fc = ae_frag_coord.xy; + float2 uv = float2(fc.x / screen_size.x, fc.y / screen_size.y); + float d = depth.sample(ae_smp_depth, uv).x; + float3 p = ae3d_shaders_ssao_world_at(inv_view_projection, uv, d); + if (d >= 0.99999 || intensity <= 0.0) { + ae_out.FragColor = float4(float3(1.0, 0.0, 0.0), 1.0); + return ae_out; + } + float2 px = float2(1.0 / screen_size.x, 1.0 / screen_size.y); + float2 ul = uv - float2(px.x, 0.0); + float dl = depth.sample(ae_smp_depth, ul).x; + float3 pl = ae3d_shaders_ssao_world_at(inv_view_projection, ul, dl); + float2 ur = uv + float2(px.x, 0.0); + float dr = depth.sample(ae_smp_depth, ur).x; + float3 pr = ae3d_shaders_ssao_world_at(inv_view_projection, ur, dr); + float2 above = float2(0.0, px.y * (-1.0)); + float2 ud = uv - above; + float dd = depth.sample(ae_smp_depth, ud).x; + float3 pd = ae3d_shaders_ssao_world_at(inv_view_projection, ud, dd); + float2 uu = uv + above; + float du = depth.sample(ae_smp_depth, uu).x; + float3 pu = ae3d_shaders_ssao_world_at(inv_view_projection, uu, du); + float3 dx = abs(dl - d) < abs(dr - d) ? p - pl : pr - p; + float3 dy = abs(dd - d) < abs(du - d) ? p - pd : pu - p; + float3 n = normalize(cross(dx, dy)); + float eye = distance(view_pos, p); + float3 up = abs(n.y) < 0.99 ? float3(0.0, 1.0, 0.0) : float3(1.0, 0.0, 0.0); + float3 t = normalize(cross(up, n)); + float3 b = cross(n, t); + float2 pixel = (-1.0) < 0.0 ? float2(fc.x, screen_size.y - fc.y) : fc; + float turn = ae3d_shaders_ssao_dither(pixel) * 6.2831853; + float occlusion = 0.0; + float i = 0.0; + while (i < 12.0) { + float f = (i + 0.5) / 12.0; + i = i + 1.0; + float a = turn + f * 15.0796; + float r = sqrt(f); + float h = sqrt(1.0 - r * r); + float3 dir = t * (cos(a) * r) + b * (sin(a) * r) + n * h; + float reach = radius * mix(0.1, 1.0, f * f); + float3 s = p + dir * reach; + float4 clip = (view_projection * float4(s, 1.0)); + if (clip.w <= 0.0) { + continue; + } + float2 suv = float2(clip.x / clip.w * 0.5 + 0.5, clip.y / clip.w * 0.5 + 0.5); + if (suv.x < 0.0 || suv.x > 1.0 || suv.y < 0.0 || suv.y > 1.0) { + continue; + } + float sd = depth.sample(ae_smp_depth, suv).x; + float scene_dist = distance(view_pos, ae3d_shaders_ssao_world_at(inv_view_projection, suv, sd)); + float sample_dist = distance(view_pos, s); + if (sample_dist - scene_dist > 0.03 * reach + eye * 0.01) { + occlusion = occlusion + smoothstep(0.0, 1.0, radius / max(abs(eye - scene_dist), 0.0001)); + } + } + occlusion = occlusion / 12.0; + float ao = clamp(1.0 - occlusion * intensity, 0.0, 1.0); + ao = mix(ao, 1.0, smoothstep(radius * 40.0, radius * 120.0, eye)); + ae_out.FragColor = float4(float3(ao, (view_projection * float4(p, 1.0)).w, 0.0), 1.0); + return ae_out; +} diff --git a/src/ae3d/metal/ssao_quoted.vert.metal b/src/ae3d/metal/ssao_quoted.vert.metal new file mode 100644 index 00000000..736f9b4a --- /dev/null +++ b/src/ae3d/metal/ssao_quoted.vert.metal @@ -0,0 +1,24 @@ +// screen: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_screen_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; +}; + +struct ae_screen_out { + float4 ae_position [[position]]; + float2 uv_in [[user(locn0)]]; +}; + +vertex ae_screen_out ae_screen_vertex( + ae_screen_in ae_in [[stage_in]]) { + ae_screen_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + ae_out.ae_position = float4(float3(inPosition.x, inPosition.y, 0.0), 1.0); + ae_out.uv_in = inTexCoord; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/ssr_quoted.frag.metal b/src/ae3d/metal/ssr_quoted.frag.metal new file mode 100644 index 00000000..ca1cda3d --- /dev/null +++ b/src/ae3d/metal/ssr_quoted.frag.metal @@ -0,0 +1,156 @@ +// ssr: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_Params { + float strength; + char ae_pad0[12]; + float4x4 inv_view_projection; + packed_float3 view_pos; + float road_height; + float4x4 view_projection; +}; +static_assert(sizeof(ae_Params) == 160, "ae_Params: std140"); +static_assert(__builtin_offsetof(ae_Params, strength) == 0, "ae_Params.strength: std140"); +static_assert(__builtin_offsetof(ae_Params, inv_view_projection) == 16, "ae_Params.inv_view_projection: std140"); +static_assert(__builtin_offsetof(ae_Params, view_pos) == 80, "ae_Params.view_pos: std140"); +static_assert(__builtin_offsetof(ae_Params, road_height) == 92, "ae_Params.road_height: std140"); +static_assert(__builtin_offsetof(ae_Params, view_projection) == 96, "ae_Params.view_projection: std140"); + +struct ae_ssr_in { + float2 uv [[user(locn0)]]; +}; + +struct ae_ssr_frag_out { + float4 FragColor [[color(0)]]; +}; + +static float3 ae3d_shaders_ssao_world_at(float4x4 inv_view_projection, float2 uv, float depth) { + float4 world = (inv_view_projection * float4(float3(uv.x * 2.0 - 1.0, uv.y * 2.0 - 1.0, depth), 1.0)); + return world.xyz / world.w; +} + +static float ae3d_shaders_ssr_hash2(float2 p) { + return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); +} + +static float ae3d_shaders_ssr_value_noise(float2 p) { + float2 i = floor(p); + float2 f = fract(p); + f = f * f * (float2(3.0, 3.0) - f * 2.0); + return mix(mix(ae3d_shaders_ssr_hash2(i), ae3d_shaders_ssr_hash2(i + float2(1.0, 0.0)), f.x), mix(ae3d_shaders_ssr_hash2(i + float2(0.0, 1.0)), ae3d_shaders_ssr_hash2(i + float2(1.0, 1.0)), f.x), f.y); +} + +static float ae3d_shaders_ssr_puddle(float2 xz) { + float n = ae3d_shaders_ssr_value_noise(xz * 0.35) * 0.6 + ae3d_shaders_ssr_value_noise(xz * 0.9) * 0.3 + ae3d_shaders_ssr_value_noise(xz * 2.7) * 0.1; + return smoothstep(0.55, 0.62, n); +} + +static float3 ae3d_shaders_ssr_reflection_at(texture2d screen, sampler ae_smp_screen, texture2d blur, sampler ae_smp_blur, float2 uv, float radius, float2 seed) { + if (radius < 0.0005) { + return screen.sample(ae_smp_screen, uv).rgb; + } + float turn = ae3d_shaders_ssr_hash2(seed) * 6.2831853; + float c = cos(turn); + float s = sin(turn); + float2 size = float2(float(screen.get_width()), float(screen.get_height())); + float2 aspect = float2(1.0, size.x / size.y); + bool wide = radius * size.x > 8.0; + float3 sum = float3(0.0, 0.0, 0.0); + float i = 0.0; + while (i < 8.0) { + float a = i * 0.7853982; + float r = radius * (0.35 + 0.65 * (i + 1.0) / 8.0); + float2 d = float2(cos(a), sin(a)) * r; + d = float2(d.x * c - d.y * s, d.x * s + d.y * c) * aspect; + float2 at = clamp(uv + d, float2(0.001, 0.001), float2(0.999, 0.999)); + sum = sum + (wide ? blur.sample(ae_smp_blur, at).rgb : screen.sample(ae_smp_screen, at).rgb); + i = i + 1.0; + } + return sum * 0.125; +} + +fragment ae_ssr_frag_out ae_ssr_fragment( + ae_ssr_in ae_in [[stage_in]], + constant ae_Params& ae_params [[buffer(0)]], + texture2d screen [[texture(0)]], sampler ae_smp_screen [[sampler(0)]], + texture2d depth [[texture(1)]], sampler ae_smp_depth [[sampler(1)]], + texture2d blur [[texture(2)]], sampler ae_smp_blur [[sampler(2)]]) { + ae_ssr_frag_out ae_out; + float2 uv = ae_in.uv; + constant float& strength = ae_params.strength; + constant float4x4& inv_view_projection = ae_params.inv_view_projection; + constant packed_float3& view_pos = ae_params.view_pos; + constant float& road_height = ae_params.road_height; + constant float4x4& view_projection = ae_params.view_projection; + float3 scene = screen.sample(ae_smp_screen, uv).rgb; + if (strength <= 0.0) { + ae_out.FragColor = float4(scene, 1.0); + return ae_out; + } + float3 p = ae3d_shaders_ssao_world_at(inv_view_projection, uv, depth.sample(ae_smp_depth, uv).x); + float2 size = float2(float(depth.get_width()), float(depth.get_height())); + float2 texel = float2(1.0 / size.x, 1.0 / size.y); + float4 here = (inv_view_projection * float4(float3(uv.x * 2.0 - 1.0, uv.y * 2.0 - 1.0, 0.0), 1.0)); + float4 next = (inv_view_projection * float4(float3((uv.x + texel.x) * 2.0 - 1.0, (uv.y + texel.y) * 2.0 - 1.0, 0.0), 1.0)); + float pixel_angle = length(normalize(next.xyz / next.w - view_pos) - normalize(here.xyz / here.w - view_pos)); + float tolerance = 0.06 + distance(view_pos, p) * pixel_angle; + if (abs(p.y - road_height) > tolerance) { + ae_out.FragColor = float4(scene, 1.0); + return ae_out; + } + float3 v = normalize(p - view_pos); + float3 r = reflect(v, float3(0.0, 1.0, 0.0)); + float cos_look = clamp(-v.y, 0.0, 1.0); + float fresnel = 0.02 + 0.98 * pow(1.0 - cos_look, 5.0); + float pool = ae3d_shaders_ssr_puddle(float2(p.x, p.z)); + float wet = mix(0.6, 1.0, pool); + float rough = mix(0.35, 0.02, pool); + float step_len = 0.25; + float3 pos = p + r * step_len; + float3 prev = p; + float3 hit = scene; + float edge_fade = 0.0; + int i = 0; + while (i < 64) { + float4 clip = (view_projection * float4(pos, 1.0)); + if (clip.w <= 0.0) { + break; + } + float2 suv = float2(clip.x / clip.w * 0.5 + 0.5, clip.y / clip.w * 0.5 + 0.5); + if (suv.x < 0.0 || suv.x > 1.0 || suv.y < 0.0 || suv.y > 1.0) { + break; + } + float behind = distance(view_pos, pos) - distance(view_pos, ae3d_shaders_ssao_world_at(inv_view_projection, suv, depth.sample(ae_smp_depth, suv).x)); + if (behind > 0.03 && behind < step_len * 2.0 + 0.2) { + float3 lo = prev; + float3 hi = pos; + int k = 0; + while (k < 4) { + float3 mid = (lo + hi) * 0.5; + float4 mclip = (view_projection * float4(mid, 1.0)); + float2 muv = float2(mclip.x / mclip.w * 0.5 + 0.5, mclip.y / mclip.w * 0.5 + 0.5); + if (distance(view_pos, mid) > distance(view_pos, ae3d_shaders_ssao_world_at(inv_view_projection, muv, depth.sample(ae_smp_depth, muv).x))) { + hi = mid; + } else { + lo = mid; + } + k = k + 1; + } + float4 hclip = (view_projection * float4(hi, 1.0)); + float2 huv = float2(hclip.x / hclip.w * 0.5 + 0.5, hclip.y / hclip.w * 0.5 + 0.5); + float travelled = distance(p, hi); + float radius = min(rough * travelled / max(distance(view_pos, hi), 0.5) * 0.35, 0.06); + hit = ae3d_shaders_ssr_reflection_at(screen, ae_smp_screen, blur, ae_smp_blur, huv, radius, uv * 1024.0); + float edge = min(min(huv.x, 1.0 - huv.x), min(huv.y, 1.0 - huv.y)); + edge_fade = clamp(edge * 8.0, 0.0, 1.0); + break; + } + prev = pos; + pos = pos + r * step_len; + step_len = step_len * 1.05; + i = i + 1; + } + ae_out.FragColor = float4(scene + hit * (strength * fresnel * wet * edge_fade), 1.0); + return ae_out; +} diff --git a/src/ae3d/metal/ssr_quoted.vert.metal b/src/ae3d/metal/ssr_quoted.vert.metal new file mode 100644 index 00000000..4f941c3f --- /dev/null +++ b/src/ae3d/metal/ssr_quoted.vert.metal @@ -0,0 +1,24 @@ +// screen: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_screen_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; +}; + +struct ae_screen_out { + float4 ae_position [[position]]; + float2 uv [[user(locn0)]]; +}; + +vertex ae_screen_out ae_screen_vertex( + ae_screen_in ae_in [[stage_in]]) { + ae_screen_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + ae_out.ae_position = float4(float3(inPosition.x, inPosition.y, 0.0), 1.0); + ae_out.uv = inTexCoord; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/src/ae3d/metal/taa_quoted.frag.metal b/src/ae3d/metal/taa_quoted.frag.metal new file mode 100644 index 00000000..17b4cb1c --- /dev/null +++ b/src/ae3d/metal/taa_quoted.frag.metal @@ -0,0 +1,75 @@ +// taa: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_Params { + float2 screen_size; + float blend; + float exposure; +}; +static_assert(sizeof(ae_Params) == 16, "ae_Params: std140"); +static_assert(__builtin_offsetof(ae_Params, screen_size) == 0, "ae_Params.screen_size: std140"); +static_assert(__builtin_offsetof(ae_Params, blend) == 8, "ae_Params.blend: std140"); +static_assert(__builtin_offsetof(ae_Params, exposure) == 12, "ae_Params.exposure: std140"); + +struct ae_taa_in { + float2 uv [[user(locn0)]]; +}; + +struct ae_taa_frag_out { + float4 FragColor [[color(0)]]; +}; + +static float3 ae3d_shaders_taa_fold(float3 c, float exposure) { + return c / (1.0 + dot(c * exposure, float3(0.2126, 0.7152, 0.0722))); +} + +static float3 ae3d_shaders_taa_unfold(float3 c, float exposure) { + return c / max(1.0 - dot(c * exposure, float3(0.2126, 0.7152, 0.0722)), 0.0001); +} + +fragment ae_taa_frag_out ae_taa_fragment( + ae_taa_in ae_in [[stage_in]], + constant ae_Params& ae_params [[buffer(0)]], + texture2d screen [[texture(0)]], sampler ae_smp_screen [[sampler(0)]], + texture2d velocity [[texture(1)]], sampler ae_smp_velocity [[sampler(1)]], + texture2d history [[texture(2)]], sampler ae_smp_history [[sampler(2)]]) { + ae_taa_frag_out ae_out; + float2 uv = ae_in.uv; + constant float2& screen_size = ae_params.screen_size; + constant float& blend = ae_params.blend; + constant float& exposure = ae_params.exposure; + float2 px = float2(1.0 / screen_size.x, 1.0 / screen_size.y); + float3 current = screen.sample(ae_smp_screen, uv).rgb; + if (blend >= 0.999) { + ae_out.FragColor = float4(current, 1.0); + return ae_out; + } + float2 prev_uv = uv - velocity.sample(ae_smp_velocity, uv).xy; + if (prev_uv.x < 0.0 || prev_uv.x > 1.0 || prev_uv.y < 0.0 || prev_uv.y > 1.0) { + ae_out.FragColor = float4(current, 1.0); + return ae_out; + } + float3 past = ae3d_shaders_taa_fold(history.sample(ae_smp_history, prev_uv).rgb, exposure); + float3 now = ae3d_shaders_taa_fold(current, exposure); + float3 low = now; + float3 high = now; + float y = -1.0; + while (y <= 1.0) { + float x = -1.0; + while (x <= 1.0) { + if (x != 0.0 || y != 0.0) { + float3 c = ae3d_shaders_taa_fold(screen.sample(ae_smp_screen, (uv + float2(x, y) * px)).rgb, exposure); + low = min(low, c); + high = max(high, c); + } + x = x + 1.0; + } + y = y + 1.0; + } + past = clamp(past, low, high); + float travel = length((prev_uv - uv) * screen_size); + float weight = clamp(blend + travel * 0.02, blend, 1.0); + ae_out.FragColor = float4(ae3d_shaders_taa_unfold(mix(past, now, weight), exposure), 1.0); + return ae_out; +} diff --git a/src/ae3d/metal/taa_quoted.vert.metal b/src/ae3d/metal/taa_quoted.vert.metal new file mode 100644 index 00000000..4f941c3f --- /dev/null +++ b/src/ae3d/metal/taa_quoted.vert.metal @@ -0,0 +1,24 @@ +// screen: written by ae3d.gpu from its quote. +#include +using namespace metal; + +struct ae_screen_in { + float3 inPosition [[attribute(0)]]; + float2 inTexCoord [[attribute(1)]]; +}; + +struct ae_screen_out { + float4 ae_position [[position]]; + float2 uv [[user(locn0)]]; +}; + +vertex ae_screen_out ae_screen_vertex( + ae_screen_in ae_in [[stage_in]]) { + ae_screen_out ae_out; + float3 inPosition = ae_in.inPosition; + float2 inTexCoord = ae_in.inTexCoord; + ae_out.ae_position = float4(float3(inPosition.x, inPosition.y, 0.0), 1.0); + ae_out.uv = inTexCoord; + ae_out.ae_position.y = -ae_out.ae_position.y; + return ae_out; +} diff --git a/tools/generate_shaders.ae b/tools/generate_shaders.ae index a6a30ae9..cf44d998 100644 --- a/tools/generate_shaders.ae +++ b/tools/generate_shaders.ae @@ -28,6 +28,10 @@ // was made from, as it fails when the GLSL it writes is not what is // checked in. // +// The same trees are written as MSL (ae3d.gpu's METAL dialect) into +// src/ae3d/metal, one file a stage, which --check holds to what they write +// as it holds the GLSL; scripts/msl_check.sh compiles them on macOS. +// // The SPIR-V is glslang's, so it is reproducible from one glslang only: // 16.6.0 is the reference (Nic, 2026-10-16), and both modes refuse any // other. --check compiles what the source makes and compares the words with @@ -73,6 +77,9 @@ const SHADERS = "src/ae3d/shaders/module.ae" // build/. const OUT_DIR = "src/ae3d/vkspirv/glsl" const WORK_DIR = "build/shaders" +// The quoted shaders' MSL (ae3d.gpu's METAL dialect), checked in as the +// GLSL is; tools/msl_check.sh compiles it with Metal's compiler on macOS. +const MSL_DIR = "src/ae3d/metal" const VKSCENE = "src/ae3d/vkscene/module.ae" const VKSPIRV = "src/ae3d/vkspirv/module.ae" const GENERATOR = "tools/generate_shaders.ae" @@ -1803,6 +1810,7 @@ main() { check = os.args_count() > 1 && os.args_get(1) == "--check" require_glslang() fs.mkdir_p(WORK_DIR) + fs.mkdir_p(MSL_DIR) source = read_text(SHADERS) if source == "" { println("generate: cannot read ${SHADERS}; run from the repository root") @@ -1905,16 +1913,19 @@ main() { output(written, "overlay_vk.frag", at_450(block(source, "FRAGMENT_OVERLAY"))) // The shaders written with `quote`: GLSL from their trees. quoted = quoted_shaders() + msl = list.new() // *Output, the quoted shaders' MSL i = 0 while i < list.size(quoted) { q = list.get_raw(quoted, i) as *Quoted if q.vertex_only { only = gpu.emit_vertex_glsl(q.shader, gpu.VULKAN, "", "") - if only == "" { + only_msl = gpu.emit_vertex_msl(q.shader, "", "") + if only == "" || only_msl == "" { println("generate: ${q.name}: ${gpu.last_error()}") os.exit(1) } output(written, "${q.name}.vert", only) + output(msl, "${q.name}.vert.metal", only_msl) i = i + 1 continue } @@ -1931,6 +1942,20 @@ main() { } output(written, "${q.name}.frag", frag) output(written, "${q.name}.vert", vert) + // And the same trees as MSL. + frag_msl = gpu.emit_msl(q.shader) + vert_msl = "" + if q.vertex != null { + vert_msl = gpu.emit_vertex_msl(q.vertex, "", "") + } else { + vert_msl = gpu.emit_screen_vertex_msl(q.shader) + } + if frag_msl == "" || vert_msl == "" { + println("generate: ${q.name} (MSL): ${gpu.last_error()}") + os.exit(1) + } + output(msl, "${q.name}.frag.metal", frag_msl) + output(msl, "${q.name}.vert.metal", vert_msl) i = i + 1 } @@ -1946,6 +1971,12 @@ main() { output(outputs, shader_path, w.text) i = i + 1 } + i = 0 + while i < list.size(msl) { + w = list.get_raw(msl, i) as *Output + output(outputs, "${MSL_DIR}/${w.path}", w.text) + i = i + 1 + } offsets_module = aether_offsets(words_placed, size, light_placed, light_stride, view_from, world_from) output(outputs, VKSCENE, offsets_module) From 33e2ce39fc3c8194ef06fde894b6580b61a3aa4b Mon Sep 17 00:00:00 2001 From: paul Date: Tue, 20 Oct 2026 23:43:14 +0100 Subject: [PATCH 3/5] msl_check: Metal's compiler over src/ae3d/metal, every warning an error tools/mslc.m compiles MSL through the device (newLibraryWithSource), behind pragmas making every clang warning an error but four groups that say nothing about a shader (C++98 compatibility, padding, Metal 4's deprecated vertex/fragment keywords, unused parameters). The runtime compiler reports diagnostics only when a source fails, hence the pragmas. scripts/msl_check.sh builds it and runs it over src/ae3d/metal, and runs `xcrun metal -Werror` too where Xcode's Metal Toolchain is installed (it is a separate download, not on the M6). ./ci.sh runs it on macOS after the generator's check; each block struct's static_asserts make this a check of the MSL's std140 layout as well. Co-Authored-By: Claude Opus 5.5 --- ci.sh | 20 +++++++++++++- scripts/msl_check.sh | 50 +++++++++++++++++++++++++++++++++++ tools/mslc.m | 63 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) create mode 100755 scripts/msl_check.sh create mode 100644 tools/mslc.m diff --git a/ci.sh b/ci.sh index 016858b1..8f454077 100755 --- a/ci.sh +++ b/ci.sh @@ -493,12 +493,30 @@ if ! ./build.sh tools/generate_shaders.ae >/tmp/ae3d_shaders.log 2>&1; then fail "generate_shaders (build)" sed "s/^/ /" /tmp/ae3d_shaders.log | head -12 elif ./build/generate_shaders --check >/tmp/ae3d_shaders.log 2>&1; then - pass "src/ae3d/vkspirv/glsl, vkscene and vkspirv's SPIR-V are what src/ae3d/shaders and the quoted shaders produce" + pass "src/ae3d/vkspirv/glsl, vkscene, vkspirv's SPIR-V and src/ae3d/metal are what src/ae3d/shaders and the quoted shaders produce" else fail "generated Vulkan shaders are out of date; run build/generate_shaders" sed "s/^/ /" /tmp/ae3d_shaders.log | head -12 fi +# The same trees' MSL (src/ae3d/metal), which the generator checks is +# current beside the GLSL, compiled by Metal's own compiler with every +# warning an error; each struct of a uniform block carries static_asserts of +# its std140 offsets, so this holds the MSL's layout to the block's too. +if [ "$(uname -s)" = "Darwin" ]; then + step "Metal shaders compile" + ./scripts/msl_check.sh >/tmp/ae3d_msl.log 2>&1 + msl_status=$? + if [ "$msl_status" -eq 0 ]; then + pass "$(tail -1 /tmp/ae3d_msl.log | sed 's/^msl_check: //')" + elif [ "$msl_status" -eq 2 ]; then + skip "src/ae3d/metal" "$(tail -1 /tmp/ae3d_msl.log)" + else + fail "src/ae3d/metal does not compile clean" + sed "s/^/ /" /tmp/ae3d_msl.log | head -20 + fi +fi + fi step "native layer, warnings as errors" diff --git a/scripts/msl_check.sh b/scripts/msl_check.sh new file mode 100755 index 00000000..958f766b --- /dev/null +++ b/scripts/msl_check.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Compile the quoted shaders' MSL (src/ae3d/metal, written by +# tools/generate_shaders.ae from the same trees as their GLSL) with Metal's +# compiler, failing on any warning. +# +# ./scripts/msl_check.sh [FILE.metal...] # default: every src/ae3d/metal/*.metal +# +# macOS only. Always through the device's compiler (tools/mslc.m, every +# warning an error); with `xcrun metal` too where the Metal Toolchain is +# installed (xcodebuild -downloadComponent MetalToolchain), -Werror. Exit 0 +# when every file compiles clean, 1 when one does not, 2 when it cannot check +# (not macOS, no compiler, no Metal device). +set -uo pipefail +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" +if [ "$(uname -s)" != "Darwin" ]; then + echo "msl_check: Metal is macOS's" + exit 2 +fi +if [ "$#" -gt 0 ]; then files=("$@"); else files=(src/ae3d/metal/*.metal); fi +mkdir -p build +if ! clang -fobjc-arc -O1 -Wall -Wextra -Werror tools/mslc.m -framework Metal -framework Foundation \ + -o build/mslc 2>build/mslc.log; then + echo "msl_check: tools/mslc.m does not build" + sed 's/^/ /' build/mslc.log | head -12 + exit 2 +fi +./build/mslc "${files[@]}" >build/mslc.out 2>&1 +status=$? +if [ "$status" -eq 3 ]; then + echo "msl_check: no Metal device" + exit 2 +fi +if [ "$status" -ne 0 ]; then + grep -v ' functions$' build/mslc.out | head -40 + echo "msl_check: Metal's compiler refused the MSL above" + exit 1 +fi +offline="not installed" +if xcrun -sdk macosx metal --version >/dev/null 2>&1; then + offline="clean" + for f in "${files[@]}"; do + if ! xcrun -sdk macosx metal -Werror -c "$f" -o /dev/null 2>build/metal.log; then + echo "msl_check: xcrun metal: $f" + sed 's/^/ /' build/metal.log | head -20 + exit 1 + fi + done +fi +echo "msl_check: ${#files[@]} MSL files compile with every warning an error (xcrun metal: $offline)" diff --git a/tools/mslc.m b/tools/mslc.m new file mode 100644 index 00000000..31c85c94 --- /dev/null +++ b/tools/mslc.m @@ -0,0 +1,63 @@ +// mslc: compile MSL sources with Metal's own compiler, through the device +// (MTLDevice newLibraryWithSource), and fail on any diagnostic. +// +// build/mslc FILE.metal... # each compiled; exit 1 if any is refused +// +// The runtime compiler reports diagnostics only when a source fails, so each +// source is compiled behind pragmas that make every warning clang knows an +// error (-Weverything), less four groups that say nothing about a shader: +// C++98 compatibility, padding a struct (the std140 blocks are padded on +// purpose), the `vertex`/`fragment` keywords Metal 4 deprecates in favour of +// attributes older Metal does not take, and an unused parameter (a helper's +// own, from its quote, or a sampler handed beside a texture only read). +// That is stricter than `xcrun metal`'s default set, which needs the Metal +// Toolchain component (scripts/msl_check.sh runs it too where it is there). +// Built and run by scripts/msl_check.sh. +#import +#import + +static NSString *const STRICT = + @"#pragma clang diagnostic error \"-Weverything\"\n" + @"#pragma clang diagnostic ignored \"-Wc++98-compat\"\n" + @"#pragma clang diagnostic ignored \"-Wc++98-compat-pedantic\"\n" + @"#pragma clang diagnostic ignored \"-Wpadded\"\n" + @"#pragma clang diagnostic ignored \"-Wdeprecated\"\n" + @"#pragma clang diagnostic ignored \"-Wunused-parameter\"\n"; + +int main(int argc, char **argv) { + @autoreleasepool { + if (argc < 2) { + fprintf(stderr, "usage: mslc FILE.metal...\n"); + return 2; + } + id device = MTLCreateSystemDefaultDevice(); + if (!device) { + fprintf(stderr, "mslc: no Metal device\n"); + return 3; + } + MTLCompileOptions *options = [MTLCompileOptions new]; + int refused = 0; + for (int i = 1; i < argc; i++) { + NSString *path = [NSString stringWithUTF8String:argv[i]]; + NSError *error = nil; + NSString *source = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error]; + if (!source) { + fprintf(stderr, "%s: cannot read it\n", argv[i]); + refused++; + continue; + } + error = nil; + id library = [device newLibraryWithSource:[STRICT stringByAppendingString:source] + options:options + error:&error]; + if (!library || error) { + // Line numbers are the source's plus the pragmas' six. + fprintf(stderr, "%s: %s\n", argv[i], error ? [[error localizedDescription] UTF8String] : "refused"); + refused++; + } else { + printf("%s: %lu functions\n", argv[i], (unsigned long)[[library functionNames] count]); + } + } + return refused ? 1 : 0; + } +} From bd52d7c420f66f6deca64ad16896fd04218bbf7b Mon Sep 17 00:00:00 2001 From: paul Date: Tue, 20 Oct 2026 23:43:14 +0100 Subject: [PATCH 4/5] docs: the MSL written (step 5), and the Metal path's choices for Nic Section 9 item 5 and section 11's "What it buys MSL": the dialect's rules, how it is held (--check, msl_check, test_gpu_msl, MoltenVK's own MSL of the same SPIR-V: interfaces equal, 506 offsets asserted on its structs), and three ways to a Metal path (#312), smallest first, with a recommendation. Co-Authored-By: Claude Opus 5.5 --- changes/2026-10-20-quoted-msl.md | 14 +++++ docs/aether_quote_design.md | 90 ++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 changes/2026-10-20-quoted-msl.md diff --git a/changes/2026-10-20-quoted-msl.md b/changes/2026-10-20-quoted-msl.md new file mode 100644 index 00000000..aea9dfd1 --- /dev/null +++ b/changes/2026-10-20-quoted-msl.md @@ -0,0 +1,14 @@ +### The quoted shaders written as MSL + +- **ae3d.gpu writes MSL** (`gpu.METAL`: `emit_msl`, `emit_vertex_msl`, + `emit_screen_vertex_msl`) from the same trees as the GLSL, with Vulkan's + conventions (each vertex shader negates its position's y as it returns + it); docs/aether_quote_design.md section 11, "What it buys MSL", has the + rules. Step 5 of section 9; no Metal renderer yet, and no aether change. +- **`src/ae3d/metal`**: every quoted shader's MSL (41 files), written by + `tools/generate_shaders.ae` beside the GLSL and held current by `--check`. +- **`scripts/msl_check.sh`** (in `./ci.sh` on macOS): Metal's compiler, + through the device (`tools/mslc.m`), every warning an error; each block + struct's std140 offsets are `static_assert`s in the text. +- **`tests/test_gpu_msl`**: what the dialect writes where it departs from + the GLSL, no GPU. diff --git a/docs/aether_quote_design.md b/docs/aether_quote_design.md index 50cdd0c9..489635a2 100644 --- a/docs/aether_quote_design.md +++ b/docs/aether_quote_design.md @@ -406,6 +406,17 @@ From mapping the compiler; nothing here is built yet. GLSL ae3d.gpu writes into the SPIR-V ae3d.vkspirv holds, and with `spirv-val` it stays CI's checker (`generate_shaders --check`). Chris's `spirv.inc` and the SPIR-V spec are where that writer would start. + *The MSL written (2026-10-20, on Aether 0.834.0); no Metal renderer yet:* + ae3d.gpu has a third dialect, `gpu.METAL` (`emit_msl`, `emit_vertex_msl`, + `emit_screen_vertex_msl`), written from the same trees as the GLSL, and + the generator writes every quoted shader's MSL into `src/ae3d/metal` + (41 files), which `--check` holds current byte for byte as it holds the + GLSL. `scripts/msl_check.sh` (in `./ci.sh` on macOS) compiles them with + Metal's compiler with every warning an error; each block struct's std140 + offsets are `static_assert`s in the text, so the compiler holds the MSL's + layout to the block the renderer fills. Section 11, "What it buys MSL", + has the rules and the choices left for a Metal path. No aether change + was needed: quote's trees carry everything the dialect reads. 6. **Vertex shaders.** ChrysaLisp's split is worth taking as an idea: a vertex shader's inputs and outputs are declared, and one vertex shader serves any pixel shader whose inputs it provides. For us the vertex @@ -762,3 +773,82 @@ struct with `[[position]]`), so varyings designed as structs map across as they are. The attribute table becomes `[[attribute(n)]]`, and the block's structs Metal's argument buffers. +*Written (2026-10-20, on Aether 0.834.0):* `gpu.METAL`, the expressions and +statements the GLSL emitter's, in a third column of the call table +(`CALL_MSL`: `t.sample(ae_smp_t, uv)` for `texture`, `.read()` for the +fetches, `rsqrt`, `atan2`, the prelude's `ae_mod` and `ae_mat3`), struct +literals as aggregates. Around them: + +- **Conventions are Vulkan's.** A stored depth is clip z as it is, the + screen's y runs down (`screen_y_sign` is -1), and each vertex shader + negates its position's y as it returns it, an exact step, so a Metal + renderer draws with the Vulkan renderer's matrices and viewports and the + frame comes out the same way up. `[[position]]` in a pixel shader is the + pixel's centre from the top left, as `gl_FragCoord` is on Vulkan. +- **Nothing is global.** Each block is a struct handed to the entry in a + buffer, each texture with a sampler beside it, each storage buffer a + pointer. The entry's first lines name each capture as a reference into + its block (`constant Eye& eye = ae_set1.eye;`), so the body reads them by + their GLSL names where it uses them, as the GLSL does (the load order + MoltenVK's fast math rounds by). A helper reading in place is handed what + it reads by name, after its own parameters, and so is each helper calling + it; a helper whose own parameter or local would hide such a name is + refused, in MSL only. +- **std140 by hand.** MSL's own layout is not std140 (a `float3` takes + sixteen bytes), so each block struct is written member by member at the + offsets ae3d.gpu gives it, padded between: a vec3 a `packed_float3`, an + array of floats, vec2s or vec3s one of `float4`s read through `.x`, + `.xy`, `.xyz`, an array's element struct of its own with a maker for its + literals (an aggregate would fill the padding). Each offset and size is a + `static_assert` in the text. +- **Outputs.** Varyings at `[[user(locnK)]]`, K the location the GLSL gives + them (an int `[[flat]]` in the pixel shader), attributes at the table's + `[[attribute(n)]]` (a mat4 four `float4`s), colour outputs four lanes wide + at `[[color(k)]]` (Metal holds an output to its attachment's channels; + MoltenVK widens them alike), the depth `[[depth(any)]]`. +- **Bindings**, one rule for a Metal renderer to follow: Vulkan set N's + block at `[[buffer(N)]]`, the storage buffers at `[[buffer(8 + k)]]`, the + textures and their samplers at `[[texture(k)]]` and `[[sampler(k)]]` in + capture order, vertex buffers from `[[buffer(16)]]`. Discrete arguments, + not argument buffers: the simplest thing that compiles, and a renderer's + choice to change. + +How it is held: + +- `generate_shaders --check`: the 41 files are what the trees write. +- `scripts/msl_check.sh`: Metal's compiler, through the device + (`tools/mslc.m`), with every warning clang has an error but four groups + (C++98 compatibility, padding, Metal 4's deprecated `vertex`/`fragment` + keywords, unused parameters). `xcrun metal` needs Xcode's Metal Toolchain + component, a download, and the script runs it too where it is installed. +- `tests/test_gpu_msl`, no GPU: what each rule writes, the offsets the + GLSL's block has, and the GLSL and OpenGL's names unchanged by writing MSL. +- Against MoltenVK's own MSL of the same shaders' SPIR-V (its SPIRV-Cross, + logged with `MVK_CONFIG_DEBUG=1`): every one of the 41 paired, the stage + interfaces the same (attributes, varying locations, outputs), and 506 of + ae3d.gpu's offsets asserted on MoltenVK's own block structs compile. That + is a check of the interfaces and layouts, not of the arithmetic: nothing + here draws with the MSL yet. + +**What is left, for Nic: the Metal path (#312).** Three ways, smallest first: + +1. **Our MSL through MoltenVK.** MoltenVK takes MSL source in place of + SPIR-V (`vkCreateShaderModule`, its MSL magic number), so the Vulkan + renderer could draw the quoted shaders from `src/ae3d/metal` on macOS + with nothing else changed, and every frame be held to the SPIR-V path's + on the M6 (frames_against, parity). It needs the MSL's bindings to be + the ones MoltenVK assigns (its argument buffers, or its discrete indices + with `MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=0`): a binding rule in the + dialect, not a backend. It tests the arithmetic, which nothing does now. +2. **A Metal harness for the screen passes.** Draw each quoted full-screen + pass through Metal itself from a small Objective-C tool, its inputs + fixed, and hold it to MoltenVK's drawing of the same pass, or to the CPU + evaluator. No renderer; it proves the MSL, not a Metal path. +3. **The Metal backend** behind the `Backend` vtable, as #312 has it: a + third renderer, its parity test and the editor's viewport on it. #312 + puts it after the Vulkan performance pass, so the structures it shares + are the settled ones. + +The recommendation is 1: the least new code, and the first time the MSL's +arithmetic is held to frames; 3 after #312's groundwork. + From e2629542091344274e9ba10b1447435181b2a71a Mon Sep 17 00:00:00 2001 From: paul Date: Wed, 21 Oct 2026 00:10:45 +0100 Subject: [PATCH 5/5] test_gpu_msl: free the shaders it makes ./ci.sh's leaks run counted the six shaders the suite made and never gave back (843 allocations under them). Each is freed now: 0 leaks. Co-Authored-By: Claude Opus 5.5 --- tests/test_gpu_msl.ae | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/test_gpu_msl.ae b/tests/test_gpu_msl.ae index 3d94a2a6..e8d2b3f1 100644 --- a/tests/test_gpu_msl.ae +++ b/tests/test_gpu_msl.ae @@ -150,6 +150,14 @@ leaf(s: *GpuShader, name: string) -> int { return 0 - 1 } +// The names of a shader made for the asking, freed after. +fresh_names(lamp: Lamp, screen: Texture, glow: Texture) -> string { + s = msl_pixel(lamp, screen, glow, 0.5) + names = uniform_names(s) + gpu.shader_free(s) + return names +} + uniform_names(s: *GpuShader) -> string { out = "" k = 0 @@ -178,13 +186,13 @@ main() { vk_before = gpu.emit_glsl(ps, gpu.VULKAN) gl_before = gpu.emit_glsl(ps, gpu.GL) vert_before = gpu.emit_vertex_glsl(vs, gpu.VULKAN, "", "") - names_before = uniform_names(msl_pixel(lamp, screen, glow, 0.5)) + names_before = fresh_names(lamp, screen, glow) check("the GLSL is written", vk_before != "" && gl_before != "" && vert_before != "") frag = gpu.emit_msl(ps) vert = gpu.emit_vertex_msl(vs, "", "") // A shader made straight after the MSL was written, its names made then. - names_after = uniform_names(msl_pixel(lamp, screen, glow, 0.5)) + names_after = fresh_names(lamp, screen, glow) if frag == "" || vert == "" { println(" ${gpu.last_error()}") } check("the MSL is written", frag != "" && vert != "") write_out("msl_pixel.frag.metal", frag) @@ -220,9 +228,11 @@ main() { has(frag, "texture2d screen [[texture(0)]], sampler ae_smp_screen [[sampler(0)]]") && has(frag, "texture2d glow [[texture(1)]], sampler ae_smp_glow [[sampler(1)]]")) check("helpers are the file's own", has(frag, "\nstatic float3 ")) - hidden = gpu.emit_msl(hidden_pixel(lamp)) + hp = hidden_pixel(lamp) + hidden = gpu.emit_msl(hp) check("a helper's own name hiding what it is handed is refused", hidden == "" && has(gpu.last_error(), "would hide it")) - check("which the GLSL is not", gpu.emit_glsl(hidden_pixel(lamp), gpu.VULKAN) != "") + check("which the GLSL is not", gpu.emit_glsl(hp, gpu.VULKAN) != "") + gpu.shader_free(hp) println("the stages") check("a mat4 attribute is four float4s at its locations", @@ -244,6 +254,8 @@ main() { // device, a word MSL keeps, still OpenGL's device. check("OpenGL's uniform names", names_after == names_before && has(names_before, " device@")) + gpu.shader_free(ps) + gpu.shader_free(vs) if g_failures > 0 { println("gpu_msl: ${g_failures} of ${g_checks} checks failed") exit(1)