diff --git a/.github/workflows/shaders.yml b/.github/workflows/shaders.yml index b6e740a3e..11c7017ab 100644 --- a/.github/workflows/shaders.yml +++ b/.github/workflows/shaders.yml @@ -1,8 +1,8 @@ # The SPIR-V and DXIL fizzy's native backend embeds (Vulkan, D3D12), compiled from their HLSL here # rather than on someone's machine, and checked against what is committed. # -# Each `X.hlsl` beside a `compiled/spv/X.spv` and `compiled/dxil/X.dxil` is compiled with -# SDL_shadercross and compared byte for byte. A difference fails the job and uploads the fresh +# Each `X.hlsl` beside compiled forms of it (`compiled/{spv,dxil,msl,glsl}/X.*`) is compiled by +# `scripts/compile-shaders.sh` and compared byte for byte. A difference fails the job and uploads the fresh # files as the `compiled-shaders` artifact, laid out as the repository has them: download it and # commit them. Before this, nothing noticed when the HLSL changed and the compiled files did not, # and Windows and Linux quietly drew the previous glass (#242, #373). @@ -20,12 +20,14 @@ on: paths: - "**/*.hlsl" - "**/shaders/compiled/**" + - "scripts/compile-shaders.sh" - ".github/workflows/shaders.yml" push: branches: [main] paths: - "**/*.hlsl" - "**/shaders/compiled/**" + - "scripts/compile-shaders.sh" - ".github/workflows/shaders.yml" workflow_dispatch: @@ -99,36 +101,23 @@ jobs: export PATH="$HOME/shadercross/bin:$PATH" export LD_LIBRARY_PATH="$HOME/shadercross/lib:${{ steps.sdl.outputs.prefix }}/lib:$LD_LIBRARY_PATH" shadercross --help > /dev/null 2>&1 || { echo "shadercross does not run"; ldd "$HOME/shadercross/bin/shadercross"; exit 1; } - stale=0 + command -v spirv-cross failed=0 - mkdir -p out - while IFS= read -r hlsl; do - dir=$(dirname "$hlsl"); name=$(basename "$hlsl" .hlsl) - for form in spv dxil; do - committed="$dir/compiled/$form/$name.$form" - [ -f "$committed" ] || continue - fresh="out/$committed" - mkdir -p "$(dirname "$fresh")" - # One that does not compile is reported, and the rest still go. - if ! (cd "$dir" && shadercross "$name.hlsl" -o "$GITHUB_WORKSPACE/$fresh"); then - echo "::error file=$hlsl::$hlsl does not compile to $form" - failed=1 - continue - fi - if [ "$form" = spv ] && ! spirv-val "$fresh"; then - echo "::error file=$hlsl::$fresh does not validate" - failed=1 - fi - if cmp -s "$committed" "$fresh"; then - echo "ok $committed" - else - echo "::error file=$hlsl::$committed is not what $hlsl compiles to: download the compiled-shaders artifact and commit it" - echo " committed $(stat -c %s "$committed") bytes, compiled $(stat -c %s "$fresh"); first differences (offset, committed, compiled):" - cmp -l "$committed" "$fresh" | head -8 || true - stale=1 - fi - done - done < <(git ls-files '*.hlsl') + bash scripts/compile-shaders.sh out || failed=1 + stale=0 + while IFS= read -r fresh; do + committed=${fresh#out/} + if cmp -s "$committed" "$fresh"; then + echo "ok $committed" + else + echo "::error file=$committed::$committed is not what its HLSL compiles to: download the compiled-shaders artifact and commit it" + echo " committed $(stat -c %s "$committed") bytes, compiled $(stat -c %s "$fresh")" + stale=1 + fi + case $fresh in + *.spv) spirv-val "$fresh" || { echo "::error file=$committed::$fresh does not validate"; failed=1; } ;; + esac + done < <(find out -type f | sort) echo "stale=$stale" >> "$GITHUB_ENV" echo "failed=$failed" >> "$GITHUB_ENV" diff --git a/backend/src/shaders/compiled/msl/program_example.fragment.msl b/backend/src/shaders/compiled/msl/program_example.fragment.msl index b32c867b6..dee14ac30 100644 --- a/backend/src/shaders/compiled/msl/program_example.fragment.msl +++ b/backend/src/shaders/compiled/msl/program_example.fragment.msl @@ -3,30 +3,26 @@ using namespace metal; -// `program_example.fragment.hlsl` in Metal, to the interface shadercross gives it: the entry -// point is `main0`, the inputs are the default vertex shader's `[[user(locnN)]]` outputs, -// textures and samplers are numbered from the draw's own (0), and the uniforms are buffer 0. - -struct main0_in +struct type_Uniforms { - float4 color [[user(locn0)]]; - float2 uv [[user(locn1)]]; + float4 data[2]; }; struct main0_out { - float4 color [[color(0)]]; + float4 out_var_SV_Target0 [[color(0)]]; }; -fragment main0_out main0( - main0_in in [[stage_in]], - texture2d Texture0 [[texture(0)]], sampler Sampler0 [[sampler(0)]], - texture2d Texture1 [[texture(1)]], sampler Sampler1 [[sampler(1)]], - constant float4 *data [[buffer(0)]]) +struct main0_in +{ + float4 in_var_COLOR [[user(locn0)]]; + float2 in_var_TEXCOORD0 [[user(locn1)]]; +}; + +fragment main0_out main0(main0_in in [[stage_in]], constant type_Uniforms& Uniforms [[buffer(0)]], texture2d Texture0 [[texture(0)]], texture2d Texture1 [[texture(1)]], sampler Sampler0 [[sampler(0)]], sampler Sampler1 [[sampler(1)]]) { main0_out out = {}; - float4 base = Texture0.sample(Sampler0, in.uv) * in.color * data[0]; - float4 extra = Texture1.sample(Sampler1, in.uv * data[1].x); - out.color = mix(base, extra, data[1].y); + out.out_var_SV_Target0 = mix((Texture0.sample(Sampler0, in.in_var_TEXCOORD0) * in.in_var_COLOR) * Uniforms.data[0], Texture1.sample(Sampler1, (in.in_var_TEXCOORD0 * Uniforms.data[1].x)), float4(Uniforms.data[1].y)); return out; } + diff --git a/core/gfx/LiquidField.zig b/core/gfx/LiquidField.zig index 79e934434..45eb62d10 100644 --- a/core/gfx/LiquidField.zig +++ b/core/gfx/LiquidField.zig @@ -1,5 +1,5 @@ //! One layer of liquid glass: rounded boxes that run together where they come close, drawn as -//! frosted, refracting glass in one pass at every pixel (`shaders/liquid_glass.glsl`). +//! frosted, refracting glass in one pass at every pixel (`shaders/liquid_glass.fragment.hlsl`). //! //! **What it is.** Each shape is a rounded box — a circle is a square rounded all the way, a //! capsule a rect rounded across its short side. Their outlines are joined by a smooth minimum @@ -148,7 +148,7 @@ pub fn clusters(self: *const LiquidField) Clusters { pub const uniform_vec4s = header_vec4s + shape_vec4s * max_shapes; -/// What the program reads, `uData` in `shaders/liquid_glass.glsl`. +/// What the program reads, `uData` in `shaders/liquid_glass.fragment.hlsl`. pub const Uniforms = extern struct { /// Where the frost starts, and 1 / its size: physical pixels to its uv. frost_map: [4]f32, @@ -231,17 +231,18 @@ const rim_line_width: f32 = 0.8; // ── Drawing ───────────────────────────────────────────────────────────────────────────────────── -/// The program for the native backend on Vulkan and D3D12, compiled by shadercross from -/// `shaders/liquid_glass.fragment.hlsl` (the commands are at its top). Copied out of the embed to -/// be aligned: Vulkan takes SPIR-V as 32-bit words. +/// The program, written once in `shaders/liquid_glass.fragment.hlsl` and compiled from it for every +/// backend (`scripts/compile-shaders.sh`, checked in CI): SPIR-V and DXIL for the native backend on +/// Vulkan and D3D12, Metal for it on macOS, GLSL ES 3.00 for the web. The binary forms are copied +/// out of the embed to be aligned: Vulkan takes SPIR-V as 32-bit words. const compiled = struct { const spirv align(8) = @embedFile("shaders/compiled/spv/liquid_glass.fragment.spv").*; const dxil align(8) = @embedFile("shaders/compiled/dxil/liquid_glass.fragment.dxil").*; }; var program: programs.Program = .from(.{ - .glsl = @embedFile("shaders/liquid_glass.glsl"), - .msl = @embedFile("shaders/liquid_glass.metal"), + .glsl = @embedFile("shaders/compiled/glsl/liquid_glass.fragment.glsl"), + .msl = @embedFile("shaders/compiled/msl/liquid_glass.fragment.msl"), .spirv = &compiled.spirv, .dxil = &compiled.dxil, }, .{ .textures = 1, .uniform_vec4s = uniform_vec4s }); @@ -431,7 +432,7 @@ pub const Sample = struct { light: f32, }; -/// `shaders/liquid_glass.glsl`'s field, line for line, over every shape: for hit tests and tests. +/// `shaders/liquid_glass.fragment.hlsl`'s field, line for line, over every shape: for hit tests and tests. pub fn sample(self: *const LiquidField, p: dvui.Point.Physical) Sample { const big = std.math.floatMax(f32); var d1: f32 = big; diff --git a/core/gfx/glass_look.zig b/core/gfx/glass_look.zig index 5207160dd..577e017a0 100644 --- a/core/gfx/glass_look.zig +++ b/core/gfx/glass_look.zig @@ -216,7 +216,7 @@ pub fn window(opacity: f32) Window { // ── The app's glass ───────────────────────────────────────────────────────────────────────────── -/// The app's glass at the opacity, as the glass program (`shaders/liquid_glass.glsl`) reads it. Shaped +/// The app's glass at the opacity, as the glass program (`shaders/liquid_glass.fragment.hlsl`) reads it. Shaped /// after Apple's lens, measured over a striped pattern: the backdrop pulled *inward* from a band /// near the rim — `bevel` of the shape's shorter half, at most `bevel_cap` points — by up to `bend` /// times that band, which magnifies it and, past 1, folds it into a mirrored, darker band just diff --git a/core/gfx/programs.zig b/core/gfx/programs.zig index b7c2f2c11..73b527c77 100644 --- a/core/gfx/programs.zig +++ b/core/gfx/programs.zig @@ -53,6 +53,11 @@ //! `MAX_VEC4` for the length of its uniforms, `uniform vec4 uData[MAX_VEC4]`. Inputs are //! `vColor` and `vTextureCoord`; samplers are `uSampler` (unit 0, the draw's own texture), //! `uTex1` and `uTex2` (the extra textures `begin` binds). +//! +//! **Or generated.** A complete GLSL ES 3.00 source — one starting with its `#version` line — is +//! compiled as it is, on WebGL2 only. That is what `scripts/compile-shaders.sh` makes from a +//! program written once in HLSL, with the names above; fizzy's glass is one +//! (`shaders/liquid_glass.fragment.hlsl`). const std = @import("std"); const dvui = @import("dvui"); diff --git a/core/gfx/shaders/compiled/glsl/liquid_glass.fragment.glsl b/core/gfx/shaders/compiled/glsl/liquid_glass.fragment.glsl new file mode 100644 index 000000000..3c3f45f42 --- /dev/null +++ b/core/gfx/shaders/compiled/glsl/liquid_glass.fragment.glsl @@ -0,0 +1,477 @@ +#version 300 es +precision highp float; +precision highp int; + +uniform highp vec4 uData[55]; +uniform highp sampler2D uSampler; +uniform highp sampler2D uTex1; + +in highp vec4 vColor; +in highp vec2 vTextureCoord; +layout(location = 0) out highp vec4 out_var_SV_Target0; + +void main() +{ + highp vec4 _678; + do + { + int _90 = int((vColor.y * 255.0) + 0.5); + int _95 = int((vColor.z * 255.0) + 0.5); + bool _111 = uData[5].y > 0.0; + highp float _113; + highp float _116; + highp vec2 _118; + highp vec2 _120; + highp vec4 _122; + highp vec4 _124; + highp float _126; + highp float _128; + highp float _130; + highp float _132; + _113 = 1000000000.0; + _116 = 1000000000.0; + _118 = vec2(0.0); + _120 = vec2(0.0); + _122 = vec4(0.0); + _124 = vec4(0.0); + _126 = 1000000000.0; + _128 = 1000000000.0; + _130 = 1000000000.0; + _132 = 1000000000.0; + highp float _117; + highp vec2 _121; + highp vec4 _125; + highp float _129; + highp float _133; + highp float _114; + highp vec2 _119; + highp vec4 _123; + highp float _127; + highp float _131; + int _134 = _90; + for (;;) + { + bool _143; + if (_134 < (_90 + _95)) + { + _143 = _134 < 16; + } + else + { + _143 = false; + } + if (_143) + { + int _145 = 3 * _134; + int _146 = 7 + _145; + int _152 = _145 + 9; + highp vec2 _156 = vTextureCoord - uData[_146 * 1 + 0].xy; + highp float _160 = min(uData[_146 * 1 + 0].z, uData[_146 * 1 + 0].w); + highp vec4 _162 = min(uData[(_145 + 8) * 1 + 0], vec4(_160)); + highp float _163 = _156.x; + highp float _184; + if (_163 > 0.0) + { + highp float _175; + if (_156.y > 0.0) + { + _175 = _162.z; + } + else + { + _175 = _162.y; + } + _184 = _175; + } + else + { + highp float _183; + if (_156.y > 0.0) + { + _183 = _162.w; + } + else + { + _183 = _162.x; + } + _184 = _183; + } + highp vec2 _188 = (abs(_156) - uData[_146 * 1 + 0].zw) + vec2(_184); + highp float _190 = (_163 < 0.0) ? (-1.0) : 1.0; + highp float _193 = (_156.y < 0.0) ? (-1.0) : 1.0; + highp float _195 = _188.x; + bool _201; + if (_195 > 0.0) + { + _201 = _188.y > 0.0; + } + else + { + _201 = false; + } + highp vec2 _215; + if (_201) + { + _215 = normalize(_188) * vec2(_190, _193); + } + else + { + highp vec2 _214; + if (_195 > _188.y) + { + _214 = vec2(_190, 0.0); + } + else + { + _214 = vec2(0.0, _193); + } + _215 = _214; + } + highp float _222 = (min(max(_195, _188.y), 0.0) + length(max(_188, vec2(0.0)))) - _184; + bool _223 = _222 < _132; + if (_223) + { + _114 = _116; + _123 = _124; + _131 = _132; + } + else + { + bool _227 = _222 < _130; + bvec4 _229 = bvec4(_227); + _114 = _227 ? _160 : _113; + _123 = vec4(_229.x ? uData[_152 * 1 + 0].x : _122.x, _229.y ? uData[_152 * 1 + 0].y : _122.y, _229.z ? uData[_152 * 1 + 0].z : _122.z, _229.w ? uData[_152 * 1 + 0].w : _122.w); + _131 = _227 ? _222 : _130; + } + _117 = _223 ? _160 : _116; + bvec4 _232 = bvec4(_223); + _125 = vec4(_232.x ? uData[_152 * 1 + 0].x : _124.x, _232.y ? uData[_152 * 1 + 0].y : _124.y, _232.z ? uData[_152 * 1 + 0].z : _124.z, _232.w ? uData[_152 * 1 + 0].w : _124.w); + _133 = _223 ? _222 : _132; + highp vec2 _277; + highp float _278; + if (uData[_152 * 1 + 0].w < 0.5) + { + highp float _243 = (vTextureCoord.x - uData[_146 * 1 + 0].x) + uData[_146 * 1 + 0].z; + highp float _245 = (uData[_146 * 1 + 0].x + uData[_146 * 1 + 0].z) - vTextureCoord.x; + highp float _250 = (vTextureCoord.y - uData[_146 * 1 + 0].y) + uData[_146 * 1 + 0].w; + highp float _252 = (uData[_146 * 1 + 0].y + uData[_146 * 1 + 0].w) - vTextureCoord.y; + highp float _256 = min(min(_243, _245), min(_250, _252)); + highp vec4 _261 = exp((vec4(_256) - vec4(_243, _245, _250, _252)) / vec4(uData[1].y)); + highp float _262 = _261.x; + highp float _263 = _261.y; + highp float _265 = _261.z; + highp float _267 = _261.w; + highp float _268 = ((_262 + _263) + _265) + _267; + _277 = vec2(_263 - _262, _267 - _265) / vec2(_268); + _278 = (uData[1].y * log(_268)) - _256; + } + else + { + _277 = _215; + _278 = _222; + } + bool _279 = _278 < _128; + if (_279) + { + _119 = _120; + _127 = _128; + } + else + { + bool _283 = _278 < _126; + bvec2 _284 = bvec2(_283); + _119 = vec2(_284.x ? _277.x : _118.x, _284.y ? _277.y : _118.y); + _127 = _283 ? _278 : _126; + } + bvec2 _287 = bvec2(_279); + _121 = vec2(_287.x ? _277.x : _120.x, _287.y ? _277.y : _120.y); + _129 = _279 ? _278 : _128; + _113 = _114; + _116 = _117; + _118 = _119; + _120 = _121; + _122 = _123; + _124 = _125; + _126 = _127; + _128 = _129; + _130 = _131; + _132 = _133; + _134++; + continue; + } + else + { + break; + } + } + highp float _289 = max(uData[1].x, 9.9999997473787516355514526367188e-05); + highp float _294 = max(_289 - abs(_132 - _130), 0.0) / _289; + highp float _295 = _294 * _294; + highp float _296 = _295 * 0.5; + highp float _301; + if (_130 < _132) + { + _301 = 1.0 - _296; + } + else + { + _301 = _296; + } + highp float _305 = min(_132, _130) - ((_295 * _289) * 0.25); + highp float _310 = max(_289 - abs(_128 - _126), 0.0) / _289; + highp float _311 = _310 * _310; + highp float _312 = _311 * 0.5; + highp float _317; + if (_126 < _128) + { + _317 = 1.0 - _312; + } + else + { + _317 = _312; + } + highp float _323 = clamp(0.5 - _305, 0.0, 1.0); + if (_323 <= 0.0) + { + discard; + } + if (vColor.x < 0.5) + { + _678 = vec4(0.0, 0.0, 0.0, _323); + break; + } + highp vec4 _334 = mix(_124, _122, vec4(_301)); + highp vec2 _336 = mix(_120, _118, vec2(_317)); + highp float _338 = max(0.0, -(min(_128, _126) - ((_311 * _289) * 0.25))); + highp float _339 = -_338; + highp float _342 = exp(_339 / uData[1].z); + highp float _348 = _334.y; + highp float _382; + highp float _383; + highp vec2 _384; + if (_111) + { + highp float _356 = max(min(uData[5].y * mix(_116, _113, _301), uData[6].w), 1.0); + highp float _359 = clamp(1.0 - (_338 / _356), 0.0, 1.0); + highp float _364 = (((uData[5].z * _356) * _348) * _359) * _359; + _382 = _359; + _383 = _364; + _384 = clamp(((vTextureCoord - (_336 * _364)) - uData[0].xy) * uData[0].zw, vec2(0.0), vec2(1.0)); + } + else + { + _382 = 0.0; + _383 = 0.0; + _384 = clamp(((vTextureCoord + (_336 * ((uData[2].x * _348) * _342))) - uData[0].xy) * uData[0].zw, vec2(0.0), vec2(1.0)); + } + highp vec4 _388 = textureLod(uSampler, _384, 0.0); + bool _390 = uData[4].w > 0.5; + highp vec4 _398; + if (_390) + { + _398 = textureLod(uTex1, _384, 0.0); + } + else + { + _398 = _388; + } + bool _403; + if (_111) + { + _403 = uData[6].y > 0.0; + } + else + { + _403 = false; + } + bool _407; + if (_403) + { + _407 = _383 > 0.5; + } + else + { + _407 = false; + } + highp vec4 _468; + highp vec4 _469; + if (_407) + { + highp vec2 _415 = (_336 * ((_383 * 0.039999999105930328369140625) * uData[6].y)) * uData[0].zw; + highp vec2 _417 = clamp(_384 + _415, vec2(0.0), vec2(1.0)); + highp vec2 _419 = clamp(_384 - _415, vec2(0.0), vec2(1.0)); + highp vec4 _423 = textureLod(uSampler, _417, 0.0); + highp float _428 = _388.w; + highp float _429 = (_423.x / max(_423.w, 9.9999997473787516355514526367188e-05)) * _428; + highp vec4 _430 = _388; + _430.x = _429; + highp vec4 _434 = textureLod(uSampler, _419, 0.0); + highp float _439 = (_434.z / max(_434.w, 9.9999997473787516355514526367188e-05)) * _428; + _430.z = _439; + highp vec4 _467; + if (_390) + { + highp vec4 _447 = textureLod(uTex1, _417, 0.0); + highp vec4 _454 = _398; + _454.x = (_447.x / max(_447.w, 9.9999997473787516355514526367188e-05)) * _398.w; + highp vec4 _458 = textureLod(uTex1, _419, 0.0); + _454.z = (_458.z / max(_458.w, 9.9999997473787516355514526367188e-05)) * _398.w; + _467 = _454; + } + else + { + highp vec4 _465 = _398; + _465.x = _429; + _465.z = _439; + _467 = _465; + } + _468 = _467; + _469 = _430; + } + else + { + _468 = _398; + _469 = _388; + } + highp float _477; + if (uData[6].x > 0.5) + { + _477 = 1.0; + } + else + { + _477 = _469.w; + } + highp vec4 _486 = vec4(_469.xyz / vec3(max(_469.w, 9.9999997473787516355514526367188e-05)), 1.0); + highp vec4 _495 = vec4(_468.xyz / vec3(max(_468.w, 9.9999997473787516355514526367188e-05)), 1.0); + bool _497 = uData[4].z > 0.5; + highp float _502; + if (_497) + { + _502 = uData[4].x; + } + else + { + _502 = 0.0; + } + highp float _511; + if (_111) + { + _511 = _502 * (1.0 - ((_382 * _382) * (1.0 - smoothstep(0.800000011920928955078125, 1.0, _502)))); + } + else + { + _511 = _502; + } + highp float _514 = 1.0 - _511; + highp float _522; + if (_111) + { + _522 = _382; + } + else + { + _522 = _342 * _342; + } + highp float _525 = clamp(((uData[2].y * _348) * _522) * _514, 0.0, 1.0); + highp vec4 _531 = ((_495 * _525) + ((mix(_495, _486, vec4(clamp(_334.x, 0.0, 1.0))) * _514) * (1.0 - _525))) + (uData[3] * _511); + highp float _536 = dot(_336, vec2(-0.707106769084930419921875)); + highp float _537 = max(_536, 0.0); + highp float _539 = max(-_536, 0.0); + highp float _542 = abs(_536); + bool _547; + if (_111) + { + _547 = uData[6].z > 0.0; + } + else + { + _547 = false; + } + highp float _567; + if (_547) + { + _567 = (uData[6].z * (1.0 - smoothstep(0.20000000298023223876953125, 0.699999988079071044921875, _542))) * exp((-max(0.0, -_305)) / (uData[2].z * 2.5)); + } + else + { + _567 = 0.85000002384185791015625 * (1.0 - smoothstep(0.0, uData[2].z * 0.625, -_305)); + } + highp float _575; + if (_111) + { + _575 = max((uData[5].w * _382) * _382, _567); + } + else + { + _575 = 0.0; + } + highp float _578 = _477 * (1.0 - _575); + highp vec4 _584 = vec4((_531 + (_486 * (1.0 - _531.w))).xyz * _578, _578 + _575); + highp float _603; + if (_111) + { + highp float _594; + if (_547) + { + _594 = uData[6].z * smoothstep(0.75, 1.0, _542); + } + else + { + _594 = 0.25; + } + _603 = _594; + } + else + { + _603 = (0.449999988079071044921875 * ((_537 * sqrt(_537)) + ((0.75 * _539) * sqrt(_539)))) + 0.0599999986588954925537109375; + } + highp float _630; + if (_547) + { + _630 = exp((-max(0.0, -_305)) / (uData[2].z * 1.25)); + } + else + { + highp float _629; + if (_111) + { + highp float _617 = -_305; + _629 = smoothstep(uData[2].z * 0.3125, uData[2].z * 0.625, _617) * (1.0 - smoothstep(uData[2].z * 1.5, uData[2].z * 2.0, _617)); + } + else + { + _629 = exp((-max(0.0, -_305)) / uData[2].z); + } + _630 = _629; + } + highp float _637; + if (_111) + { + _637 = 0.0; + } + else + { + _637 = (0.039999999105930328369140625 * exp(_339 / uData[1].w)) * _537; + } + highp float _643; + if (_497) + { + _643 = uData[4].y; + } + else + { + _643 = 0.0; + } + highp vec3 _653 = _584.xyz + vec3(clamp((_643 + _334.z) + ((_348 * uData[2].w) * ((_630 * _603) + _637)), 0.0, 1.0)); + highp vec4 _655 = clamp(vec4(_653.x, _653.y, _653.z, _584.w), vec4(0.0), vec4(1.0)); + highp float _662 = max(_655.w, max(_655.x, max(_655.y, _655.z))); + highp vec4 _663 = _655; + _663.w = _662; + highp vec3 _674 = _663.xyz + vec3(((fract(52.98291778564453125 * fract(dot(vTextureCoord, vec2(0.067110560834407806396484375, 0.005837149918079376220703125)))) - 0.5) * uData[5].x) * _662); + _678 = clamp(vec4(_674.x, _674.y, _674.z, _663.w), vec4(0.0), vec4(1.0)) * _323; + break; + } while(false); + out_var_SV_Target0 = _678; +} + diff --git a/core/gfx/shaders/compiled/msl/liquid_glass.fragment.msl b/core/gfx/shaders/compiled/msl/liquid_glass.fragment.msl new file mode 100644 index 000000000..bce3c888d --- /dev/null +++ b/core/gfx/shaders/compiled/msl/liquid_glass.fragment.msl @@ -0,0 +1,484 @@ +#include +#include + +using namespace metal; + +struct type_Uniforms +{ + float4 uData[55]; +}; + +struct main0_out +{ + float4 out_var_SV_Target0 [[color(0)]]; +}; + +struct main0_in +{ + float4 in_var_COLOR [[user(locn0)]]; + float2 in_var_TEXCOORD0 [[user(locn1)]]; +}; + +fragment main0_out main0(main0_in in [[stage_in]], constant type_Uniforms& Uniforms [[buffer(0)]], texture2d Frost [[texture(0)]], texture2d Sharp [[texture(1)]], sampler FrostSampler [[sampler(0)]], sampler SharpSampler [[sampler(1)]]) +{ + main0_out out = {}; + float4 _678; + do + { + int _90 = int((in.in_var_COLOR.y * 255.0) + 0.5); + int _95 = int((in.in_var_COLOR.z * 255.0) + 0.5); + bool _111 = Uniforms.uData[5].y > 0.0; + float _113; + float _116; + float2 _118; + float2 _120; + float4 _122; + float4 _124; + float _126; + float _128; + float _130; + float _132; + _113 = 1000000000.0; + _116 = 1000000000.0; + _118 = float2(0.0); + _120 = float2(0.0); + _122 = float4(0.0); + _124 = float4(0.0); + _126 = 1000000000.0; + _128 = 1000000000.0; + _130 = 1000000000.0; + _132 = 1000000000.0; + float _117; + float2 _121; + float4 _125; + float _129; + float _133; + float _114; + float2 _119; + float4 _123; + float _127; + float _131; + int _134 = _90; + for (;;) + { + bool _143; + if (_134 < (_90 + _95)) + { + _143 = _134 < 16; + } + else + { + _143 = false; + } + if (_143) + { + int _145 = 3 * _134; + int _146 = 7 + _145; + int _152 = _145 + 9; + float2 _156 = in.in_var_TEXCOORD0 - Uniforms.uData[_146].xy; + float _160 = precise::min(Uniforms.uData[_146].z, Uniforms.uData[_146].w); + float4 _162 = precise::min(Uniforms.uData[_145 + 8], float4(_160)); + float _163 = _156.x; + float _184; + if (_163 > 0.0) + { + float _175; + if (_156.y > 0.0) + { + _175 = _162.z; + } + else + { + _175 = _162.y; + } + _184 = _175; + } + else + { + float _183; + if (_156.y > 0.0) + { + _183 = _162.w; + } + else + { + _183 = _162.x; + } + _184 = _183; + } + float2 _188 = (abs(_156) - Uniforms.uData[_146].zw) + float2(_184); + float _190 = (_163 < 0.0) ? (-1.0) : 1.0; + float _193 = (_156.y < 0.0) ? (-1.0) : 1.0; + float _195 = _188.x; + bool _201; + if (_195 > 0.0) + { + _201 = _188.y > 0.0; + } + else + { + _201 = false; + } + float2 _215; + if (_201) + { + _215 = fast::normalize(_188) * float2(_190, _193); + } + else + { + float2 _214; + if (_195 > _188.y) + { + _214 = float2(_190, 0.0); + } + else + { + _214 = float2(0.0, _193); + } + _215 = _214; + } + float _222 = (precise::min(precise::max(_195, _188.y), 0.0) + length(precise::max(_188, float2(0.0)))) - _184; + bool _223 = _222 < _132; + if (_223) + { + _114 = _116; + _123 = _124; + _131 = _132; + } + else + { + bool _227 = _222 < _130; + _114 = _227 ? _160 : _113; + _123 = select(_122, Uniforms.uData[_152], bool4(_227)); + _131 = _227 ? _222 : _130; + } + _117 = _223 ? _160 : _116; + _125 = select(_124, Uniforms.uData[_152], bool4(_223)); + _133 = _223 ? _222 : _132; + float2 _277; + float _278; + if (Uniforms.uData[_152].w < 0.5) + { + float _243 = (in.in_var_TEXCOORD0.x - Uniforms.uData[_146].x) + Uniforms.uData[_146].z; + float _245 = (Uniforms.uData[_146].x + Uniforms.uData[_146].z) - in.in_var_TEXCOORD0.x; + float _250 = (in.in_var_TEXCOORD0.y - Uniforms.uData[_146].y) + Uniforms.uData[_146].w; + float _252 = (Uniforms.uData[_146].y + Uniforms.uData[_146].w) - in.in_var_TEXCOORD0.y; + float _256 = precise::min(precise::min(_243, _245), precise::min(_250, _252)); + float4 _261 = exp((float4(_256) - float4(_243, _245, _250, _252)) / float4(Uniforms.uData[1].y)); + float _262 = _261.x; + float _263 = _261.y; + float _265 = _261.z; + float _267 = _261.w; + float _268 = ((_262 + _263) + _265) + _267; + _277 = float2(_263 - _262, _267 - _265) / float2(_268); + _278 = (Uniforms.uData[1].y * log(_268)) - _256; + } + else + { + _277 = _215; + _278 = _222; + } + bool _279 = _278 < _128; + if (_279) + { + _119 = _120; + _127 = _128; + } + else + { + bool _283 = _278 < _126; + _119 = select(_118, _277, bool2(_283)); + _127 = _283 ? _278 : _126; + } + _121 = select(_120, _277, bool2(_279)); + _129 = _279 ? _278 : _128; + _113 = _114; + _116 = _117; + _118 = _119; + _120 = _121; + _122 = _123; + _124 = _125; + _126 = _127; + _128 = _129; + _130 = _131; + _132 = _133; + _134++; + continue; + } + else + { + break; + } + } + float _289 = precise::max(Uniforms.uData[1].x, 9.9999997473787516355514526367188e-05); + float _294 = precise::max(_289 - abs(_132 - _130), 0.0) / _289; + float _295 = _294 * _294; + float _296 = _295 * 0.5; + float _301; + if (_130 < _132) + { + _301 = 1.0 - _296; + } + else + { + _301 = _296; + } + float _305 = precise::min(_132, _130) - ((_295 * _289) * 0.25); + float _310 = precise::max(_289 - abs(_128 - _126), 0.0) / _289; + float _311 = _310 * _310; + float _312 = _311 * 0.5; + float _317; + if (_126 < _128) + { + _317 = 1.0 - _312; + } + else + { + _317 = _312; + } + float _323 = fast::clamp(0.5 - _305, 0.0, 1.0); + if (_323 <= 0.0) + { + discard_fragment(); + } + if (in.in_var_COLOR.x < 0.5) + { + _678 = float4(0.0, 0.0, 0.0, _323); + break; + } + float4 _334 = mix(_124, _122, float4(_301)); + float2 _336 = mix(_120, _118, float2(_317)); + float _338 = precise::max(0.0, -(precise::min(_128, _126) - ((_311 * _289) * 0.25))); + float _339 = -_338; + float _342 = exp(_339 / Uniforms.uData[1].z); + float _348 = _334.y; + float _382; + float _383; + float2 _384; + if (_111) + { + float _356 = precise::max(precise::min(Uniforms.uData[5].y * mix(_116, _113, _301), Uniforms.uData[6].w), 1.0); + float _359 = fast::clamp(1.0 - (_338 / _356), 0.0, 1.0); + float _364 = (((Uniforms.uData[5].z * _356) * _348) * _359) * _359; + _382 = _359; + _383 = _364; + _384 = fast::clamp(((in.in_var_TEXCOORD0 - (_336 * _364)) - Uniforms.uData[0].xy) * Uniforms.uData[0].zw, float2(0.0), float2(1.0)); + } + else + { + _382 = 0.0; + _383 = 0.0; + _384 = fast::clamp(((in.in_var_TEXCOORD0 + (_336 * ((Uniforms.uData[2].x * _348) * _342))) - Uniforms.uData[0].xy) * Uniforms.uData[0].zw, float2(0.0), float2(1.0)); + } + float4 _388 = Frost.sample(FrostSampler, _384, level(0.0)); + bool _390 = Uniforms.uData[4].w > 0.5; + float4 _398; + if (_390) + { + _398 = Sharp.sample(SharpSampler, _384, level(0.0)); + } + else + { + _398 = _388; + } + bool _403; + if (_111) + { + _403 = Uniforms.uData[6].y > 0.0; + } + else + { + _403 = false; + } + bool _407; + if (_403) + { + _407 = _383 > 0.5; + } + else + { + _407 = false; + } + float4 _468; + float4 _469; + if (_407) + { + float2 _415 = (_336 * ((_383 * 0.039999999105930328369140625) * Uniforms.uData[6].y)) * Uniforms.uData[0].zw; + float2 _417 = fast::clamp(_384 + _415, float2(0.0), float2(1.0)); + float2 _419 = fast::clamp(_384 - _415, float2(0.0), float2(1.0)); + float4 _423 = Frost.sample(FrostSampler, _417, level(0.0)); + float _428 = _388.w; + float _429 = (_423.x / precise::max(_423.w, 9.9999997473787516355514526367188e-05)) * _428; + float4 _430 = _388; + _430.x = _429; + float4 _434 = Frost.sample(FrostSampler, _419, level(0.0)); + float _439 = (_434.z / precise::max(_434.w, 9.9999997473787516355514526367188e-05)) * _428; + _430.z = _439; + float4 _467; + if (_390) + { + float4 _447 = Sharp.sample(SharpSampler, _417, level(0.0)); + float4 _454 = _398; + _454.x = (_447.x / precise::max(_447.w, 9.9999997473787516355514526367188e-05)) * _398.w; + float4 _458 = Sharp.sample(SharpSampler, _419, level(0.0)); + _454.z = (_458.z / precise::max(_458.w, 9.9999997473787516355514526367188e-05)) * _398.w; + _467 = _454; + } + else + { + float4 _465 = _398; + _465.x = _429; + _465.z = _439; + _467 = _465; + } + _468 = _467; + _469 = _430; + } + else + { + _468 = _398; + _469 = _388; + } + float _477; + if (Uniforms.uData[6].x > 0.5) + { + _477 = 1.0; + } + else + { + _477 = _469.w; + } + float4 _486 = float4(_469.xyz / float3(precise::max(_469.w, 9.9999997473787516355514526367188e-05)), 1.0); + float4 _495 = float4(_468.xyz / float3(precise::max(_468.w, 9.9999997473787516355514526367188e-05)), 1.0); + bool _497 = Uniforms.uData[4].z > 0.5; + float _502; + if (_497) + { + _502 = Uniforms.uData[4].x; + } + else + { + _502 = 0.0; + } + float _511; + if (_111) + { + _511 = _502 * (1.0 - ((_382 * _382) * (1.0 - smoothstep(0.800000011920928955078125, 1.0, _502)))); + } + else + { + _511 = _502; + } + float _514 = 1.0 - _511; + float _522; + if (_111) + { + _522 = _382; + } + else + { + _522 = _342 * _342; + } + float _525 = fast::clamp(((Uniforms.uData[2].y * _348) * _522) * _514, 0.0, 1.0); + float4 _531 = ((_495 * _525) + ((mix(_495, _486, float4(fast::clamp(_334.x, 0.0, 1.0))) * _514) * (1.0 - _525))) + (Uniforms.uData[3] * _511); + float _536 = dot(_336, float2(-0.707106769084930419921875)); + float _537 = precise::max(_536, 0.0); + float _539 = precise::max(-_536, 0.0); + float _542 = abs(_536); + bool _547; + if (_111) + { + _547 = Uniforms.uData[6].z > 0.0; + } + else + { + _547 = false; + } + float _567; + if (_547) + { + _567 = (Uniforms.uData[6].z * (1.0 - smoothstep(0.20000000298023223876953125, 0.699999988079071044921875, _542))) * exp((-precise::max(0.0, -_305)) / (Uniforms.uData[2].z * 2.5)); + } + else + { + _567 = 0.85000002384185791015625 * (1.0 - smoothstep(0.0, Uniforms.uData[2].z * 0.625, -_305)); + } + float _575; + if (_111) + { + _575 = precise::max((Uniforms.uData[5].w * _382) * _382, _567); + } + else + { + _575 = 0.0; + } + float _578 = _477 * (1.0 - _575); + float4 _584 = float4((_531 + (_486 * (1.0 - _531.w))).xyz * _578, _578 + _575); + float _603; + if (_111) + { + float _594; + if (_547) + { + _594 = Uniforms.uData[6].z * smoothstep(0.75, 1.0, _542); + } + else + { + _594 = 0.25; + } + _603 = _594; + } + else + { + _603 = (0.449999988079071044921875 * ((_537 * sqrt(_537)) + ((0.75 * _539) * sqrt(_539)))) + 0.0599999986588954925537109375; + } + float _630; + if (_547) + { + _630 = exp((-precise::max(0.0, -_305)) / (Uniforms.uData[2].z * 1.25)); + } + else + { + float _629; + if (_111) + { + float _617 = -_305; + _629 = smoothstep(Uniforms.uData[2].z * 0.3125, Uniforms.uData[2].z * 0.625, _617) * (1.0 - smoothstep(Uniforms.uData[2].z * 1.5, Uniforms.uData[2].z * 2.0, _617)); + } + else + { + _629 = exp((-precise::max(0.0, -_305)) / Uniforms.uData[2].z); + } + _630 = _629; + } + float _637; + if (_111) + { + _637 = 0.0; + } + else + { + _637 = (0.039999999105930328369140625 * exp(_339 / Uniforms.uData[1].w)) * _537; + } + float _643; + if (_497) + { + _643 = Uniforms.uData[4].y; + } + else + { + _643 = 0.0; + } + float3 _653 = _584.xyz + float3(fast::clamp((_643 + _334.z) + ((_348 * Uniforms.uData[2].w) * ((_630 * _603) + _637)), 0.0, 1.0)); + float4 _655 = fast::clamp(float4(_653.x, _653.y, _653.z, _584.w), float4(0.0), float4(1.0)); + float _662 = precise::max(_655.w, precise::max(_655.x, precise::max(_655.y, _655.z))); + float4 _663 = _655; + _663.w = _662; + float3 _674 = _663.xyz + float3(((fract(52.98291778564453125 * fract(dot(in.in_var_TEXCOORD0, float2(0.067110560834407806396484375, 0.005837149918079376220703125)))) - 0.5) * Uniforms.uData[5].x) * _662); + _678 = fast::clamp(float4(_674.x, _674.y, _674.z, _663.w), float4(0.0), float4(1.0)) * _323; + break; + } while(false); + out.out_var_SV_Target0 = _678; + return out; +} + diff --git a/core/gfx/shaders/liquid_glass.fragment.hlsl b/core/gfx/shaders/liquid_glass.fragment.hlsl index 42bf83ad9..31f1d22f2 100644 --- a/core/gfx/shaders/liquid_glass.fragment.hlsl +++ b/core/gfx/shaders/liquid_glass.fragment.hlsl @@ -1,13 +1,19 @@ -// `liquid_glass.glsl` in HLSL, for fizzy's native backend on Vulkan and D3D12: the same program -// line for line as it and `liquid_glass.metal` — change all three together, and -// `LiquidField.sample` with them. shadercross compiles it to the two forms `LiquidField` embeds: +// Liquid glass (`core/gfx/LiquidField.zig`): rounded boxes run together by a smooth minimum, +// frosted and refracting, in one pass at every pixel. // -// shadercross liquid_glass.fragment.hlsl -o compiled/spv/liquid_glass.fragment.spv -// shadercross liquid_glass.fragment.hlsl -o compiled/dxil/liquid_glass.fragment.dxil +// The one source of the program: written here, in HLSL, and generated from it for every backend +// by `scripts/compile-shaders.sh` — SPIR-V (Vulkan) and DXIL (D3D12) for fizzy's native backend, +// Metal for it on macOS, GLSL ES 3.00 for the web — into `compiled/`, which CI checks against this +// (`.github/workflows/shaders.yml`). Never edit those by hand: change this, and commit what CI +// compiles. `LiquidField.sample` mirrors the field on the CPU, line for line; change it with this. +// +// Each quad's vertex colour says what it draws: r the pass (0 punches the glass's coverage out +// of what is there, 1 adds the glass), g and b the first of its shapes and how many. // // To the interface the native backend's programs take (`program_example.fragment.hlsl`): the // default vertex shader's outputs as inputs, the draw's own texture (the frost) at slot 0, the -// sharp picture at 1, the uniforms as one array. +// sharp picture at 1, the uniforms as one array. On the web the same, by the names +// `core.gfx.programs` gives them (the generator renames them). #define G 7 #define MAX_SHAPES 16 @@ -32,6 +38,8 @@ cbuffer Uniforms : register(b0, space3) float4 uData[G + 3 * MAX_SHAPES]; }; +// Signed distance to a box of half-size `b` at the origin, its corners rounded `r` +// (top-left, top-right, bottom-right, bottom-left; y down), and which way is out. float sdRoundBox(float2 p, float2 b, float4 r, out float2 g) { float rr = p.x > 0.0 ? (p.y > 0.0 ? r.z : r.y) : (p.y > 0.0 ? r.w : r.x); @@ -47,6 +55,8 @@ float sdRoundBox(float2 p, float2 b, float4 r, out float2 g) return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - rr; } +// `liquid_glass.fieldAt`: a soft minimum of the distances to the box's four sides, so the way +// out turns smoothly round a corner and never folds along a diagonal. Negative inside. float softBox(float2 p, float4 s, float k, out float2 o) { float4 d = float4(p.x - s.x + s.z, s.x + s.z - p.x, p.y - s.y + s.w, s.y + s.w - p.y); @@ -57,6 +67,7 @@ float softBox(float2 p, float4 s, float k, out float2 o) return k * log(sum) - m; } +// iq's quadratic smooth minimum, and how much of `b` is in it. float smin(float a, float b, float k, out float m) { float h = max(k - abs(a - b), 0.0) / k; @@ -82,8 +93,11 @@ float4 main(PSInput input) : SV_Target0 float4 g4 = uData[4]; float4 g5 = uData[5]; // dither; the lens: its band's share of the shape, its bend, its shade float4 g6 = uData[6]; // opaque window; the lens: dispersion, a drop's diagonal light (0: a window's hairline), the band's cap + // Apple's lens (`glass_look.inApp`): the picture pulled inward from a band near the rim, + // magnified and, past a bend of 1, folded there. Off, the earlier glass: pulled from outside. bool inward = g5.y > 0.0; + // The two nearest shapes, for the outline and for the way out. float d1 = 1e9; float d2 = 1e9; float4 m1 = float4(0.0, 0.0, 0.0, 0.0); float4 m2 = float4(0.0, 0.0, 0.0, 0.0); float h1 = 1e9; float h2 = 1e9; float f1 = 1e9; float f2 = 1e9; float2 o1 = float2(0.0, 0.0); float2 o2 = float2(0.0, 0.0); @@ -122,6 +136,10 @@ float4 main(PSInput input) : SV_Target0 // Level 0 explicitly: the frost has no mips, and an implicit level after `discard` is a // derivative in divergent control flow, which Metal and GLSL let pass and HLSL does not. +// What the glass shows here. Apple's lens: from further in, within the band near the rim — `t` +// 1 at the edge, 0 a band in — by `bend` bands at the edge, falling off as t², so the picture +// magnifies toward the rim and folds where the slope passes 1. The earlier glass: further out +// along the way out, as steep as it is (`seen`). float t = 0.0; float disp = 0.0; float2 uv; @@ -166,6 +184,8 @@ float4 main(PSInput input) : SV_Target0 frost = float4(frost.rgb / max(frost.a, 1e-4), 1.0); sharp = float4(sharp.rgb / max(sharp.a, 1e-4), 1.0); +// The frost at its share of a frost / tint mix, the blur coming in from sharp. The window's +// colour comes in whatever the blur: the opacity is the colour, under clear glass too. float mixv = g4.z > 0.5 ? g4.x : 0.0; // Apple's lens: the window's colour fades out across the band, as an OS window's does // (`glass_look.window`, its feather: strong most of the way, gone at the edge), so the edge stays @@ -173,8 +193,11 @@ float4 main(PSInput input) : SV_Target0 // too (`glass_look.shine_start`, 0.8 to 1). if (inward) mixv *= 1.0 - t * t * (1.0 - smoothstep(0.8, 1.0, mixv)); float4 c = lerp(sharp, frost, b) * (1.0 - mixv); +// The rim's clearer glass over it (`drawClear`): for Apple's lens, the band, so the rim stays +// clear glass while the middle frosts. float a = clamp(g2.y * lens * (inward ? t : steepR * steepR) * (1.0 - mixv), 0.0, 1.0); c = sharp * a + c * (1.0 - sharp.a * a); +// The tint (`addTint`). c += g3 * mixv; // A translucent tint lies over the frost, not over what is behind the window: the glass is // opaque here, and `under` alone says how much of the desktop's material shows through it. @@ -199,6 +222,7 @@ float4 main(PSInput input) : SV_Target0 : 0.85 * (1.0 - smoothstep(0.0, 0.5 * px, -D)); float shade = inward ? max(g5.w * t * t, edge_dark) : 0.0; float4 o = float4(c.rgb * (under * (1.0 - shade)), under * (1.0 - shade) + shade); +// The lift and the rim's light (`drawLift`), brightest facing the top left. float spec = inward ? (diagonal ? g6.z * smoothstep(0.75, 1.0, af) : 0.25) : 0.45 * (toward * sqrt(toward) + 0.75 * away * sqrt(away)) + 0.06; float line_ = diagonal ? exp(-max(0.0, -D) / px) diff --git a/core/gfx/shaders/liquid_glass.glsl b/core/gfx/shaders/liquid_glass.glsl deleted file mode 100644 index 1b2c605e0..000000000 --- a/core/gfx/shaders/liquid_glass.glsl +++ /dev/null @@ -1,224 +0,0 @@ -// Liquid glass (`core/gfx/LiquidField.zig`): rounded boxes run together by a smooth minimum, -// frosted and refracting, in one pass at every pixel. Written against the program prelude -// (`core/gfx/programs.zig`): VARYING, TEX, FRAG_COLOR, MAX_VEC4. -// -// Mirrors `LiquidField.sample` line for line, as `liquid_glass.metal` and -// `liquid_glass.fragment.hlsl` (compiled to SPIR-V and DXIL) mirror it; change them all together. -// -// Each quad's vertex colour says what it draws: r the pass (0 punches the glass's coverage out -// of what is there, 1 adds the glass), g and b the first of its shapes and how many. - -#define G 7 -#define MAX_SHAPES 16 - -VARYING vec4 vColor; -VARYING vec2 vTextureCoord; - -uniform sampler2D uSampler; // the frost -uniform sampler2D uTex1; // the picture before the blur -uniform vec4 uData[MAX_VEC4]; - -// Signed distance to a box of half-size `b` at the origin, its corners rounded `r` -// (top-left, top-right, bottom-right, bottom-left; y down), and which way is out. -float sdRoundBox(vec2 p, vec2 b, vec4 r, out vec2 g) { - float rr = p.x > 0.0 ? (p.y > 0.0 ? r.z : r.y) : (p.y > 0.0 ? r.w : r.x); - vec2 q = abs(p) - b + rr; - vec2 s = vec2(p.x < 0.0 ? -1.0 : 1.0, p.y < 0.0 ? -1.0 : 1.0); - if (q.x > 0.0 && q.y > 0.0) { - g = normalize(q) * s; - } else if (q.x > q.y) { - g = vec2(s.x, 0.0); - } else { - g = vec2(0.0, s.y); - } - return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - rr; -} - -// `liquid_glass.fieldAt`: a soft minimum of the distances to the box's four sides, so the way -// out turns smoothly round a corner and never folds along a diagonal. Negative inside. -float softBox(vec2 p, vec4 s, float k, out vec2 o) { - vec4 d = vec4(p.x - s.x + s.z, s.x + s.z - p.x, p.y - s.y + s.w, s.y + s.w - p.y); - float m = min(min(d.x, d.y), min(d.z, d.w)); - vec4 w = exp((m - d) / k); - float sum = w.x + w.y + w.z + w.w; - o = vec2(w.y - w.x, w.w - w.z) / sum; - return k * log(sum) - m; -} - -// iq's quadratic smooth minimum, and how much of `b` is in it. -float smin(float a, float b, float k, out float m) { - float h = max(k - abs(a - b), 0.0) / k; - m = h * h * 0.5; - if (b < a) m = 1.0 - m; - return min(a, b) - h * h * k * 0.25; -} - -float noise(vec2 p) { - return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715)))); -} - -void main() { - int first = int(vColor.g * 255.0 + 0.5); - int count = int(vColor.b * 255.0 + 0.5); - vec2 p = vTextureCoord; - vec4 g0 = uData[0]; // the frost: where it starts, and 1 / its size - vec4 g1 = uData[1]; // merge k, softness, refraction depth, light depth - vec4 g2 = uData[2]; // reach, clarity, rim line width, light - vec4 g3 = uData[3]; // tint, premultiplied - vec4 g4 = uData[4]; // mix, lift, has tint, has sharp - vec4 g5 = uData[5]; // dither; the lens: its band's share of the shape, its bend, its shade - vec4 g6 = uData[6]; // opaque window; the lens: dispersion, a drop's diagonal light (0: a window's hairline), the band's cap - // Apple's lens (`glass_look.inApp`): the picture pulled inward from a band near the rim, - // magnified and, past a bend of 1, folded there. Off, the earlier glass: pulled from outside. - bool inward = g5.y > 0.0; - - // The two nearest shapes, for the outline and for the way out. - float d1 = 1e9; float d2 = 1e9; vec4 m1 = vec4(0.0); vec4 m2 = vec4(0.0); - float h1 = 1e9; float h2 = 1e9; - float f1 = 1e9; float f2 = 1e9; vec2 o1 = vec2(0.0); vec2 o2 = vec2(0.0); - for (int i = 0; i < MAX_SHAPES; i++) { - if (i < first) continue; - if (i >= first + count) break; - vec4 s0 = uData[G + 3 * i]; - vec4 s1 = uData[G + 3 * i + 1]; - vec4 s2 = uData[G + 3 * i + 2]; - vec2 g; - float d = sdRoundBox(p - s0.xy, s0.zw, min(s1, vec4(min(s0.z, s0.w))), g); - float hs = min(s0.z, s0.w); - if (d < d1) { d2 = d1; m2 = m1; h2 = h1; d1 = d; m1 = s2; h1 = hs; } else if (d < d2) { d2 = d; m2 = s2; h2 = hs; } - vec2 o; - float f = d; - if (s2.w < 0.5) { - f = softBox(p, s0, g1.y, o); - } else { - o = g; - } - if (f < f1) { f2 = f1; o2 = o1; f1 = f; o1 = o; } else if (f < f2) { f2 = f; o2 = o; } - } - float k = max(g1.x, 0.0001); - float wm; float wf; - float D = smin(d1, d2, k, wm); - float F = smin(f1, f2, k, wf); - float cov = clamp(0.5 - D, 0.0, 1.0); - if (cov <= 0.0) discard; - if (vColor.r < 0.5) { - FRAG_COLOR = vec4(0.0, 0.0, 0.0, cov); - return; - } - - vec4 mat = mix(m1, m2, wm); // blur, lens, light, field - vec2 outv = mix(o1, o2, wf); - float soft = max(0.0, -F); - float steepR = exp(-soft / g1.z); - float steepL = exp(-soft / g1.w); - float b = clamp(mat.x, 0.0, 1.0); - float lens = mat.y; - - // What the glass shows here. Apple's lens: from further in, within the band near the rim — `t` - // 1 at the edge, 0 a band in — by `bend` bands at the edge, falling off as t², so the picture - // magnifies toward the rim and folds where the slope passes 1. The earlier glass: further out - // along the way out, as steep as it is (`seen`). - float t = 0.0; - float disp = 0.0; - vec2 uv; - if (inward) { - float W = max(min(g5.y * mix(h1, h2, wm), g6.w), 1.0); - t = clamp(1.0 - soft / W, 0.0, 1.0); - disp = g5.z * W * lens * t * t; - uv = clamp((p - outv * disp - g0.xy) * g0.zw, 0.0, 1.0); - } else { - uv = clamp((p + outv * (g2.x * lens * steepR) - g0.xy) * g0.zw, 0.0, 1.0); - } - vec4 frost = TEX(uSampler, uv); - vec4 sharp = g4.w > 0.5 ? TEX(uTex1, uv) : frost; - // A little colour fringe where the band bends hardest: red from a touch less far in, blue a - // touch further. - // The colour fringe: red and blue from either side, each un-premultiplied by its own alpha and - // put back at the middle's. By the middle's alone, at the edge of a clear part of the window - // the two alphas differ widely, and the fringe came out as red and blue specks. - if (inward && g6.y > 0.0 && disp > 0.5) { - vec2 du = outv * (disp * 0.04 * g6.y) * g0.zw; - vec2 uvr = clamp(uv + du, 0.0, 1.0); - vec2 uvb = clamp(uv - du, 0.0, 1.0); - vec4 frost_r = TEX(uSampler, uvr); - frost.r = frost_r.r / max(frost_r.a, 1e-4) * frost.a; - vec4 frost_b = TEX(uSampler, uvb); - frost.b = frost_b.b / max(frost_b.a, 1e-4) * frost.a; - if (g4.w > 0.5) { - vec4 sharp_r = TEX(uTex1, uvr); - sharp.r = sharp_r.r / max(sharp_r.a, 1e-4) * sharp.a; - vec4 sharp_b = TEX(uTex1, uvb); - sharp.b = sharp_b.b / max(sharp_b.a, 1e-4) * sharp.a; - } else { - sharp.r = frost.r; - sharp.b = frost.b; - } - } - // Worked out over an opaque picture of what the glass covers, then made exactly as - // see-through as that is (`under`): over a translucent window (vibrancy, Acrylic) the - // desktop's material shows through the glass as much as through the window round it. Laid - // over the translucent picture as it was, the rim — where the clear glass lies over the - // frost — came out more opaque than the face, and the material lit the face and not the rim. - // Opaque where the window is (`LiquidField.publishOpaqueWindow`): its alpha is only its shape. - float under = uData[6].x > 0.5 ? 1.0 : frost.a; - frost = vec4(frost.rgb / max(frost.a, 1e-4), 1.0); - sharp = vec4(sharp.rgb / max(sharp.a, 1e-4), 1.0); - - // The frost at its share of a frost / tint mix, the blur coming in from sharp. The window's - // colour comes in whatever the blur: the opacity is the colour, under clear glass too. - float mixv = g4.z > 0.5 ? g4.x : 0.0; - // Apple's lens: the window's colour fades out across the band, as an OS window's does - // (`glass_look.window`, its feather: strong most of the way, gone at the edge), so the edge stays - // the lens bending what is behind it — unless the colour is near whole, when it covers the edge - // too (`glass_look.shine_start`, 0.8 to 1). - if (inward) mixv *= 1.0 - t * t * (1.0 - smoothstep(0.8, 1.0, mixv)); - vec4 c = mix(sharp, frost, b) * (1.0 - mixv); - // The rim's clearer glass over it (`drawClear`): for Apple's lens, the band, so the rim stays - // clear glass while the middle frosts. - float a = clamp(g2.y * lens * (inward ? t : steepR * steepR) * (1.0 - mixv), 0.0, 1.0); - c = sharp * a + c * (1.0 - sharp.a * a); - // The tint (`addTint`). - c += g3 * mixv; - // A translucent tint lies over the frost, not over what is behind the window: the glass is - // opaque here, and `under` alone says how much of the desktop's material shows through it. - c += frost * (1.0 - c.a); - // The way the edge faces, against a light from the top left. - float facing = dot(outv, vec2(-0.70710678, -0.70710678)); - float toward = max(facing, 0.0); - float away = max(-facing, 0.0); - // Apple's lens has two edges, measured beside the OS's (`g6.z`, `glass_look.InApp.diagonal`): - // - a drop's (> 0): lit along the diagonal the light runs, top left and bottom right, about - // three pixels in and only near the diagonal; darkened across the other, top right and - // bottom left; nothing at the sides square to the light; - // - a window's (0): a dark hairline at the very edge and a bright one, two pixels, just inside - // it, the same on every side (the OS window's own border, as Liquid Glass windows have). - // Nothing across the band in either. Shade is the glass's own, not the picture's: a dark layer - // over it, as whole where the picture is see-through as where it is not. `px` is a point, in - // pixels (the rim line's width is 0.8 of one, `LiquidField.rim_line_width`). - float px = g2.z * 1.25; - float af = abs(facing); - bool diagonal = inward && g6.z > 0.0; - float edge_dark = diagonal ? g6.z * (1.0 - smoothstep(0.2, 0.7, af)) * exp(-max(0.0, -D) / (2.0 * px)) - : 0.85 * (1.0 - smoothstep(0.0, 0.5 * px, -D)); - float shade = inward ? max(g5.w * t * t, edge_dark) : 0.0; - vec4 o = vec4(c.rgb * (under * (1.0 - shade)), under * (1.0 - shade) + shade); - // The lift and the rim's light (`drawLift`), brightest facing the top left. - float spec = inward ? (diagonal ? g6.z * smoothstep(0.75, 1.0, af) : 0.25) - : 0.45 * (toward * sqrt(toward) + 0.75 * away * sqrt(away)) + 0.06; - float line = diagonal ? exp(-max(0.0, -D) / px) - : inward ? smoothstep(0.25 * px, 0.5 * px, -D) * (1.0 - smoothstep(1.2 * px, 1.6 * px, -D)) - : exp(-max(0.0, -D) / g2.z); - float lit = line * spec + (inward ? 0.0 : 0.04 * steepL * toward); - float lift = (g4.z > 0.5 ? g4.y : 0.0) + mat.z; - // The glass's own light, added whole. Scaled with the picture, it went wherever the picture - // was see-through — over the explorer, which is the window's OS glass — and the glass all but - // vanished there, rim and all, where Liquid Glass keeps its bright rim over anything. - o.rgb += vec3(clamp(lift + lens * g2.w * lit, 0.0, 1.0)); - o = clamp(o, 0.0, 1.0); - // Light over a see-through picture carries its own alpha: brighter than its alpha, a pixel is - // no colour premultiplied, and the frost captured from this frame next (`rgb / a`) blew it up - // into bright specks wherever the window is clear — over the explorer. Opaque, alpha is 1. - o.a = max(o.a, max(o.r, max(o.g, o.b))); - o.rgb += (noise(gl_FragCoord.xy) - 0.5) * g5.x * o.a; - FRAG_COLOR = clamp(o, 0.0, 1.0) * cov; -} diff --git a/core/gfx/shaders/liquid_glass.metal b/core/gfx/shaders/liquid_glass.metal deleted file mode 100644 index 994ecd444..000000000 --- a/core/gfx/shaders/liquid_glass.metal +++ /dev/null @@ -1,217 +0,0 @@ -// `liquid_glass.glsl` in Metal, for fizzy's native backend (`backend/`): the same -// program line for line — change it, `liquid_glass.fragment.hlsl` and `LiquidField.sample` -// together. To the interface the native backend's programs take: entry point `main0`, the -// default vertex shader's outputs as inputs, the draw's own texture (the frost) at 0, the sharp -// picture at 1, the uniforms as buffer 0. - -#include -using namespace metal; - -#define G 7 -#define MAX_SHAPES 16 - -struct main0_in -{ - float4 color [[user(locn0)]]; - float2 uv [[user(locn1)]]; -}; - -struct main0_out -{ - float4 color [[color(0)]]; -}; - -static float sdRoundBox(float2 p, float2 b, float4 r, thread float2 &g) -{ - float rr = p.x > 0.0 ? (p.y > 0.0 ? r.z : r.y) : (p.y > 0.0 ? r.w : r.x); - float2 q = abs(p) - b + rr; - float2 s = float2(p.x < 0.0 ? -1.0 : 1.0, p.y < 0.0 ? -1.0 : 1.0); - if (q.x > 0.0 && q.y > 0.0) { - g = normalize(q) * s; - } else if (q.x > q.y) { - g = float2(s.x, 0.0); - } else { - g = float2(0.0, s.y); - } - return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - rr; -} - -static float softBox(float2 p, float4 s, float k, thread float2 &o) -{ - float4 d = float4(p.x - s.x + s.z, s.x + s.z - p.x, p.y - s.y + s.w, s.y + s.w - p.y); - float m = min(min(d.x, d.y), min(d.z, d.w)); - float4 w = exp((m - d) / k); - float sum = w.x + w.y + w.z + w.w; - o = float2(w.y - w.x, w.w - w.z) / sum; - return k * log(sum) - m; -} - -static float smin(float a, float b, float k, thread float &m) -{ - float h = max(k - abs(a - b), 0.0) / k; - m = h * h * 0.5; - if (b < a) m = 1.0 - m; - return min(a, b) - h * h * k * 0.25; -} - -static float noise(float2 p) -{ - return fract(52.9829189 * fract(dot(p, float2(0.06711056, 0.00583715)))); -} - -fragment main0_out main0( - main0_in in [[stage_in]], - float4 frag [[position]], - texture2d Frost [[texture(0)]], sampler FrostSampler [[sampler(0)]], - texture2d Sharp [[texture(1)]], sampler SharpSampler [[sampler(1)]], - constant float4 *uData [[buffer(0)]]) -{ - main0_out out = {}; - int first = int(in.color.g * 255.0 + 0.5); - int count = int(in.color.b * 255.0 + 0.5); - float2 p = in.uv; - float4 g0 = uData[0]; - float4 g1 = uData[1]; - float4 g2 = uData[2]; - float4 g3 = uData[3]; - float4 g4 = uData[4]; - float4 g5 = uData[5]; // dither; the lens: its band's share of the shape, its bend, its shade - float4 g6 = uData[6]; // opaque window; the lens: dispersion, a drop's diagonal light (0: a window's hairline), the band's cap - bool inward = g5.y > 0.0; - - float d1 = 1e9; float d2 = 1e9; float4 m1 = float4(0.0); float4 m2 = float4(0.0); - float h1 = 1e9; float h2 = 1e9; - float f1 = 1e9; float f2 = 1e9; float2 o1 = float2(0.0); float2 o2 = float2(0.0); - for (int i = first; i < first + count && i < MAX_SHAPES; i++) { - float4 s0 = uData[G + 3 * i]; - float4 s1 = uData[G + 3 * i + 1]; - float4 s2 = uData[G + 3 * i + 2]; - float2 g; - float d = sdRoundBox(p - s0.xy, s0.zw, min(s1, float4(min(s0.z, s0.w))), g); - float hs = min(s0.z, s0.w); - if (d < d1) { d2 = d1; m2 = m1; h2 = h1; d1 = d; m1 = s2; h1 = hs; } else if (d < d2) { d2 = d; m2 = s2; h2 = hs; } - float2 o; - float f = d; - if (s2.w < 0.5) { - f = softBox(p, s0, g1.y, o); - } else { - o = g; - } - if (f < f1) { f2 = f1; o2 = o1; f1 = f; o1 = o; } else if (f < f2) { f2 = f; o2 = o; } - } - float k = max(g1.x, 0.0001); - float wm; float wf; - float D = smin(d1, d2, k, wm); - float F = smin(f1, f2, k, wf); - float cov = clamp(0.5 - D, 0.0, 1.0); - if (cov <= 0.0) discard_fragment(); - if (in.color.r < 0.5) { - out.color = float4(0.0, 0.0, 0.0, cov); - return out; - } - - float4 mat = mix(m1, m2, wm); - float2 outv = mix(o1, o2, wf); - float soft = max(0.0, -F); - float steepR = exp(-soft / g1.z); - float steepL = exp(-soft / g1.w); - float b = clamp(mat.x, 0.0, 1.0); - float lens = mat.y; - - float t = 0.0; - float disp = 0.0; - float2 uv; - if (inward) { - float W = max(min(g5.y * mix(h1, h2, wm), g6.w), 1.0); - t = clamp(1.0 - soft / W, 0.0, 1.0); - disp = g5.z * W * lens * t * t; - uv = clamp((p - outv * disp - g0.xy) * g0.zw, 0.0, 1.0); - } else { - uv = clamp((p + outv * (g2.x * lens * steepR) - g0.xy) * g0.zw, 0.0, 1.0); - } - float4 frost = Frost.sample(FrostSampler, uv); - float4 sharp = g4.w > 0.5 ? Sharp.sample(SharpSampler, uv) : frost; - // The colour fringe: red and blue from either side, each un-premultiplied by its own alpha and - // put back at the middle's. By the middle's alone, at the edge of a clear part of the window - // the two alphas differ widely, and the fringe came out as red and blue specks. - if (inward && g6.y > 0.0 && disp > 0.5) { - float2 du = outv * (disp * 0.04 * g6.y) * g0.zw; - float2 uvr = clamp(uv + du, 0.0, 1.0); - float2 uvb = clamp(uv - du, 0.0, 1.0); - float4 frost_r = Frost.sample(FrostSampler, uvr); - frost.r = frost_r.r / max(frost_r.a, 1e-4) * frost.a; - float4 frost_b = Frost.sample(FrostSampler, uvb); - frost.b = frost_b.b / max(frost_b.a, 1e-4) * frost.a; - if (g4.w > 0.5) { - float4 sharp_r = Sharp.sample(SharpSampler, uvr); - sharp.r = sharp_r.r / max(sharp_r.a, 1e-4) * sharp.a; - float4 sharp_b = Sharp.sample(SharpSampler, uvb); - sharp.b = sharp_b.b / max(sharp_b.a, 1e-4) * sharp.a; - } else { - sharp.r = frost.r; - sharp.b = frost.b; - } - } - // Worked out over an opaque picture of what the glass covers, then made exactly as - // see-through as that is (`under`): over a translucent window (vibrancy, Acrylic) the - // desktop's material shows through the glass as much as through the window round it. Laid - // over the translucent picture as it was, the rim — where the clear glass lies over the - // frost — came out more opaque than the face, and the material lit the face and not the rim. - // Opaque where the window is (`LiquidField.publishOpaqueWindow`): its alpha is only its shape. - float under = uData[6].x > 0.5 ? 1.0 : frost.a; - frost = float4(frost.rgb / max(frost.a, 1e-4), 1.0); - sharp = float4(sharp.rgb / max(sharp.a, 1e-4), 1.0); - - float mixv = g4.z > 0.5 ? g4.x : 0.0; - // Apple's lens: the window's colour fades out across the band, as an OS window's does - // (`glass_look.window`, its feather: strong most of the way, gone at the edge), so the edge stays - // the lens bending what is behind it — unless the colour is near whole, when it covers the edge - // too (`glass_look.shine_start`, 0.8 to 1). - if (inward) mixv *= 1.0 - t * t * (1.0 - smoothstep(0.8, 1.0, mixv)); - float4 c = mix(sharp, frost, b) * (1.0 - mixv); - float a = clamp(g2.y * lens * (inward ? t : steepR * steepR) * (1.0 - mixv), 0.0, 1.0); - c = sharp * a + c * (1.0 - sharp.a * a); - c += g3 * mixv; - // A translucent tint lies over the frost, not over what is behind the window: the glass is - // opaque here, and `under` alone says how much of the desktop's material shows through it. - c += frost * (1.0 - c.a); - // The way the edge faces, against a light from the top left. - float facing = dot(outv, float2(-0.70710678, -0.70710678)); - float toward = max(facing, 0.0); - float away = max(-facing, 0.0); - // Apple's lens has two edges, measured beside the OS's (`g6.z`, `glass_look.InApp.diagonal`): - // - a drop's (> 0): lit along the diagonal the light runs, top left and bottom right, about - // three pixels in and only near the diagonal; darkened across the other, top right and - // bottom left; nothing at the sides square to the light; - // - a window's (0): a dark hairline at the very edge and a bright one, two pixels, just inside - // it, the same on every side (the OS window's own border, as Liquid Glass windows have). - // Nothing across the band in either. Shade is the glass's own, not the picture's: a dark layer - // over it, as whole where the picture is see-through as where it is not. `px` is a point, in - // pixels (the rim line's width is 0.8 of one, `LiquidField.rim_line_width`). - float px = g2.z * 1.25; - float af = abs(facing); - bool diagonal = inward && g6.z > 0.0; - float edge_dark = diagonal ? g6.z * (1.0 - smoothstep(0.2, 0.7, af)) * exp(-max(0.0, -D) / (2.0 * px)) - : 0.85 * (1.0 - smoothstep(0.0, 0.5 * px, -D)); - float shade = inward ? max(g5.w * t * t, edge_dark) : 0.0; - float4 o = float4(c.rgb * (under * (1.0 - shade)), under * (1.0 - shade) + shade); - float spec = inward ? (diagonal ? g6.z * smoothstep(0.75, 1.0, af) : 0.25) - : 0.45 * (toward * sqrt(toward) + 0.75 * away * sqrt(away)) + 0.06; - float line = diagonal ? exp(-max(0.0, -D) / px) - : inward ? smoothstep(0.25 * px, 0.5 * px, -D) * (1.0 - smoothstep(1.2 * px, 1.6 * px, -D)) - : exp(-max(0.0, -D) / g2.z); - float lit = line * spec + (inward ? 0.0 : 0.04 * steepL * toward); - float lift = (g4.z > 0.5 ? g4.y : 0.0) + mat.z; - // The glass's own light, added whole. Scaled with the picture, it went wherever the picture - // was see-through — over the explorer, which is the window's OS glass — and the glass all but - // vanished there, rim and all, where Liquid Glass keeps its bright rim over anything. - o.rgb += float3(clamp(lift + lens * g2.w * lit, 0.0, 1.0)); - o = clamp(o, 0.0, 1.0); - // Light over a see-through picture carries its own alpha: brighter than its alpha, a pixel is - // no colour premultiplied, and the frost captured from this frame next (`rgb / a`) blew it up - // into bright specks wherever the window is clear — over the explorer. Opaque, alpha is 1. - o.a = max(o.a, max(o.r, max(o.g, o.b))); - o.rgb += (noise(frag.xy) - 0.5) * g5.x * o.a; - out.color = clamp(o, 0.0, 1.0) * cov; - return out; -} diff --git a/scripts/compile-shaders.sh b/scripts/compile-shaders.sh new file mode 100755 index 000000000..7f890b65f --- /dev/null +++ b/scripts/compile-shaders.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# Compile every HLSL shader that has compiled forms committed beside it, into OUT (same layout): +# +# X.hlsl → compiled/spv/X.spv SPIR-V (Vulkan) shadercross +# compiled/dxil/X.dxil DXIL (D3D12) shadercross +# compiled/msl/X.msl Metal (SDL_GPU, macOS) shadercross +# compiled/glsl/X.glsl GLSL ES 3.00 (WebGL2) spirv-cross, from the SPIR-V +# +# Only the forms that exist in the repository are made: a shader opts into one by committing it +# (an empty file to begin with). The HLSL is the one source; the rest are generated and checked +# (`.github/workflows/shaders.yml`), never edited by hand. +# +# The GLSL is complete source, for the web backend's programs (`core.gfx.programs`): its inputs +# are named as the web's vertex shader writes them (`vColor`, `vTextureCoord`), its samplers by +# unit (`uSampler` 0, `uTex1` 1), its uniforms `uData`, the names `web/web.js` binds. +# +# Needs `shadercross` and `spirv-cross` on PATH. Exits non-zero if any shader does not compile. +set -euo pipefail +OUT=${1:?usage: compile-shaders.sh OUT} +# Absolute: each shader compiles from its own directory (its `#include`s are relative). +mkdir -p "$OUT"; OUT=$(cd "$OUT" && pwd) +failed=0 +while IFS= read -r hlsl; do + dir=$(dirname "$hlsl"); name=$(basename "$hlsl" .hlsl) + for form in spv dxil msl glsl; do + [ -f "$dir/compiled/$form/$name.$form" ] || continue + dest="$OUT/$dir/compiled/$form/$name.$form" + mkdir -p "$(dirname "$dest")" + case $form in + spv | dxil | msl) + if ! (cd "$dir" && shadercross "$name.hlsl" -o "$dest"); then + echo "::error file=$hlsl::$hlsl does not compile to $form"; failed=1 + fi ;; + glsl) + spv="$OUT/$dir/compiled/spv/$name.spv" + [ -f "$spv" ] || (cd "$dir" && shadercross "$name.hlsl" -o "$spv.tmp" && mv "$spv.tmp" "$spv") + # NaN checks relaxed: HLSL's `min` and `max` came out wrapped in `isnan` tests, a cost the + # web's frame pays at every pixel for nothing the program relies on. + if ! spirv-cross "$spv" --es --version 300 --flatten-ubo --relax-nan-checks \ + --rename-interface-variable in 0 vColor \ + --rename-interface-variable in 1 vTextureCoord \ + --output "$dest.raw"; then + echo "::error file=$hlsl::$hlsl does not cross-compile to GLSL"; failed=1; continue + fi + # The names the web binds: the samplers by unit (spirv-cross names a texture and sampler it + # combines after both), the uniforms `uData` (it names a flattened block for its type). And + # floats high precision by default, as the web's own programs are: a program reads pixel + # positions. + sed -E \ + -e 's/\bSPIRV_Cross_CombinedFrostFrostSampler\b/uSampler/g' \ + -e 's/\bSPIRV_Cross_CombinedSharpSharpSampler\b/uTex1/g' \ + -e 's/\btype_Uniforms\b/uData/g' \ + -e 's/^precision mediump float;$/precision highp float;/' \ + "$dest.raw" > "$dest" + rm "$dest.raw" ;; + esac + done +done < <(git ls-files '*.hlsl') +exit $failed diff --git a/web/web.js b/web/web.js index 4fb689b72..acef9caf7 100644 --- a/web/web.js +++ b/web/web.js @@ -654,7 +654,18 @@ export class Dvui { }, wasm_programCreate: (ptr, len, textures, uniform_vec4s) => { const gl = this.gl; - const src = this.programPrelude(uniform_vec4s) + this.stringFromPointer(ptr, len); + const body = this.stringFromPointer(ptr, len); + // A complete source — one generated from a program's HLSL, GLSL ES 3.00 + // (`scripts/compile-shaders.sh`) — is compiled as it is, on WebGL2 only. On WebGL1 + // it fails, and whoever drew with it falls back (the glass to its meshes). + const complete = body.startsWith("#version"); + if (complete && !this.webgl2) { + const id = this.newProgramId; + this.newProgramId += 1; + this.programs.set(id, { prog: null, fs: null, loc: null, status: 0 }); + return id; + } + const src = complete ? body : this.programPrelude(uniform_vec4s) + body; const vs = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vs, this.webgl2 ? vertexShaderSource_webgl2 : vertexShaderSource_webgl); gl.compileShader(vs);