/* GENERATED by rhi/shaders_gl/gen_analog_gl.py - do not edit.
 * Source: rhi/shaders_vulkan/. Regenerate after changing the
 * signal math there; the GL and Vulkan backends share it. */
#ifndef GL_ANALOG_DOWNSAMPLE_GLSL_H
#define GL_ANALOG_DOWNSAMPLE_GLSL_H

/* Push-constant members, as GL uniform names:
 *   vec2     reg_src_size
 *   vec2     reg_native_size
 *   int      reg_sdr_eotf
 */

static const char *analog_downsample_glsl =
   "#version 330 core\n"
   "\n"
   "precision highp float;\n"
   "precision highp int;\n"
   "\n"
   "/* Pass 0 of the analog path, only when internal resolution is above 1x.\n"
   " *\n"
   " * Resolves the supersampled framebuffer down to native before anything is\n"
   " * modulated. This is the right order and not merely the cheap one: the console\n"
   " * emits a fixed-rate signal - 2560 base samples per active line, one sample per\n"
   " * scanline - and no amount of internal resolution changes that. Extra samples\n"
   " * buy a better-resolved *source* going into the encoder, which is exactly the\n"
   " * supersample-then-keep-native-output approach, not a wider signal.\n"
   " *\n"
   " * Done as its own pass because folding it into the encode would put a\n"
   " * scale_x * scale_y gather inside a 95-tap filter loop - 1520 texel reads per\n"
   " * output sample at 8x. Here it is scale^2 reads per native pixel, once.\n"
   " *\n"
   " * Box rather than anything cleverer: the samples being combined are point\n"
   " * samples of the same pixel's coverage, so an unweighted mean is what\n"
   " * supersampling means. The band-limiting that follows is the real filter.\n"
   " *\n"
   " * Averaged as light. Supersampling estimates how much light the pixel emits,\n"
   " * which means the transfer function goes inside the mean - averaging the\n"
   " * gamma-encoded values instead darkens every edge, by up to 62% where a sample\n"
   " * set straddles black and white. The result is re-encoded on the way out\n"
   " * because everything downstream of here is a voltage waveform. */\n"
   "\n"
   "/* ---- begin hdr.h ---- */\n"
   "/* ---- Shared HDR10 (PQ Rec.2020) output math -------------------------------\n"
   " * Included by the Vulkan display shaders (quad.frag, mipmap_resolve.frag)\n"
   " * under -DHDR. Every entry point is parameterised - paper white and the gamut\n"
   " * mode are passed in - so each shader supplies them from its own push\n"
   " * constant rather than this header reaching into a fixed `registers` layout.\n"
   " *\n"
   " * The colour math matches the prboom \"Color Format = HDR\" path and\n"
   " * RetroArch's own HDR composition, so an HDR frame lands at the same\n"
   " * brightness and saturation as the SDR one:\n"
   " *   - display transfer is a pure pow(2.4) (RetroArch linearises SDR with 2.4\n"
   " *     in its Vulkan/D3D HDR shaders; the sRGB piecewise curve is wrong here\n"
   " *     and lifts blacks),\n"
   " *   - ordinary content is scaled to paper white,\n"
   " *   - Rec.709 -> target primaries keyed to the frontend \"Colour Boost\" (same\n"
   " *     matrices RetroArch applies, so switching SDR<->HDR10 does not shift\n"
   " *     saturation),\n"
   " *   - SMPTE ST.2084 (PQ) encode over 0..10000 nits.\n"
   " * Runs in highp; precision qualifiers are ignored on the desktop Vulkan\n"
   " * target but keep the intent explicit. */\n"
   "#ifndef HDR_H\n"
   "#define HDR_H\n"
   "\n"
   "const highp float PQ_M1     = 2610.0 / 16384.0;\n"
   "const highp float PQ_M2     = (2523.0 / 4096.0) * 128.0;\n"
   "const highp float PQ_C1     = 3424.0 / 4096.0;\n"
   "const highp float PQ_C2     = (2413.0 / 4096.0) * 32.0;\n"
   "const highp float PQ_C3     = (2392.0 / 4096.0) * 32.0;\n"
   "const highp float PQ_MAXNITS = 10000.0;\n"
   "\n"
   "highp vec3 pq_encode(highp vec3 nits)\n"
   "{\n"
   "	highp vec3 y  = clamp(nits / PQ_MAXNITS, vec3(0.0), vec3(1.0));\n"
   "	highp vec3 ym = pow(y, vec3(PQ_M1));\n"
   "	return pow((PQ_C1 + PQ_C2 * ym) / (1.0 + PQ_C3 * ym), vec3(PQ_M2));\n"
   "}\n"
   "\n"
   "/* Inverse of sdr_to_linear. Needed wherever something has to be averaged as\n"
   " * light and then handed back to a signal-domain consumer. */\n"
   "highp vec3 linear_to_sdr(highp vec3 c, int sdr_eotf)\n"
   "{\n"
   "	c = max(c, vec3(0.0));\n"
   "	if (sdr_eotf == 1)\n"
   "		return pow(c, vec3(1.0 / 2.2));\n"
   "	if (sdr_eotf == 2)\n"
   "		return mix(1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055,\n"
   "		           c * 12.92,\n"
   "		           lessThanEqual(c, vec3(0.0031308)));\n"
   "	return pow(c, vec3(1.0 / 2.4));\n"
   "}\n"
   "\n"
   "/* Source primaries, applied to linear light before the target rotation below.\n"
   " *\n"
   " * The framebuffer is R'G'B', but which chromaticities those coordinates *mean*\n"
   " * is a property of the display the content was authored on, and PSX-era content\n"
   " * was not authored on Rec.709. This is orthogonal to the cable: primaries are a\n"
   " * property of the authoring monitor, not the wire, so RGB output wants the same\n"
   " * treatment as composite.\n"
   " *\n"
   " *   1  SMPTE-C  (BT.601 525) - what NTSC-era studio monitors used\n"
   " *   2  EBU      (BT.601 625) - PAL. Differs from Rec.709 in green alone and by\n"
   " *                              only 4%, which is why PAL barely moves.\n"
   " *   3  NTSC 1953             - the original, aspirational FCC primaries.\n"
   " *                              Enormously wider: its green lands at -0.40 in\n"
   " *                              Rec.709, i.e. 40% outside the gamut, so it is\n"
   " *                              only actually representable in a wide container.\n"
   " *                              Reportedly retained in Japan, though the better\n"
   " *                              documented NTSC-J difference is black setup, so\n"
   " *                              treat this as flavour rather than accuracy.\n"
   " *\n"
   " * Every one of these maps some primary outside Rec.709, so on an SDR output the\n"
   " * result clips; under HDR10 the Rec.2020 container holds it. Default is 0, no\n"
   " * rotation, which leaves the 24-bit path and the HDR path agreeing. */\n"
   "highp vec3 src_primaries_to_709(highp vec3 c, int src_primaries)\n"
   "{\n"
   "	if (src_primaries == 1)          /* SMPTE-C */\n"
   "		return vec3(\n"
   "			 0.939542064 * c.r +  0.050181357 * c.g +  0.010276579 * c.b,\n"
   "			 0.017772223 * c.r +  0.965792862 * c.g +  0.016434914 * c.b,\n"
   "			-0.001621600 * c.r + -0.004369750 * c.g +  1.005991350 * c.b);\n"
   "	else if (src_primaries == 2)     /* EBU */\n"
   "		return vec3(\n"
   "			 1.044043209 * c.r + -0.044043209 * c.g,\n"
   "			                       c.g,\n"
   "			                       0.011793378 * c.g +  0.988206622 * c.b);\n"
   "	else if (src_primaries == 3)     /* NTSC 1953, Bradford-adapted C -> D65 */\n"
   "		return vec3(\n"
   "			 1.486156846 * c.r + -0.403554906 * c.g + -0.082601940 * c.b,\n"
   "			-0.025101109 * c.r +  0.954024686 * c.g +  0.071076423 * c.b,\n"
   "			-0.027224002 * c.r + -0.044095233 * c.g +  1.071319235 * c.b);\n"
   "	return c;\n"
   "}\n"
   "\n"
   "/* Gamut rotation, applied to linear light. Cases mirror prboom / RetroArch:\n"
   " * 0 Accurate (709->2020), 1 Expanded, 2 Wide (709->P3), 3 Super (no rotation). */\n"
   "highp vec3 rec709_to_target(highp vec3 c, int expand_gamut)\n"
   "{\n"
   "	if (expand_gamut == 1)          /* Expanded */\n"
   "		return vec3(\n"
   "			 0.6274040 * c.r +  0.3292820 * c.g +  0.0433136 * c.b,\n"
   "			 0.0457456 * c.r +  0.9417770 * c.g +  0.0124772 * c.b,\n"
   "			-0.0012106 * c.r +  0.0176041 * c.g +  0.9836070 * c.b);\n"
   "	else if (expand_gamut == 2)     /* Wide (DCI-P3) */\n"
   "		return vec3(\n"
   "			 0.8215873 * c.r +  0.1763479 * c.g +  0.0020641 * c.b,\n"
   "			 0.0328261 * c.r +  0.9695096 * c.g + -0.0023367 * c.b,\n"
   "			 0.0188038 * c.r +  0.0725063 * c.g +  0.9086907 * c.b);\n"
   "	else if (expand_gamut == 3)     /* Super (stay Rec.709) */\n"
   "		return c;\n"
   "	/* Accurate: proper Rec.709 -> Rec.2020 */\n"
   "	return vec3(\n"
   "		0.6274040 * c.r + 0.3292820 * c.g + 0.0433136 * c.b,\n"
   "		0.0690970 * c.r + 0.9195400 * c.g + 0.0113612 * c.b,\n"
   "		0.0163916 * c.r + 0.0880132 * c.g + 0.8955950 * c.b);\n"
   "}\n"
   "\n"
   "/* Reference SDR transfer, applied to the whole signal (see encode_hdr10).\n"
   " *   0  BT.1886 pure 2.4 - matches RetroArch's own SDR->HDR composition and a\n"
   " *      TV-like reference display. Historical behaviour, still the default.\n"
   " *   1  pure 2.2 - closer to a PC monitor tracking sRGB's nominal gamma.\n"
   " *   2  sRGB piecewise - what Windows assumes when it composites SDR content\n"
   " *      into an HDR desktop. Lifts the toe relative to 2.4.\n"
   " * This is a viewing-reference choice, not a correctness one: it decides\n"
   " * whether 30-bit HDR lands at the same brightness the 24-bit path did on the\n"
   " * same display. Against a 2.2 monitor, 2.4 is 12.9% down in linear light at\n"
   " * code 0.5 and 24.2% down at 0.25, which reads as \"HDR looks dimmer and more\n"
   " * contrasty\". All three agree exactly at 0.0 and 1.0, so paper white and the\n"
   " * roll-off knee threshold do not move, and all extend monotonically past 1.0\n"
   " * so the additive overshoot decodes with the same curve as everything else. */\n"
   "highp vec3 sdr_to_linear(highp vec3 c, int sdr_eotf)\n"
   "{\n"
   "	if (sdr_eotf == 1)\n"
   "		return pow(c, vec3(2.2));\n"
   "	if (sdr_eotf == 2)\n"
   "		return mix(pow((c + vec3(0.055)) / 1.055, vec3(2.4)),\n"
   "		           c / 12.92,\n"
   "		           lessThanEqual(c, vec3(0.04045)));\n"
   "	return pow(c, vec3(2.4));\n"
   "}\n"
   "\n"
   "highp float filmic_shoulder(highp float x)\n"
   "{\n"
   "	/* Punchier alternative to Reinhard's o/(o+1) as the highlight-shoulder\n"
   "	 * magnitude: maps the normalised overshoot [0,inf) to [0,1), rising faster\n"
   "	 * and reaching the ceiling sooner (0.63 vs 0.50 at x=1, 0.86 vs 0.67 at\n"
   "	 * x=2), which is the behaviour the \"filmic\" roll-off option documents.\n"
   "	 *\n"
   "	 * Both shoulders have to satisfy S'(0) == 1: that is what keeps the encode\n"
   "	 * C1-continuous where the overshoot region joins reference white, because\n"
   "	 * the slope just above white is S'(0) * d(lin)/dc against d(lin)/dc just\n"
   "	 * below it. The Narkowicz ACES fit previously used here has S'(0) = b/e =\n"
   "	 * 0.214 and so dropped the slope 4.7x exactly at white. Normalising it\n"
   "	 * (scaling the input by e/b) restores the origin slope but sends the peak\n"
   "	 * derivative to ~8.4, trading the step for a worse spike a few percent\n"
   "	 * above white. 1 - exp(-x) has unit slope at the origin and a maximum\n"
   "	 * derivative of 1, so it is smooth on both counts, and is cheaper than the\n"
   "	 * rational fit it replaces. */\n"
   "	return 1.0 - exp(-x);\n"
   "}\n"
   "\n"
   "/* Source primaries for a signal-domain consumer: decode, rotate, re-encode.\n"
   " *\n"
   " * The HDR path gets this for free inside encode_hdr10, which is already\n"
   " * holding linear light. An SDR output has to round-trip for it, which is two\n"
   " * extra pow() per pixel - so the identity case returns immediately, and a\n"
   " * default configuration pays nothing.\n"
   " *\n"
   " * Every one of the rotations maps some primary outside Rec.709, and an SDR\n"
   " * output clips those. That is a real loss and the reason NTSC 1953 in\n"
   " * particular only makes sense on a wide-gamut display - but clipping the\n"
   " * out-of-gamut corners is still closer to the intended picture than\n"
   " * interpreting SMPTE-C coordinates as Rec.709 ones, which is what happens\n"
   " * otherwise. */\n"
   "highp vec3 sdr_apply_src_primaries(highp vec3 rgb, int sdr_eotf, int src_primaries)\n"
   "{\n"
   "	if (src_primaries == 0)\n"
   "		return rgb;\n"
   "	return linear_to_sdr(\n"
   "		src_primaries_to_709(sdr_to_linear(max(rgb, vec3(0.0)), sdr_eotf), src_primaries),\n"
   "		sdr_eotf);\n"
   "}\n"
   "\n"
   "/* Entry point for callers that already hold linear light, so it is not decoded\n"
   " * and re-encoded on the way in. `scene_linear` is normalised with 1.0 at\n"
   " * reference white; values above that are the additive overshoot the roll-off\n"
   " * compresses. */\n"
   "highp vec3 encode_hdr10_linear(highp vec3 scene_linear, highp float paper_white_nits,\n"
   "                               highp float peak_nits, int expand_gamut, int shoulder,\n"
   "                               int src_primaries)\n"
   "{\n"
   "	/* STEP 3: one transfer function across the whole range, then compress\n"
   "	 * whatever lands above paper white.\n"
   "	 *\n"
   "	 * The signal is PSX-native and gamma-encoded; sdr_to_linear is the\n"
   "	 * display transfer (defaulting to the pure 2.4 RetroArch uses in its own\n"
   "	 * Vulkan/D3D HDR shaders). Applying it to the whole value - including the >1.0 left in the 16F\n"
   "	 * framebuffer by additive blends - is what keeps the encode continuous.\n"
   "	 * Decoding only [0,1] and treating the overshoot as if it were already\n"
   "	 * linear light mixes two domains in one sum, and the slope then steps by\n"
   "	 * headroom/(2.4*paper_white) - 1.67x at 200/1000 nits - exactly at\n"
   "	 * reference white, which contours any gradient that crosses it and makes\n"
   "	 * the deband grain visibly coarsen at the same threshold.\n"
   "	 *\n"
   "	 * Content in [0,1] is untouched by this: the transfer maps 1.0 to 1.0 for\n"
   "	 * every sdr_eotf, so sdr_to_linear(c)*paper_white <= paper white, so the roll-off below never engages and the standard range still\n"
   "	 * maps bit-for-bit onto the SDR result.\n"
   "	 *\n"
   "	 * The knee is driven by the peak (brightest) overshoot channel and the\n"
   "	 * overshoot is scaled proportionally, rather than kneeing each channel on\n"
   "	 * its own: a per-channel roll-off compresses the brightest channel hardest,\n"
   "	 * which desaturates a hot coloured highlight toward white (a saturated\n"
   "	 * additive red would wash out as it brightens). Scaling by the shared\n"
   "	 * factor keeps the overshoot's chromaticity, so a hot additive red stays\n"
   "	 * red. Dim channels are left alone, as before. */\n"
   "	/* peak_nits is what the display can actually reach, supplied by the\n"
   "	 * frontend through GET_HDR_MAX_NITS. Everything above paper white and\n"
   "	 * below it is the headroom available for highlights, so a wrong value is\n"
   "	 * either wasted range or clipping - which is why this stopped being the\n"
   "	 * hardcoded 1000 it was.\n"
   "	 *\n"
   "	 * The frontend may report a peak below paper white if the user has set it\n"
   "	 * that way; headroom then clamps to zero and the roll-off degenerates to a\n"
   "	 * clamp at paper white, which is the right behaviour rather than a\n"
   "	 * negative range. */\n"
   "	highp float headroom = max(peak_nits - paper_white_nits, 0.0);\n"
   "	highp vec3  lin      = src_primaries_to_709(max(scene_linear, vec3(0.0)), src_primaries)\n"
   "	                     * paper_white_nits;\n"
   "	/* Linear-light overshoot. Forced to zero when there is no headroom\n"
   "	 * (paper white at or above the ceiling), which clamps to paper white\n"
   "	 * instead of letting the knee pass the value through unscaled. */\n"
   "	highp vec3  over     = (headroom > 0.0)\n"
   "	                          ? max(lin - vec3(paper_white_nits), vec3(0.0))\n"
   "	                          : vec3(0.0);\n"
   "	highp float omax     = max(over.r, max(over.g, over.b));\n"
   "	highp float o        = (omax > 0.0) ? (omax / headroom) : 0.0;\n"
   "	/* Per-unit-overshoot factor S(o)/o, shared across channels. shoulder\n"
   "	 * selects S: 0 = Reinhard o/(o+1) (S/o = 1/(o+1)); 1 = filmic. Both have\n"
   "	 * S'(0) == 1, so the peak channel leaves white at the same slope it\n"
   "	 * arrived with and asymptotes to the ceiling.\n"
   "	 *\n"
   "	 * (1-exp(-o))/o cancels catastrophically as o -> 0 - fp32 loses 40% of the\n"
   "	 * value by o = 1e-7, and that is precisely the neighbourhood of reference\n"
   "	 * white - so the small-o branch uses the series 1 - o/2 + o^2/6, which is\n"
   "	 * good to ~1e-7 at the crossover and exact at the origin. */\n"
   "	highp float knee     = 0.0;\n"
   "	if (o > 0.0)\n"
   "	{\n"
   "		if (shoulder == 1)\n"
   "			knee = (o < 1e-2)\n"
   "			          ? (1.0 - o * (0.5 - o * (1.0 / 6.0)))\n"
   "			          : (filmic_shoulder(o) / o);\n"
   "		else\n"
   "			knee = 1.0 / (o + 1.0);\n"
   "	}\n"
   "	lin = min(lin, vec3(paper_white_nits)) + over * knee;\n"
   "	lin = rec709_to_target(lin, expand_gamut);\n"
   "	return pq_encode(lin);\n"
   "}\n"
   "\n"
   "highp vec3 encode_hdr10(highp vec3 rgb, highp float paper_white_nits, highp float peak_nits,\n"
   "                        int expand_gamut, int shoulder, int sdr_eotf, int src_primaries)\n"
   "{\n"
   "	return encode_hdr10_linear(sdr_to_linear(max(rgb, vec3(0.0)), sdr_eotf),\n"
   "	                           paper_white_nits, peak_nits, expand_gamut, shoulder,\n"
   "	                           src_primaries);\n"
   "}\n"
   "\n"
   "/* ---- Debanding dither -----------------------------------------------------\n"
   " * For the genuinely-8-bit paths whose gradients are already quantised and\n"
   " * would band at 10-bit. ~1 8-bit-LSB of triangular-PDF noise, spatially\n"
   " * distributed with interleaved gradient noise (blue-noise-like, no texture),\n"
   " * applied in gamma space. TPDF decorrelates the noise from the signal, so it\n"
   " * reads as a faint even grain. Not for interpolated content (mipmap resolve,\n"
   " * YUV chroma) - that already carries sub-8-bit precision 10-bit preserves. */\n"
   "highp float hdr_ign(highp vec2 p, highp vec2 k)  /* IGN along direction k -> [0,1) */\n"
   "{\n"
   "	return fract(52.9829189 * fract(dot(p, k)));\n"
   "}\n"
   "\n"
   "highp float hdr_tri(highp float u)  /* uniform [0,1) -> triangular (-1,1) */\n"
   "{\n"
   "	return u < 0.5 ? sqrt(2.0 * u) - 1.0 : 1.0 - sqrt(2.0 - 2.0 * u);\n"
   "}\n"
   "\n"
   "highp vec3 hdr_deband(highp vec3 rgb, highp vec2 fragcoord)\n"
   "{\n"
   "	/* Per-channel IGN *direction* (not a shared field offset) gives three\n"
   "	 * decorrelated fields, so the grain carries no chroma tint (pairwise\n"
   "	 * channel correlation ~0). Each is remapped to a triangular PDF. */\n"
   "	highp vec3 t = vec3(\n"
   "		hdr_tri(hdr_ign(fragcoord, vec2( 0.06711056,  0.00583715))),\n"
   "		hdr_tri(hdr_ign(fragcoord, vec2( 0.00583715,  0.06711056))),\n"
   "		hdr_tri(hdr_ign(fragcoord, vec2( 0.06711056, -0.00583715))));\n"
   "	return rgb + t * (1.0 / 255.0);\n"
   "}\n"
   "\n"
   "/* Luma-only variant for the YUV FMV path. Its chroma is reconstructed at\n"
   " * sub-8-bit precision (2x2 average + bilinear) that 10-bit preserves, so it\n"
   " * must NOT be dithered; only the per-pixel 8-bit luma steps. An equal offset\n"
   " * to R,G,B is chroma-neutral through the BT.601 matrix (U,V rows sum ~0, Y\n"
   " * row 1), so one triangular sample on all channels dithers luma alone. */\n"
   "highp vec3 hdr_deband_luma(highp vec3 rgb, highp vec2 fragcoord)\n"
   "{\n"
   "	highp float t = hdr_tri(hdr_ign(fragcoord, vec2(0.06711056, 0.00583715)));\n"
   "	return rgb + vec3(t) * (1.0 / 255.0);\n"
   "}\n"
   "\n"
   "#endif /* HDR_H */\n"
   "/* ---- end hdr.h ---- */\n"
   "\n"
   "in highp vec2 vUV;\n"
   "out highp vec4 FragColor;\n"
   "\n"
   "uniform sampler2D uSource;\n"
   "\n"
   "uniform vec2 reg_src_size;\n"
   "uniform vec2 reg_native_size;\n"
   "uniform int reg_sdr_eotf;\n"
   "\n"
   "void main()\n"
   "{\n"
   "	highp vec2  scale = reg_src_size / reg_native_size;\n"
   "	highp ivec2 n     = ivec2(max(floor(scale + 0.5), vec2(1.0)));\n"
   "	highp vec2  base   = floor(gl_FragCoord.xy) * scale;\n"
   "\n"
   "	highp vec3  acc = vec3(0.0);\n"
   "	highp float cnt = 0.0;\n"
   "	int x, y;\n"
   "\n"
   "	for (y = 0; y < n.y; y++)\n"
   "	{\n"
   "		for (x = 0; x < n.x; x++)\n"
   "		{\n"
   "			highp vec2 t = clamp(base + vec2(float(x), float(y)) + 0.5,\n"
   "			                     vec2(0.0), reg_src_size - 0.5);\n"
   "			acc += sdr_to_linear(max(textureLod(uSource, t / reg_src_size, 0.0).rgb,\n"
   "			                         vec3(0.0)), reg_sdr_eotf);\n"
   "			cnt += 1.0;\n"
   "		}\n"
   "	}\n"
   "\n"
   "	FragColor = vec4(linear_to_sdr(acc / max(cnt, 1.0), reg_sdr_eotf), 1.0);\n"
   "}\n"
   ;

