GTK窗口包含GLArea及其他组件时无法正确调整大小的问题
GTK窗口内嵌GLArea添加其他控件后调整大小渲染异常修复
问题现象
- 仅在GTK窗口中放置
GLArea单个组件时,窗口大小调整操作可正常完成,渲染无异常 - 向容器中添加按钮等其他非GL组件后,调整窗口大小会出现渲染故障,表现为显示区域错位、部分区域残留旧画面
- 该问题和OpenGL调用逻辑无关,移除所有GL渲染代码仍可复现,复现环境为Ubuntu 20.04
复现代码
带Glium渲染的初始版本
use gtk::prelude::*; use glium::Surface; use std::os::raw::c_void; // 该结构体为gtk-glium项目的通用实现 struct GLAreaBackend { glarea: gtk::GLArea, } unsafe impl glium::backend::Backend for GLAreaBackend { fn swap_buffers(&self) -> Result<(), glium::SwapBuffersError> { // GTK会在每次"render"信号触发后自行交换缓冲区 Ok(()) } unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { gl_loader::get_proc_address(symbol) as *const _ } fn get_framebuffer_dimensions(&self) -> (u32, u32) { (self.glarea.allocated_width().try_into().unwrap(), self.glarea.allocated_height().try_into().unwrap()) } fn is_current(&self) -> bool { // GTK会在每次"render"信号触发时自行将当前GL上下文设为活跃 true } unsafe fn make_current(&self) { self.glarea.make_current(); } } impl GLAreaBackend { fn new(glarea: gtk::GLArea) -> Self { Self { glarea } } } fn main() { let application = gtk::Application::builder().build(); application.connect_activate(|app| { let window = gtk::ApplicationWindow::builder() .application(app) .title("test app") .default_width(640) .default_height(480) .build(); gl_loader::init_gl(); let gl_area = gtk::GLArea::builder() .expand(true) .halign(gtk::Align::Fill) .valign(gtk::Align::Fill) .auto_render(true) .has_alpha(false) .build(); let button = gtk::Button::with_label("press me!"); let container = gtk::Box::new(gtk::Orientation::Vertical, 0); container.pack_start(&gl_area, true, true, 0); // 取消下一行注释添加按钮后即可复现渲染异常 //container.pack_start(&button, false, false, 0); window.add(&container); window.show_all(); // 初始化Glium上下文 let gl_context = unsafe { glium::backend::Context::new(GLAreaBackend::new(gl_area.clone()), true, glium::debug::DebugCallbackBehavior::DebugMessageOnError).unwrap() }; gl_area.connect_render(move |_, _| { let mut frame = glium::Frame::new(gl_context.clone(), gl_context.get_framebuffer_dimensions()); frame.clear_color(1.0, 1.0, 0.5, 1.0); frame.finish().unwrap(); Inhibit(true) }); }); application.run(); }
最简复现版本(无OpenGL调用)
use gtk::prelude::*; fn main() { let application = gtk::Application::builder().build(); application.connect_activate(|app| { let window = gtk::ApplicationWindow::builder() .application(app) .default_width(640) .default_height(480) .build(); let gl_area = gtk::GLArea::builder() .auto_render(false) .build(); let button = gtk::Button::with_label("press me!"); let container = gtk::Box::new(gtk::Orientation::Vertical, 0); container.pack_start(&gl_area, true, true, 0); // 取消下一行注释添加按钮后即可复现问题 //container.pack_start(&button, false, false, 0); window.add(&container); window.show_all(); }); application.run(); }
问题原因
这是GTK3在X11后端下的已知行为问题:
- 当
GLArea所在父容器存在多个子控件时,窗口大小调整触发的容器重排流程,不会自动给GLArea发送重绘请求,auto_render配置在resize场景下不会自动生效 - 尺寸变化后
GLArea的帧缓冲不会自动同步新分配的控件大小,如果继续使用旧尺寸渲染就会出现显示错位、部分区域花屏 - 第三方GL封装(比如Glium)如果缓存了旧的帧缓冲尺寸,没有实时读取
GLArea最新分配的宽高,会进一步加剧渲染异常
修复方案
- 显式监听
GLArea的resize信号,尺寸变化时主动调用queue_render()触发重绘 - 渲染逻辑中不要使用缓存的帧缓冲尺寸,每次渲染都实时读取
GLArea最新的分配宽高 - 使用原生OpenGL渲染时,需要在resize回调中更新视口(Viewport)参数,匹配新的控件尺寸
关键修复代码
在创建gl_area完成后添加resize信号绑定:
// 监听尺寸变化,主动触发重绘 gl_area.connect_resize(|glarea, _width, _height| { glarea.queue_render(); // 使用原生OpenGL时在这里添加视口更新逻辑: // unsafe { gl::Viewport(0, 0, width, height); } });
- 对于Glium渲染场景:现有
GLAreaBackend的get_framebuffer_dimensions方法已经实时从GLArea读取最新宽高,加上上述resize回调触发重绘即可正常工作 - 对于无渲染的最简场景:将
auto_render设为true,同时添加上述resize回调,即可让GLArea正确同步尺寸,不会出现渲染残留
内容的提问来源于stack exchange,提问作者LyraSolomon
相关产品推荐
相关产品推荐

