Loading...
Searching...
No Matches

GPU RGBA->NV12 encoder (semi-planar 4:2:0). More...

Inheritance diagram for score::gfx::NV12Encoder:
score::gfx::GPUVideoEncoder

Detailed Description

GPU RGBA->NV12 encoder (semi-planar 4:2:0).

Two render passes: Pass 1: Y plane -> R8 texture at full resolution Pass 2: UV plane -> interleaved chroma at half vertical resolution

Two readbacks. The caller concatenates Y + UV data for GStreamer video/x-raw,format=NV12.

The UV plane renders into an R8 target of width x height/2, one byte per texel: U in even columns, V in odd ones – the same byte layout an RG8 target at width/2 x height/2 produces.

It must not be RG8. An RG8 row here is (w/2) * 2 = w bytes, and where that is not 4-byte aligned the readback comes back the right total SIZE with its rows progressively shifted: row 0 correct, the rest drifting. 722 and 1922 are even, so 4:2:0 can express them, and both are real NDI sizes. Measured against a source varying only vertically, where every chroma row must be one repeated value, 287 of 288 rows came back non-constant on OpenGL and on Vulkan; against ffmpeg's own nv12 at 722x576, 30% of bytes differed.

Sampling is unchanged – one bilinear tap at the centre of the 2x2 block – so the values match the RG8 path wherever it was readable at all.

Public Member Functions

void init (QRhi &rhi, const RenderState &state, QRhiTexture *inputRGBA, int width, int height, const QString &colorConversion) override
 
void exec (QRhi &rhi, QRhiCommandBuffer &cb) override
 
int planeCount () const override
 Number of readback planes (1 for UYVY, 2 for NV12, 3 for I420).
 
const QRhiReadbackResult & readback (int plane) const override
 Get the readback result for a given plane. Valid after endOffscreenFrame.
 
void release () override
 Release all GPU resources.
 
- Public Member Functions inherited from score::gfx::GPUVideoEncoder
virtual QRhiTexture * outputTexture () const noexcept
 
virtual void setReadbackEnabled (bool) noexcept
 

Public Attributes

QRhiTexture * m_yTexture {}
 
QRhiTextureRenderTarget * m_yRT {}
 
QRhiRenderPassDescriptor * m_yRP {}
 
QRhiShaderResourceBindings * m_ySRB {}
 
QRhiGraphicsPipeline * m_yPipeline {}
 
QRhiReadbackResult m_yReadback {}
 
QRhiTexture * m_uvTexture {}
 
QRhiTextureRenderTarget * m_uvRT {}
 
QRhiRenderPassDescriptor * m_uvRP {}
 
QRhiShaderResourceBindings * m_uvSRB {}
 
QRhiGraphicsPipeline * m_uvPipeline {}
 
QRhiReadbackResult m_uvReadback {}
 
QRhiSampler * m_sampler {}
 
int m_width {}
 
int m_height {}
 

Static Public Attributes

static constexpr const char * y_frag
 
static constexpr const char * uv_frag
 
- Static Public Attributes inherited from score::gfx::GPUVideoEncoder
static constexpr const char * vertex_shader
 
static constexpr const char * rgb_to_yuv_glsl
 
static constexpr const char * y_flip_glsl
 Fragment shader Y-flip: on GL, flip v_texcoord.y. On Metal/HLSL, no flip.
 

Member Function Documentation

◆ exec()

void score::gfx::NV12Encoder::exec ( QRhi &  rhi,
QRhiCommandBuffer &  cb 
)
inlineoverridevirtual

Execute the conversion pass. Call inside beginOffscreenFrame/endOffscreenFrame. Renders the conversion shader and schedules GPU->CPU readback(s).

Implements score::gfx::GPUVideoEncoder.

◆ init()

void score::gfx::NV12Encoder::init ( QRhi &  rhi,
const RenderState &  state,
QRhiTexture *  inputRGBA,
int  width,
int  height,
const QString &  colorConversion 
)
inlineoverridevirtual

