edgefirst-image 0.34.0

High-performance image processing with hardware acceleration for edge AI
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
// SPDX-FileCopyrightText: Copyright 2026 Au-Zone Technologies
// SPDX-License-Identifier: Apache-2.0

//! GL shader sources shared across platform backends (macOS ANGLE + Linux).

// ---------------------------------------------------------------------------
// Portable `#version 300 es` fragment shaders shared verbatim by both the
// Linux (PBO/DMA EGLImage) and macOS (IOSurface/ANGLE) render paths, so the
// GLSL bytes are validated identically on every backend.
//
// (YUV→RGB matrix conversion lives in the per-backend NV shaders — Linux
// `shaders::generate_nv_to_rgba_shader_2d` and the macOS `NV_TO_RGBA_FRAGMENT`,
// each carrying the six per-tensor colorimetry uniforms from
// `crate::colorimetry::yuv_to_rgb_coeffs`. Packed YUYV uses the driver Path A
// EGL color-space/range hints. Only the colorimetry-agnostic float packers
// are shared here.)
// ---------------------------------------------------------------------------

/// RGBA8 -> packed planar-float fragment shader (`PlanarRgb` / `PlanarRgba`,
/// F16 or F32).
///
/// The render target is a single `RGBA16F` (F16) or `RGBA32F` (F32) texture
/// sized `(W/4, C*H)` for a `C`-plane destination. Each output texel packs 4
/// float channel samples into its four components, so a linear readout of the
/// rendered texture produces a tightly-packed `[C, H, W]` buffer (CHW order,
/// one plane per row-band). Width `W` must be a multiple of 4.
///
/// The plane index comes from the surface geometry (`floor(oy / H)`), not from
/// a uniform, so a `PlanarRgba` surface of height `4*H` selects plane 3 on its
/// own; the sampler carries a branch for each of the four planes and
/// `pad_color` is a `vec4` so plane 3 pads with the same alpha the CPU
/// reference writes.
///
/// This is the single source of truth consumed by the Linux (PBO/DMA), macOS
/// (IOSurface/ANGLE) and Windows (D3D11 texture) render paths. The GLSL bytes
/// are validated against the CPU converter by the on-target F16 round-trip
/// tests, the macOS IOSurface integration tests and
/// `windows_float_destinations_match_the_cpu_converter_for_every_layout`.
pub(crate) const PLANAR_RGB_F16_PACKED_FRAGMENT: &str = r#"#version 300 es
precision highp float;
precision highp int;
uniform sampler2D src;
uniform vec2 dst_image_size;  // (W, H) — destination plane size
uniform vec4 src_rect_uv;     // (origin_u, origin_v, size_u, size_v)
uniform vec4 src_extent;      // (u_min, v_min, u_max, v_max) a sample may reach
uniform vec4 dst_rect_px;     // (origin_x, origin_y, w, h) within one plane
uniform vec4 pad_color;       // per-channel normalized pad value (RGBA)
out vec4 frag;

// Sample one planar element. Returns the per-channel value at
// (in_plane_x, in_plane_y) for the given plane (0=R, 1=G, 2=B, 3=A).
// Pad value is returned when (in_plane_x, in_plane_y) is outside
// `dst_rect_px`.
float sample_planar_element(float in_plane_x, float in_plane_y, float plane) {
    bool inside_dst = (in_plane_x >= dst_rect_px.x) &&
                      (in_plane_x <  dst_rect_px.x + dst_rect_px.z) &&
                      (in_plane_y >= dst_rect_px.y) &&
                      (in_plane_y <  dst_rect_px.y + dst_rect_px.w);
    if (inside_dst) {
        float u = (in_plane_x - dst_rect_px.x) / dst_rect_px.z;
        float v = (in_plane_y - dst_rect_px.y) / dst_rect_px.w;
        vec2 src_uv = clamp(src_rect_uv.xy + vec2(u, v) * src_rect_uv.zw,
                            src_extent.xy, src_extent.zw);
        vec4 rgba = texture(src, src_uv);
        if (plane < 0.5) {
            return rgba.r;
        } else if (plane < 1.5) {
            return rgba.g;
        } else if (plane < 2.5) {
            return rgba.b;
        } else {
            return rgba.a;
        }
    } else {
        if (plane < 0.5) {
            return pad_color.r;
        } else if (plane < 1.5) {
            return pad_color.g;
        } else if (plane < 2.5) {
            return pad_color.b;
        } else {
            return pad_color.a;
        }
    }
}

