From 49ff883be7b425f758bae4b46d1b771b92aaea44 Mon Sep 17 00:00:00 2001 From: toorux Date: Sat, 10 Oct 2026 00:13:19 +0800 Subject: [PATCH] Correct top-down decoded video orientation in casting windows --- native/cast_frame.h | 3 ++- tests/cast_frame.cpp | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/native/cast_frame.h b/native/cast_frame.h index 746790b..7efb347 100644 --- a/native/cast_frame.h +++ b/native/cast_frame.h @@ -21,7 +21,8 @@ struct CastFrame { static auto blit=(PFNGLBLITFRAMEBUFFERPROC)glXGetProcAddressARB((const GLubyte*)"glBlitFramebuffer"); static auto del=(PFNGLDELETEFRAMEBUFFERSPROC)glXGetProcAddressARB((const GLubyte*)"glDeleteFramebuffers"); if(!gen||!bind||!attach||!blit||!del)return target; - if(!composite)glGenTextures(1,&composite);glBindTexture(GL_TEXTURE_2D,composite);if(composite_width!=target_width||composite_height!=target_height){glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,target_width,target_height,0,GL_BGRA,GL_UNSIGNED_BYTE,nullptr);composite_width=target_width;composite_height=target_height;}GLuint fbo[2];gen(2,fbo);bind(GL_READ_FRAMEBUFFER,fbo[0]);attach(GL_READ_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,texture,0);bind(GL_DRAW_FRAMEBUFFER,fbo[1]);attach(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,composite,0);blit(0,0,width,height,0,0,target_width,target_height,GL_COLOR_BUFFER_BIT,GL_LINEAR); + if(!composite)glGenTextures(1,&composite);glBindTexture(GL_TEXTURE_2D,composite);if(composite_width!=target_width||composite_height!=target_height){glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,target_width,target_height,0,GL_BGRA,GL_UNSIGNED_BYTE,nullptr);composite_width=target_width;composite_height=target_height;}GLuint fbo[2];gen(2,fbo);bind(GL_READ_FRAMEBUFFER,fbo[0]);attach(GL_READ_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,texture,0);bind(GL_DRAW_FRAMEBUFFER,fbo[1]);attach(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,composite,0);// Decoder frames are top-down; CEF controls are already flipped for GL. + blit(0,height,width,0,0,0,target_width,target_height,GL_COLOR_BUFFER_BIT,GL_LINEAR); // CEF paints transparent, premultiplied controls over the full video plane. // Keep its texture intact so partial CEF damage never contains stale video. bind(GL_FRAMEBUFFER,fbo[1]);glPushAttrib(GL_ALL_ATTRIB_BITS);glViewport(0,0,target_width,target_height);glDisable(GL_DEPTH_TEST);glDisable(GL_SCISSOR_TEST);glEnable(GL_TEXTURE_2D);glEnable(GL_BLEND);glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA);glColor4f(1,1,1,1);glBindTexture(GL_TEXTURE_2D,target); diff --git a/tests/cast_frame.cpp b/tests/cast_frame.cpp index 89ef8e2..b8e1351 100644 --- a/tests/cast_frame.cpp +++ b/tests/cast_frame.cpp @@ -14,7 +14,14 @@ int main(){ CastFrame frame;frame.width=frame.height=2;glGenTextures(1,&frame.texture);glBindTexture(GL_TEXTURE_2D,frame.texture);glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);std::vector blue(16);for(size_t n=0;n controls(64);for(size_t n=0;n pixels(64);glGetTexImage(GL_TEXTURE_2D,0,GL_RGBA,GL_UNSIGNED_BYTE,pixels.data());for(size_t n=0;n=126&&pixels[n+2]<=128);assert(pixels[n+3]==255);}glBindTexture(GL_TEXTURE_2D,ui);glGetTexImage(GL_TEXTURE_2D,0,GL_RGBA,GL_UNSIGNED_BYTE,pixels.data());assert(pixels==controls); - std::fill(controls.begin(),controls.end(),0);glTexSubImage2D(GL_TEXTURE_2D,0,0,0,4,4,GL_RGBA,GL_UNSIGNED_BYTE,controls.data());output=frame.draw(ui,4,4);glBindTexture(GL_TEXTURE_2D,output);glGetTexImage(GL_TEXTURE_2D,0,GL_RGBA,GL_UNSIGNED_BYTE,pixels.data());assert(pixels[0]==0&&pixels[2]==255);assert(glGetError()==GL_NO_ERROR);glDeleteTextures(1,&ui); + std::fill(controls.begin(),controls.end(),0);glTexSubImage2D(GL_TEXTURE_2D,0,0,0,4,4,GL_RGBA,GL_UNSIGNED_BYTE,controls.data());output=frame.draw(ui,4,4);glBindTexture(GL_TEXTURE_2D,output);glGetTexImage(GL_TEXTURE_2D,0,GL_RGBA,GL_UNSIGNED_BYTE,pixels.data());assert(pixels[0]==0&&pixels[2]==255); + // Raw decoder rows are top-down: red/green above blue/yellow. + // The composited GL texture must put blue/yellow on its bottom row. + const uint8_t corners[]={255,0,0,255, 0,255,0,255, 0,0,255,255, 255,255,0,255}; + glBindTexture(GL_TEXTURE_2D,frame.texture);glTexSubImage2D(GL_TEXTURE_2D,0,0,0,2,2,GL_RGBA,GL_UNSIGNED_BYTE,corners); + output=frame.draw(ui,2,2);glBindTexture(GL_TEXTURE_2D,output);std::vector upright(16);glGetTexImage(GL_TEXTURE_2D,0,GL_RGBA,GL_UNSIGNED_BYTE,upright.data()); + const std::vector expected={0,0,255,255, 255,255,0,255, 255,0,0,255, 0,255,0,255};assert(upright==expected); + assert(glGetError()==GL_NO_ERROR);glDeleteTextures(1,&ui); } glXMakeCurrent(display,None,nullptr);glXDestroyGLXPixmap(display,surface);XFreePixmap(display,pixmap);glXDestroyContext(display,context);XFree(visual);XCloseDisplay(display);std::cout<<"Full-frame video and transparent controls composition passed\n"; }