#version 450 // Sample the glyph atlas (R = coverage 0..1) and mix the cell's bg up to its fg // by that coverage. Fully opaque: the swapchain is SDR, no blending needed. layout(set = 2, binding = 0) uniform sampler2D u_atlas; layout(location = 0) in vec2 v_uv; layout(location = 1) in vec3 v_fg; layout(location = 2) in vec3 v_bg; layout(location = 3) in vec2 v_cell_uv; layout(location = 4) flat in uvec4 v_effect; layout(location = 0) out vec4 o_col; float cellNoise(uint serial, uint col, uint row) { uint x = serial ^ (col * 0x9e3779b9u) ^ (row * 0x85ebca6bu); x ^= x >> 16; x *= 0x7feb352du; x ^= x >> 15; x *= 0x846ca68bu; x ^= x >> 16; return float(x & 0xffffu) / 65535.0; } float asciiGlyph(vec2 uv, int glyph) { vec2 p = uv * 2.0 - 1.0; float thin = 0.12; float dotShape = 1.0 - smoothstep(0.10, 0.20, length(p - vec2(0.0, 0.45))); float barH = 1.0 - smoothstep(thin, thin + 0.08, abs(p.y)); float barV = 1.0 - smoothstep(thin, thin + 0.08, abs(p.x)); if (glyph == 0) return dotShape; if (glyph == 1) return max(dotShape, 1.0 - smoothstep(0.10, 0.20, length(p + vec2(0.0, 0.45)))); if (glyph == 2) return max(barH, barV); if (glyph == 3) { float d0 = 1.0 - smoothstep(thin, thin + 0.08, abs(p.x - p.y)); float d1 = 1.0 - smoothstep(thin, thin + 0.08, abs(p.x + p.y)); return max(max(d0, d1), max(barH, barV)); } if (glyph == 4) { float rails = max( 1.0 - smoothstep(thin, thin + 0.08, abs(abs(p.x) - 0.34)), 1.0 - smoothstep(thin, thin + 0.08, abs(abs(p.y) - 0.34)) ); return rails; } float ring = 1.0 - smoothstep(thin, thin + 0.08, abs(length(p * vec2(0.8, 1.0)) - 0.48)); return max(ring, glyph == 6 ? dotShape : 0.0); } void main() { float a = texture(u_atlas, v_uv).r; bool oldLayer = (v_effect.x & 0x80000000u) != 0u; uint effect = v_effect.x & 0x7fffffffu; float progress = clamp(uintBitsToFloat(v_effect.y), 0.0, 1.0); uint cellCol = v_effect.w & 0xffffu; uint cellRow = v_effect.w >> 16; float noise = cellNoise(v_effect.z, cellCol, cellRow); vec3 cell = mix(v_bg, v_fg, a); // 3 = data dissolve. Changed cells retain the frozen old cell until their // stable threshold, then atomically become the new cell. Exact endpoints // mirror panel_animation.dissolveRevealed, including the zero hash. if (effect == 3u) { bool reveal = progress >= 1.0 || (progress > 0.0 && noise < progress); if (oldLayer ? reveal : !reveal) discard; o_col = vec4(cell, 1.0); return; } // 4 = ASCII materialization. A diagonal/noise threshold matches the TTY // compositor's idea: cells briefly become punctuation, then their real // atlas glyph arrives. The procedural marks keep the GUI implementation // entirely shader-side. if (effect == 4u) { if (progress <= 0.0) { if (!oldLayer) discard; o_col = vec4(cell, 1.0); return; } if (progress >= 1.0) { if (oldLayer) discard; o_col = vec4(cell, 1.0); return; } float diagonal = float((cellCol + cellRow * 2u) % 17u) / 17.0; float threshold = noise * 0.72 + diagonal * 0.28; if (progress >= threshold) { if (oldLayer) discard; o_col = vec4(cell, 1.0); return; } if (progress + 0.22 >= threshold) { if (oldLayer) discard; int glyph = int(floor(noise * 7.0)); float ink = asciiGlyph(v_cell_uv, min(glyph, 6)); vec3 dimInk = mix(v_bg, v_fg, 0.62); o_col = vec4(mix(v_bg, dimInk, ink), 1.0); return; } if (!oldLayer) discard; o_col = vec4(cell, 1.0); return; } // FreeType's hinted grayscale bitmap is already the intended coverage. // Preserve it exactly; weight-changing gamma hacks blur small stems. o_col = vec4(cell, 1.0); }