void main() {
    // gl_FragCoord is at pixel center (n+0.5). Floor for the integer
    // index of the output pixel on the (W/4 × 3H) surface.
    int ox = int(floor(gl_FragCoord.x));
    int oy = int(floor(gl_FragCoord.y));

    float plane = floor(float(oy) / dst_image_size.y);
    float in_plane_y = float(oy) - plane * dst_image_size.y + 0.5;

    // Sample 4 consecutive in-plane x positions. Pixel center is
    // (x+0.5) so the first element of pixel `ox` starts at logical
    // tensor column `ox*4` — add 0.5 for the texel center sampled
    // by the bilinear filter.
    float base_x = float(ox * 4) + 0.5;
    float e0 = sample_planar_element(base_x + 0.0, in_plane_y, plane);
    float e1 = sample_planar_element(base_x + 1.0, in_plane_y, plane);
    float e2 = sample_planar_element(base_x + 2.0, in_plane_y, plane);
    float e3 = sample_planar_element(base_x + 3.0, in_plane_y, plane);

    // An RGBA16F attachment narrows each f32 to f16 on write; an RGBA32F
    // one keeps it. Either way the resulting byte layout in the surface is
    // exactly the [C, H, W] planar order the consumer expects.
    frag = vec4(e0, e1, e2, e3);
}
"#;

/// RGBA8 -> packed interleaved-float fragment shader (`Rgb`, F16 or F32).
///
/// The render target is a single `RGBA16F` (F16) or `RGBA32F` (F32) texture
/// sized `(W*3/4, H)`. Each output texel packs 4 consecutive elements of the
/// destination's `[H, W, 3]` stream into its four components, so a linear
/// readout produces a tightly-packed NHWC buffer. `W*3` must divide into whole
/// texels, which for a 3-channel row means `W % 4 == 0`.
///
/// Same crop and pad contract as the planar packer: the destination element
/// index gives the pixel and channel, `dst_rect_px` decides whether that pixel
/// is content or pad, and `src_rect_uv` maps content onto the source. Sampling
/// the source once per element rather than packing an already-rendered u8
/// intermediate keeps the result identical to the F32 NHWC PBO path, which
/// samples the same way.
pub(crate) const FLOAT_NHWC_PACKED_FRAGMENT: &str = r#"#version 300 es
precision highp float;
precision highp int;
uniform sampler2D src;
uniform vec4 src_rect_uv;     // (origin_u, origin_v, size_u, size_v)
uniform vec4 src_extent;      // (u_min, v_min, u_max, v_max) a sample may reach
uniform vec4 dst_rect_px;     // (origin_x, origin_y, w, h) in pixel space
uniform vec4 pad_color;       // per-channel normalized pad value (RGBA)
out vec4 frag;

// Sample one element of the interleaved [H, W, 3] stream. `element` is the
// element's index within its row, `oy` the destination row; the pad value is
// returned when the element's pixel lies outside `dst_rect_px`.
float sample_interleaved_element(int element, int oy) {
    int px = element / 3;
    int channel = element - px * 3;
    float fx = float(px);
    float fy = float(oy);
    bool inside_dst = (fx >= dst_rect_px.x) &&
                      (fx <  dst_rect_px.x + dst_rect_px.z) &&
                      (fy >= dst_rect_px.y) &&
                      (fy <  dst_rect_px.y + dst_rect_px.w);
    if (!inside_dst) {
        return (channel == 0) ? pad_color.r
             : (channel == 1) ? pad_color.g : pad_color.b;
    }
    // Pixel center (+0.5) so LINEAR filtering resamples about the right
    // point, matching the F32 NHWC PBO shader.
    float u = (fx + 0.5 - dst_rect_px.x) / dst_rect_px.z;
    float v = (fy + 0.5 - dst_rect_px.y) / dst_rect_px.w;
    vec2 src_uv = clamp(src_rect_uv.xy + vec2(u, v) * src_rect_uv.zw,
                        src_extent.xy, src_extent.zw);
    vec4 rgba = texture(src, src_uv);
    return (channel == 0) ? rgba.r
         : (channel == 1) ? rgba.g : rgba.b;
}

