#include // ---------------------------------------------------------------------------- // Base Variant (Branchless, No Bloom) // ---------------------------------------------------------------------------- // This shader is the bloom-disabled base variant of the Wolf CRT post-process // path. // // Why keep a separate base file? // - Dart/CPU side can select this program when bloom is off. // - That avoids bloom texture taps entirely in the hot path. // - This is a direct performance optimization, not just stylistic separation. // // Why branchless math throughout? // - Fragment programs run many pixels in lockstep. // - Divergent control flow can reduce throughput. // - Mask-based selection using mix/step/smoothstep keeps execution uniform. // // For Dart developers: // - Think dataflow over fields of values, not object-level control logic. // - Most operations transform scalar/vector fields per pixel. // - Alternative outcomes are blended by masks instead of branching. // Output surface size in pixels for the current draw call. uniform vec2 uResolution; // One source texel step in UV space: (1/width, 1/height). // This keeps neighborhood kernels stable across resolutions. uniform vec2 uTexel; // 1.0 enables CRT warp/scanline stack, 0.0 keeps only base AA stack. // Even though this is conceptually boolean, it is expressed as float so the // shader can blend outcomes natively with mask math. uniform float uEffectsEnabled; // Engine time in seconds used to animate scanline travel. uniform float uTime; // Source frame produced by the software renderer. uniform sampler2D uTexture; out vec4 fragColor; // Perceptual brightness approximation for edge detection. // This uses Rec.601-style luma weighting. float luma(vec3 color) { return dot(color, vec3(0.299, 0.587, 0.114)); } // Returns 1.0 when uv is inside [0,1] on both axes, else 0.0. // Implemented branchlessly as a product of step() tests. float uvInsideMask(vec2 uv) { vec2 lower = step(vec2(0.0), uv); vec2 upper = step(uv, vec2(1.0)); return lower.x * lower.y * upper.x * upper.y; } void main() { // Normalize destination pixel coordinate into UV space. vec2 uv = FlutterFragCoord().xy / uResolution; // -------------------------------------------------------------------------- // 1) CRT warp selection (branchless) // -------------------------------------------------------------------------- // The centered radius term drives a mild barrel distortion. vec2 centered = uv * 2.0 - 1.0; float radius2 = dot(centered, centered); vec2 warpedUv = centered * (1.0 + radius2 * 0.045) * 0.5 + 0.5; // Mix between linear and warped UV with a float mask. // In Dart, this is conceptually: useWarped ? warpedUv : uv. vec2 effectiveUv = mix(uv, warpedUv, uEffectsEnabled); // Clamp once to keep all downstream texture fetches in bounds. vec2 sampleUv = clamp(effectiveUv, 0.0, 1.0); // 1.0 for screen interior, 0.0 outside. float insideMask = uvInsideMask(effectiveUv); // Bezel contributes only where warped coordinates leave the source screen. float bezelMask = (1.0 - insideMask) * uEffectsEnabled; // -------------------------------------------------------------------------- // 2) Lightweight edge-aware AA // -------------------------------------------------------------------------- // Sample center + cardinal neighbors. vec4 centerSample = texture(uTexture, sampleUv); vec3 sampleN = texture(uTexture, clamp(sampleUv + vec2(0.0, -uTexel.y), 0.0, 1.0)).rgb; vec3 sampleS = texture(uTexture, clamp(sampleUv + vec2(0.0, uTexel.y), 0.0, 1.0)).rgb; vec3 sampleE = texture(uTexture, clamp(sampleUv + vec2(uTexel.x, 0.0), 0.0, 1.0)).rgb; vec3 sampleW = texture(uTexture, clamp(sampleUv + vec2(-uTexel.x, 0.0), 0.0, 1.0)).rgb; // Luma span estimates local contrast; high span implies stronger edge. float lumaCenter = luma(centerSample.rgb); float lumaMin = min( lumaCenter, min(min(luma(sampleN), luma(sampleS)), min(luma(sampleE), luma(sampleW))) ); float lumaMax = max( lumaCenter, max(max(luma(sampleN), luma(sampleS)), max(luma(sampleE), luma(sampleW))) ); float edgeAmount = smoothstep(0.03, 0.18, max(lumaMax - lumaMin, 0.0001)); vec3 neighborhoodAvg = (sampleN + sampleS + sampleE + sampleW) * 0.25; // Blend toward neighborhood average near likely edges. vec3 aaColor = mix(centerSample.rgb, neighborhoodAvg, edgeAmount * 0.45); // -------------------------------------------------------------------------- // 3) CRT scanline/sweep/vignette stack // -------------------------------------------------------------------------- float scanlineBand = 0.88 + 0.12 * sin(sampleUv.y * uResolution.y * 3.14159265); float sweepPos = fract(uTime * 0.08); float sweepBand = 1.0 + 0.16 * exp(-pow((sampleUv.y - sweepPos) * 120.0, 2.0)); vec2 centeredUv = sampleUv * 2.0 - 1.0; float vignette = smoothstep(1.15, 0.25, length(centeredUv)); float centerLift = 1.0 + 0.08 * (1.0 - length(centeredUv)); vec3 crtColor = aaColor; crtColor *= scanlineBand * sweepBand * centerLift; crtColor *= mix(0.62, 1.0, vignette); // Effects mask decides whether CRT modulation is applied. vec3 screenColor = mix(aaColor, crtColor, uEffectsEnabled); // -------------------------------------------------------------------------- // 4) Bezel shading path (branchless selection) // -------------------------------------------------------------------------- // This base variant intentionally does not include bloom calculations. // edgeDelta is non-zero when warp pushes UV outside source bounds. vec2 edgeDelta = effectiveUv - sampleUv; float overflow = max(abs(edgeDelta.x), abs(edgeDelta.y)); // Sample inward from clamped border to pull scene tint into bezel. vec2 inwardDir = normalize(-edgeDelta + vec2(1e-6)); vec2 bleedStep = vec2(uTexel.x * 1.6, uTexel.y * 1.6); vec2 bleedUv1 = clamp(sampleUv + inwardDir * bleedStep, 0.0, 1.0); vec2 bleedUv2 = clamp(sampleUv + inwardDir * bleedStep * 2.6, 0.0, 1.0); vec2 bleedUv3 = clamp(sampleUv + inwardDir * bleedStep * 4.2, 0.0, 1.0); vec3 edgeBleedColor = texture(uTexture, sampleUv).rgb * 0.52 + texture(uTexture, bleedUv1).rgb * 0.28 + texture(uTexture, bleedUv2).rgb * 0.14 + texture(uTexture, bleedUv3).rgb * 0.06; // Aspect-corrected radial metrics for bezel falloff shaping. vec2 aspectScale = vec2(uResolution.x / max(uResolution.y, 1.0), 1.0); float bezelDistance = length(edgeDelta * aspectScale); vec2 clampedCentered = sampleUv * 2.0 - 1.0; float cornerFactor = smoothstep(0.60, 1.15, length(clampedCentered)); // Shading layers: // - verticalShade: slight top/bottom tonal variance // - depthShade: darkens with overflow depth // - grain: subtle analog texture // - moldedHighlight: narrow inner edge highlight float verticalShade = 0.88 + 0.07 * (1.0 - (FlutterFragCoord().y / uResolution.y)); float depthShade = 1.0 - smoothstep(0.0, 0.058, overflow) * 0.34; float grain = sin(FlutterFragCoord().x * 0.21 + FlutterFragCoord().y * 0.11) * 0.006; float moldedHighlight = smoothstep(0.072, 0.0, overflow) * 0.028; float bezelGlow = exp(-bezelDistance * 82.0) * mix(1.0, 0.56, cornerFactor); float innerLip = exp(-bezelDistance * 170.0) * 0.10; float bleedStrength = smoothstep(0.12, 0.0, overflow) * (0.78 - cornerFactor * 0.26); vec3 bezelColor = vec3(0.225, 0.225, 0.215) * verticalShade * depthShade + edgeBleedColor * bezelGlow * bleedStrength * 1.12 + edgeBleedColor * innerLip * 0.36 + vec3(moldedHighlight) + vec3(grain); // Final branchless selection between emissive screen and bezel. vec3 outColor = mix(screenColor, bezelColor, bezelMask); float outAlpha = mix(centerSample.a, 1.0, bezelMask); fragColor = vec4(outColor, outAlpha); }