Files
qemu/ui/gtk-egl.c
Weifeng Liu e01b61963f gtk: Skip drawing if console surface is NULL
In gtk draw/render callbacks, add an early NULL check for the console
surface and skip drawing if it's NULL. Otherwise, attempting to fetch
its width and height crash. This change fixes Coverity CID 1610328.

In practice, this case wouldn't happen at all because we always install
a placeholder surface to the console when there is nothing to display.

Resolves: Coverity CID 1610328
Signed-off-by: Weifeng Liu <weifeng.liu.z@gmail.com>
Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Message-ID: <20250714141758.10062-1-weifeng.liu.z@gmail.com>
2025-09-30 11:06:36 +04:00

446 lines
13 KiB
C

/*
* GTK UI -- egl opengl code.
*
* Note that gtk 3.16+ (released 2015-03-23) has a GtkGLArea widget,
* which is GtkDrawingArea like widget with opengl rendering support.
*
* This code handles opengl support on older gtk versions, using egl
* to get a opengl context for the X11 window.
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu/main-loop.h"
#include "qemu/error-report.h"
#include "trace.h"
#include "ui/console.h"
#include "ui/gtk.h"
#include "ui/egl-helpers.h"
#include "ui/shader.h"
#include "system/system.h"
static void gtk_egl_set_scanout_mode(VirtualConsole *vc, bool scanout)
{
if (vc->gfx.scanout_mode == scanout) {
return;
}
vc->gfx.scanout_mode = scanout;
if (!vc->gfx.scanout_mode) {
eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
vc->gfx.esurface, vc->gfx.ectx);
egl_fb_destroy(&vc->gfx.guest_fb);
if (vc->gfx.surface) {
surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds);
surface_gl_create_texture(vc->gfx.gls, vc->gfx.ds);
}
}
}
/** DisplayState Callbacks (opengl version) **/
void gd_egl_init(VirtualConsole *vc)
{
GdkWindow *gdk_window = gtk_widget_get_window(vc->gfx.drawing_area);
if (!gdk_window) {
return;
}
Window x11_window = gdk_x11_window_get_xid(gdk_window);
if (!x11_window) {
return;
}
vc->gfx.ectx = qemu_egl_init_ctx();
vc->gfx.esurface = qemu_egl_init_surface_x11
(vc->gfx.ectx, (EGLNativeWindowType)x11_window);
assert(vc->gfx.esurface);
}
void gd_egl_draw(VirtualConsole *vc)
{
GdkWindow *window;
#ifdef CONFIG_GBM
QemuDmaBuf *dmabuf = vc->gfx.guest_fb.dmabuf;
int fence_fd;
#endif
int ww, wh, pw, ph, gs;
if (!vc->gfx.gls || !vc->gfx.ds) {
return;
}
window = gtk_widget_get_window(vc->gfx.drawing_area);
gs = gdk_window_get_scale_factor(window);
ww = gdk_window_get_width(window);
wh = gdk_window_get_height(window);
pw = ww * gs;
ph = wh * gs;
if (vc->gfx.scanout_mode) {
#ifdef CONFIG_GBM
if (dmabuf) {
if (!qemu_dmabuf_get_draw_submitted(dmabuf)) {
return;
} else {
qemu_dmabuf_set_draw_submitted(dmabuf, false);
}
}
#endif
gd_egl_scanout_flush(&vc->gfx.dcl, 0, 0, vc->gfx.w, vc->gfx.h);
gd_update_scale(vc, ww, wh,
surface_width(vc->gfx.ds),
surface_height(vc->gfx.ds));
glFlush();
#ifdef CONFIG_GBM
if (dmabuf) {
egl_dmabuf_create_fence(dmabuf);
fence_fd = qemu_dmabuf_get_fence_fd(dmabuf);
if (fence_fd >= 0) {
qemu_set_fd_handler(fence_fd, gd_hw_gl_flushed, NULL, vc);
return;
}
graphic_hw_gl_block(vc->gfx.dcl.con, false);
}
#endif
} else {
eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
vc->gfx.esurface, vc->gfx.ectx);
surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, pw, ph);
surface_gl_render_texture(vc->gfx.gls, vc->gfx.ds);
eglSwapBuffers(qemu_egl_display, vc->gfx.esurface);
gd_update_scale(vc, ww, wh,
surface_width(vc->gfx.ds),
surface_height(vc->gfx.ds));
glFlush();
}
}
void gd_egl_update(DisplayChangeListener *dcl,
int x, int y, int w, int h)
{
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
if (!vc->gfx.gls || !vc->gfx.ds) {
return;
}
eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
vc->gfx.esurface, vc->gfx.ectx);
surface_gl_update_texture(vc->gfx.gls, vc->gfx.ds, x, y, w, h);
vc->gfx.glupdates++;
eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE,
EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
void gd_egl_refresh(DisplayChangeListener *dcl)
{
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
gd_update_monitor_refresh_rate(
vc, vc->window ? vc->window : vc->gfx.drawing_area);
if (vc->gfx.guest_fb.dmabuf &&
qemu_dmabuf_get_draw_submitted(vc->gfx.guest_fb.dmabuf)) {
gd_egl_draw(vc);
return;
}
if (!vc->gfx.esurface) {
gd_egl_init(vc);
if (!vc->gfx.esurface) {
return;
}
vc->gfx.gls = qemu_gl_init_shader();
if (vc->gfx.ds) {
surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds);
surface_gl_create_texture(vc->gfx.gls, vc->gfx.ds);
}
#ifdef CONFIG_GBM
if (vc->gfx.guest_fb.dmabuf) {
egl_dmabuf_release_texture(vc->gfx.guest_fb.dmabuf);
gd_egl_scanout_dmabuf(dcl, vc->gfx.guest_fb.dmabuf);
}
#endif
}
graphic_hw_update(dcl->con);
if (vc->gfx.glupdates) {
vc->gfx.glupdates = 0;
gtk_egl_set_scanout_mode(vc, false);
gd_egl_draw(vc);
}
}
void gd_egl_switch(DisplayChangeListener *dcl,
DisplaySurface *surface)
{
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
bool resized = true;
trace_gd_switch(vc->label, surface_width(surface), surface_height(surface));
if (vc->gfx.ds &&
surface_width(vc->gfx.ds) == surface_width(surface) &&
surface_height(vc->gfx.ds) == surface_height(surface)) {
resized = false;
}
eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
vc->gfx.esurface, vc->gfx.ectx);
surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds);
vc->gfx.ds = surface;
if (vc->gfx.gls) {
surface_gl_create_texture(vc->gfx.gls, vc->gfx.ds);
}
if (resized) {
gd_update_windowsize(vc);
}
eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
}
QEMUGLContext gd_egl_create_context(DisplayGLCtx *dgc,
QEMUGLParams *params)
{
VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc);
eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
vc->gfx.esurface, vc->gfx.ectx);
return qemu_egl_create_context(dgc, params);
}
void gd_egl_scanout_disable(DisplayChangeListener *dcl)
{
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
vc->gfx.w = 0;
vc->gfx.h = 0;
gtk_egl_set_scanout_mode(vc, false);
}
void gd_egl_scanout_texture(DisplayChangeListener *dcl,
uint32_t backing_id, bool backing_y_0_top,
uint32_t backing_width, uint32_t backing_height,
uint32_t x, uint32_t y,
uint32_t w, uint32_t h,
void *d3d_tex2d)
{
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
vc->gfx.x = x;
vc->gfx.y = y;
vc->gfx.w = w;
vc->gfx.h = h;
vc->gfx.y0_top = backing_y_0_top;
if (!vc->gfx.esurface) {
gd_egl_init(vc);
if (!vc->gfx.esurface) {
return;
}
}
eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
vc->gfx.esurface, vc->gfx.ectx);
gtk_egl_set_scanout_mode(vc, true);
egl_fb_setup_for_tex(&vc->gfx.guest_fb, backing_width, backing_height,
backing_id, false);
}
void gd_egl_scanout_dmabuf(DisplayChangeListener *dcl,
QemuDmaBuf *dmabuf)
{
#ifdef CONFIG_GBM
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
uint32_t x, y, width, height, backing_width, backing_height, texture;
bool y0_top;
eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
vc->gfx.esurface, vc->gfx.ectx);
egl_dmabuf_import_texture(dmabuf);
texture = qemu_dmabuf_get_texture(dmabuf);
if (!texture) {
return;
}
x = qemu_dmabuf_get_x(dmabuf);
y = qemu_dmabuf_get_y(dmabuf);
width = qemu_dmabuf_get_width(dmabuf);
height = qemu_dmabuf_get_height(dmabuf);
backing_width = qemu_dmabuf_get_backing_width(dmabuf);
backing_height = qemu_dmabuf_get_backing_height(dmabuf);
y0_top = qemu_dmabuf_get_y0_top(dmabuf);
gd_egl_scanout_texture(dcl, texture, y0_top, backing_width, backing_height,
x, y, width, height, NULL);
if (qemu_dmabuf_get_allow_fences(dmabuf)) {
vc->gfx.guest_fb.dmabuf = dmabuf;
}
#endif
}
void gd_egl_cursor_dmabuf(DisplayChangeListener *dcl,
QemuDmaBuf *dmabuf, bool have_hot,
uint32_t hot_x, uint32_t hot_y)
{
#ifdef CONFIG_GBM
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
uint32_t backing_width, backing_height, texture;
if (dmabuf) {
egl_dmabuf_import_texture(dmabuf);
texture = qemu_dmabuf_get_texture(dmabuf);
if (!texture) {
return;
}
backing_width = qemu_dmabuf_get_backing_width(dmabuf);
backing_height = qemu_dmabuf_get_backing_height(dmabuf);
egl_fb_setup_for_tex(&vc->gfx.cursor_fb, backing_width, backing_height,
texture, false);
} else {
egl_fb_destroy(&vc->gfx.cursor_fb);
}
#endif
}
void gd_egl_cursor_position(DisplayChangeListener *dcl,
uint32_t pos_x, uint32_t pos_y)
{
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
vc->gfx.cursor_x = pos_x * vc->gfx.scale_x;
vc->gfx.cursor_y = pos_y * vc->gfx.scale_y;
}
void gd_egl_scanout_flush(DisplayChangeListener *dcl,
uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
GdkWindow *window;
int px_offset, py_offset;
int gs;
int pw_widget, ph_widget, pw_surface, ph_surface;
int ww_widget, wh_widget, ww_surface, wh_surface;
int fbw, fbh;
if (!vc->gfx.scanout_mode) {
return;
}
if (!vc->gfx.guest_fb.framebuffer) {
return;
}
eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
vc->gfx.esurface, vc->gfx.ectx);
window = gtk_widget_get_window(vc->gfx.drawing_area);
gs = gdk_window_get_scale_factor(window);
ww_widget = gdk_window_get_width(window);
wh_widget = gdk_window_get_height(window);
fbw = surface_width(vc->gfx.ds);
fbh = surface_height(vc->gfx.ds);
gd_update_scale(vc, ww_widget, wh_widget, fbw, fbh);
ww_surface = fbw * vc->gfx.scale_x;
wh_surface = fbh * vc->gfx.scale_y;
pw_widget = ww_widget * gs;
ph_widget = wh_widget * gs;
pw_surface = ww_surface * gs;
ph_surface = wh_surface * gs;
px_offset = 0;
py_offset = 0;
if (pw_widget > pw_surface) {
px_offset = (pw_widget - pw_surface) / 2;
}
if (ph_widget > ph_surface) {
py_offset = (ph_widget - ph_surface) / 2;
}
egl_fb_setup_default(&vc->gfx.win_fb, pw_surface, ph_surface,
px_offset, py_offset);
if (vc->gfx.cursor_fb.texture) {
egl_texture_blit(vc->gfx.gls, &vc->gfx.win_fb, &vc->gfx.guest_fb,
vc->gfx.y0_top);
egl_texture_blend(vc->gfx.gls, &vc->gfx.win_fb, &vc->gfx.cursor_fb,
vc->gfx.y0_top,
vc->gfx.cursor_x, vc->gfx.cursor_y,
vc->gfx.scale_x, vc->gfx.scale_y);
} else {
egl_fb_blit(&vc->gfx.win_fb, &vc->gfx.guest_fb, !vc->gfx.y0_top);
}
#ifdef CONFIG_GBM
if (vc->gfx.guest_fb.dmabuf) {
egl_dmabuf_create_sync(vc->gfx.guest_fb.dmabuf);
}
#endif
eglSwapBuffers(qemu_egl_display, vc->gfx.esurface);
}
void gd_egl_flush(DisplayChangeListener *dcl,
uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
GtkWidget *area = vc->gfx.drawing_area;
if (vc->gfx.guest_fb.dmabuf &&
!qemu_dmabuf_get_draw_submitted(vc->gfx.guest_fb.dmabuf)) {
graphic_hw_gl_block(vc->gfx.dcl.con, true);
qemu_dmabuf_set_draw_submitted(vc->gfx.guest_fb.dmabuf, true);
gtk_egl_set_scanout_mode(vc, true);
gtk_widget_queue_draw_area(area, x, y, w, h);
return;
}
gd_egl_scanout_flush(&vc->gfx.dcl, x, y, w, h);
}
void gtk_egl_init(DisplayGLMode mode)
{
GdkDisplay *gdk_display = gdk_display_get_default();
Display *x11_display = gdk_x11_display_get_xdisplay(gdk_display);
if (qemu_egl_init_dpy_x11(x11_display, mode) < 0) {
return;
}
display_opengl = 1;
}
int gd_egl_make_current(DisplayGLCtx *dgc,
QEMUGLContext ctx)
{
VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc);
if (!eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
vc->gfx.esurface, ctx)) {
error_report("egl: eglMakeCurrent failed: %s", qemu_egl_get_error_string());
return -1;
}
return 0;
}