use crate::Error;
use log::error;
use std::ffi::{c_char, CString};
use std::ptr::null;
use std::str::FromStr;
pub(super) fn compile_shader_from_str(
shader: u32,
shader_source: &str,
shader_name: &str,
) -> Result<(), ()> {
let src = match CString::from_str(shader_source) {
Ok(v) => v,
Err(_) => return Err(()),
};
let src_ptr = src.as_ptr();
unsafe {
edgefirst_gl::gl::ShaderSource(shader, 1, &raw const src_ptr, null());
edgefirst_gl::gl::CompileShader(shader);
let mut is_compiled = 0;
edgefirst_gl::gl::GetShaderiv(
shader,
edgefirst_gl::gl::COMPILE_STATUS,
&raw mut is_compiled,
);
if is_compiled == 0 {
let mut max_length = 0;
edgefirst_gl::gl::GetShaderiv(
shader,
edgefirst_gl::gl::INFO_LOG_LENGTH,
&raw mut max_length,
);
let mut error_log: Vec<u8> = vec![0; max_length as usize];
edgefirst_gl::gl::GetShaderInfoLog(
shader,
max_length,
&raw mut max_length,
error_log.as_mut_ptr() as *mut c_char,
);
let msg = CString::from_vec_with_nul(error_log)
.ok()
.and_then(|c| c.into_string().ok())
.unwrap_or_else(|| "<non-UTF8 shader log>".to_string());
error!("Shader '{}' failed: {:?}\n", shader_name, msg);
edgefirst_gl::gl::DeleteShader(shader);
return Err(());
}
Ok(())
}
}
pub(super) fn check_gl_error(name: &str, line: u32) -> Result<(), Error> {
unsafe {
let err = edgefirst_gl::gl::GetError();
if err != edgefirst_gl::gl::NO_ERROR {
error!("GL Error: {name}:{line}: {err:#X}");
return Err(Error::OpenGl(format!("{err:#X}")));
}
}
Ok(())
}
pub(super) fn generate_vertex_shader() -> &'static str {
super::shaders_common::VERTEX_SHADER
}
pub(super) fn generate_texture_fragment_shader() -> &'static str {
"\
#version 300 es
precision mediump float;
uniform sampler2D tex;
uniform highp vec4 src_extent;
in vec3 fragPos;
in highp vec2 tc;
out vec4 color;
void main(){
color = texture(tex, clamp(tc, src_extent.xy, src_extent.zw));
}
"
}
pub(super) fn generate_texture_fragment_shader_yuv() -> &'static str {
"\
#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
uniform samplerExternalOES tex;
uniform highp vec4 src_extent;
in vec3 fragPos;
in highp vec2 tc;
out vec4 color;
void main(){
color = texture(tex, clamp(tc, src_extent.xy, src_extent.zw));
}
"
}
pub(super) fn generate_planar_rgb_shader() -> &'static str {
"\
#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
uniform samplerExternalOES tex;
uniform highp vec4 src_extent;
in vec3 fragPos;
in highp vec2 tc;
out vec4 color;
void main(){
color = texture(tex, clamp(tc, src_extent.xy, src_extent.zw));
}
"
}
pub(super) fn generate_texture_int8_shader() -> &'static str {
"\
#version 300 es
precision highp float;
uniform sampler2D tex;
uniform vec4 src_extent;
in vec3 fragPos;
in vec2 tc;
out vec4 color;
#ifdef QUANT_MAPPED
uniform highp vec3 q_scale; // q = clamp(floor(v * q_scale + q_offset), q_lo, q_hi)
uniform highp vec3 q_offset;
uniform highp vec3 q_lo;
uniform highp vec3 q_hi;
uniform highp vec3 q_zero; // zero point / 255; q_wrap marks a negative stored value
uniform highp vec3 q_wrap;
highp vec3 quantize(highp vec3 v) {
highp vec3 q = clamp(floor(v * q_scale + q_offset), q_lo, q_hi);
return q * (1.0 / 255.0) + q_zero + step(q, q_wrap) * (256.0 / 255.0);
}
#else
// The plain XOR 0x80 bias, `(q + 128) mod 256`, as the CPU `byte ^ 0x80`.
highp vec3 quantize(highp vec3 v) {
highp vec3 q = floor(v * 255.0 + 0.5);
return mod(q + 128.0, 256.0) / 255.0;
}
#endif
void main(){
vec4 c = texture(tex, clamp(tc, src_extent.xy, src_extent.zw));
color = vec4(quantize(c.rgb), c.a);
}
"
}
pub(super) fn generate_texture_int8_shader_yuv() -> &'static str {
"\
#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision highp float;
uniform samplerExternalOES tex;
uniform highp vec4 src_extent;
in vec3 fragPos;
in vec2 tc;
out vec4 color;
#ifdef QUANT_MAPPED
uniform highp vec3 q_scale; // q = clamp(floor(v * q_scale + q_offset), q_lo, q_hi)
uniform highp vec3 q_offset;
uniform highp vec3 q_lo;
uniform highp vec3 q_hi;
uniform highp vec3 q_zero; // zero point / 255; q_wrap marks a negative stored value
uniform highp vec3 q_wrap;
highp vec3 quantize(highp vec3 v) {
highp vec3 q = clamp(floor(v * q_scale + q_offset), q_lo, q_hi);
return q * (1.0 / 255.0) + q_zero + step(q, q_wrap) * (256.0 / 255.0);
}
#else
// The plain XOR 0x80 bias, `(q + 128) mod 256`, as the CPU `byte ^ 0x80`.
highp vec3 quantize(highp vec3 v) {
highp vec3 q = floor(v * 255.0 + 0.5);
return mod(q + 128.0, 256.0) / 255.0;
}
#endif
void main(){
vec4 c = texture(tex, clamp(tc, src_extent.xy, src_extent.zw));
color = vec4(quantize(c.rgb), c.a);
}
"
}
pub(super) fn generate_planar_rgb_int8_shader() -> &'static str {
"\
#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision highp float;
uniform samplerExternalOES tex;
uniform highp vec4 src_extent;
in vec3 fragPos;
in vec2 tc;
out vec4 color;
#ifdef QUANT_MAPPED
uniform highp vec3 q_scale; // q = clamp(floor(v * q_scale + q_offset), q_lo, q_hi)
uniform highp vec3 q_offset;
uniform highp vec3 q_lo;
uniform highp vec3 q_hi;
uniform highp vec3 q_zero; // zero point / 255; q_wrap marks a negative stored value
uniform highp vec3 q_wrap;
highp vec3 quantize(highp vec3 v) {
highp vec3 q = clamp(floor(v * q_scale + q_offset), q_lo, q_hi);
return q * (1.0 / 255.0) + q_zero + step(q, q_wrap) * (256.0 / 255.0);
}
#else
// The plain XOR 0x80 bias, `(q + 128) mod 256`, as the CPU `byte ^ 0x80`.
highp vec3 quantize(highp vec3 v) {
highp vec3 q = floor(v * 255.0 + 0.5);
return mod(q + 128.0, 256.0) / 255.0;
}
#endif
void main(){
vec4 c = texture(tex, clamp(tc, src_extent.xy, src_extent.zw));
color = vec4(quantize(c.rgb), c.a);
}
"
}
pub(super) fn generate_planar_rgb_shader_2d() -> &'static str {
"\
#version 300 es
precision mediump float;
uniform sampler2D tex;
in vec3 fragPos;
in vec2 tc;
out vec4 color;
void main(){
color = texture(tex, tc);
}
"
}
pub(super) fn generate_planar_rgb_int8_shader_2d() -> &'static str {
"\
#version 300 es
precision highp float;
uniform sampler2D tex;
in vec3 fragPos;
in vec2 tc;
out vec4 color;
#ifdef QUANT_MAPPED
uniform highp vec3 q_scale; // q = clamp(floor(v * q_scale + q_offset), q_lo, q_hi)
uniform highp vec3 q_offset;
uniform highp vec3 q_lo;
uniform highp vec3 q_hi;
uniform highp vec3 q_zero; // zero point / 255; q_wrap marks a negative stored value
uniform highp vec3 q_wrap;
highp vec3 quantize(highp vec3 v) {
highp vec3 q = clamp(floor(v * q_scale + q_offset), q_lo, q_hi);
return q * (1.0 / 255.0) + q_zero + step(q, q_wrap) * (256.0 / 255.0);
}
#else
// The plain XOR 0x80 bias, `(q + 128) mod 256`, as the CPU `byte ^ 0x80`.
highp vec3 quantize(highp vec3 v) {
highp vec3 q = floor(v * 255.0 + 0.5);
return mod(q + 128.0, 256.0) / 255.0;
}
#endif
void main(){
vec4 c = texture(tex, tc);
color = vec4(quantize(c.rgb), c.a);
}
"
}
pub(super) fn generate_segmentation_shader() -> &'static str {
"\
#version 300 es
precision mediump float;
precision mediump sampler2DArray;
uniform sampler2DArray tex;
uniform vec4 colors[20];
uniform int background_index;
uniform float opacity;
in vec3 fragPos;
in highp vec2 tc;
in vec4 fragColor;
out vec4 color;
float max_arg(const in vec4 args, out int argmax) {
if (args[0] >= args[1] && args[0] >= args[2] && args[0] >= args[3]) {
argmax = 0;
return args[0];
}
if (args[1] >= args[0] && args[1] >= args[2] && args[1] >= args[3]) {
argmax = 1;
return args[1];
}
if (args[2] >= args[0] && args[2] >= args[1] && args[2] >= args[3]) {
argmax = 2;
return args[2];
}
argmax = 3;
return args[3];
}
void main() {
mediump int layers = textureSize(tex, 0).z;
float max_all = -4.0;
int max_ind = 0;
for (int i = 0; i < layers; i++) {
vec4 d = texture(tex, vec3(tc, i));
int max_ind_ = 0;
float max_ = max_arg(d, max_ind_);
if (max_ <= max_all) { continue; }
max_all = max_;
max_ind = i*4 + max_ind_;
}
if (max_ind == background_index) {
discard;
}
max_ind = max_ind % 20;
vec4 c = colors[max_ind];
color = vec4(c.rgb, c.a * opacity);
}
"
}
pub(super) fn generate_instanced_segmentation_shader() -> &'static str {
"\
#version 300 es
precision mediump float;
uniform sampler2D mask0;
uniform vec4 colors[20];
uniform int class_index;
uniform float opacity;
in vec3 fragPos;
in vec2 tc;
in vec4 fragColor;
out vec4 color;
void main() {
float r0 = texture(mask0, tc).r;
float edge = smoothstep(0.5, 0.65, r0);
if (edge <= 0.0) {
discard;
}
vec4 c = colors[class_index % 20];
color = vec4(c.rgb, c.a * edge * opacity);
}
"
}
pub(super) fn generate_proto_segmentation_shader() -> &'static str {
"\
#version 300 es
precision highp float;
precision highp sampler2DArray;
uniform sampler2DArray proto_tex; // ceil(num_protos/4) layers, RGBA = 4 channels per layer
uniform vec4 mask_coeff[8]; // 32 coefficients packed as 8 vec4s
uniform vec4 colors[20];
uniform int class_index;
uniform int num_layers;
uniform float opacity;
in vec2 tc;
out vec4 color;
void main() {
float acc = 0.0;
for (int i = 0; i < num_layers; i++) {
// texture() returns bilinearly interpolated proto values (GL_LINEAR)
acc += dot(mask_coeff[i], texture(proto_tex, vec3(tc, float(i))));
}
float mask = 1.0 / (1.0 + exp(-acc)); // sigmoid
if (mask < 0.5) discard;
vec4 c = colors[class_index % 20];
color = vec4(c.rgb, c.a * opacity);
}
"
}
pub(super) fn generate_proto_segmentation_shader_int8_nearest() -> &'static str {
"\
#version 300 es
precision highp float;
precision highp int;
precision highp isampler2DArray;
uniform isampler2DArray proto_tex; // 32 layers, R channel = 1 proto per layer
uniform vec4 mask_coeff[8]; // 32 coefficients packed as 8 vec4s
uniform vec4 colors[20];
uniform int class_index;
uniform int num_protos;
uniform float proto_scale;
uniform float proto_scaled_zp; // -zero_point * scale
uniform float opacity;
in vec2 tc;
out vec4 color;
void main() {
ivec3 tex_size = textureSize(proto_tex, 0);
int ix = clamp(int(tc.x * float(tex_size.x)), 0, tex_size.x - 1);
int iy = clamp(int(tc.y * float(tex_size.y)), 0, tex_size.y - 1);
float acc = 0.0;
for (int k = 0; k < num_protos; k++) {
float raw = float(texelFetch(proto_tex, ivec3(ix, iy, k), 0).r);
float val = raw * proto_scale + proto_scaled_zp;
acc += mask_coeff[k / 4][k % 4] * val;
}
float mask = 1.0 / (1.0 + exp(-acc));
if (mask < 0.5) discard;
vec4 c = colors[class_index % 20];
color = vec4(c.rgb, c.a * opacity);
}
"
}
pub(super) fn generate_proto_segmentation_shader_int8_bilinear() -> &'static str {
"\
#version 300 es
precision highp float;
precision highp int;
precision highp isampler2DArray;
uniform isampler2DArray proto_tex; // 32 layers, R channel = 1 proto per layer
uniform vec4 mask_coeff[8]; // 32 coefficients packed as 8 vec4s
uniform vec4 colors[20];
uniform int class_index;
uniform int num_protos;
uniform float proto_scale;
uniform float proto_scaled_zp; // -zero_point * scale
uniform float opacity;
in vec2 tc;
out vec4 color;
void main() {
ivec3 tex_size = textureSize(proto_tex, 0);
// Compute continuous position (matching GL_LINEAR convention: center at +0.5)
vec2 pos = tc * vec2(tex_size.xy) - 0.5;
vec2 f = fract(pos);
ivec2 p0 = ivec2(floor(pos));
ivec2 p1 = p0 + 1;
// Clamp to texture bounds
p0 = clamp(p0, ivec2(0), tex_size.xy - 1);
p1 = clamp(p1, ivec2(0), tex_size.xy - 1);
float w00 = (1.0 - f.x) * (1.0 - f.y);
float w10 = f.x * (1.0 - f.y);
float w01 = (1.0 - f.x) * f.y;
float w11 = f.x * f.y;
float acc = 0.0;
for (int k = 0; k < num_protos; k++) {
float r00 = float(texelFetch(proto_tex, ivec3(p0.x, p0.y, k), 0).r);
float r10 = float(texelFetch(proto_tex, ivec3(p1.x, p0.y, k), 0).r);
float r01 = float(texelFetch(proto_tex, ivec3(p0.x, p1.y, k), 0).r);
float r11 = float(texelFetch(proto_tex, ivec3(p1.x, p1.y, k), 0).r);
float interp = r00 * w00 + r10 * w10 + r01 * w01 + r11 * w11;
float val = interp * proto_scale + proto_scaled_zp;
acc += mask_coeff[k / 4][k % 4] * val;
}
float mask = 1.0 / (1.0 + exp(-acc));
if (mask < 0.5) discard;
vec4 c = colors[class_index % 20];
color = vec4(c.rgb, c.a * opacity);
}
"
}
pub(super) fn generate_proto_dequant_shader_int8() -> &'static str {
"\
#version 300 es
precision highp float;
precision highp int;
precision highp isampler2DArray;
uniform isampler2DArray proto_tex; // 32 layers of R8I (1 proto per layer)
uniform float proto_scale;
uniform float proto_scaled_zp; // -zero_point * scale
uniform int base_layer; // first proto index for this output layer (0, 4, 8, ...)
in vec2 tc;
out vec4 color;
void main() {
ivec3 tex_size = textureSize(proto_tex, 0);
int ix = clamp(int(tc.x * float(tex_size.x)), 0, tex_size.x - 1);
int iy = clamp(int(tc.y * float(tex_size.y)), 0, tex_size.y - 1);
vec4 result;
for (int c = 0; c < 4; c++) {
int layer = base_layer + c;
// Tail guard: when num_protos % 4 != 0 the last output layer's
// trailing channels have no source proto. texelFetch beyond the
// array depth is undefined in GLSL ES (a NaN there would survive
// the zero coefficients downstream: NaN * 0 = NaN), so emit an
// explicit zero proto instead.
if (layer < tex_size.z) {
float raw = float(texelFetch(proto_tex, ivec3(ix, iy, layer), 0).r);
result[c] = raw * proto_scale + proto_scaled_zp;
} else {
result[c] = 0.0;
}
}
color = result;
}
"
}
pub(super) fn generate_proto_segmentation_shader_f32() -> &'static str {
"\
#version 300 es
precision highp float;
precision highp sampler2DArray;
uniform sampler2DArray proto_tex; // 32 layers, R channel = 1 proto per layer
uniform vec4 mask_coeff[8]; // 32 coefficients packed as 8 vec4s
uniform vec4 colors[20];
uniform int class_index;
uniform int num_protos;
uniform float opacity;
in vec2 tc;
out vec4 color;
void main() {
float acc = 0.0;
for (int k = 0; k < num_protos; k++) {
// texture() returns bilinearly interpolated proto value (GL_LINEAR on R32F)
float val = texture(proto_tex, vec3(tc, float(k))).r;
acc += mask_coeff[k / 4][k % 4] * val;
}
float mask = 1.0 / (1.0 + exp(-acc));
if (mask < 0.5) discard;
vec4 c = colors[class_index % 20];
color = vec4(c.rgb, c.a * opacity);
}
"
}
pub(super) fn generate_packed_f32_nhwc_shader() -> &'static str {
"\
#version 300 es
precision highp float;
precision highp int;
uniform sampler2D u_tex; // RGBA8 source, normalized fetch -> [0,1]
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 pad value as written (RGBA)
uniform vec4 norm_scale; // output = sample * norm_scale + norm_offset
uniform vec4 norm_offset;
out float frag_value;
void main() {
int ox = int(gl_FragCoord.x);
int oy = int(gl_FragCoord.y);
int channel = ox % 3;
int px = ox / 3;
int py = oy;
bool inside = (float(px) >= dst_rect_px.x) &&
(float(px) < dst_rect_px.x + dst_rect_px.z) &&
(float(py) >= dst_rect_px.y) &&
(float(py) < dst_rect_px.y + dst_rect_px.w);
if (!inside) {
frag_value = (channel == 0) ? pad_color.r
: (channel == 1) ? pad_color.g : pad_color.b;
return;
}
float u = (float(px) + 0.5 - dst_rect_px.x) / dst_rect_px.z;
float v = (float(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);
vec4 rgba = texture(u_tex, src_uv) * norm_scale + norm_offset;
frag_value = (channel == 0) ? rgba.r
: (channel == 1) ? rgba.g : rgba.b;
}
"
}
pub(super) fn generate_planar_rgb_f16_packed_shader() -> &'static str {
super::shaders_common::PLANAR_RGB_F16_PACKED_FRAGMENT
}
pub(super) fn generate_float_nhwc_packed_shader() -> &'static str {
super::shaders_common::FLOAT_NHWC_PACKED_FRAGMENT
}
pub(super) fn generate_float_rgba_shader() -> &'static str {
super::shaders_common::FLOAT_RGBA_FRAGMENT
}
pub(super) fn generate_color_shader() -> &'static str {
"\
#version 300 es
precision mediump float;
uniform vec4 colors[20];
uniform int class_index;
uniform float opacity;
out vec4 color;
void main() {
int index = class_index % 20;
vec4 c = colors[index];
color = vec4(c.rgb, c.a * opacity);
}
"
}
pub(super) fn generate_packed_rgba8_shader_2d() -> &'static str {
"\
#version 300 es
precision highp float;
precision highp int;
uniform sampler2D tex;
out vec4 color;
void main() {
// gl_FragCoord is at pixel center (n+0.5). Use floor() for robust
// integer pixel index on all GPUs (Vivante, Mali, Adreno).
int out_x = int(floor(gl_FragCoord.x));
int out_y = int(floor(gl_FragCoord.y));
int base = out_x * 4;
// 4 consecutive byte indices map to at most 2 source pixels
int px0 = base / 3;
int px1 = (base + 3) / 3;
vec4 s0 = texelFetch(tex, ivec2(px0, out_y), 0);
vec4 s1 = (px1 != px0) ? texelFetch(tex, ivec2(px1, out_y), 0) : s0;
// Extract channels based on phase (base % 3)
int phase = base - px0 * 3;
if (phase == 0) {
color = vec4(s0.r, s0.g, s0.b, s1.r);
} else if (phase == 1) {
color = vec4(s0.g, s0.b, s1.r, s1.g);
} else {
color = vec4(s0.b, s1.r, s1.g, s1.b);
}
}
"
}
pub(super) fn generate_packed_rgba8_int8_shader_2d() -> &'static str {
"\
#version 300 es
precision highp float;
precision highp int;
uniform sampler2D tex;
out vec4 color;
#ifdef QUANT_MAPPED
uniform highp vec3 q_scale; // q = clamp(floor(v * q_scale + q_offset), q_lo, q_hi)
uniform highp vec3 q_offset;
uniform highp vec3 q_lo;
uniform highp vec3 q_hi;
uniform highp vec3 q_zero; // zero point / 255; q_wrap marks a negative stored value
uniform highp vec3 q_wrap;
highp vec3 quantize(highp vec3 v) {
highp vec3 q = clamp(floor(v * q_scale + q_offset), q_lo, q_hi);
return q * (1.0 / 255.0) + q_zero + step(q, q_wrap) * (256.0 / 255.0);
}
#else
// The plain XOR 0x80 bias, `(q + 128) mod 256`, as the CPU `byte ^ 0x80`.
vec4 int8_bias(vec4 v) {
vec4 q = floor(v * 255.0 + 0.5);
return mod(q + 128.0, 256.0) / 255.0;
}
#endif
void main() {
// gl_FragCoord is at pixel center (n+0.5). Use floor() for robust
// integer pixel index on all GPUs (Vivante, Mali, Adreno).
int out_x = int(floor(gl_FragCoord.x));
int out_y = int(floor(gl_FragCoord.y));
int base = out_x * 4;
// 4 consecutive byte indices map to at most 2 source pixels
int px0 = base / 3;
int px1 = (base + 3) / 3;
vec4 s0 = texelFetch(tex, ivec2(px0, out_y), 0);
vec4 s1 = (px1 != px0) ? texelFetch(tex, ivec2(px1, out_y), 0) : s0;
// Extract channels based on phase (base % 3), then apply int8 bias
int phase = base - px0 * 3;
#ifdef QUANT_MAPPED
// Quantize per pixel: a packed texel mixes channels by phase.
vec3 q0 = quantize(s0.rgb);
vec3 q1 = quantize(s1.rgb);
if (phase == 0) {
color = vec4(q0.r, q0.g, q0.b, q1.r);
} else if (phase == 1) {
color = vec4(q0.g, q0.b, q1.r, q1.g);
} else {
color = vec4(q0.b, q1.r, q1.g, q1.b);
}
#else
if (phase == 0) {
color = int8_bias(vec4(s0.r, s0.g, s0.b, s1.r));
} else if (phase == 1) {
color = int8_bias(vec4(s0.g, s0.b, s1.r, s1.g));
} else {
color = int8_bias(vec4(s0.b, s1.r, s1.g, s1.b));
}
#endif
}
"
}
pub(super) fn generate_nv_to_rgba_shader_2d() -> &'static str {
super::shaders_common::NV_RGBA_FRAGMENT
}
pub(super) fn generate_nv_to_rgba_int8_shader_2d() -> &'static str {
super::shaders_common::NV_RGBA_INT8_FRAGMENT
}
pub(super) fn generate_nv_to_rgba_bilinear_shader_2d() -> &'static str {
super::shaders_common::NV_RGBA_BILINEAR_FRAGMENT
}
pub(super) fn generate_nv_to_rgba_bilinear_int8_shader_2d() -> &'static str {
super::shaders_common::NV_RGBA_BILINEAR_INT8_FRAGMENT
}
pub(super) fn generate_nv_to_rgba_hw_bilinear_shader_2d() -> &'static str {
super::shaders_common::NV_RGBA_HW_BILINEAR_FRAGMENT
}
pub(super) fn generate_nv_to_rgba_hw_bilinear_int8_shader_2d() -> &'static str {
super::shaders_common::NV_RGBA_HW_BILINEAR_INT8_FRAGMENT
}
pub(super) fn generate_nv_to_rgba_y2y_shader() -> &'static str {
super::shaders_common::NV_RGBA_Y2Y_FRAGMENT
}
pub(super) fn generate_nv_to_rgba_y2y_int8_shader() -> &'static str {
super::shaders_common::NV_RGBA_Y2Y_INT8_FRAGMENT
}
pub(super) fn generate_proto_repack_compute_shader() -> &'static str {
"\
#version 310 es
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
layout(std430, binding = 0) readonly buffer ProtoSSBO {
int packed_data[];
};
layout(r32i, binding = 0) writeonly uniform highp iimage2DArray dst_tex;
uniform int width;
uniform int height;
uniform int num_protos;
void main() {
int x = int(gl_GlobalInvocationID.x);
int y = int(gl_GlobalInvocationID.y);
if (x >= width || y >= height) return;
int base = (y * width + x) * num_protos;
for (int k = 0; k < num_protos; k++) {
int byte_offset = base + k;
int word_idx = byte_offset >> 2;
int byte_idx = byte_offset & 3;
int word = packed_data[word_idx];
int val = (word >> (byte_idx * 8)) & 0xFF;
if (val >= 128) val -= 256;
imageStore(dst_tex, ivec3(x, y, k), ivec4(val, 0, 0, 0));
}
}
"
}
pub(super) fn quant_mapped(src: &str) -> String {
match src.split_once('\n') {
Some((version, rest)) => format!("{version}\n#define QUANT_MAPPED\n{rest}"),
None => src.to_owned(),
}
}
#[cfg(test)]
mod quant_build_tests {
#[test]
fn int8_shaders_build_both_ways() {
let int8 = [
super::generate_texture_int8_shader(),
super::generate_texture_int8_shader_yuv(),
super::generate_planar_rgb_int8_shader(),
super::generate_planar_rgb_int8_shader_2d(),
super::generate_packed_rgba8_int8_shader_2d(),
super::generate_nv_to_rgba_int8_shader_2d(),
super::generate_nv_to_rgba_bilinear_int8_shader_2d(),
super::generate_nv_to_rgba_hw_bilinear_int8_shader_2d(),
super::generate_nv_to_rgba_y2y_int8_shader(),
super::super::shaders_common::YUYV_RGBA_2D_INT8_FRAGMENT,
];
for src in int8 {
assert!(src.contains("#ifdef QUANT_MAPPED"), "{src}");
let mapped = super::quant_mapped(src);
let mut lines = mapped.lines();
assert!(lines.next().unwrap().starts_with("#version"));
assert_eq!(lines.next(), Some("#define QUANT_MAPPED"));
}
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tc_precision {
fn all_shaders() -> Vec<(&'static str, &'static str)> {
vec![
("generate_vertex_shader", super::generate_vertex_shader()),
(
"generate_texture_fragment_shader",
super::generate_texture_fragment_shader(),
),
(
"generate_texture_fragment_shader_yuv",
super::generate_texture_fragment_shader_yuv(),
),
(
"generate_planar_rgb_shader",
super::generate_planar_rgb_shader(),
),
(
"generate_texture_int8_shader",
super::generate_texture_int8_shader(),
),
(
"generate_texture_int8_shader_yuv",
super::generate_texture_int8_shader_yuv(),
),
(
"generate_planar_rgb_int8_shader",
super::generate_planar_rgb_int8_shader(),
),
(
"generate_planar_rgb_shader_2d",
super::generate_planar_rgb_shader_2d(),
),
(
"generate_planar_rgb_int8_shader_2d",
super::generate_planar_rgb_int8_shader_2d(),
),
(
"generate_segmentation_shader",
super::generate_segmentation_shader(),
),
(
"generate_instanced_segmentation_shader",
super::generate_instanced_segmentation_shader(),
),
(
"generate_proto_segmentation_shader",
super::generate_proto_segmentation_shader(),
),
(
"generate_proto_segmentation_shader_int8_nearest",
super::generate_proto_segmentation_shader_int8_nearest(),
),
(
"generate_proto_segmentation_shader_int8_bilinear",
super::generate_proto_segmentation_shader_int8_bilinear(),
),
(
"generate_proto_dequant_shader_int8",
super::generate_proto_dequant_shader_int8(),
),
(
"generate_proto_segmentation_shader_f32",
super::generate_proto_segmentation_shader_f32(),
),
(
"generate_packed_f32_nhwc_shader",
super::generate_packed_f32_nhwc_shader(),
),
(
"generate_planar_rgb_f16_packed_shader",
super::generate_planar_rgb_f16_packed_shader(),
),
(
"generate_float_nhwc_packed_shader",
super::generate_float_nhwc_packed_shader(),
),
(
"generate_float_rgba_shader",
super::generate_float_rgba_shader(),
),
("generate_color_shader", super::generate_color_shader()),
(
"generate_packed_rgba8_shader_2d",
super::generate_packed_rgba8_shader_2d(),
),
(
"generate_packed_rgba8_int8_shader_2d",
super::generate_packed_rgba8_int8_shader_2d(),
),
(
"generate_nv_to_rgba_shader_2d",
super::generate_nv_to_rgba_shader_2d(),
),
(
"generate_nv_to_rgba_int8_shader_2d",
super::generate_nv_to_rgba_int8_shader_2d(),
),
(
"generate_nv_to_rgba_bilinear_shader_2d",
super::generate_nv_to_rgba_bilinear_shader_2d(),
),
(
"generate_nv_to_rgba_bilinear_int8_shader_2d",
super::generate_nv_to_rgba_bilinear_int8_shader_2d(),
),
(
"generate_nv_to_rgba_hw_bilinear_shader_2d",
super::generate_nv_to_rgba_hw_bilinear_shader_2d(),
),
(
"generate_nv_to_rgba_hw_bilinear_int8_shader_2d",
super::generate_nv_to_rgba_hw_bilinear_int8_shader_2d(),
),
(
"generate_nv_to_rgba_y2y_shader",
super::generate_nv_to_rgba_y2y_shader(),
),
(
"generate_nv_to_rgba_y2y_int8_shader",
super::generate_nv_to_rgba_y2y_int8_shader(),
),
(
"generate_proto_repack_compute_shader",
super::generate_proto_repack_compute_shader(),
),
(
"YUYV_RGBA_2D_FRAGMENT",
super::super::shaders_common::YUYV_RGBA_2D_FRAGMENT,
),
(
"YUYV_RGBA_2D_INT8_FRAGMENT",
super::super::shaders_common::YUYV_RGBA_2D_INT8_FRAGMENT,
),
(
"NV_RGBA_FRAGMENT",
super::super::shaders_common::NV_RGBA_FRAGMENT,
),
(
"NV_RGBA_INT8_FRAGMENT",
super::super::shaders_common::NV_RGBA_INT8_FRAGMENT,
),
(
"NV_RGBA_BILINEAR_FRAGMENT",
super::super::shaders_common::NV_RGBA_BILINEAR_FRAGMENT,
),
(
"NV_RGBA_BILINEAR_INT8_FRAGMENT",
super::super::shaders_common::NV_RGBA_BILINEAR_INT8_FRAGMENT,
),
(
"NV_RGBA_HW_BILINEAR_FRAGMENT",
super::super::shaders_common::NV_RGBA_HW_BILINEAR_FRAGMENT,
),
(
"NV_RGBA_HW_BILINEAR_INT8_FRAGMENT",
super::super::shaders_common::NV_RGBA_HW_BILINEAR_INT8_FRAGMENT,
),
(
"NV_RGBA_Y2Y_FRAGMENT",
super::super::shaders_common::NV_RGBA_Y2Y_FRAGMENT,
),
(
"NV_RGBA_Y2Y_INT8_FRAGMENT",
super::super::shaders_common::NV_RGBA_Y2Y_INT8_FRAGMENT,
),
]
}
fn strip_comments(src: &str) -> String {
let b = src.as_bytes();
let mut out = String::with_capacity(src.len());
let mut i = 0;
while i < b.len() {
if b[i] == b'/' && i + 1 < b.len() && b[i + 1] == b'/' {
while i < b.len() && b[i] != b'\n' {
i += 1;
}
} else if b[i] == b'/' && i + 1 < b.len() && b[i + 1] == b'*' {
i += 2;
while i + 1 < b.len() && !(b[i] == b'*' && b[i + 1] == b'/') {
i += 1;
}
i = (i + 2).min(b.len());
} else {
out.push(b[i] as char);
i += 1;
}
}
out
}
fn tc_identifier_offsets(src: &str) -> Vec<usize> {
let b = src.as_bytes();
let ident = |c: u8| c.is_ascii_alphanumeric() || c == b'_';
let mut out = Vec::new();
let mut i = 0;
while let Some(k) = src[i..].find("tc") {
let at = i + k;
let before_ok = at == 0 || !ident(b[at - 1]);
let after_ok = at + 2 >= b.len() || !ident(b[at + 2]);
if before_ok && after_ok {
out.push(at);
}
i = at + 2;
}
out
}
fn verbatim_fetch_count(src: &str) -> usize {
let mut n = 0;
let mut i = 0;
while let Some(k) = src[i..].find("texture(") {
let at = i + k + "texture(".len();
let rest = &src[at..];
let name_len = rest
.find(|c: char| !(c.is_ascii_alphanumeric() || c == '_'))
.unwrap_or(0);
if name_len > 0 && rest[name_len..].starts_with(", tc)") {
n += 1;
}
i = at;
}
n
}
fn tc_precision_and_use(src: &str) -> Option<(&'static str, bool)> {
let src = &strip_comments(src);
let decl = src
.lines()
.find(|l| l.trim_start().starts_with("in ") && l.trim_end().ends_with("vec2 tc;"))?;
let declared = if decl.contains("highp") {
Some("highp")
} else if decl.contains("mediump") {
Some("mediump")
} else if decl.contains("lowp") {
Some("lowp")
} else {
None
};
let file_default = src
.lines()
.find_map(|l| l.trim().strip_prefix("precision ")?.strip_suffix(" float;"))
.map(|p| match p {
"highp" => "highp",
"mediump" => "mediump",
"lowp" => "lowp",
_ => "unknown",
})
.unwrap_or("unknown");
let effective = declared.unwrap_or(file_default);
let in_decl = tc_identifier_offsets(decl).len();
let total = tc_identifier_offsets(src).len() - in_decl;
Some((effective, total > verbatim_fetch_count(src)))
}
#[test]
fn shaders_that_compute_on_tc_declare_it_highp() {
let mut offenders = Vec::new();
let mut checked = 0;
for (name, src) in all_shaders() {
let Some((precision, computes)) = tc_precision_and_use(src) else {
continue;
};
checked += 1;
if computes && precision != "highp" {
offenders.push(format!("{name} (tc is {precision})"));
}
}
let expected = all_shaders()
.iter()
.filter(|(_, src)| {
[
"in vec2 tc;",
"in highp vec2 tc;",
"in mediump vec2 tc;",
"in lowp vec2 tc;",
]
.iter()
.any(|d| src.contains(d))
})
.count();
assert_eq!(
checked, expected,
"the declaration parser matched {checked} shaders but {expected} contain an \
`in ... vec2 tc;` declaration — the parser stopped matching one"
);
assert!(
offenders.is_empty(),
"these shaders do arithmetic on a `tc` that is not `highp`; on Mali the \
fp16 ALU quantizes the sampled coordinate to a 2^-11 step, which is \
0.625 texel on a 1280-wide source, and the measured shift was 0.40 \
texel -- a truncation, not a rounding, so the error does not halve: \
{offenders:?}"
);
}
fn shader_names_in_source() -> Vec<String> {
let mut names = Vec::new();
let this = include_str!("shaders.rs");
let mut i = 0;
while let Some(k) = this[i..].find("fn generate_") {
let at = i + k + "fn ".len();
let rest = &this[at..];
let n = rest.find('(').unwrap_or(0);
if n > 0
&& rest[..n]
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_')
{
names.push(rest[..n].to_string());
}
i = at;
}
let common = include_str!("shaders_common.rs");
let mut i = 0;
while let Some(k) = common[i..].find("const ") {
let at = i + k + "const ".len();
let rest = &common[at..];
let n = rest
.find(|c: char| !(c.is_ascii_alphanumeric() || c == '_'))
.unwrap_or(0);
let name = &rest[..n];
if n > 0 && name.ends_with("_FRAGMENT") {
let body = &common[at + n..];
let end = body.find("\npub(crate) const ").unwrap_or(body.len());
let own = &body[..end];
let declares_tc = own.contains("vec2 tc;")
|| own.match_indices("!()").any(|(bang, _)| {
let start = own[..bang]
.rfind(|c: char| !(c.is_ascii_alphanumeric() || c == '_'))
.map_or(0, |p| p + 1);
let def = format!("macro_rules! {} ", &own[start..bang]);
common.find(&def).is_some_and(|at| {
let text = &common[at + def.len()..];
let len = text.find("\nmacro_rules! ").unwrap_or(text.len());
text[..len].contains("vec2 tc;")
})
});
if declares_tc {
names.push(name.to_string());
}
}
i = at;
}
names
}
#[test]
fn the_list_names_every_shader() {
let listed: Vec<String> = all_shaders().iter().map(|(n, _)| n.to_string()).collect();
let mut unique = listed.clone();
unique.sort();
let dup_len = unique.len();
unique.dedup();
let duplicates: Vec<&String> = if unique.len() == dup_len {
Vec::new()
} else {
listed
.iter()
.filter(|n| listed.iter().filter(|m| m == n).count() > 1)
.collect()
};
assert!(
duplicates.is_empty(),
"`all_shaders` lists these names more than once: {duplicates:?}"
);
let mut declared = shader_names_in_source();
declared.sort();
declared.dedup();
let missing: Vec<&String> = declared.iter().filter(|n| !unique.contains(n)).collect();
let extra: Vec<&String> = unique.iter().filter(|n| !declared.contains(n)).collect();
assert!(
missing.is_empty() && extra.is_empty(),
"`all_shaders` is out of step with the sources. Declared but not listed \
(add them, so they are precision-checked): {missing:?}. Listed but not \
declared (renamed or removed): {extra:?}"
);
}
#[test]
fn tc_scan_matches_the_identifier_only() {
assert_eq!(tc_identifier_offsets("tc").len(), 1);
assert_eq!(tc_identifier_offsets("tc.x * 2.0").len(), 1);
assert_eq!(tc_identifier_offsets("vec3(tc, i)").len(), 1);
assert_eq!(tc_identifier_offsets("tcoord + stc + tc_2").len(), 0);
assert_eq!(verbatim_fetch_count("texture(tex, tc)"), 1);
assert_eq!(verbatim_fetch_count("texture(tex, clamp(tc, a, b))"), 0);
assert_eq!(verbatim_fetch_count("texture(tex, vec3(tc, i))"), 0);
}
#[test]
fn a_commented_out_sample_does_not_mask_an_arithmetic_use() {
let src = "\
#version 300 es
precision mediump float;
uniform sampler2D tex;
in vec2 tc;
out vec4 color;
void main(){
// color = texture(tex, tc);
color = texture(tex, clamp(tc, vec2(0.0), vec2(1.0)));
}
";
assert_eq!(verbatim_fetch_count(&strip_comments(src)), 0);
assert_eq!(
tc_precision_and_use(src),
Some(("mediump", true)),
"the commented sample must not hide the clamp"
);
let only_comment = "\
#version 300 es
precision mediump float;
in vec2 tc;
void main(){ /* texture(tex, tc) */ color = vec4(0.0); }
";
assert_eq!(tc_precision_and_use(only_comment), Some(("mediump", false)));
}
}