/* Clutter. * An OpenGL based 'interactive canvas' library. * Authored By Matthew Allum * Copyright (C) 2006-2007 OpenedHand * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "clutter-backend-glx.h" #include "clutter-stage-glx.h" #include "clutter-glx.h" #include "clutter-profile.h" #include "clutter-actor-private.h" #include "clutter-debug.h" #include "clutter-event.h" #include "clutter-enum-types.h" #include "clutter-feature.h" #include "clutter-main.h" #include "clutter-private.h" #include "clutter-stage-private.h" #include "cogl/cogl.h" #include #include #include #include #include #include #ifdef HAVE_DRM #include #endif static void clutter_stage_window_iface_init (ClutterStageWindowIface *iface); static ClutterStageWindowIface *clutter_stage_glx_parent_iface = NULL; G_DEFINE_TYPE_WITH_CODE (ClutterStageGLX, _clutter_stage_glx, CLUTTER_TYPE_STAGE_X11, G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_STAGE_WINDOW, clutter_stage_window_iface_init)); static void clutter_stage_glx_unrealize (ClutterStageWindow *stage_window) { ClutterBackend *backend = clutter_get_default_backend (); ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (backend); ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window); ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (stage_window); /* Note unrealize should free up any backend stage related resources */ CLUTTER_NOTE (BACKEND, "Unrealizing stage"); clutter_x11_trap_x_errors (); if (stage_glx->glxwin != None) { glXDestroyWindow (backend_x11->xdpy, stage_glx->glxwin); stage_glx->glxwin = None; } if (!stage_x11->is_foreign_xwin && stage_x11->xwin != None) { XDestroyWindow (backend_x11->xdpy, stage_x11->xwin); stage_x11->xwin = None; } else stage_x11->xwin = None; XSync (backend_x11->xdpy, False); clutter_x11_untrap_x_errors (); CLUTTER_MARK (); } static gboolean clutter_stage_glx_realize (ClutterStageWindow *stage_window) { ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window); ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (stage_window); ClutterBackendX11 *backend_x11; ClutterBackendGLX *backend_glx; ClutterBackend *backend; int event_flags; CLUTTER_NOTE (ACTOR, "Realizing stage '%s' [%p]", G_OBJECT_TYPE_NAME (stage_window), stage_window); backend = clutter_get_default_backend (); backend_glx = CLUTTER_BACKEND_GLX (backend); backend_x11 = CLUTTER_BACKEND_X11 (backend); if (stage_x11->xwin == None) { XSetWindowAttributes xattr; unsigned long mask; XVisualInfo *xvisinfo; gfloat width, height; CLUTTER_NOTE (MISC, "Creating stage X window"); xvisinfo = clutter_backend_x11_get_visual_info (backend_x11); if (xvisinfo == NULL) { g_critical ("Unable to find suitable GL visual."); return FALSE; } /* window attributes */ xattr.background_pixel = WhitePixel (backend_x11->xdpy, backend_x11->xscreen_num); xattr.border_pixel = 0; xattr.colormap = XCreateColormap (backend_x11->xdpy, backend_x11->xwin_root, xvisinfo->visual, AllocNone); mask = CWBorderPixel | CWColormap; /* Call get_size - this will either get the geometry size (which * before we create the window is set to 640x480), or if a size * is set, it will get that. This lets you set a size on the * stage before it's realized. */ clutter_actor_get_size (CLUTTER_ACTOR (stage_x11->wrapper), &width, &height); stage_x11->xwin_width = (gint)width; stage_x11->xwin_height = (gint)height; stage_x11->xwin = XCreateWindow (backend_x11->xdpy, backend_x11->xwin_root, 0, 0, stage_x11->xwin_width, stage_x11->xwin_height, 0, xvisinfo->depth, InputOutput, xvisinfo->visual, mask, &xattr); CLUTTER_NOTE (BACKEND, "Stage [%p], window: 0x%x, size: %dx%d", stage_window, (unsigned int) stage_x11->xwin, stage_x11->xwin_width, stage_x11->xwin_height); XFree (xvisinfo); } if (stage_glx->glxwin == None) { int major; int minor; GLXFBConfig config; /* Try and create a GLXWindow to use with extensions dependent on * GLX versions >= 1.3 that don't accept regular X Windows as GLX * drawables. */ if (glXQueryVersion (backend_x11->xdpy, &major, &minor) && major == 1 && minor >= 3 && _clutter_backend_glx_get_fbconfig (backend_glx, &config)) { stage_glx->glxwin = glXCreateWindow (backend_x11->xdpy, config, stage_x11->xwin, NULL); } } /* the masks for the events we want to select on a stage window; * KeyPressMask and KeyReleaseMask are necessary even with XI1 * because key events are broken with that extension, and will * be fixed by XI2 */ event_flags = StructureNotifyMask | FocusChangeMask | ExposureMask | PropertyChangeMask | EnterWindowMask | LeaveWindowMask | KeyPressMask | KeyReleaseMask; /* if we don't use XI1 then we also want core pointer events */ if (!clutter_x11_has_xinput ()) event_flags |= (ButtonPressMask | ButtonReleaseMask | PointerMotionMask); #ifdef HAVE_XINPUT else _clutter_x11_select_events (stage_x11->xwin); #endif /* we unconditionally select input events even with event retrieval * disabled because we need to guarantee that the Clutter internal * state is maintained when calling clutter_x11_handle_event() without * requiring applications or embedding toolkits to select events * themselves. if we did that, we'd have to document the events to be * selected, and also update applications and embedding toolkits each * time we added a new mask, or a new class of events. * * see: http://bugzilla.clutter-project.org/show_bug.cgi?id=998 * for the rationale of why we did conditional selection. it is now * clear that a compositor should clear out the input region, since * it cannot assume a perfectly clean slate coming from us. * * see: http://bugzilla.clutter-project.org/show_bug.cgi?id=2228 * for an example of things that break if we do conditional event * selection. */ XSelectInput (backend_x11->xdpy, stage_x11->xwin, event_flags); #ifdef GLX_INTEL_swap_event if (clutter_feature_available (CLUTTER_FEATURE_SWAP_EVENTS)) { GLXDrawable drawable = stage_glx->glxwin ? stage_glx->glxwin : stage_x11->xwin; /* similarly to above, we unconditionally select this event * because we rely on it to advance the master clock, and * drive redraw/relayout, animations and event handling. */ glXSelectEvent (backend_x11->xdpy, drawable, GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK); } #endif /* GLX_INTEL_swap_event */ /* no user resize.. */ clutter_stage_x11_fix_window_size (stage_x11, stage_x11->xwin_width, stage_x11->xwin_height); clutter_stage_x11_set_wm_protocols (stage_x11); CLUTTER_NOTE (BACKEND, "Successfully realized stage"); /* chain up to the StageX11 implementation */ return clutter_stage_glx_parent_iface->realize (stage_window); } static int clutter_stage_glx_get_pending_swaps (ClutterStageWindow *stage_window) { ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (stage_window); return stage_glx->pending_swaps; } static void clutter_stage_glx_dispose (GObject *gobject) { G_OBJECT_CLASS (_clutter_stage_glx_parent_class)->dispose (gobject); } static void _clutter_stage_glx_class_init (ClutterStageGLXClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->dispose = clutter_stage_glx_dispose; } static void _clutter_stage_glx_init (ClutterStageGLX *stage) { } static gboolean clutter_stage_glx_has_redraw_clips (ClutterStageWindow *stage_window) { ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (stage_window); /* NB: at the start of each new frame there is an implied clip that * clips everything (i.e. nothing would be drawn) so we need to make * sure we return True in the un-initialized case here. * * NB: a clip width of 0 means a full stage redraw has been queued * so we effectively don't have any redraw clips in that case. */ if (!stage_glx->initialized_redraw_clip || (stage_glx->initialized_redraw_clip && stage_glx->bounding_redraw_clip.width != 0)) return TRUE; else return FALSE; } static gboolean clutter_stage_glx_ignoring_redraw_clips (ClutterStageWindow *stage_window) { ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (stage_window); /* NB: a clip width of 0 means a full stage redraw is required */ if (stage_glx->initialized_redraw_clip && stage_glx->bounding_redraw_clip.width == 0) return TRUE; else return FALSE; } /* A redraw clip represents (in stage coordinates) the bounding box of * something that needs to be redraw. Typically they are added to the * StageWindow as a result of clutter_actor_queue_clipped_redraw() by * actors such as ClutterGLXTexturePixmap. All redraw clips are * discarded after the next paint. * * A NULL stage_clip means the whole stage needs to be redrawn. * * What we do with this information: * - we keep track of the bounding box for all redraw clips * - when we come to redraw; if the bounding box is smaller than the * stage we scissor the redraw to that box and use * GLX_MESA_copy_sub_buffer to present the redraw to the front * buffer. Some heuristics are used to decide when a clipped redraw * should be promoted into a full stage redraw. * * Currently we simply check that the bounding box height is < 300 * pixels. * * XXX: we don't have any empirical data telling us what a sensible * thresholds is! * * TODO - we should use different heuristics depending on whether the * framebuffer is on screen and not redirected by a compositor VS * offscreen (either due to compositor redirection or because we are * rendering to a CoglOffscreen framebuffer) * * When not redirected glXCopySubBuffer (on intel hardware at least) * will block the GPU until the vertical trace is at the optimal point * so the copy can be done without tearing. In this case we don't want * to copy tall regions because they increase the average time spent * blocking the GPU. * * When rendering offscreen (CoglOffscreen or redirected by * compositor) then no extra synchronization is needed before the copy * can start. * * In all cases we need to consider that glXCopySubBuffer implies a * blit which may be avoided by promoting to a full stage redraw if: * - the framebuffer is redirected offscreen or a CoglOffscreen. * - the framebuffer is onscreen and fullscreen. * By promoting to a full stage redraw we trade off the cost involved * in rasterizing the extra pixels vs avoiding to use a blit to * present the back buffer. * */ static void clutter_stage_glx_add_redraw_clip (ClutterStageWindow *stage_window, ClutterGeometry *stage_clip) { ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (stage_window); /* If we are already forced to do a full stage redraw then bail early */ if (clutter_stage_glx_ignoring_redraw_clips (stage_window)) return; /* A NULL stage clip means a full stage redraw has been queued and * we keep track of this by setting a zero width * stage_glx->bounding_redraw_clip */ if (stage_clip == NULL) { stage_glx->bounding_redraw_clip.width = 0; stage_glx->initialized_redraw_clip = TRUE; return; } /* Ignore requests to add degenerate/empty clip rectangles */ if (stage_clip->width == 0 || stage_clip->height == 0) return; if (!stage_glx->initialized_redraw_clip) { stage_glx->bounding_redraw_clip.x = stage_clip->x; stage_glx->bounding_redraw_clip.y = stage_clip->y; stage_glx->bounding_redraw_clip.width = stage_clip->width; stage_glx->bounding_redraw_clip.height = stage_clip->height; } else if (stage_glx->bounding_redraw_clip.width > 0) { clutter_geometry_union (&stage_glx->bounding_redraw_clip, stage_clip, &stage_glx->bounding_redraw_clip); } #if 0 redraw_area = (stage_glx->bounding_redraw_clip.width * stage_glx->bounding_redraw_clip.height); stage_area = stage_x11->xwin_width * stage_x11->xwin_height; /* Redrawing and blitting >70% of the stage is assumed to be more * expensive than redrawing the additional 30% to avoid the blit. * * FIXME: This threshold was plucked out of thin air! */ if (redraw_area > (stage_area * 0.7f)) { g_print ("DEBUG: clipped redraw too big, forcing full redraw\n"); /* Set a zero width clip to force a full redraw */ stage_glx->bounding_redraw_clip.width = 0; } #endif stage_glx->initialized_redraw_clip = TRUE; } static void clutter_stage_window_iface_init (ClutterStageWindowIface *iface) { clutter_stage_glx_parent_iface = g_type_interface_peek_parent (iface); iface->realize = clutter_stage_glx_realize; iface->unrealize = clutter_stage_glx_unrealize; iface->get_pending_swaps = clutter_stage_glx_get_pending_swaps; iface->add_redraw_clip = clutter_stage_glx_add_redraw_clip; iface->has_redraw_clips = clutter_stage_glx_has_redraw_clips; iface->ignoring_redraw_clips = clutter_stage_glx_ignoring_redraw_clips; /* the rest is inherited from ClutterStageX11 */ } #ifdef HAVE_DRM static int drm_wait_vblank(int fd, drm_wait_vblank_t *vbl) { int ret, rc; do { ret = ioctl(fd, DRM_IOCTL_WAIT_VBLANK, vbl); vbl->request.type &= ~_DRM_VBLANK_RELATIVE; rc = errno; } while (ret && rc == EINTR); return rc; } #endif /* HAVE_DRM */ static void wait_for_vblank (ClutterBackendGLX *backend_glx) { if (backend_glx->vblank_type == CLUTTER_VBLANK_NONE) return; if (backend_glx->wait_video_sync) { unsigned int retraceCount; CLUTTER_NOTE (BACKEND, "Waiting for vblank (wait_video_sync)"); backend_glx->get_video_sync (&retraceCount); backend_glx->wait_video_sync (2, (retraceCount + 1) % 2, &retraceCount); } else { #ifdef HAVE_DRM drm_wait_vblank_t blank; CLUTTER_NOTE (BACKEND, "Waiting for vblank (drm)"); blank.request.type = _DRM_VBLANK_RELATIVE; blank.request.sequence = 1; blank.request.signal = 0; drm_wait_vblank (backend_glx->dri_fd, &blank); #else CLUTTER_NOTE (BACKEND, "No vblank mechanism found"); #endif /* HAVE_DRM */ } } void _clutter_stage_glx_redraw (ClutterStageGLX *stage_glx, ClutterStage *stage) { ClutterBackend *backend; ClutterBackendX11 *backend_x11; ClutterBackendGLX *backend_glx; ClutterStageX11 *stage_x11; GLXDrawable drawable; unsigned int video_sync_count; gboolean may_use_clipped_redraw; gboolean use_clipped_redraw; CLUTTER_STATIC_TIMER (painting_timer, "Redrawing", /* parent */ "Painting actors", "The time spent painting actors", 0 /* no application private data */); CLUTTER_STATIC_TIMER (swapbuffers_timer, "Redrawing", /* parent */ "glXSwapBuffers", "The time spent blocked by glXSwapBuffers", 0 /* no application private data */); CLUTTER_STATIC_TIMER (blit_sub_buffer_timer, "Redrawing", /* parent */ "glx_blit_sub_buffer", "The time spent in _glx_blit_sub_buffer", 0 /* no application private data */); backend = clutter_get_default_backend (); backend_x11 = CLUTTER_BACKEND_X11 (backend); backend_glx = CLUTTER_BACKEND_GLX (backend); stage_x11 = CLUTTER_STAGE_X11 (stage_glx); CLUTTER_TIMER_START (_clutter_uprof_context, painting_timer); if (G_LIKELY (backend_glx->can_blit_sub_buffer) && /* NB: a zero width redraw clip == full stage redraw */ stage_glx->bounding_redraw_clip.width != 0 && /* some drivers struggle to get going and produce some junk * frames when starting up... */ G_LIKELY (stage_glx->frame_count > 3) && /* While resizing a window clipped redraws are disabled to avoid * artefacts. See clutter-event-x11.c:event_translate for a * detailed explanation */ G_LIKELY (stage_x11->clipped_redraws_cool_off == 0)) may_use_clipped_redraw = TRUE; else may_use_clipped_redraw = FALSE; if (may_use_clipped_redraw && G_LIKELY (!(clutter_paint_debug_flags & CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS))) use_clipped_redraw = TRUE; else use_clipped_redraw = FALSE; if (use_clipped_redraw) { cogl_clip_push_window_rectangle (stage_glx->bounding_redraw_clip.x, stage_glx->bounding_redraw_clip.y, stage_glx->bounding_redraw_clip.width, stage_glx->bounding_redraw_clip.height); _clutter_stage_do_paint (stage, &stage_glx->bounding_redraw_clip); cogl_clip_pop (); } else _clutter_stage_do_paint (stage, NULL); if (clutter_paint_debug_flags & CLUTTER_DEBUG_REDRAWS && may_use_clipped_redraw) { ClutterGeometry *clip = &stage_glx->bounding_redraw_clip; static CoglMaterial *outline = NULL; CoglHandle vbo; float x_1 = clip->x; float x_2 = clip->x + clip->width; float y_1 = clip->y; float y_2 = clip->y + clip->height; float quad[8] = { x_1, y_1, x_2, y_1, x_2, y_2, x_1, y_2 }; CoglMatrix modelview; if (outline == NULL) { outline = cogl_material_new (); cogl_material_set_color4ub (outline, 0xff, 0x00, 0x00, 0xff); } vbo = cogl_vertex_buffer_new (4); cogl_vertex_buffer_add (vbo, "gl_Vertex", 2, /* n_components */ COGL_ATTRIBUTE_TYPE_FLOAT, FALSE, /* normalized */ 0, /* stride */ quad); cogl_vertex_buffer_submit (vbo); cogl_push_matrix (); cogl_matrix_init_identity (&modelview); _clutter_actor_apply_modelview_transform (CLUTTER_ACTOR (stage), &modelview); cogl_set_modelview_matrix (&modelview); cogl_set_source (outline); cogl_vertex_buffer_draw (vbo, COGL_VERTICES_MODE_LINE_LOOP, 0 , 4); cogl_pop_matrix (); cogl_object_unref (vbo); } cogl_flush (); CLUTTER_TIMER_STOP (_clutter_uprof_context, painting_timer); if (stage_x11->xwin == None) return; drawable = stage_glx->glxwin ? stage_glx->glxwin : stage_x11->xwin; /* If we might ever use _clutter_backend_glx_blit_sub_buffer then we * always need to keep track of the video_sync_count so that we can * throttle blits. * * Note: we get the count *before* we issue any glXCopySubBuffer or * blit_sub_buffer request in case the count would go up before * returning control to us. */ if (backend_glx->can_blit_sub_buffer && backend_glx->get_video_sync) backend_glx->get_video_sync (&video_sync_count); /* push on the screen */ if (use_clipped_redraw) { ClutterGeometry *clip = &stage_glx->bounding_redraw_clip; ClutterGeometry copy_area; CLUTTER_NOTE (BACKEND, "_glx_blit_sub_buffer (window: 0x%lx, " "x: %d, y: %d, " "width: %d, height: %d)", (unsigned long) drawable, stage_glx->bounding_redraw_clip.x, stage_glx->bounding_redraw_clip.y, stage_glx->bounding_redraw_clip.width, stage_glx->bounding_redraw_clip.height); /* XXX: It seems there will be a race here in that the stage * window may be resized before glXCopySubBufferMESA is handled * and so we may copy the wrong region. I can't really see how * we can handle this with the current state of X but at least * in this case a full redraw should be queued by the resize * anyway so it should only exhibit temporary artefacts. */ copy_area.y = clutter_actor_get_height (CLUTTER_ACTOR (stage)) - clip->y - clip->height; copy_area.x = clip->x; copy_area.width = clip->width; copy_area.height = clip->height; /* glXCopySubBufferMESA and glBlitFramebuffer are not integrated * with the glXSwapIntervalSGI mechanism which we usually use to * throttle the Clutter framerate to the vertical refresh and so * we have to manually wait for the vblank period... */ /* Here 'is_synchronized' only means that the blit won't cause a * tear, ie it won't prevent multiple blits per retrace if they * can all be performed in the blanking period. If that's the * case then we still want to use the vblank sync menchanism but * we only need it to throttle redraws. */ if (!backend_glx->blit_sub_buffer_is_synchronized) { /* XXX: note that glXCopySubBuffer, at least for Intel, is * synchronized with the vblank but glBlitFramebuffer may * not be so we use the same scheme we do when calling * glXSwapBuffers without the swap_control extension and * call glFinish () before waiting for the vblank period. * * See where we call glXSwapBuffers for more details. */ glFinish (); wait_for_vblank (CLUTTER_BACKEND_GLX (backend)); } else if (backend_glx->get_video_sync) { /* If we have the GLX_SGI_video_sync extension then we can * be a bit smarter about how we throttle blits by avoiding * any waits if we can see that the video sync count has * already progressed. */ if (backend_glx->last_video_sync_count == video_sync_count) wait_for_vblank (CLUTTER_BACKEND_GLX (backend)); } else wait_for_vblank (CLUTTER_BACKEND_GLX (backend)); CLUTTER_TIMER_START (_clutter_uprof_context, blit_sub_buffer_timer); _clutter_backend_glx_blit_sub_buffer (backend_glx, drawable, copy_area.x, copy_area.y, copy_area.width, copy_area.height); CLUTTER_TIMER_STOP (_clutter_uprof_context, blit_sub_buffer_timer); } else { CLUTTER_NOTE (BACKEND, "glXSwapBuffers (display: %p, window: 0x%lx)", backend_x11->xdpy, (unsigned long) drawable); /* If we have GLX swap buffer events then glXSwapBuffers will return * immediately and we need to track that there is a swap in * progress... */ if (clutter_feature_available (CLUTTER_FEATURE_SWAP_EVENTS)) stage_glx->pending_swaps++; if (backend_glx->vblank_type != CLUTTER_VBLANK_GLX_SWAP && backend_glx->vblank_type != CLUTTER_VBLANK_NONE) { /* If we are going to wait for VBLANK manually, we not only * need to flush out pending drawing to the GPU before we * sleep, we need to wait for it to finish. Otherwise, we * may end up with the situation: * * - We finish drawing - GPU drawing continues * - We go to sleep - GPU drawing continues * VBLANK - We call glXSwapBuffers - GPU drawing continues * - GPU drawing continues * - Swap buffers happens * * Producing a tear. Calling glFinish() first will cause us * to properly wait for the next VBLANK before we swap. This * obviously does not happen when we use _GLX_SWAP and let * the driver do the right thing */ glFinish (); wait_for_vblank (CLUTTER_BACKEND_GLX (backend)); } CLUTTER_TIMER_START (_clutter_uprof_context, swapbuffers_timer); glXSwapBuffers (backend_x11->xdpy, drawable); CLUTTER_TIMER_STOP (_clutter_uprof_context, swapbuffers_timer); } backend_glx->last_video_sync_count = video_sync_count; /* reset the redraw clipping for the next paint... */ stage_glx->initialized_redraw_clip = FALSE; stage_glx->frame_count++; }