void main() {
    // gl_FragCoord is at pixel center (n+0.5). Floor for the integer index
    // of the output texel on the (W*3/4 x H) surface.
    int ox = int(floor(gl_FragCoord.x));
    int oy = int(floor(gl_FragCoord.y));
    int base = ox * 4;
    frag = vec4(sample_interleaved_element(base + 0, oy),
                sample_interleaved_element(base + 1, oy),
                sample_interleaved_element(base + 2, oy),
                sample_interleaved_element(base + 3, oy));
}
"#;

/// RGBA8 -> float `Rgba` fragment shader: the float paths' crop and pad
/// contract at one texel per pixel.
///
/// The render target is an `RGBA16F` (F16) or `RGBA32F` (F32) texture sized
/// `(W, H)` — an `Rgba` float destination needs no packing, so the whole
/// fetched texel is the output, alpha included.
///
/// Addressing comes from `gl_FragCoord`, not the interpolated texcoord: the
/// other float shaders read the destination row straight off `gl_FragCoord.y`
/// (framebuffer row 0 is the surface's first row of memory) and the quad's
/// texcoords run the other way down the viewport.
pub(crate) const FLOAT_RGBA_FRAGMENT: &str = r#"#version 300 es
precision highp float;
precision highp int;
uniform sampler2D src;
uniform vec4 src_rect_uv;     // (origin_u, origin_v, size_u, size_v)
uniform vec4 src_extent;      // (u_min, v_min, u_max, v_max) a sample may reach
uniform vec4 dst_rect_px;     // (origin_x, origin_y, w, h) in pixel space
uniform vec4 pad_color;       // per-channel normalized pad value (RGBA)
out vec4 color;

void main() {
    float px = float(int(floor(gl_FragCoord.x)));
    float py = float(int(floor(gl_FragCoord.y)));
    bool inside_dst = (px >= dst_rect_px.x) &&
                      (px <  dst_rect_px.x + dst_rect_px.z) &&
                      (py >= dst_rect_px.y) &&
                      (py <  dst_rect_px.y + dst_rect_px.w);
    if (!inside_dst) {
        color = pad_color;
        return;
    }
    // Pixel center (+0.5), as in the packed float shaders.
    float u = (px + 0.5 - dst_rect_px.x) / dst_rect_px.z;
    float v = (py + 0.5 - dst_rect_px.y) / dst_rect_px.w;
    vec2 src_uv = clamp(src_rect_uv.xy + vec2(u, v) * src_rect_uv.zw,
                        src_extent.xy, src_extent.zw);
    color = texture(src, src_uv);
}
"#;

/// Fullscreen-quad vertex shader shared by both backends. Position is a `vec3`
/// (NDC, `z` unused for the 2D quad but carried so the same shader serves the
/// segmentation passes); passes `fragPos`/`tc` to the fragment stage. The VBO
/// feeds `pos` (location 0) and `texCoord` (location 1).
pub(crate) const VERTEX_SHADER: &str = "\
#version 300 es
precision mediump float;
layout(location = 0) in vec3 pos;
layout(location = 1) in vec2 texCoord;

out vec3 fragPos;
out vec2 tc;

void main() {
    fragPos = pos;
    tc = texCoord;

    gl_Position = vec4(pos, 1.0);
}
";

