@@ -25,18 +25,25 @@ void main() {
2525}
2626)GLSL" ;
2727
28- // Forward colortrans: y = clamp((x + offset) * gain, 0, 1).
29- // samplerExternalOES lets the driver convert NV12 → RGB transparently.
28+ // Color-correct + OSD composite.
29+ // samplerExternalOES converts NV12 → RGB implicitly during sampling.
30+ // osd_tex is a plain RGBA texture (DRM_FORMAT_ARGB8888 imported as EGLImage).
31+ // has_osd: 1.0 = composite OSD, 0.0 = pass through (transparent OSD fallback).
3032static const char * kFrag = R"GLSL(
3133#extension GL_OES_EGL_image_external : require
3234precision mediump float;
3335varying vec2 v_uv;
3436uniform samplerExternalOES tex;
37+ uniform sampler2D osd_tex;
3538uniform float gain;
3639uniform float offset;
40+ uniform float has_osd;
3741void main() {
3842 vec3 c = texture2D(tex, v_uv).rgb;
39- gl_FragColor = vec4(clamp((c + offset) * gain, 0.0, 1.0), 1.0);
43+ vec4 video = vec4(clamp((c + offset) * gain, 0.0, 1.0), 1.0);
44+ vec4 osd = texture2D(osd_tex, v_uv);
45+ float alpha = has_osd * osd.a;
46+ gl_FragColor = vec4(alpha * osd.rgb + (1.0 - alpha) * video.rgb, 1.0);
4047}
4148)GLSL" ;
4249
@@ -166,13 +173,24 @@ bool FrameColorCorrect::build_shader() {
166173 glDeleteShader (vs); glDeleteShader (fs);
167174 if (!prog_) return false ;
168175
169- loc_tex_ = glGetUniformLocation (prog_, " tex" );
170- loc_gain_ = glGetUniformLocation (prog_, " gain" );
171- loc_offset_ = glGetUniformLocation (prog_, " offset" );
176+ loc_tex_ = glGetUniformLocation (prog_, " tex" );
177+ loc_gain_ = glGetUniformLocation (prog_, " gain" );
178+ loc_offset_ = glGetUniformLocation (prog_, " offset" );
179+ loc_osd_tex_ = glGetUniformLocation (prog_, " osd_tex" );
180+ loc_has_osd_ = glGetUniformLocation (prog_, " has_osd" );
172181 return true ;
173182}
174183
175184bool FrameColorCorrect::create_targets () {
185+ // 1×1 fully-transparent texture used when no OSD is active.
186+ glGenTextures (1 , &osd_tex_gl_);
187+ glBindTexture (GL_TEXTURE_2D , osd_tex_gl_);
188+ const uint8_t transparent[4 ] = {0 , 0 , 0 , 0 };
189+ glTexImage2D (GL_TEXTURE_2D , 0 , GL_RGBA , 1 , 1 , 0 ,
190+ GL_RGBA , GL_UNSIGNED_BYTE , transparent);
191+ glTexParameteri (GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_NEAREST );
192+ glTexParameteri (GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_NEAREST );
193+
176194 for (int i = 0 ; i < kTargets ; i++) {
177195 Target& t = targets_[i];
178196
@@ -232,8 +250,7 @@ bool FrameColorCorrect::create_targets() {
232250
233251bool FrameColorCorrect::process (int src_fd, uint32_t src_w, uint32_t src_h,
234252 uint32_t src_hs, uint32_t src_vs,
235- int dst_fd, uint32_t dst_w, uint32_t dst_h,
236- uint32_t dst_hs, uint32_t dst_vs)
253+ int dst_fd, uint32_t dst_hs, uint32_t dst_vs)
237254{
238255 // --- Import NV12 source as EGLImage (two-plane) -------------------------
239256 EGLint uv_offset = (EGLint)((size_t )src_hs * src_vs);
@@ -274,10 +291,17 @@ bool FrameColorCorrect::process(int src_fd, uint32_t src_w, uint32_t src_h,
274291 glBindFramebuffer (GL_FRAMEBUFFER , tgt.fbo );
275292 glViewport (0 , 0 , (GLsizei)width_, (GLsizei)height_);
276293
294+ // Bind OSD on texture unit 1.
295+ glActiveTexture (GL_TEXTURE1 );
296+ glBindTexture (GL_TEXTURE_2D , osd_tex_gl_);
297+
298+ glActiveTexture (GL_TEXTURE0 );
277299 glUseProgram (prog_);
278- glUniform1i (loc_tex_, 0 );
279- glUniform1f (loc_gain_, gain_);
280- glUniform1f (loc_offset_, offset_);
300+ glUniform1i (loc_tex_, 0 );
301+ glUniform1i (loc_osd_tex_, 1 );
302+ glUniform1f (loc_gain_, gain_);
303+ glUniform1f (loc_offset_, offset_);
304+ glUniform1f (loc_has_osd_, osd_prime_fd_ >= 0 ? 1 .0f : 0 .0f );
281305
282306 // Flip V so that top-origin NV12 memory maps to the top of the GL FB.
283307 const GLfloat verts[] = {
@@ -302,17 +326,18 @@ bool FrameColorCorrect::process(int src_fd, uint32_t src_w, uint32_t src_h,
302326 glDeleteTextures (1 , &src_tex);
303327 eglDestroyImageKHR_ (dpy_, src_img);
304328
305- // --- RGA: convert RGBA target → NV12 destination -----------------------
306- // DRM_FORMAT_ARGB8888 is BGRA in memory on little-endian → RK_FORMAT_BGRA_8888
307- // When dst dimensions differ from src, RGA performs resize + CSC in one pass.
329+ // --- RGA: CSC RGBA target → NV12 destination ---------------------------
330+ // GBM BO is at output size (width_ × height_), dst is the same size,
331+ // so this is pure colour-space conversion with no resize.
332+ // DRM_FORMAT_ARGB8888 is BGRA in memory on little-endian → RK_FORMAT_BGRA_8888.
308333 rga_buffer_t src_rga = wrapbuffer_fd_t (
309334 tgt.prime_fd ,
310335 (int )width_, (int )height_,
311336 (int )tgt.stride_px , (int )height_,
312337 RK_FORMAT_BGRA_8888 );
313338 rga_buffer_t dst_rga = wrapbuffer_fd_t (
314339 dst_fd,
315- (int )dst_w , (int )dst_h ,
340+ (int )width_ , (int )height_ ,
316341 (int )dst_hs, (int )dst_vs,
317342 RK_FORMAT_YCbCr_420_SP);
318343
@@ -324,7 +349,70 @@ bool FrameColorCorrect::process(int src_fd, uint32_t src_w, uint32_t src_h,
324349 return true ;
325350}
326351
352+ void FrameColorCorrect::set_osd (int prime_fd, uint32_t w, uint32_t h, uint32_t stride_px)
353+ {
354+ if (prime_fd == osd_prime_fd_ && w == osd_w_ && h == osd_h_)
355+ return ; // same buffer — EGLImage still valid
356+
357+ // Release old EGLImage if any.
358+ if (osd_img_ != EGL_NO_IMAGE_KHR ) {
359+ eglDestroyImageKHR_ (dpy_, osd_img_);
360+ osd_img_ = EGL_NO_IMAGE_KHR ;
361+ }
362+
363+ osd_prime_fd_ = prime_fd;
364+ osd_w_ = w;
365+ osd_h_ = h;
366+ osd_stride_px_ = stride_px;
367+
368+ const EGLint osd_attrs[] = {
369+ EGL_WIDTH , (EGLint)w,
370+ EGL_HEIGHT , (EGLint)h,
371+ EGL_LINUX_DRM_FOURCC_EXT , (EGLint)DRM_FORMAT_ARGB8888 ,
372+ EGL_DMA_BUF_PLANE0_FD_EXT , prime_fd,
373+ EGL_DMA_BUF_PLANE0_OFFSET_EXT , 0 ,
374+ EGL_DMA_BUF_PLANE0_PITCH_EXT , (EGLint)(stride_px * 4 ),
375+ EGL_NONE
376+ };
377+ osd_img_ = eglCreateImageKHR_ (dpy_, EGL_NO_CONTEXT ,
378+ EGL_LINUX_DMA_BUF_EXT , nullptr , osd_attrs);
379+ if (osd_img_ == EGL_NO_IMAGE_KHR ) {
380+ spdlog::warn (" FrameCC: eglCreateImageKHR for OSD failed (0x{:x})" , eglGetError ());
381+ osd_prime_fd_ = -1 ;
382+ return ;
383+ }
384+
385+ // Re-bind the osd texture object to the new EGLImage.
386+ glBindTexture (GL_TEXTURE_2D , osd_tex_gl_);
387+ glEGLImageTargetTexture2DOES_ (GL_TEXTURE_2D , osd_img_);
388+ glTexParameteri (GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_LINEAR );
389+ glTexParameteri (GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_LINEAR );
390+ glTexParameteri (GL_TEXTURE_2D , GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE );
391+ glTexParameteri (GL_TEXTURE_2D , GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE );
392+ }
393+
394+ void FrameColorCorrect::clear_osd ()
395+ {
396+ if (osd_prime_fd_ < 0 ) return ;
397+ if (osd_img_ != EGL_NO_IMAGE_KHR ) {
398+ eglDestroyImageKHR_ (dpy_, osd_img_);
399+ osd_img_ = EGL_NO_IMAGE_KHR ;
400+ }
401+ osd_prime_fd_ = -1 ;
402+ // Restore the 1×1 transparent fallback.
403+ const uint8_t transparent[4 ] = {0 , 0 , 0 , 0 };
404+ glBindTexture (GL_TEXTURE_2D , osd_tex_gl_);
405+ glTexImage2D (GL_TEXTURE_2D , 0 , GL_RGBA , 1 , 1 , 0 ,
406+ GL_RGBA , GL_UNSIGNED_BYTE , transparent);
407+ }
408+
327409void FrameColorCorrect::destroy_targets () {
410+ if (osd_img_ != EGL_NO_IMAGE_KHR ) {
411+ eglDestroyImageKHR_ (dpy_, osd_img_); osd_img_ = EGL_NO_IMAGE_KHR ;
412+ }
413+ osd_prime_fd_ = -1 ;
414+ if (osd_tex_gl_) { glDeleteTextures (1 , &osd_tex_gl_); osd_tex_gl_ = 0 ; }
415+
328416 for (int i = 0 ; i < kTargets ; i++) {
329417 Target& t = targets_[i];
330418 if (t.fbo ) { glDeleteFramebuffers (1 , &t.fbo ); t.fbo = 0 ; }
0 commit comments