float boxMask(float2 point, float2 halfSize, float feather) { // Signed-distance box mask gives the chart and border pixel-sized feathered // edges without branching per side. float2 distanceToEdge = abs(point) - halfSize; float outsideDistance = length(max(distanceToEdge, float2(0.0, 0.0))); float insideDistance = min(max(distanceToEdge.x, distanceToEdge.y), 0.0); float signedDistance = outsideDistance + insideDistance; return 1.0 - smoothstep(0.0, max(feather, 0.00001), signedDistance); } float rec709Oetf(float linearLevel) { float value = saturate(linearLevel); if (value < 0.018) return 4.5 * value; return 1.099 * pow(value, 0.45) - 0.099; } float applyToneCurve(float linearLevel) { float value = saturate(linearLevel); if (toneCurve == 1) { float safeGamma = max(gammaEncode, 0.001); return pow(value, 1.0 / safeGamma); } if (toneCurve == 2) return rec709Oetf(value); return value; } float patchBrightness(int patchIndex, int count) { int clampedIndex = clamp(patchIndex, 0, max(count - 1, 0)); // Each patch is one stop brighter than the previous patch until it clips at // the requested peak level, matching the Xyla-style exposure ramp. float linearLevel = baseLevel * exp2(float(clampedIndex)); linearLevel = min(linearLevel, peakLevel); return applyToneCurve(linearLevel); } float4 shadeVideo(ShaderContext context) { float2 resolution = max(context.outputResolution, float2(1.0, 1.0)); float2 uv = saturate(context.uv); float2 centered = uv - 0.5; float feather = 1.5 / min(resolution.x, resolution.y); int count = int(clamp(round(patchCount), 2.0, 21.0)); float2 chartHalfSize = vertical ? float2(0.18, 0.46) * chartScale : float2(0.46, 0.18) * chartScale; float chartMask = boxMask(centered, chartHalfSize, feather); float borderMask = chartMask - boxMask(centered, max(chartHalfSize - float2(feather * 3.0, feather * 3.0), float2(0.0, 0.0)), feather); float axis = vertical ? centered.y : centered.x; float crossAxis = vertical ? centered.x : centered.y; float axisHalfSize = vertical ? chartHalfSize.y : chartHalfSize.x; float crossHalfSize = vertical ? chartHalfSize.x : chartHalfSize.y; float normalizedAxis = (axis + axisHalfSize) / max(axisHalfSize * 2.0, 0.0001); float patchPosition = clamp(normalizedAxis, 0.0, 0.999999) * float(count); int patchIndex = int(floor(patchPosition)); if (reverseOrder) patchIndex = count - 1 - patchIndex; // Build each patch as a slot along the main axis, then mask the cross-axis // extents so vertical and horizontal charts share the same logic. float patchSlotCenter = (floor(patchPosition) + 0.5) / float(count); float localAxis = abs(normalizedAxis - patchSlotCenter) * float(count) * 2.0; float safeGapSize = saturate(gapSize); float axisMask = 1.0 - smoothstep(1.0 - safeGapSize, 1.0 - safeGapSize + feather * float(count) * 2.0, localAxis); float crossMask = 1.0 - smoothstep(crossHalfSize, crossHalfSize + feather, abs(crossAxis)); float insideAxis = step(0.0, normalizedAxis) * step(normalizedAxis, 1.0); float patchMask = axisMask * crossMask * insideAxis; float level = patchBrightness(patchIndex, count); float chartBackground = saturate(backgroundLevel) * chartMask; float border = saturate(borderLevel) * borderMask; float grayscale = max(max(chartBackground, border), level * patchMask); float4 chartColor = float4(grayscale, grayscale, grayscale, 1.0); return saturate(lerp(chartColor, context.sourceColor, sourceMix)); }