// ---------------------------------------------------------------------------
// NV12/NV16/NV24 (semi-planar YUV, single R8 plane) -> RGBA, shared across
// backends. Both the Linux (DMA-BUF EGLImage) and macOS (IOSurface/ANGLE) paths
// decode the SAME combined-plane byte layout (`PixelFormat::chroma_layout` +
// `combined_plane_height`) using the **divide-free** addressing form: direct 2D
// `texelFetch` + a branchless `carry` for NV24's wrapping 2W-byte chroma row.
// No per-pixel integer divide/modulo — that is pathologically slow on Vivante
// GC7000UL / V3D and is also the software-emulated slow path on Apple GPUs
// (variable divisor), so the divide-free form is the right default everywhere.
//
// Both backends use the same `VERTEX_SHADER` (above), so the fragment shader is
// byte-identical on both — ONE source string. The bytes are validated on-target
// (Linux) and frozen by `nv_fragment_byte_identical` below.

/// Declarations shared by the NV->RGBA program and its int8 twin, ending with
/// `nv_rgb(x, y)`: the exact YUV->RGB conversion of the luma texel at (x, y)
/// and its co-sited chroma. References the `chroma_shift`/`chroma_lines`/
/// `tex_width` layout uniforms and the six colorimetry uniforms.
macro_rules! nv_rgba_header {
    () => {
        "\
#version 300 es
precision highp float;
precision highp int;
uniform highp sampler2D src;
uniform ivec2 img_size;
uniform int tex_width;
uniform ivec2 chroma_shift;
uniform int chroma_lines;
// Inclusive texel bounds (x0, y0, x1, y1) of the source crop: every tap is
// clamped to them, so a crop never reads pixels from outside itself.
uniform ivec4 src_rect;
// Hardware-filtered program only: the chroma plane as (U, V) texels on unit 1,
// the image's share of each texture (`*_scale`), and the crop's half-texel-
// inset rectangle on each (`*_clamp`), which keeps the LINEAR kernel inside
// the crop and the luma rows.
uniform highp sampler2D uv_tex;
uniform vec2 luma_scale;
uniform vec4 luma_clamp;
uniform vec2 chroma_scale;
uniform vec4 chroma_clamp;
// Per-tensor colorimetry (YUV→RGB matrix + range), set by draw_nv_texture_2d
// from the source tensor's resolved colorimetry. Path B applies the matrix in
// the shader, so it is correct regardless of driver EGL color-hint support.
uniform float y_offset;
uniform float y_scale;
uniform float c_vr;
uniform float c_ug;
uniform float c_vg;
uniform float c_ub;
in vec3 fragPos;
in vec2 tc;
out vec4 color;

// Floor expanded luma at 0 to match the CPU `yuv` crate's saturating (Y-16)
// term (limited footroom Y<16 → 0). The top is left uncapped — the crate lets
// headroom exceed 1.0 and relies on the final RGB clamp, so the GL path must
// too. No-op for full range (y_offset=0, y_scale=1).
vec3 nv_yuv_to_rgb(float yv, float u, float v) {
    float yp = max((yv - y_offset) * y_scale, 0.0);
    float up = u - 128.0 / 255.0;
    float vp = v - 128.0 / 255.0;
    return clamp(vec3(yp + c_vr * vp, yp - c_ug * up - c_vg * vp, yp + c_ub * up), 0.0, 1.0);
}

vec3 nv_rgb(int x, int y) {
    // Luma: direct 2D texel — no per-pixel integer divide/modulo (very slow on
    // some embedded GPUs, e.g. Vivante GC7000UL).
    float yv = texelFetch(src, ivec2(x, y), 0).r;

    // Interleaved CbCr plane begins at buffer row `h`. Each image-chroma-row
    // spans `chroma_lines` R8 rows: NV24's 2W-byte row wraps once at tex_width
    // (carry); NV12/NV16 fit one row. `cx` is even so `cx+1` stays in-row.
    int ccol = x >> chroma_shift.x;
    int crow = y >> chroma_shift.y;
    int ccol2 = ccol * 2;
    int carry = ccol2 >= tex_width ? 1 : 0;
    int cy = img_size.y + crow * chroma_lines + carry;
    int cx = ccol2 - carry * tex_width;
    float u = texelFetch(src, ivec2(cx, cy), 0).r;
    float v = texelFetch(src, ivec2(cx + 1, cy), 0).r;
    return nv_yuv_to_rgb(yv, u, v);
}

"
    };
}