static const char *analog_downsample_glsl_ES =
   "#version 300 es\n"
   "precision highp float;\n"
   "precision highp int;\n"
   "\n"
   "precision highp float;\n"
   "precision highp int;\n"
   "\n"
   "/* Pass 0 of the analog path, only when internal resolution is above 1x.\n"
   " *\n"
   " * Resolves the supersampled framebuffer down to native before anything is\n"
   " * modulated. This is the right order and not merely the cheap one: the console\n"
   " * emits a fixed-rate signal - 2560 base samples per active line, one sample per\n"
   " * scanline - and no amount of internal resolution changes that. Extra samples\n"
   " * buy a better-resolved *source* going into the encoder, which is exactly the\n"
   " * supersample-then-keep-native-output approach, not a wider signal.\n"
   " *\n"
   " * Done as its own pass because folding it into the encode would put a\n"
   " * scale_x * scale_y gather inside a 95-tap filter loop - 1520 texel reads per\n"
   " * output sample at 8x. Here it is scale^2 reads per native pixel, once.\n"
   " *\n"
   " * Box rather than anything cleverer: the samples being combined are point\n"
   " * samples of the same pixel's coverage, so an unweighted mean is what\n"
   " * supersampling means. The band-limiting that follows is the real filter.\n"
   " *\n"
   " * Averaged as light. Supersampling estimates how much light the pixel emits,\n"
   " * which means the transfer function goes inside the mean - averaging the\n"
   " * gamma-encoded values instead darkens every edge, by up to 62% where a sample\n"
   " * set straddles black and white. The result is re-encoded on the way out\n"
   " * because everything downstream of here is a voltage waveform. */\n"
   "\n"
   "/* ---- begin hdr.h ---- */\n"
   "/* ---- Shared HDR10 (PQ Rec.2020) output math -------------------------------\n"
   " * Included by the Vulkan display shaders (quad.frag, mipmap_resolve.frag)\n"
   " * under -DHDR. Every entry point is parameterised - paper white and the gamut\n"
   " * mode are passed in - so each shader supplies them from its own push\n"
   " * constant rather than this header reaching into a fixed `registers` layout.\n"
   " *\n"
   " * The colour math matches the prboom \"Color Format = HDR\" path and\n"
   " * RetroArch's own HDR composition, so an HDR frame lands at the same\n"
   " * brightness and saturation as the SDR one:\n"
   " *   - display transfer is a pure pow(2.4) (RetroArch linearises SDR with 2.4\n"
   " *     in its Vulkan/D3D HDR shaders; the sRGB piecewise curve is wrong here\n"
   " *     and lifts blacks),\n"
   " *   - ordinary content is scaled to paper white,\n"
   " *   - Rec.709 -> target primaries keyed to the frontend \"Colour Boost\" (same\n"
   " *     matrices RetroArch applies, so switching SDR<->HDR10 does not shift\n"
   " *     saturation),\n"
   " *   - SMPTE ST.2084 (PQ) encode over 0..10000 nits.\n"
   " * Runs in highp; precision qualifiers are ignored on the desktop Vulkan\n"
   " * target but keep the intent explicit. */\n"
   "#ifndef HDR_H\n"
   "#define HDR_H\n"
   "\n"
   "const highp float PQ_M1     = 2610.0 / 16384.0;\n"
   "const highp float PQ_M2     = (2523.0 / 4096.0) * 128.0;\n"
   "const highp float PQ_C1     = 3424.0 / 4096.0;\n"
   "const highp float PQ_C2     = (2413.0 / 4096.0) * 32.0;\n"
   "const highp float PQ_C3     = (2392.0 / 4096.0) * 32.0;\n"
   "const highp float PQ_MAXNITS = 10000.0;\n"
   "\n"
   "highp vec3 pq_encode(highp vec3 nits)\n"
   "{\n"
   "	highp vec3 y  = clamp(nits / PQ_MAXNITS, vec3(0.0), vec3(1.0));\n"
   "	highp vec3 ym = pow(y, vec3(PQ_M1));\n"
   "	return pow((PQ_C1 + PQ_C2 * ym) / (1.0 + PQ_C3 * ym), vec3(PQ_M2));\n"
   "}\n"
   "\n"
   "/* Inverse of sdr_to_linear. Needed wherever something has to be averaged as\n"
   " * light and then handed back to a signal-domain consumer. */\n"
   "highp vec3 linear_to_sdr(highp vec3 c, int sdr_eotf)\n"
   "{\n"
   "	c = max(c, vec3(0.0));\n"
   "	if (sdr_eotf == 1)\n"
   "		return pow(c, vec3(1.0 / 2.2));\n"
   "	if (sdr_eotf == 2)\n"
   "		return mix(1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055,\n"
   "		           c * 12.92,\n"
   "		           lessThanEqual(c, vec3(0.0031308)));\n"
   "	return pow(c, vec3(1.0 / 2.4));\n"
   "}\n"
   "\n"
   "/* Source primaries, applied to linear light before the target rotation below.\n"
   " *\n"
   " * The framebuffer is R'G'B', but which chromaticities those coordinates *mean*\n"
   " * is a property of the display the content was authored on, and PSX-era content\n"
   " * was not authored on Rec.709. This is orthogonal to the cable: primaries are a\n"
   " * property of the authoring monitor, not the wire, so RGB output wants the same\n"
   " * treatment as composite.\n"
   " *\n"
   " *   1  SMPTE-C  (BT.601 525) - what NTSC-era studio monitors used\n"
   " *   2  EBU      (BT.601 625) - PAL. Differs from Rec.709 in green alone and by\n"
   " *                              only 4%, which is why PAL barely moves.\n"
   " *   3  NTSC 1953             - the original, aspirational FCC primaries.\n"
   " *                              Enormously wider: its green lands at -0.40 in\n"
   " *                              Rec.709, i.e. 40% outside the gamut, so it is\n"
   " *                              only actually representable in a wide container.\n"
   " *                              Reportedly retained in Japan, though the better\n"
   " *                              documented NTSC-J difference is black setup, so\n"
   " *                              treat this as flavour rather than accuracy.\n"
   " *\n"
   " * Every one of these maps some primary outside Rec.709, so on an SDR output the\n"
   " * result clips; under HDR10 the Rec.2020 container holds it. Default is 0, no\n"
   " * rotation, which leaves the 24-bit path and the HDR path agreeing. */\n"
   "highp vec3 src_primaries_to_709(highp vec3 c, int src_primaries)\n"
   "{\n"
   "	if (src_primaries == 1)          /* SMPTE-C */\n"
   "		return vec3(\n"
   "			 0.939542064 * c.r +  0.050181357 * c.g +  0.010276579 * c.b,\n"
   "			 0.017772223 * c.r +  0.965792862 * c.g +  0.016434914 * c.b,\n"
   "			-0.001621600 * c.r + -0.004369750 * c.g +  1.005991350 * c.b);\n"
   "	else if (src_primaries == 2)     /* EBU */\n"
   "		return vec3(\n"
   "			 1.044043209 * c.r + -0.044043209 * c.g,\n"
   "			                       c.g,\n"
   "			                       0.011793378 * c.g +  0.988206622 * c.b);\n"
   "	else if (src_primaries == 3)     /* NTSC 1953, Bradford-adapted C -> D65 */\n"
   "		return vec3(\n"
   "			 1.486156846 * c.r + -0.403554906 * c.g + -0.082601940 * c.b,\n"
   "			-0.025101109 * c.r +  0.954024686 * c.g +  0.071076423 * c.b,\n"
   "			-0.027224002 * c.r + -0.044095233 * c.g +  1.071319235 * c.b);\n"
   "	return c;\n"
   "}\n"
   "\n"
   "/* Gamut rotation, applied to linear light. Cases mirror prboom / RetroArch:\n"
   " * 0 Accurate (709->2020), 1 Expanded, 2 Wide (709->P3), 3 Super (no rotation). */\n"
   "highp vec3 rec709_to_target(highp vec3 c, int expand_gamut)\n"
   "{\n"
   "	if (expand_gamut == 1)          /* Expanded */\n"
   "		return vec3(\n"
   "			 0.6274040 * c.r +  0.3292820 * c.g +  0.0433136 * c.b,\n"
   "			 0.0457456 * c.r +  0.9417770 * c.g +  0.0124772 * c.b,\n"
   "			-0.0012106 * c.r +  0.0176041 * c.g +  0.9836070 * c.b);\n"
   "	else if (expand_gamut == 2)     /* Wide (DCI-P3) */\n"
   "		return vec3(\n"
   "			 0.8215873 * c.r +  0.1763479 * c.g +  0.0020641 * c.b,\n"
   "			 0.0328261 * c.r +  0.9695096 * c.g + -0.0023367 * c.b,\n"
   "			 0.0188038 * c.r +  0.0725063 * c.g +  0.9086907 * c.b);\n"
   "	else if (expand_gamut == 3)     /* Super (stay Rec.709) */\n"
   "		return c;\n"
   "	/* Accurate: proper Rec.709 -> Rec.2020 */\n"
   "	return vec3(\n"
   "		0.6274040 * c.r + 0.3292820 * c.g + 0.0433136 * c.b,\n"
   "		0.0690970 * c.r + 0.9195400 * c.g + 0.0113612 * c.b,\n"
   "		0.0163916 * c.r + 0.0880132 * c.g + 0.8955950 * c.b);\n"
   "}\n"
   "\n"
   "/* Reference SDR transfer, applied to the whole signal (see encode_hdr10).\n"
   " *   0  BT.1886 pure 2.4 - matches RetroArch's own SDR->HDR composition and a\n"
   " *      TV-like reference display. Historical behaviour, still the default.\n"
   " *   1  pure 2.2 - closer to a PC monitor tracking sRGB's nominal gamma.\n"
   " *   2  sRGB piecewise - what Windows assumes when it composites SDR content\n"
   " *      into an HDR desktop. Lifts the toe relative to 2.4.\n"
   " * This is a viewing-reference choice, not a correctness one: it decides\n"
   " * whether 30-bit HDR lands at the same brightness the 24-bit path did on the\n"
   " * same display. Against a 2.2 monitor, 2.4 is 12.9% down in linear light at\n"
   " * code 0.5 and 24.2% down at 0.25, which reads as \"HDR looks dimmer and more\n"
   " * contrasty\". All three agree exactly at 0.0 and 1.0, so paper white and the\n"
   " * roll-off knee threshold do not move, and all extend monotonically past 1.0\n"
   " * so the additive overshoot decodes with the same curve as everything else. */\n"
   "highp vec3 sdr_to_linear(highp vec3 c, int sdr_eotf)\n"
   "{\n"
   "	if (sdr_eotf == 1)\n"
   "		return pow(c, vec3(2.2));\n"
   "	if (sdr_eotf == 2)\n"
   "		return mix(pow((c + vec3(0.055)) / 1.055, vec3(2.4)),\n"
   "		           c / 12.92,\n"
   "		           lessThanEqual(c, vec3(0.04045)));\n"
   "	return pow(c, vec3(2.4));\n"
   "}\n"
   "\n"
   "highp float filmic_shoulder(highp float x)\n"
   "{\n"
   "	/* Punchier alternative to Reinhard's o/(o+1) as the highlight-shoulder\n"
   "	 * magnitude: maps the normalised overshoot [0,inf) to [0,1), rising faster\n"
   "	 * and reaching the ceiling sooner (0.63 vs 0.50 at x=1, 0.86 vs 0.67 at\n"
   "	 * x=2), which is the behaviour the \"filmic\" roll-off option documents.\n"
   "	 *\n"
   "	 * Both shoulders have to satisfy S'(0) == 1: that is what keeps the encode\n"
   "	 * C1-continuous where the overshoot region joins reference white, because\n"
   "	 * the slope just above white is S'(0) * d(lin)/dc against d(lin)/dc just\n"
   "	 * below it. The Narkowicz ACES fit previously used here has S'(0) = b/e =\n"
   "	 * 0.214 and so dropped the slope 4.7x exactly at white. Normalising it\n"
   "	 * (scaling the input by e/b) restores the origin slope but sends the peak\n"
   "	 * derivative to ~8.4, trading the step for a worse spike a few percent\n"
   "	 * above white. 1 - exp(-x) has unit slope at the origin and a maximum\n"
   "	 * derivative of 1, so it is smooth on both counts, and is cheaper than the\n"
   "	 * rational fit it replaces. */\n"
   "	return 1.0 - exp(-x);\n"
   "}\n"
   "\n"
   "/* Source primaries for a signal-domain consumer: decode, rotate, re-encode.\n"
   " *\n"
   " * The HDR path gets this for free inside encode_hdr10, which is already\n"
   " * holding linear light. An SDR output has to round-trip for it, which is two\n"
   " * extra pow() per pixel - so the identity case returns immediately, and a\n"
   " * default configuration pays nothing.\n"
   " *\n"
   " * Every one of the rotations maps some primary outside Rec.709, and an SDR\n"
   " * output clips those. That is a real loss and the reason NTSC 1953 in\n"
   " * particular only makes sense on a wide-gamut display - but clipping the\n"
   " * out-of-gamut corners is still closer to the intended picture than\n"
   " * interpreting SMPTE-C coordinates as Rec.709 ones, which is what happens\n"
   " * otherwise. */\n"
   "highp vec3 sdr_apply_src_primaries(highp vec3 rgb, int sdr_eotf, int src_primaries)\n"
   "{\n"
   "	if (src_primaries == 0)\n"
   "		return rgb;\n"
   "	return linear_to_sdr(\n"
   "		src_primaries_to_709(sdr_to_linear(max(rgb, vec3(0.0)), sdr_eotf), src_primaries),\n"
   "		sdr_eotf);\n"
   "}\n"
   "\n"
   "/* Entry point for callers that already hold linear light, so it is not decoded\n"
   " * and re-encoded on the way in. `scene_linear` is normalised with 1.0 at\n"
   " * reference white; values above that are the additive overshoot the roll-off\n"
   " * compresses. */\n"
   "highp vec3 encode_hdr10_linear(highp vec3 scene_linear, highp float paper_white_nits,\n"
   "                               highp float peak_nits, int expand_gamut, int shoulder,\n"
   "                               int src_primaries)\n"
   "{\n"
   "	/* STEP 3: one transfer function across the whole range, then compress\n"
   "	 * whatever lands above paper white.\n"
   "	 *\n"
   "	 * The signal is PSX-native and gamma-encoded; sdr_to_linear is the\n"
   "	 * display transfer (defaulting to the pure 2.4 RetroArch uses in its own\n"
   "	 * Vulkan/D3D HDR shaders). Applying it to the whole value - including the >1.0 left in the 16F\n"
   "	 * framebuffer by additive blends - is what keeps the encode continuous.\n"
   "	 * Decoding only [0,1] and treating the overshoot as if it were already\n"
   "	 * linear light mixes two domains in one sum, and the slope then steps by\n"
   "	 * headroom/(2.4*paper_white) - 1.67x at 200/1000 nits - exactly at\n"
   "	 * reference white, which contours any gradient that crosses it and makes\n"
   "	 * the deband grain visibly coarsen at the same threshold.\n"
   "	 *\n"
   "	 * Content in [0,1] is untouched by this: the transfer maps 1.0 to 1.0 for\n"
   "	 * every sdr_eotf, so sdr_to_linear(c)*paper_white <= paper white, so the roll-off below never engages and the standard range still\n"
   "	 * maps bit-for-bit onto the SDR result.\n"
   "	 *\n"
   "	 * The knee is driven by the peak (brightest) overshoot channel and the\n"
   "	 * overshoot is scaled proportionally, rather than kneeing each channel on\n"
   "	 * its own: a per-channel roll-off compresses the brightest channel hardest,\n"
   "	 * which desaturates a hot coloured highlight toward white (a saturated\n"
   "	 * additive red would wash out as it brightens). Scaling by the shared\n"
   "	 * factor keeps the overshoot's chromaticity, so a hot additive red stays\n"
   "	 * red. Dim channels are left alone, as before. */\n"
   "	/* peak_nits is what the display can actually reach, supplied by the\n"
   "	 * frontend through GET_HDR_MAX_NITS. Everything above paper white and\n"
   "	 * below it is the headroom available for highlights, so a wrong value is\n"
   "	 * either wasted range or clipping - which is why this stopped being the\n"
   "	 * hardcoded 1000 it was.\n"
   "	 *\n"
   "	 * The frontend may report a peak below paper white if the user has set it\n"
   "	 * that way; headroom then clamps to zero and the roll-off degenerates to a\n"
   "	 * clamp at paper white, which is the right behaviour rather than a\n"
   "	 * negative range. */\n"
   "	highp float headroom = max(peak_nits - paper_white_nits, 0.0);\n"
   "	highp vec3  lin      = src_primaries_to_709(max(scene_linear, vec3(0.0)), src_primaries)\n"
   "	                     * paper_white_nits;\n"
   "	/* Linear-light overshoot. Forced to zero when there is no headroom\n"
   "	 * (paper white at or above the ceiling), which clamps to paper white\n"
   "	 * instead of letting the knee pass the value through unscaled. */\n"
   "	highp vec3  over     = (headroom > 0.0)\n"
   "	                          ? max(lin - vec3(paper_white_nits), vec3(0.0))\n"
   "	                          : vec3(0.0);\n"
   "	highp float omax     = max(over.r, max(over.g, over.b));\n"
   "	highp float o        = (omax > 0.0) ? (omax / headroom) : 0.0;\n"
   "	/* Per-unit-overshoot factor S(o)/o, shared across channels. shoulder\n"
   "	 * selects S: 0 = Reinhard o/(o+1) (S/o = 1/(o+1)); 1 = filmic. Both have\n"
   "	 * S'(0) == 1, so the peak channel leaves white at the same slope it\n"
   "	 * arrived with and asymptotes to the ceiling.\n"
   "	 *\n"
   "	 * (1-exp(-o))/o cancels catastrophically as o -> 0 - fp32 loses 40% of the\n"
   "	 * value by o = 1e-7, and that is precisely the neighbourhood of reference\n"
   "	 * white - so the small-o branch uses the series 1 - o/2 + o^2/6, which is\n"
   "	 * good to ~1e-7 at the crossover and exact at the origin. */\n"
   "	highp float knee     = 0.0;\n"
   "	if (o > 0.0)\n"
   "	{\n"
   "		if (shoulder == 1)\n"
   "			knee = (o < 1e-2)\n"
   "			          ? (1.0 - o * (0.5 - o * (1.0 / 6.0)))\n"
   "			          : (filmic_shoulder(o) / o);\n"
   "		else\n"
   "			knee = 1.0 / (o + 1.0);\n"
   "	}\n"
   "	lin = min(lin, vec3(paper_white_nits)) + over * knee;\n"
   "	lin = rec709_to_target(lin, expand_gamut);\n"
   "	return pq_encode(lin);\n"
   "}\n"
   "\n"
   "highp vec3 encode_hdr10(highp vec3 rgb, highp float paper_white_nits, highp float peak_nits,\n"
   "                        int expand_gamut, int shoulder, int sdr_eotf, int src_primaries)\n"
   "{\n"
   "	return encode_hdr10_linear(sdr_to_linear(max(rgb, vec3(0.0)), sdr_eotf),\n"
   "	                           paper_white_nits, peak_nits, expand_gamut, shoulder,\n"
   "	                           src_primaries);\n"
   "}\n"
   "\n"
   "/* ---- Debanding dither -----------------------------------------------------\n"
   " * For the genuinely-8-bit paths whose gradients are already quantised and\n"
   " * would band at 10-bit. ~1 8-bit-LSB of triangular-PDF noise, spatially\n"
   " * distributed with interleaved gradient noise (blue-noise-like, no texture),\n"
   " * applied in gamma space. TPDF decorrelates the noise from the signal, so it\n"
   " * reads as a faint even grain. Not for interpolated content (mipmap resolve,\n"
   " * YUV chroma) - that already carries sub-8-bit precision 10-bit preserves. */\n"
   "highp float hdr_ign(highp vec2 p, highp vec2 k)  /* IGN along direction k -> [0,1) */\n"
   "{\n"
   "	return fract(52.9829189 * fract(dot(p, k)));\n"
   "}\n"
   "\n"
   "highp float hdr_tri(highp float u)  /* uniform [0,1) -> triangular (-1,1) */\n"
   "{\n"
   "	return u < 0.5 ? sqrt(2.0 * u) - 1.0 : 1.0 - sqrt(2.0 - 2.0 * u);\n"
   "}\n"
   "\n"
   "highp vec3 hdr_deband(highp vec3 rgb, highp vec2 fragcoord)\n"
   "{\n"
   "	/* Per-channel IGN *direction* (not a shared field offset) gives three\n"
   "	 * decorrelated fields, so the grain carries no chroma tint (pairwise\n"
   "	 * channel correlation ~0). Each is remapped to a triangular PDF. */\n"
   "	highp vec3 t = vec3(\n"
   "		hdr_tri(hdr_ign(fragcoord, vec2( 0.06711056,  0.00583715))),\n"
   "		hdr_tri(hdr_ign(fragcoord, vec2( 0.00583715,  0.06711056))),\n"
   "		hdr_tri(hdr_ign(fragcoord, vec2( 0.06711056, -0.00583715))));\n"
   "	return rgb + t * (1.0 / 255.0);\n"
   "}\n"
   "\n"
   "/* Luma-only variant for the YUV FMV path. Its chroma is reconstructed at\n"
   " * sub-8-bit precision (2x2 average + bilinear) that 10-bit preserves, so it\n"
   " * must NOT be dithered; only the per-pixel 8-bit luma steps. An equal offset\n"
   " * to R,G,B is chroma-neutral through the BT.601 matrix (U,V rows sum ~0, Y\n"
   " * row 1), so one triangular sample on all channels dithers luma alone. */\n"
   "highp vec3 hdr_deband_luma(highp vec3 rgb, highp vec2 fragcoord)\n"
   "{\n"
   "	highp float t = hdr_tri(hdr_ign(fragcoord, vec2(0.06711056, 0.00583715)));\n"
   "	return rgb + vec3(t) * (1.0 / 255.0);\n"
   "}\n"
   "\n"
   "#endif /* HDR_H */\n"
   "/* ---- end hdr.h ---- */\n"
   "\n"
   "in highp vec2 vUV;\n"
   "out highp vec4 FragColor;\n"
   "\n"
   "uniform sampler2D uSource;\n"
   "\n"
   "uniform vec2 reg_src_size;\n"
   "uniform vec2 reg_native_size;\n"
   "uniform int reg_sdr_eotf;\n"
   "\n"
   "void main()\n"
   "{\n"
   "	highp vec2  scale = reg_src_size / reg_native_size;\n"
   "	highp ivec2 n     = ivec2(max(floor(scale + 0.5), vec2(1.0)));\n"
   "	highp vec2  base   = floor(gl_FragCoord.xy) * scale;\n"
   "\n"
   "	highp vec3  acc = vec3(0.0);\n"
   "	highp float cnt = 0.0;\n"
   "	int x, y;\n"
   "\n"
   "	for (y = 0; y < n.y; y++)\n"
   "	{\n"
   "		for (x = 0; x < n.x; x++)\n"
   "		{\n"
   "			highp vec2 t = clamp(base + vec2(float(x), float(y)) + 0.5,\n"
   "			                     vec2(0.0), reg_src_size - 0.5);\n"
   "			acc += sdr_to_linear(max(textureLod(uSource, t / reg_src_size, 0.0).rgb,\n"
   "			                         vec3(0.0)), reg_sdr_eotf);\n"
   "			cnt += 1.0;\n"
   "		}\n"
   "	}\n"
   "\n"
   "	FragColor = vec4(linear_to_sdr(acc / max(cnt, 1.0), reg_sdr_eotf), 1.0);\n"
   "}\n"
   ;

#endif /* GL_ANALOG_DOWNSAMPLE_GLSL_H */
