pub const SHADER_SOURCE: &str = r"
#include <metal_stdlib>
using namespace metal;
struct Uniforms {
float2 viewport_size;
float2 texture_size;
float time;
uint pixel_format;
float padding[2];
};
struct TexturedVertexOut {
float4 position [[position]];
float2 texcoord;
};
// Fullscreen quad vertex shader with aspect ratio correction
vertex TexturedVertexOut vertex_fullscreen(uint vid [[vertex_id]], constant Uniforms& uniforms [[buffer(0)]]) {
TexturedVertexOut out;
float va = uniforms.viewport_size.x / uniforms.viewport_size.y;
float ta = uniforms.texture_size.x / uniforms.texture_size.y;
float sx = ta > va ? 1.0 : ta / va;
float sy = ta > va ? va / ta : 1.0;
float2 positions[4] = { float2(-sx, -sy), float2(sx, -sy), float2(-sx, sy), float2(sx, sy) };
float2 texcoords[4] = { float2(0.0, 1.0), float2(1.0, 1.0), float2(0.0, 0.0), float2(1.0, 0.0) };
out.position = float4(positions[vid], 0.0, 1.0);
out.texcoord = texcoords[vid];
return out;
}
// BGRA/RGB texture fragment shader
fragment float4 fragment_textured(TexturedVertexOut in [[stage_in]], texture2d<float> tex [[texture(0)]]) {
constexpr sampler s(mag_filter::linear, min_filter::linear);
return tex.sample(s, in.texcoord);
}
// YCbCr to RGB conversion (BT.709 matrix for HD video)
float4 ycbcr_to_rgb(float y, float2 cbcr, bool full_range) {
float y_adj = full_range ? y : (y - 16.0/255.0) * (255.0/219.0);
float cb = cbcr.x - 0.5;
float cr = cbcr.y - 0.5;
// BT.709 conversion matrix
float r = y_adj + 1.5748 * cr;
float g = y_adj - 0.1873 * cb - 0.4681 * cr;
float b = y_adj + 1.8556 * cb;
return float4(saturate(float3(r, g, b)), 1.0);
}
// YCbCr biplanar (420v/420f) fragment shader
fragment float4 fragment_ycbcr(TexturedVertexOut in [[stage_in]],
texture2d<float> y_tex [[texture(0)]],
texture2d<float> cbcr_tex [[texture(1)]],
constant Uniforms& uniforms [[buffer(0)]]) {
constexpr sampler s(mag_filter::linear, min_filter::linear);
float y = y_tex.sample(s, in.texcoord).r;
float2 cbcr = cbcr_tex.sample(s, in.texcoord).rg;
bool full_range = (uniforms.pixel_format == 0x34323066); // '420f'
return ycbcr_to_rgb(y, cbcr, full_range);
}
// Colored vertex input/output for UI overlays
struct ColoredVertex {
float2 position [[attribute(0)]];
float4 color [[attribute(1)]];
};
struct ColoredVertexOut {
float4 position [[position]];
float4 color;
};
// Colored vertex shader for UI elements (position in pixels, converted to NDC)
vertex ColoredVertexOut vertex_colored(ColoredVertex in [[stage_in]], constant Uniforms& uniforms [[buffer(1)]]) {
ColoredVertexOut out;
float2 ndc = (in.position / uniforms.viewport_size) * 2.0 - 1.0;
ndc.y = -ndc.y;
out.position = float4(ndc, 0.0, 1.0);
out.color = in.color;
return out;
}
// Colored fragment shader for UI elements
fragment float4 fragment_colored(ColoredVertexOut in [[stage_in]]) {
return in.color;
}
";Expand description
Metal shader source for rendering captured frames
This shader supports:
- BGRA and BGR10A2 single-plane formats
- YCbCr 4:2:0 biplanar formats (420v and 420f)
- Aspect-ratio-preserving fullscreen quad
§Uniforms
The shader expects a Uniforms buffer:
viewport_size: float2- Current viewport dimensionstexture_size: float2- Source texture dimensionstime: float- Animation time (optional)pixel_format: uint-FourCCpixel format code
§Usage
- Compile shader with
device.new_library_with_source(SHADER_SOURCE, ...) - Create pipeline with
vertex_fullscreen+fragment_textured(for BGRA/L10R) - Or use
vertex_fullscreen+fragment_ycbcr(for 420v/420f) - Bind plane0 to texture slot 0, plane1 to texture slot 1 (for YCbCr)