/// `main()` statements of the single-tap NV programs, for draws that map the
/// crop 1:1: reads the texel under the fragment and leaves it in `rgb`. `tc`
/// spans the crop's outer edges, so `tc * size` is the source position.
macro_rules! nv_rgb_nearest_body {
    () => {
        "    ivec2 i = clamp(ivec2(tc * vec2(img_size)), src_rect.xy, src_rect.zw);
    vec3 rgb = nv_rgb(i.x, i.y);
"
    };
}

/// `main()` statements of the bilinear NV programs, for draws that rescale
/// the crop: half-pixel-centred bilinear, the GL_LINEAR / OpenCV INTER_LINEAR
/// convention, with every tap clamped to the crop. Blending the four texels'
/// converted RGB equals converting at native resolution and then resizing, as
/// the CPU backend does.
macro_rules! nv_rgb_bilinear_body {
    () => {
        "    vec2 p = tc * vec2(img_size) - 0.5;
    vec2 p0 = floor(p);
    vec2 f = p - p0;
    ivec2 i0 = clamp(ivec2(p0), src_rect.xy, src_rect.zw);
    ivec2 i1 = clamp(ivec2(p0) + 1, src_rect.xy, src_rect.zw);
    vec3 rgb = mix(mix(nv_rgb(i0.x, i0.y), nv_rgb(i1.x, i0.y), f.x),
                   mix(nv_rgb(i0.x, i1.y), nv_rgb(i1.x, i1.y), f.x), f.y);
"
    };
}

/// `main()` statements of the hardware-filtered bilinear NV programs: luma
/// from the combined plane and chroma from its (U, V) view, each through the
/// texture unit's LINEAR filter, then the exact YUV->RGB matrix. Filtering
/// chroma on its own grid centres each chroma sample on the luma pixels it
/// covers, as JPEG sites it.
macro_rules! nv_rgb_hw_bilinear_body {
    () => {
        "    vec2 luv = clamp(tc * luma_scale, luma_clamp.xy, luma_clamp.zw);
    vec2 cuv = clamp(tc * chroma_scale, chroma_clamp.xy, chroma_clamp.zw);
    vec2 c = texture(uv_tex, cuv).rg;
    vec3 rgb = nv_yuv_to_rgb(texture(src, luv).r, c.r, c.g);
"
    };
}

/// The XOR 0x80 bias (`(q + 128) mod 256`) the int8 NV programs apply to
/// their output, as the other int8 shaders do.
macro_rules! nv_int8_bias {
    () => {
        "vec3 int8_bias(vec3 v) {
    vec3 q = floor(v * 255.0 + 0.5);
    return mod(q + 128.0, 256.0) / 255.0;
}

"
    };
}

/// YUYV (sampled as a GL_RG texture: R=Y, G=alternating U/V) → RGBA.
/// Each output pixel samples its own texel and the horizontal partner to
/// recover both chroma components; the YUV→RGB matrix + range come from
/// the per-tensor colorimetry uniforms (same names as the NV program).
/// `src_size` is the texture's texel grid and `src_extent` the logical
/// image's rectangle on it (`render::sample_clamp_rect`), which differ when
/// the import covers more of the texture than the logical image.
/// Portable `sampler2D` — shared by the IOSurface zero-copy source path
/// and any future heap-YUYV upload path.
/// Declarations shared by the YUYV->RGBA program and its int8 twin.
macro_rules! yuyv_rgba_2d_header {
    () => {
        r#"#version 300 es
precision highp float;
uniform highp sampler2D tex;
uniform vec2 src_size;
uniform vec4 src_extent;
uniform float y_offset;
uniform float y_scale;
uniform float c_vr;
uniform float c_ug;
uniform float c_vg;
uniform float c_ub;
in vec3 fragPos;
in vec2 tc;
out vec4 color;

"#
    };
}

