// Native macOS panel and scene postprocess. Panel kernels consume the core's // plain tracks; the final full-window kernel is the CRT. Panel easing/noise // stays in step with panel_animation.zig. // // The three entry points are `extern "C" [[stitchable]]`. Both halves are // load-bearing: this source is compiled at runtime by // CIKernel.kernels(withMetalString:), which looks for stitchable functions and // fails the whole compile with "cannot find a valid stitchable Metal function // in the source" if it finds none — leaving the view with no postprocessor and // every effect silently off. #include #include using namespace metal; constant float PARDES_TAU = 6.28318530718; float pardesNoise(float2 p) { float3 q = fract(float3(p.x, p.y, p.x) * 0.1031); q += dot(q, q.yzx + 33.33); return fract((q.x + q.y) * q.z); } // Exact integer hash from panel_animation.cellNoise. Pane serial and logical // cell coordinates, not destination pixels, keep TTY and GPU reveal decisions // stable across scale factors and geometry transforms. float pardesCellNoise(float2 serialParts, float colValue, float rowValue) { uint serial = uint(max(serialParts.x, 0.0)) | (uint(max(serialParts.y, 0.0)) << 16); uint col = uint(max(colValue, 0.0)); uint row = uint(max(rowValue, 0.0)); 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; } float4 pardesSceneSample( coreimage::sampler source, float2 origin, float2 size, float2 uv ) { return coreimage::unpremultiply( source.sample(source.transform(origin + uv * size))); } float3 pardesSceneLinear( coreimage::sampler source, float2 origin, float2 size, float2 uv ) { return coreimage::srgb_to_linear(pardesSceneSample(source, origin, size, uv).rgb); } bool pardesInside(float2 p, float4 box) { return p.x >= box.x && p.y >= box.y && p.x < box.x + box.z && p.y < box.y + box.w; } // Character effects (ASCII byte walks, glyph motion, per-cell locks) are // composed in Pardes core, so this kernel never eases them: it clips their // panel batch and draws the finished glyphs. bool pardesComposedInCore(float effect) { return effect == 4.0 || effect >= 6.0; } float pardesPanelProgress(float effect, float frame) { float frames = effect == 1.0 ? 12.0 : effect == 2.0 ? 14.0 : effect == 3.0 ? 10.0 : effect == 5.0 ? 12.0 : 1.0; if (frames <= 1.0 || frame >= frames - 1.0) return 1.0; float t = clamp(frame / (frames - 1.0), 0.0, 1.0); if (effect == 1.0 || effect == 5.0) { // horizontal/vertical slide float u = 1.0 - t; return 1.0 - u * u * u; } if (effect == 2.0) { // zoom: ease-out back, including its overshoot const float c1 = 1.70158; const float c3 = c1 + 1.0; float u = t - 1.0; return 1.0 + c3 * u * u * u + c1 * u * u; } if (effect == 3.0) return t * t * (3.0 - 2.0 * t); // dissolve return t; // core-composed cells need no progress; retained for ABI completeness } // Prepare every geometric target before any translated panel is drawn. A // vertical opener restores the old frame in its fixed clip; a closing pane is // already absent from `canvas` and therefore needs no clear at all. extern "C" [[stitchable]] float4 pardesPanelClear( coreimage::sampler canvas, coreimage::sampler previous, float4 target, float4 ground, float effect, float phase, coreimage::destination destination ) { float2 coord = destination.coord(); if (!pardesInside(coord, target)) return canvas.sample(canvas.transform(coord)); if (effect == 1.0 || effect == 2.0) return ground; if (effect == 5.0 && phase == 0.0) return previous.sample(previous.transform(coord)); return canvas.sample(canvas.transform(coord)); } // One panel draw over the accumulated image. Current and previous are complete // CoreText+attachment rasters. That makes a closing pane safe after its core // allocation is gone and makes ASCII/dissolve true old-to-new data effects. extern "C" [[stitchable]] float4 pardesPanel( coreimage::sampler current, coreimage::sampler previous, coreimage::sampler changedMask, coreimage::sampler canvas, float4 target, float4 from, float4 to, float effect, float phase, float frame, float2 serialParts, float2 cellSize, float2 gridOrigin, coreimage::destination destination ) { float2 coord = destination.coord(); float4 underneath = canvas.sample(canvas.transform(coord)); float progress = pardesPanelProgress(effect, frame); float2 safeCell = max(cellSize, float2(1.0)); // ASCII bytes were already composed in Pardes core before CoreText built // `current`. Pixel attachments have no character value and intentionally // pass through unchanged, so this kernel only clips the pane batch. if (pardesComposedInCore(effect)) { if (!pardesInside(coord, target)) return underneath; return current.sample(current.transform(coord)); } // Dissolve stays at canonical geometry. Unchanged cells bypass the effect // completely; changed cells retain the exact old raster until their // deterministic threshold is crossed. if (effect == 3.0) { if (!pardesInside(coord, target)) return underneath; // During a live resize the grid is still anchored at the view's top, // leaving a partial strip below its final logical row. Account for // that strip before indexing the compact cell mask. float2 absoluteCell = floor((coord - gridOrigin) / safeCell); float changed = changedMask.sample(changedMask.transform( absoluteCell + float2(0.5))).r; if (changed < 0.5) return underneath; float4 oldPixel = previous.sample(previous.transform(coord)); float4 newPixel = current.sample(current.transform(coord)); // Exact endpoint guards are not redundant: a noise hash can be zero // or one, and frame zero/last must still be wholly old/new. if (progress <= 0.0) return oldPixel; if (progress >= 1.0) return newPixel; // Core/TTY coordinates are relative to the target's TOP-left. Core // Image is bottom-up, hence the reversed row term. float2 cell = float2( floor((coord.x - target.x) / safeCell.x), floor((target.y + target.w - coord.y) / safeCell.y)); float2 cellCount = max(round(target.zw / safeCell), float2(1.0)); cell = clamp(cell, float2(0.0), cellCount - 1.0); float noise = pardesCellNoise(serialParts, cell.x, cell.y); return noise < progress ? newPixel : oldPixel; } float4 shown = mix(from, to, progress); shown.zw = max(shown.zw, float2(0.0)); if (!pardesInside(coord, shown) || shown.z <= 0.0 || shown.w <= 0.0) return underneath; // Vertical lifecycle motion is deliberately confined to the pane's own // old/new box. It never travels across or clips a surviving neighbour. if (effect == 5.0 && !pardesInside(coord, target)) return underneath; float2 within = (coord - shown.xy) / shown.zw; float2 sourceCoord = target.xy + within * target.zw; if (!pardesInside(sourceCoord, target)) return underneath; return phase == 2.0 ? previous.sample(previous.transform(sourceCoord)) : current.sample(current.transform(sourceCoord)); } extern "C" [[stitchable]] float4 pardesScene( coreimage::sampler source, float timeSeconds, float frame, float crtOn, float2 sceneSize, float2 sceneOrigin, coreimage::destination destination ) { const float barrel = 0.016; const float edgeSoftnessPx = 3.5; const float chromaPx = 0.28; const float bloomRadiusPx = 1.75; const float bloomThreshold = 0.60; const float bloomSoftness = 0.25; const float bloomStrength = 0.045; const float scanPeriodPx = 3.0; const float scanStrength = 0.055; const float maskPeriodPx = 3.0; const float maskStrength = 0.015; const float vignetteStrength = 0.11; const float humStrength = 0.0025; const float noiseStrength = 0.0020; const float exposure = 1.012; float2 size = sceneSize; float2 origin = sceneOrigin; float2 pixel = destination.coord() - origin; float2 texel = 1.0 / size; float2 cc = pixel / size * 2.0 - 1.0; float r2 = dot(cc, cc); float2 uv = cc * (1.0 + barrel * r2 * crtOn) * 0.5 + 0.5; float edge = min(min(uv.x, uv.y), min(1.0 - uv.x, 1.0 - uv.y)); // Match GLSL's opaque black outside the tube. Transparent black would // reveal AppKit's backdrop and make the same CRT effect have no tube on a // transparent theme. if (crtOn > 0.5 && edge <= 0.0) return float4(0.0, 0.0, 0.0, 1.0); float4 center = pardesSceneSample(source, origin, size, uv); float3 scene = center.rgb; // Without the CRT the scene passes through, exact. if (crtOn <= 0.5) { return coreimage::premultiply(float4(clamp(scene, 0.0, 1.0), center.a)); } float3 centerLinear = coreimage::srgb_to_linear(scene); float2 radialChroma = cc * (chromaPx * r2) * texel; float3 redSample = pardesSceneLinear(source, origin, size, uv + radialChroma); float3 blueSample = pardesSceneLinear(source, origin, size, uv - radialChroma); float3 col = float3( redSample.r, centerLinear.g, blueSample.b ); float2 bloom = texel * bloomRadiusPx; float3 bloomSum = float3(0.0); float2 bloomOffsets[4] = { float2(bloom.x, 0.0), float2(-bloom.x, 0.0), float2(0.0, bloom.y), float2(0.0, -bloom.y) }; for (uint i = 0; i < 4; i++) { // Deliberately raw, like GLSL brightPass. float4 sampleValue = pardesSceneSample( source, origin, size, uv + bloomOffsets[i]); float3 linearValue = coreimage::srgb_to_linear(sampleValue.rgb); float peak = max(linearValue.r, max(linearValue.g, linearValue.b)); bloomSum += linearValue * smoothstep(bloomThreshold, bloomThreshold + bloomSoftness, peak); } col += bloomSum * (0.25 * bloomStrength); float scanWave = 0.5 + 0.5 * cos(pixel.y * PARDES_TAU / scanPeriodPx); col *= 1.0 - scanStrength * scanWave * scanWave; float3 mask = 1.0 + maskStrength * cos( pixel.x * PARDES_TAU / maskPeriodPx + float3(0.0, PARDES_TAU / 3.0, 2.0 * PARDES_TAU / 3.0)); col *= mask; float2 edgePx = min(uv, 1.0 - uv) * size; float tube = smoothstep(0.0, edgeSoftnessPx, min(edgePx.x, edgePx.y)); float vignette = 1.0 - vignetteStrength * pow(clamp(r2 * 0.5, 0.0, 1.0), 1.6); float hum = 1.0 + humStrength * sin((uv.y * 1.35 - timeSeconds * 0.11) * PARDES_TAU); col *= tube * vignette * hum * exposure; float luma = dot(col, float3(0.2126, 0.7152, 0.0722)); float2 noiseSeed = pixel + float2(frame * 17.0, frame * 59.0); col += (pardesNoise(noiseSeed) - 0.5) * noiseStrength * (0.15 + 0.85 * clamp(luma, 0.0, 1.0)); return coreimage::premultiply(float4( coreimage::linear_to_srgb(clamp(col, 0.0, 1.0)), center.a)); }