Create render targets, textures, samplers, and the graphics pipeline.

Parameters
rhiThe QRhi instance.
stateThe render state (for shader compilation).
inputRGBAThe scene RGBA texture to read from.
widthSource width in pixels.
heightSource height in pixels.
colorConversionGLSL code from colorMatrixOut() defining convert_from_rgb(). If empty, defaults to BT.709 full range.

Implements score::gfx::GPUVideoEncoder.

◆ planeCount()

int score::gfx::NV12Encoder::planeCount ( ) const
inlineoverridevirtual

Number of readback planes (1 for UYVY, 2 for NV12, 3 for I420).

Implements score::gfx::GPUVideoEncoder.

◆ readback()

const QRhiReadbackResult & score::gfx::NV12Encoder::readback ( int  plane) const
inlineoverridevirtual

Get the readback result for a given plane. Valid after endOffscreenFrame.

Implements score::gfx::GPUVideoEncoder.

◆ release()

void score::gfx::NV12Encoder::release ( )
inlineoverridevirtual

Release all GPU resources.

Implements score::gfx::GPUVideoEncoder.

Member Data Documentation

◆ uv_frag

constexpr const char* score::gfx::NV12Encoder::uv_frag
staticconstexpr
Initial value:
= R"_(#version 450
layout(location = 0) in vec2 v_texcoord;
layout(location = 0) out vec4 fragColor;
layout(binding = 3) uniform sampler2D src_tex;
)_" "%1" R"_(
vec2 flip_y(vec2 tc) {
// Only OpenGL. The rest of the engine puts its geometry through
// renderer.clipSpaceCorrMatrix, which negates Y on Vulkan; this pass draws
// a hardcoded triangle in raw NDC and does not, so the correction it needs
// is not the same one. Flipping on Vulkan as well handed libav, GStreamer,
// NDI and every other consumer an upside-down picture.
return tc;
return vec2(tc.x, 1.0 - tc.y);
}
void main() {
ivec2 sz = textureSize(src_tex, 0);
// floor() the product, not the product minus half a texel: at output
// texel i the interpolated value is exactly i + 0.5, so flooring it has
// half a texel of slack either way where flooring i itself has none.
int b = int(floor(v_texcoord.x * float(sz.x)));
int cy = int(floor(v_texcoord.y * float(sz.y >> 1)));
vec2 csz = vec2(float(sz.x >> 1), float(sz.y >> 1));
vec2 tc = (vec2(float(b >> 1), float(cy)) + 0.5) / csz;
vec3 yuv = convert_from_rgb(texture(src_tex, flip_y(tc)).rgb);
fragColor = vec4(((b & 1) == 0) ? yuv.y : yuv.z, 0.0, 0.0, 1.0);
}
)_"

◆ y_frag

constexpr const char* score::gfx::NV12Encoder::y_frag
staticconstexpr
Initial value:
= R"_(#version 450
layout(location = 0) in vec2 v_texcoord;
layout(location = 0) out vec4 fragColor;
layout(binding = 3) uniform sampler2D src_tex;
)_" "%1" R"_(
vec2 flip_y(vec2 tc) {
// Only OpenGL. The rest of the engine puts its geometry through
// renderer.clipSpaceCorrMatrix, which negates Y on Vulkan; this pass draws
// a hardcoded triangle in raw NDC and does not, so the correction it needs
// is not the same one. Flipping on Vulkan as well handed libav, GStreamer,
// NDI and every other consumer an upside-down picture.
return tc;
return vec2(tc.x, 1.0 - tc.y);
}
void main() {
vec3 rgb = texture(src_tex, flip_y(v_texcoord)).rgb;
vec3 yuv = convert_from_rgb(rgb);
fragColor = vec4(yuv.x, 0.0, 0.0, 1.0);
}
)_"

The documentation for this struct was generated from the following file: