/* * ***** BEGIN GPL LICENSE BLOCK ***** * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * The Original Code is Copyright (C) 2007 Blender Foundation. * All rights reserved. * * * Contributor(s): Blender Foundation * * ***** END GPL LICENSE BLOCK ***** */ /** \file blender/windowmanager/intern/wm_draw.c * \ingroup wm * * Handle OpenGL buffers for windowing, also paint cursor. */ #include #include #include "DNA_listBase.h" #include "DNA_object_types.h" #include "DNA_camera_types.h" #include "DNA_screen_types.h" #include "DNA_windowmanager_types.h" #include "DNA_userdef_types.h" #include "DNA_view3d_types.h" #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" #include "BLI_utildefines.h" #include "BIF_gl.h" #include "BKE_context.h" #include "BKE_image.h" #include "BKE_main.h" #include "BKE_screen.h" #include "BKE_scene.h" #include "BKE_workspace.h" #include "GHOST_C-api.h" #include "ED_node.h" #include "ED_view3d.h" #include "ED_screen.h" #include "GPU_draw.h" #include "GPU_extensions.h" #include "GPU_framebuffer.h" #include "GPU_immediate.h" #include "GPU_matrix.h" #include "GPU_texture.h" #include "GPU_viewport.h" #include "RE_engine.h" #include "WM_api.h" #include "WM_types.h" #include "wm.h" #include "wm_draw.h" #include "wm_window.h" #include "wm_event_system.h" #ifdef WITH_OPENSUBDIV # include "BKE_subsurf.h" #endif /* ******************* paint cursor *************** */ static void wm_paintcursor_draw(bContext *C, ARegion *ar) { wmWindowManager *wm = CTX_wm_manager(C); wmWindow *win = CTX_wm_window(C); bScreen *screen = WM_window_get_active_screen(win); wmPaintCursor *pc; if (ar->visible && ar == screen->active_region) { for (pc = wm->paintcursors.first; pc; pc = pc->next) { if (pc->poll == NULL || pc->poll(C)) { /* Prevent drawing outside region. */ GLint scissor[4]; glGetIntegerv(GL_SCISSOR_BOX, scissor); glScissor(ar->winrct.xmin, ar->winrct.ymin, BLI_rcti_size_x(&ar->winrct) + 1, BLI_rcti_size_y(&ar->winrct) + 1); if (ELEM(win->grabcursor, GHOST_kGrabWrap, GHOST_kGrabHide)) { int x = 0, y = 0; wm_get_cursor_position(win, &x, &y); pc->draw(C, x, y, pc->customdata); } else { pc->draw(C, win->eventstate->x, win->eventstate->y, pc->customdata); } glScissor(scissor[0], scissor[1], scissor[2], scissor[3]); } } } } static bool wm_draw_region_stereo_set(Main *bmain, ScrArea *sa, ARegion *ar, eStereoViews sview) { /* We could detect better when stereo is actually needed, by inspecting the * image in the image editor and sequencer. */ if (ar->regiontype != RGN_TYPE_WINDOW) { return false; } switch (sa->spacetype) { case SPACE_IMAGE: { SpaceImage *sima = sa->spacedata.first; sima->iuser.multiview_eye = sview; return true; } case SPACE_VIEW3D: { View3D *v3d = sa->spacedata.first; if (v3d->camera && v3d->camera->type == OB_CAMERA) { Camera *cam = v3d->camera->data; CameraBGImage *bgpic = cam->bg_images.first; v3d->multiview_eye = sview; if (bgpic) bgpic->iuser.multiview_eye = sview; return true; } return false; } case SPACE_NODE: { SpaceNode *snode = sa->spacedata.first; if ((snode->flag & SNODE_BACKDRAW) && ED_node_is_compositor(snode)) { Image *ima = BKE_image_verify_viewer(bmain, IMA_TYPE_COMPOSITE, "Viewer Node"); ima->eye = sview; return true; } return false; } case SPACE_SEQ: { SpaceSeq *sseq = sa->spacedata.first; sseq->multiview_eye = sview; return true; } } return false; } /* ********************* drawing ****************** */ static void wm_area_mark_invalid_backbuf(ScrArea *sa) { if (sa->spacetype == SPACE_VIEW3D) ((View3D *)sa->spacedata.first)->flag |= V3D_INVALID_BACKBUF; } static void wm_region_test_render_do_draw(const Scene *scene, struct Depsgraph *depsgraph, ScrArea *sa, ARegion *ar) { /* tag region for redraw from render engine preview running inside of it */ if (sa->spacetype == SPACE_VIEW3D && ar->regiontype == RGN_TYPE_WINDOW) { RegionView3D *rv3d = ar->regiondata; RenderEngine *engine = rv3d->render_engine; GPUViewport *viewport = WM_draw_region_get_viewport(ar, 0); if (engine && (engine->flag & RE_ENGINE_DO_DRAW)) { View3D *v3d = sa->spacedata.first; rcti border_rect; /* do partial redraw when possible */ if (ED_view3d_calc_render_border(scene, depsgraph, v3d, ar, &border_rect)) ED_region_tag_redraw_partial(ar, &border_rect); else ED_region_tag_redraw(ar); engine->flag &= ~RE_ENGINE_DO_DRAW; } else if (viewport && GPU_viewport_do_update(viewport)) { ED_region_tag_redraw(ar); } } } static bool wm_region_use_viewport(ScrArea *sa, ARegion *ar) { return (sa->spacetype == SPACE_VIEW3D && ar->regiontype == RGN_TYPE_WINDOW); } /********************** draw all **************************/ /* - reference method, draw all each time */ typedef struct WindowDrawCB { struct WindowDrawCB *next, *prev; void(*draw)(const struct wmWindow *, void *); void *customdata; } WindowDrawCB; void *WM_draw_cb_activate( wmWindow *win, void(*draw)(const struct wmWindow *, void *), void *customdata) { WindowDrawCB *wdc = MEM_callocN(sizeof(*wdc), "WindowDrawCB"); BLI_addtail(&win->drawcalls, wdc); wdc->draw = draw; wdc->customdata = customdata; return wdc; } void WM_draw_cb_exit(wmWindow *win, void *handle) { for (WindowDrawCB *wdc = win->drawcalls.first; wdc; wdc = wdc->next) { if (wdc == (WindowDrawCB *)handle) { BLI_remlink(&win->drawcalls, wdc); MEM_freeN(wdc); return; } } } static void wm_draw_callbacks(wmWindow *win) { for (WindowDrawCB *wdc = win->drawcalls.first; wdc; wdc = wdc->next) { wdc->draw(win, wdc->customdata); } } /************************* Region drawing. ******************************** * * Each region draws into its own framebuffer, which is then blit on the * window draw buffer. This helps with fast redrawing if only some regions * change. It also means we can share a single context for multiple windows, * so that for example VAOs can be shared between windows. */ static void wm_draw_region_buffer_free(ARegion *ar) { if (ar->draw_buffer) { for (int view = 0; view < 2; view++) { if (ar->draw_buffer->offscreen[view]) { GPU_offscreen_free(ar->draw_buffer->offscreen[view]); } if (ar->draw_buffer->viewport[view]) { GPU_viewport_free(ar->draw_buffer->viewport[view]); } } MEM_freeN(ar->draw_buffer); ar->draw_buffer = NULL; } } static void wm_draw_offscreen_texture_parameters(GPUOffScreen *offscreen) { /* Setup offscreen color texture for drawing. */ GPUTexture *texture = GPU_offscreen_color_texture(offscreen); /* We don't support multisample textures here. */ BLI_assert(GPU_texture_target(texture) == GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, GPU_texture_opengl_bindcode(texture)); /* No mipmaps or filtering. */ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); /* GL_TEXTURE_BASE_LEVEL = 0 by default */ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, 0); } static void wm_draw_region_buffer_create(ARegion *ar, bool stereo, bool use_viewport) { if (ar->draw_buffer) { if (ar->draw_buffer->stereo != stereo) { /* Free draw buffer on stereo changes. */ wm_draw_region_buffer_free(ar); } else { /* Free offscreen buffer on size changes. Viewport auto resizes. */ GPUOffScreen *offscreen = ar->draw_buffer->offscreen[0]; if (offscreen && (GPU_offscreen_width(offscreen) != ar->winx || GPU_offscreen_height(offscreen) != ar->winy)) { wm_draw_region_buffer_free(ar); } } } if (!ar->draw_buffer) { if (use_viewport) { /* Allocate viewport which includes an offscreen buffer with depth * multisample, etc. */ ar->draw_buffer = MEM_callocN(sizeof(wmDrawBuffer), "wmDrawBuffer"); ar->draw_buffer->viewport[0] = GPU_viewport_create(); ar->draw_buffer->viewport[1] = (stereo) ? GPU_viewport_create() : NULL; } else { /* Allocate offscreen buffer if it does not exist. This one has no * depth or multisample buffers. 3D view creates own buffers with * the data it needs. */ GPUOffScreen *offscreen = GPU_offscreen_create(ar->winx, ar->winy, 0, false, false, NULL); if (!offscreen) { return; } wm_draw_offscreen_texture_parameters(offscreen); GPUOffScreen *offscreen_right = NULL; if (stereo) { offscreen_right = GPU_offscreen_create(ar->winx, ar->winy, 0, false, false, NULL); if (!offscreen_right) { GPU_offscreen_free(offscreen); return; } wm_draw_offscreen_texture_parameters(offscreen_right); } ar->draw_buffer = MEM_callocN(sizeof(wmDrawBuffer), "wmDrawBuffer"); ar->draw_buffer->offscreen[0] = offscreen; ar->draw_buffer->offscreen[1] = offscreen_right; } ar->draw_buffer->bound_view = -1; ar->draw_buffer->stereo = stereo; } } static void wm_draw_region_bind(ARegion *ar, int view) { if (!ar->draw_buffer) { return; } if (ar->draw_buffer->viewport[view]) { GPU_viewport_bind(ar->draw_buffer->viewport[view], &ar->winrct); } else { GPU_offscreen_bind(ar->draw_buffer->offscreen[view], false); /* For now scissor is expected by region drawing, we could disable it * and do the enable/disable in the specific cases that setup scissor. */ glEnable(GL_SCISSOR_TEST); glScissor(0, 0, ar->winx, ar->winy); } ar->draw_buffer->bound_view = view; } static void wm_draw_region_unbind(ARegion *ar, int view) { if (!ar->draw_buffer) { return; } ar->draw_buffer->bound_view = -1; if (ar->draw_buffer->viewport[view]) { GPU_viewport_unbind(ar->draw_buffer->viewport[view]); } else { glDisable(GL_SCISSOR_TEST); GPU_offscreen_unbind(ar->draw_buffer->offscreen[view], false); } } static void wm_draw_region_blit(ARegion *ar, int view) { if (!ar->draw_buffer) { return; } if (ar->draw_buffer->viewport[view]) { GPU_viewport_draw_to_screen(ar->draw_buffer->viewport[view], &ar->winrct); } else { GPU_offscreen_draw_to_screen(ar->draw_buffer->offscreen[view], ar->winrct.xmin, ar->winrct.ymin); } } GPUTexture *wm_draw_region_texture(ARegion *ar, int view) { if (!ar->draw_buffer) { return NULL; } if (ar->draw_buffer->viewport[view]) { return GPU_viewport_color_texture(ar->draw_buffer->viewport[view]); } else { return GPU_offscreen_color_texture(ar->draw_buffer->offscreen[view]); } } void wm_draw_region_blend(ARegion *ar, int view, bool blend) { if (!ar->draw_buffer) { return; } /* Alpha is always 1, except when blend timer is running. */ float alpha = ED_region_blend_alpha(ar); if (alpha <= 0.0f) { return; } if (!blend) { alpha = 1.0f; } /* setup actual texture */ GPUTexture *texture = wm_draw_region_texture(ar, view); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, GPU_texture_opengl_bindcode(texture)); /* wmOrtho for the screen has this same offset */ const float halfx = GLA_PIXEL_OFS / (BLI_rcti_size_x(&ar->winrct) + 1); const float halfy = GLA_PIXEL_OFS / (BLI_rcti_size_y(&ar->winrct) + 1); if (blend) { /* GL_ONE because regions drawn offscreen have premultiplied alpha. */ glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); } GPUShader *shader = GPU_shader_get_builtin_shader(GPU_SHADER_2D_IMAGE_RECT_COLOR); GPU_shader_bind(shader); glUniform1i(GPU_shader_get_uniform(shader, "image"), 0); glUniform4f(GPU_shader_get_uniform(shader, "rect_icon"), halfx, halfy, 1.0f + halfx, 1.0f + halfy); glUniform4f(GPU_shader_get_uniform(shader, "rect_geom"), ar->winrct.xmin, ar->winrct.ymin, ar->winrct.xmax + 1, ar->winrct.ymax + 1); glUniform4f(GPU_shader_get_builtin_uniform(shader, GPU_UNIFORM_COLOR), alpha, alpha, alpha, alpha); GPU_draw_primitive(GPU_PRIM_TRI_STRIP, 4); glBindTexture(GL_TEXTURE_2D, 0); if (blend) { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_BLEND); } } GPUViewport *WM_draw_region_get_viewport(ARegion *ar, int view) { if (!ar->draw_buffer) { return NULL; } return ar->draw_buffer->viewport[view]; } GPUViewport *WM_draw_region_get_bound_viewport(ARegion *ar) { if (!ar->draw_buffer || ar->draw_buffer->bound_view == -1) { return NULL; } int view = ar->draw_buffer->bound_view; return ar->draw_buffer->viewport[view]; } static void wm_draw_window_offscreen(bContext *C, wmWindow *win, bool stereo) { Main *bmain = CTX_data_main(C); wmWindowManager *wm = CTX_wm_manager(C); bScreen *screen = WM_window_get_active_screen(win); /* Draw screen areas into own frame buffer. */ ED_screen_areas_iter(win, screen, sa) { CTX_wm_area_set(C, sa); /* Compute UI layouts for dynamically size regions. */ for (ARegion *ar = sa->regionbase.first; ar; ar = ar->next) { if (ar->visible && ar->do_draw && ar->type && ar->type->layout) { CTX_wm_region_set(C, ar); ED_region_do_layout(C, ar); CTX_wm_region_set(C, NULL); } } ED_area_update_region_sizes(wm, win, sa); /* Then do actual drawing of regions. */ for (ARegion *ar = sa->regionbase.first; ar; ar = ar->next) { if (ar->visible && ar->do_draw) { CTX_wm_region_set(C, ar); bool use_viewport = wm_region_use_viewport(sa, ar); if (stereo && wm_draw_region_stereo_set(bmain, sa, ar, STEREO_LEFT_ID)) { wm_draw_region_buffer_create(ar, true, use_viewport); for (int view = 0; view < 2; view++) { eStereoViews sview; if (view == 0) { sview = STEREO_LEFT_ID; } else { sview = STEREO_RIGHT_ID; wm_draw_region_stereo_set(bmain, sa, ar, sview); } wm_draw_region_bind(ar, view); ED_region_do_draw(C, ar); wm_draw_region_unbind(ar, view); } } else { wm_draw_region_buffer_create(ar, false, use_viewport); wm_draw_region_bind(ar, 0); ED_region_do_draw(C, ar); wm_draw_region_unbind(ar, 0); } ar->do_draw = false; CTX_wm_region_set(C, NULL); } } wm_area_mark_invalid_backbuf(sa); CTX_wm_area_set(C, NULL); } /* Draw menus into their own framebuffer. */ for (ARegion *ar = screen->regionbase.first; ar; ar = ar->next) { if (ar->visible) { CTX_wm_menu_set(C, ar); if (ar->type && ar->type->layout) { /* UI code reads the OpenGL state, but we have to refesh * the UI layout beforehand in case the menu size changes. */ wmViewport(&ar->winrct); ar->type->layout(C, ar); } wm_draw_region_buffer_create(ar, false, false); wm_draw_region_bind(ar, 0); glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); ED_region_do_draw(C, ar); wm_draw_region_unbind(ar, 0); ar->do_draw = false; CTX_wm_menu_set(C, NULL); } } } static void wm_draw_window_onscreen(bContext *C, wmWindow *win, int view) { wmWindowManager *wm = CTX_wm_manager(C); bScreen *screen = WM_window_get_active_screen(win); /* Draw into the window framebuffer, in full window coordinates. */ wmWindowViewport(win); glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); /* Blit non-overlapping area regions. */ ED_screen_areas_iter(win, screen, sa) { for (ARegion *ar = sa->regionbase.first; ar; ar = ar->next) { if (ar->visible && ar->overlap == false) { if (view == -1 && ar->draw_buffer && ar->draw_buffer->stereo) { /* Stereo drawing from textures. */ if (win->stereo3d_format->display_mode == S3D_DISPLAY_ANAGLYPH) { wm_stereo3d_draw_anaglyph(win, ar); } else { wm_stereo3d_draw_interlace(win, ar); } } else { /* Blit from offscreen buffer. */ wm_draw_region_blit(ar, 0); } } } } /* Draw paint cursors. */ if (wm->paintcursors.first) { ED_screen_areas_iter(win, screen, sa) { for (ARegion *ar = sa->regionbase.first; ar; ar = ar->next) { if (ar->visible && ar == screen->active_region) { CTX_wm_area_set(C, sa); CTX_wm_region_set(C, ar); /* make region ready for draw, scissor, pixelspace */ wm_paintcursor_draw(C, ar); CTX_wm_region_set(C, NULL); CTX_wm_area_set(C, NULL); } } } wmWindowViewport(win); } /* Blend in overlapping area regions */ ED_screen_areas_iter(win, screen, sa) { for (ARegion *ar = sa->regionbase.first; ar; ar = ar->next) { if (ar->visible && ar->overlap) { wm_draw_region_blend(ar, 0, true); } } } /* After area regions so we can do area 'overlay' drawing. */ ED_screen_draw_edges(win); wm_draw_callbacks(win); /* Blend in floating regions (menus). */ for (ARegion *ar = screen->regionbase.first; ar; ar = ar->next) { if (ar->visible) { wm_draw_region_blend(ar, 0, true); } } /* always draw, not only when screen tagged */ if (win->gesture.first) wm_gesture_draw(win); /* needs pixel coords in screen */ if (wm->drags.first) { wm_drags_draw(C, win, NULL); } } static void wm_draw_window(bContext *C, wmWindow *win) { bScreen *screen = WM_window_get_active_screen(win); bool stereo = WM_stereo3d_enabled(win, false); /* Draw area regions into their own framebuffer. This way we can redraw * the areas that need it, and blit the rest from existing framebuffers. */ wm_draw_window_offscreen(C, win, stereo); /* Now we draw into the window framebuffer, in full window coordinates. */ if (!stereo) { /* Regular mono drawing. */ wm_draw_window_onscreen(C, win, -1); } else if (win->stereo3d_format->display_mode == S3D_DISPLAY_PAGEFLIP) { /* For pageflip we simply draw to both back buffers. */ glDrawBuffer(GL_BACK_LEFT); wm_draw_window_onscreen(C, win, 0); glDrawBuffer(GL_BACK_RIGHT); wm_draw_window_onscreen(C, win, 1); glDrawBuffer(GL_BACK); } else if (ELEM(win->stereo3d_format->display_mode, S3D_DISPLAY_ANAGLYPH, S3D_DISPLAY_INTERLACE)) { /* For anaglyph and interlace, we draw individual regions with * stereo framebuffers using different shaders. */ wm_draw_window_onscreen(C, win, -1); } else { /* For side-by-side and top-bottom, we need to render each view to an * an offscreen texture and then draw it. This used to happen for all * stereo methods, but it's less efficient than drawing directly. */ const int width = WM_window_pixels_x(win); const int height = WM_window_pixels_y(win); GPUOffScreen *offscreen = GPU_offscreen_create(width, height, 0, false, false, NULL); if (offscreen) { GPUTexture *texture = GPU_offscreen_color_texture(offscreen); wm_draw_offscreen_texture_parameters(offscreen); for (int view = 0; view < 2; view++) { /* Draw view into offscreen buffer. */ GPU_offscreen_bind(offscreen, false); wm_draw_window_onscreen(C, win, view); GPU_offscreen_unbind(offscreen, false); /* Draw offscreen buffer to screen. */ glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, GPU_texture_opengl_bindcode(texture)); if (win->stereo3d_format->display_mode == S3D_DISPLAY_SIDEBYSIDE) { wm_stereo3d_draw_sidebyside(win, view); } else { wm_stereo3d_draw_topbottom(win, view); } glBindTexture(GL_TEXTURE_2D, 0); } GPU_offscreen_free(offscreen); } else { /* Still draw something in case of allocation failure. */ wm_draw_window_onscreen(C, win, 0); } } screen->do_draw = false; } /****************** main update call **********************/ /* quick test to prevent changing window drawable */ static bool wm_draw_update_test_window(wmWindow *win) { Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); struct Depsgraph *depsgraph = BKE_scene_get_depsgraph(scene, view_layer, true); bScreen *screen = WM_window_get_active_screen(win); ARegion *ar; bool do_draw = false; for (ar = screen->regionbase.first; ar; ar = ar->next) { if (ar->do_draw_overlay) { screen->do_draw_paintcursor = true; ar->do_draw_overlay = false; } if (ar->visible && ar->do_draw) do_draw = true; } ED_screen_areas_iter(win, screen, sa) { for (ar = sa->regionbase.first; ar; ar = ar->next) { wm_region_test_render_do_draw(scene, depsgraph, sa, ar); if (ar->visible && ar->do_draw) do_draw = true; } } if (do_draw) return true; if (screen->do_refresh) return true; if (screen->do_draw) return true; if (screen->do_draw_gesture) return true; if (screen->do_draw_paintcursor) return true; if (screen->do_draw_drag) return true; return false; } void WM_paint_cursor_tag_redraw(wmWindow *win, ARegion *UNUSED(ar)) { if (win) { bScreen *screen = WM_window_get_active_screen(win); screen->do_draw_paintcursor = true; } } void wm_draw_update(bContext *C) { Main *bmain = CTX_data_main(C); wmWindowManager *wm = CTX_wm_manager(C); wmWindow *win; #ifdef WITH_OPENSUBDIV BKE_subsurf_free_unused_buffers(); #endif GPU_free_unused_buffers(bmain); for (win = wm->windows.first; win; win = win->next) { #ifdef WIN32 GHOST_TWindowState state = GHOST_GetWindowState(win->ghostwin); if (state == GHOST_kWindowStateMinimized) { /* do not update minimized windows, gives issues on Intel (see T33223) * and AMD (see T50856). it seems logical to skip update for invisible * window anyway. */ continue; } #endif if (wm_draw_update_test_window(win)) { bScreen *screen = WM_window_get_active_screen(win); CTX_wm_window_set(C, win); /* sets context window+screen */ wm_window_make_drawable(wm, win); /* notifiers for screen redraw */ ED_screen_ensure_updated(wm, win, screen); wm_draw_window(C, win); screen->do_draw_gesture = false; screen->do_draw_paintcursor = false; screen->do_draw_drag = false; wm_window_swap_buffers(win); CTX_wm_window_set(C, NULL); } } } void wm_draw_region_clear(wmWindow *win, ARegion *UNUSED(ar)) { bScreen *screen = WM_window_get_active_screen(win); screen->do_draw = true; } void WM_draw_region_free(ARegion *ar) { wm_draw_region_buffer_free(ar); ar->visible = 0; } void WM_redraw_windows(bContext *C) { wmWindow *win_prev = CTX_wm_window(C); ScrArea *area_prev = CTX_wm_area(C); ARegion *ar_prev = CTX_wm_region(C); wm_draw_update(C); CTX_wm_window_set(C, win_prev); CTX_wm_area_set(C, area_prev); CTX_wm_region_set(C, ar_prev); }