/// `main()` statements shared by the YUYV->RGBA program and its int8 twin:
/// reads `tc` and the uniforms above, leaves the pixel in `r`, `g`, `b`.
macro_rules! yuyv_rgb_body {
    () => {
        r#"    vec2 texel = vec2(1.0) / src_size;
    // `src_extent` keeps the texel index inside the logical image when the
    // texture is larger than it; `src_size` is the texture's own grid.
    vec2 col = floor(clamp(tc, src_extent.xy, src_extent.zw) * src_size);
    bool even = mod(col.x, 2.0) < 0.5;
    vec2 self_uv = (col + vec2(0.5)) * texel;
    // The clamp bounds `col`, not the partner. On an odd logical width the
    // last column is even, so its partner is one texel past the logical
    // image: stale pool content on a narrowed import, and the column itself
    // under CLAMP_TO_EDGE otherwise. Out of scope, and unchanged by the
    // clamp: a YUYV chroma pair spans two columns, so an odd width has no
    // partner to read in any layout (the allocation is `width` texels wide,
    // not rounded up), and the engine's YUYV coverage is even widths only.
    vec2 pair_uv = (col + vec2(even ? 1.5 : -0.5, 0.5)) * texel;

    vec4 self_rg = texture(tex, self_uv);
    vec4 pair_rg = texture(tex, pair_uv);
    float y = self_rg.r;
    float u, v;
    if (even) { u = self_rg.g; v = pair_rg.g; }
    else      { v = self_rg.g; u = pair_rg.g; }

    // Identical matrix/range math to the NV program: floor the expanded
    // luma at 0 (limited-range footroom folds to 0; full range is a no-op
    // with y_offset=0/y_scale=1), top end left to the per-channel clamp.
    float yp = max((y - y_offset) * y_scale, 0.0);
    float up = u - 128.0 / 255.0;
    float vp = v - 128.0 / 255.0;
    float r = clamp(yp + c_vr * vp, 0.0, 1.0);
    float g = clamp(yp - c_ug * up - c_vg * vp, 0.0, 1.0);
    float b = clamp(yp + c_ub * up, 0.0, 1.0);
"#
    };
}

pub(crate) const YUYV_RGBA_2D_FRAGMENT: &str = concat!(
    yuyv_rgba_2d_header!(),
    "void main() {\n",
    yuyv_rgb_body!(),
    "    color = vec4(r, g, b, 1.0);\n}\n"
);

/// Int8 variant of [`YUYV_RGBA_2D_FRAGMENT`]: the same pixel, with the XOR
/// 0x80 bias every int8 program applies (`(q + 128) mod 256`, matching the
/// CPU `byte ^ 0x80`).
pub(crate) const YUYV_RGBA_2D_INT8_FRAGMENT: &str = concat!(
    yuyv_rgba_2d_header!(),
    "vec3 int8_bias(vec3 v) {\n",
    "    vec3 q = floor(v * 255.0 + 0.5);\n",
    "    return mod(q + 128.0, 256.0) / 255.0;\n",
    "}\n\n",
    "void main() {\n",
    yuyv_rgb_body!(),
    "    color = vec4(int8_bias(vec3(r, g, b)), 1.0);\n}\n"
);

/// NV->RGBA fragment shader (Path B) for draws that do not rescale. Vertex
/// stage ([`VERTEX_SHADER`]) provides `fragPos`/`tc`; output is `color`. The
/// bytes are validated on-target (Linux) and frozen by the golden test below.
pub(crate) const NV_RGBA_FRAGMENT: &str = concat!(
    nv_rgba_header!(),
    "void main() {\n",
    nv_rgb_nearest_body!(),
    "    color = vec4(rgb, 1.0);\n}\n"
);

/// Int8 variant of [`NV_RGBA_FRAGMENT`].
pub(crate) const NV_RGBA_INT8_FRAGMENT: &str = concat!(
    nv_rgba_header!(),
    nv_int8_bias!(),
    "void main() {\n",
    nv_rgb_nearest_body!(),
    "    color = vec4(int8_bias(rgb), 1.0);\n}\n"
);

/// NV->RGBA fragment shader (Path B) for draws that rescale: bilinear. A
/// separate program rather than a uniform branch in [`NV_RGBA_FRAGMENT`]:
/// Vivante GC7000UL does not reliably honour that branch, and on V3D and
/// Adreno the untaken half still slows 1:1 draws.
pub(crate) const NV_RGBA_BILINEAR_FRAGMENT: &str = concat!(
    nv_rgba_header!(),
    "void main() {\n",
    nv_rgb_bilinear_body!(),
    "    color = vec4(rgb, 1.0);\n}\n"
);

/// NV->RGBA fragment shader (Path B) for draws that rescale, where the chroma
/// plane can be bound as its own texture: bilinear through the texture unit,
/// two filtered fetches per pixel instead of the twelve of
/// [`NV_RGBA_BILINEAR_FRAGMENT`], which remains the fallback.
pub(crate) const NV_RGBA_HW_BILINEAR_FRAGMENT: &str = concat!(
    nv_rgba_header!(),
    "void main() {\n",
    nv_rgb_hw_bilinear_body!(),
    "    color = vec4(rgb, 1.0);\n}\n"
);

/// Int8 variant of [`NV_RGBA_HW_BILINEAR_FRAGMENT`].
pub(crate) const NV_RGBA_HW_BILINEAR_INT8_FRAGMENT: &str = concat!(
    nv_rgba_header!(),
    nv_int8_bias!(),
    "void main() {\n",
    nv_rgb_hw_bilinear_body!(),
    "    color = vec4(int8_bias(rgb), 1.0);\n}\n"
);

/// Int8 variant of [`NV_RGBA_BILINEAR_FRAGMENT`].
pub(crate) const NV_RGBA_BILINEAR_INT8_FRAGMENT: &str = concat!(
    nv_rgba_header!(),
    nv_int8_bias!(),
    "void main() {\n",
    nv_rgb_bilinear_body!(),
    "    color = vec4(int8_bias(rgb), 1.0);\n}\n"
);

#[cfg(test)]
mod nv_shader_golden {
    /// The golden with CRLF normalized to LF. A checkout made with
    /// `core.autocrlf=true` (the Git for Windows default) gives
    /// `include_str!` CRLF line endings while rustc normalizes the string
    /// literal to LF. `.gitattributes` pins `*.glsl` to LF; this covers
    /// checkouts made before that attribute existed.
    fn golden(src: &str) -> String {
        src.replace("\r\n", "\n")
    }

    /// The NV->RGBA shader source is validated on-target (V3D/Mali/Vivante/Tegra);
    /// its bytes must not drift. `golden/nv_rgba_linux.glsl` is the frozen
    /// reference. This test runs on every platform (the module is uncfg'd), so
    /// byte-identity is enforced on the macOS and Windows hosts too, not just
    /// the Linux lane.
    #[test]
    fn nv_fragment_byte_identical() {
        assert_eq!(
            super::NV_RGBA_FRAGMENT,
            golden(include_str!("golden/nv_rgba_linux.glsl")).as_str(),
            "NV->RGBA shader bytes drifted from the on-target-validated golden"
        );
        assert_eq!(
            super::NV_RGBA_BILINEAR_FRAGMENT,
            golden(include_str!("golden/nv_rgba_bilinear_linux.glsl")).as_str(),
            "bilinear NV->RGBA shader bytes drifted from the on-target-validated golden"
        );
        assert_eq!(
            super::NV_RGBA_HW_BILINEAR_FRAGMENT,
            golden(include_str!("golden/nv_rgba_hw_bilinear_linux.glsl")).as_str(),
            "hardware-bilinear NV->RGBA shader bytes drifted from the on-target-validated golden"
        );
    }

    #[test]
    fn vertex_byte_identical() {
        assert_eq!(
            super::VERTEX_SHADER,
            golden(include_str!("golden/vertex.glsl")).as_str(),
            "vertex shader bytes drifted from the on-target-validated golden"
        );
    }
}