From e0b1f518cf2da9d8e7c29cc02efc77b6cc7a58f0 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 28 Nov 2019 15:47:53 +0100 Subject: [PATCH 1/6] Add a glfwSwapBuffersWithDamage() symbol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This new API helps the user’s compositor reduce memory bandwidth and thus power usage by only re-rendering a bunch of dirty rectangles instead of the entire application buffer. There is no guarantee that it will effectively get used, it is perfectly valid to continue damaging the entire buffer instead like with glfwSwapBuffers() in some cases. --- include/GLFW/glfw3.h | 70 ++++++++++++++++++++++++++++++++++++++++++++ src/context.c | 20 +++++++++++++ src/internal.h | 2 ++ 3 files changed, 92 insertions(+) diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 8b5b87dd1..a918baa53 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -1780,6 +1780,33 @@ typedef struct GLFWimage unsigned char* pixels; } GLFWimage; +/*! @brief Rectangle. + * + * This describes a single 2D box. The origin is the bottom-left point of the + * window. + * + * @sa @ref buffer_swap + * + * @since Added in version 3.4. + * + * @ingroup window + */ +typedef struct GLFWrect +{ + /*! The starting horizontal coordinate, in pixels, of this rect. + */ + int x; + /*! The starting vertical coordinate, in pixels, of this rect. + */ + int y; + /*! The width, in pixels, of this rect. + */ + int width; + /*! The height, in pixels, of this rect. + */ + int height; +} GLFWrect; + /*! @brief Gamepad input state * * This describes the input state of a gamepad. @@ -5584,6 +5611,7 @@ GLFWAPI GLFWwindow* glfwGetCurrentContext(void); * @thread_safety This function may be called from any thread. * * @sa @ref buffer_swap + * @sa @ref glfwSwapBuffersWithDamage * @sa @ref glfwSwapInterval * * @since Added in version 1.0. @@ -5593,6 +5621,48 @@ GLFWAPI GLFWwindow* glfwGetCurrentContext(void); */ GLFWAPI void glfwSwapBuffers(GLFWwindow* window); +/*! @brief Swaps the front and back buffers of the specified window with damage + * hints. + * + * This function swaps the front and back buffers of the specified window when + * rendering with OpenGL or OpenGL ES. If the swap interval is greater than + * zero, the GPU driver waits the specified number of screen updates before + * swapping the buffers. + * + * On supported platforms, this function can damage only the specified rects + * instead of the entire buffer. This is only one possible behaviour, it is + * perfectly acceptable to damage the entire buffer so you shouldn’t rely on + * that and keep providing a fully up to date buffer. + * + * The specified window must have an OpenGL or OpenGL ES context. Specifying + * a window without a context will generate a @ref GLFW_NO_WINDOW_CONTEXT + * error. + * + * This function does not apply to Vulkan. If you are rendering with Vulkan, + * see `vkQueuePresentKHR` instead. + * + * @param[in] window The window whose buffers to swap. + * @param[in] rects The rects to update. + * @param[in] n_rects How many rects there are. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref + * GLFW_NO_WINDOW_CONTEXT and @ref GLFW_PLATFORM_ERROR. + * + * @remark __EGL:__ The context of the specified window must be current on the + * calling thread. + * + * @thread_safety This function may be called from any thread. + * + * @sa @ref buffer_swap + * @sa @ref glfwSwapBuffers + * @sa @ref glfwSwapInterval + * + * @since Added in version 3.4. + * + * @ingroup window + */ +GLFWAPI void glfwSwapBuffersWithDamage(GLFWwindow* window, GLFWrect* rects, int n_rects); + /*! @brief Sets the swap interval for the current context. * * This function sets the swap interval for the current OpenGL or OpenGL ES diff --git a/src/context.c b/src/context.c index 48311e5fd..8f5b868a7 100644 --- a/src/context.c +++ b/src/context.c @@ -657,6 +657,26 @@ GLFWAPI void glfwSwapBuffers(GLFWwindow* handle) window->context.swapBuffers(window); } +GLFWAPI void glfwSwapBuffersWithDamage(GLFWwindow* handle, GLFWrect* rects, int n_rects) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + assert(window != NULL); + + _GLFW_REQUIRE_INIT(); + + if (window->context.client == GLFW_NO_API) + { + _glfwInputError(GLFW_NO_WINDOW_CONTEXT, + "Cannot swap buffers of a window that has no OpenGL or OpenGL ES context"); + return; + } + + if (window->context.swapBuffersWithDamage) + window->context.swapBuffersWithDamage(window, rects, n_rects); + else + window->context.swapBuffers(window); +} + GLFWAPI void glfwSwapInterval(int interval) { _GLFWwindow* window; diff --git a/src/internal.h b/src/internal.h index 4c75c9b1c..ab0d1ebdc 100644 --- a/src/internal.h +++ b/src/internal.h @@ -78,6 +78,7 @@ typedef struct _GLFWmutex _GLFWmutex; typedef void (* _GLFWmakecontextcurrentfun)(_GLFWwindow*); typedef void (* _GLFWswapbuffersfun)(_GLFWwindow*); +typedef void (* _GLFWswapbufferswithdamagefun)(_GLFWwindow*,GLFWrect*,int); typedef void (* _GLFWswapintervalfun)(int); typedef int (* _GLFWextensionsupportedfun)(const char*); typedef GLFWglproc (* _GLFWgetprocaddressfun)(const char*); @@ -350,6 +351,7 @@ struct _GLFWcontext _GLFWmakecontextcurrentfun makeCurrent; _GLFWswapbuffersfun swapBuffers; + _GLFWswapbufferswithdamagefun swapBuffersWithDamage; _GLFWswapintervalfun swapInterval; _GLFWextensionsupportedfun extensionSupported; _GLFWgetprocaddressfun getProcAddress; From 8e354c2259fb18a5881a640195bd654ffd77f697 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 28 Nov 2019 15:51:08 +0100 Subject: [PATCH 2/6] EGL: Implement glfwSwapBufferWithDamage() This is provided by the EGL_KHR_swap_buffers_with_damage extension. --- src/egl_context.c | 24 ++++++++++++++++++++++++ src/egl_context.h | 4 ++++ 2 files changed, 28 insertions(+) diff --git a/src/egl_context.c b/src/egl_context.c index 6a33396f8..91cfefe3e 100644 --- a/src/egl_context.c +++ b/src/egl_context.c @@ -232,6 +232,23 @@ static void swapBuffersEGL(_GLFWwindow* window) eglSwapBuffers(_glfw.egl.display, window->context.egl.surface); } +static void swapBuffersWithDamageEGL(_GLFWwindow* window, GLFWrect* rects, int n_rects) +{ + if (window != _glfwPlatformGetTls(&_glfw.contextSlot)) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "EGL: The context must be current on the calling thread when swapping buffers"); + return; + } + + if (eglSwapBuffersWithDamageKHR) + eglSwapBuffersWithDamageKHR(_glfw.egl.display, + window->context.egl.surface, + (EGLint*)rects, (EGLint)n_rects); + else + eglSwapBuffers(_glfw.egl.display, window->context.egl.surface); +} + static void swapIntervalEGL(int interval) { eglSwapInterval(_glfw.egl.display, interval); @@ -425,6 +442,12 @@ GLFWbool _glfwInitEGL(void) extensionSupportedEGL("EGL_KHR_get_all_proc_addresses"); _glfw.egl.KHR_context_flush_control = extensionSupportedEGL("EGL_KHR_context_flush_control"); + _glfw.egl.KHR_swap_buffers_with_damage = + extensionSupportedEGL("EGL_KHR_swap_buffers_with_damage"); + + if (_glfw.egl.KHR_swap_buffers_with_damage) + _glfw.egl.SwapBuffersWithDamageKHR = (PFN_eglSwapBuffersWithDamageKHR) + _glfw.egl.GetProcAddress("eglSwapBuffersWithDamageKHR"); return GLFW_TRUE; } @@ -694,6 +717,7 @@ GLFWbool _glfwCreateContextEGL(_GLFWwindow* window, window->context.makeCurrent = makeContextCurrentEGL; window->context.swapBuffers = swapBuffersEGL; + window->context.swapBuffersWithDamage = swapBuffersWithDamageEGL; window->context.swapInterval = swapIntervalEGL; window->context.extensionSupported = extensionSupportedEGL; window->context.getProcAddress = getProcAddressEGL; diff --git a/src/egl_context.h b/src/egl_context.h index 8bfb7db6a..5c78ab27a 100644 --- a/src/egl_context.h +++ b/src/egl_context.h @@ -132,6 +132,7 @@ typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapBuffers)(EGLDisplay,EGLSurface); typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapInterval)(EGLDisplay,EGLint); typedef const char* (EGLAPIENTRY * PFN_eglQueryString)(EGLDisplay,EGLint); typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*); +typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapBuffersWithDamageKHR)(EGLDisplay,EGLSurface,EGLint*,EGLint); #define eglGetConfigAttrib _glfw.egl.GetConfigAttrib #define eglGetConfigs _glfw.egl.GetConfigs #define eglGetDisplay _glfw.egl.GetDisplay @@ -148,6 +149,7 @@ typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*); #define eglSwapInterval _glfw.egl.SwapInterval #define eglQueryString _glfw.egl.QueryString #define eglGetProcAddress _glfw.egl.GetProcAddress +#define eglSwapBuffersWithDamageKHR _glfw.egl.SwapBuffersWithDamageKHR #define _GLFW_EGL_CONTEXT_STATE _GLFWcontextEGL egl #define _GLFW_EGL_LIBRARY_CONTEXT_STATE _GLFWlibraryEGL egl @@ -178,6 +180,7 @@ typedef struct _GLFWlibraryEGL GLFWbool KHR_gl_colorspace; GLFWbool KHR_get_all_proc_addresses; GLFWbool KHR_context_flush_control; + GLFWbool KHR_swap_buffers_with_damage; void* handle; @@ -197,6 +200,7 @@ typedef struct _GLFWlibraryEGL PFN_eglSwapInterval SwapInterval; PFN_eglQueryString QueryString; PFN_eglGetProcAddress GetProcAddress; + PFN_eglSwapBuffersWithDamageKHR SwapBuffersWithDamageKHR; } _GLFWlibraryEGL; From a94b96c9548f6600ca37e455e4d61ce0da1c5950 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 8 Dec 2019 16:14:57 +0100 Subject: [PATCH 3/6] Wayland: Bump wl_compositor for damage_buffer wl_surface supports a damage_buffer request since its version 4, which requires wl_compositor to have been bound at that version too. damage_buffer can then be used by the EGL implementation to optimise eglSwapBuffersWithDamageKHR(). --- src/wl_init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wl_init.c b/src/wl_init.c index 9e692f0e6..d5c6f74f3 100644 --- a/src/wl_init.c +++ b/src/wl_init.c @@ -783,7 +783,7 @@ static void registryHandleGlobal(void* data, { if (strcmp(interface, "wl_compositor") == 0) { - _glfw.wl.compositorVersion = min(3, version); + _glfw.wl.compositorVersion = min(4, version); _glfw.wl.compositor = wl_registry_bind(registry, name, &wl_compositor_interface, _glfw.wl.compositorVersion); From 0d2ce23071a771666d65ec3c5caf341202ecac87 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 8 Dec 2019 17:24:50 +0100 Subject: [PATCH 4/6] Add a glfwGetBufferAge() symbol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This new API lets the application know when the current buffer was last written to. If it returns 0, it means the user shouldn’t rely on what was last present in this buffer and should draw everything again, as usual. This provides a significant boost in efficiency, especially on tiling GPUs, by letting the application avoid to clear and redraw everything at every frame. --- include/GLFW/glfw3.h | 31 +++++++++++++++++++++++++++++++ src/context.c | 20 ++++++++++++++++++++ src/internal.h | 2 ++ 3 files changed, 53 insertions(+) diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index a918baa53..2ba67f43e 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -5656,6 +5656,7 @@ GLFWAPI void glfwSwapBuffers(GLFWwindow* window); * @sa @ref buffer_swap * @sa @ref glfwSwapBuffers * @sa @ref glfwSwapInterval + * @sa @ref glfwGetBufferAge * * @since Added in version 3.4. * @@ -5709,6 +5710,36 @@ GLFWAPI void glfwSwapBuffersWithDamage(GLFWwindow* window, GLFWrect* rects, int */ GLFWAPI void glfwSwapInterval(int interval); +/*! @brief Returns the buffer age of the window’s current buffer. + * + * This function returns the age of the current buffer, in frames. It may be + * used to redraw only the parts of the buffer that have changed since this + * buffer was last used, thus avoiding a clear and draw of the entire buffer. + * + * A context must be current on the calling thread. Calling this function + * without a current context will cause a @ref GLFW_NO_CURRENT_CONTEXT error. + * + * This function does not apply to Vulkan. If you are rendering with Vulkan, + * see the present mode of your swapchain instead. + * + * @param[in] window The window whose buffers to swap. + * @return The age of the back buffer if the extension is available, or 0 + * otherwise. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref + * GLFW_NO_CURRENT_CONTEXT and @ref GLFW_PLATFORM_ERROR. + * + * @thread_safety This function may be called from any thread. + * + * @sa @ref buffer_swap + * @sa @ref glfwSwapBuffersWithDamage + * + * @since Added in version 3.4. + * + * @ingroup context + */ +GLFWAPI int glfwGetBufferAge(GLFWwindow* window); + /*! @brief Returns whether the specified extension is available. * * This function returns whether the specified diff --git a/src/context.c b/src/context.c index 8f5b868a7..90c1c0945 100644 --- a/src/context.c +++ b/src/context.c @@ -694,6 +694,26 @@ GLFWAPI void glfwSwapInterval(int interval) window->context.swapInterval(interval); } +GLFWAPI int glfwGetBufferAge(GLFWwindow* handle) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + assert(window != NULL); + + _GLFW_REQUIRE_INIT_OR_RETURN(0); + + if (window->context.client == GLFW_NO_API) + { + _glfwInputError(GLFW_NO_WINDOW_CONTEXT, + "Cannot get buffer age of a window that has no OpenGL or OpenGL ES context"); + return 0; + } + + if (window->context.getBufferAge) + return window->context.getBufferAge(window); + + return 0; +} + GLFWAPI int glfwExtensionSupported(const char* extension) { _GLFWwindow* window; diff --git a/src/internal.h b/src/internal.h index ab0d1ebdc..8ea273c48 100644 --- a/src/internal.h +++ b/src/internal.h @@ -80,6 +80,7 @@ typedef void (* _GLFWmakecontextcurrentfun)(_GLFWwindow*); typedef void (* _GLFWswapbuffersfun)(_GLFWwindow*); typedef void (* _GLFWswapbufferswithdamagefun)(_GLFWwindow*,GLFWrect*,int); typedef void (* _GLFWswapintervalfun)(int); +typedef int (* _GLFWgetbufferagefun)(_GLFWwindow*); typedef int (* _GLFWextensionsupportedfun)(const char*); typedef GLFWglproc (* _GLFWgetprocaddressfun)(const char*); typedef void (* _GLFWdestroycontextfun)(_GLFWwindow*); @@ -353,6 +354,7 @@ struct _GLFWcontext _GLFWswapbuffersfun swapBuffers; _GLFWswapbufferswithdamagefun swapBuffersWithDamage; _GLFWswapintervalfun swapInterval; + _GLFWgetbufferagefun getBufferAge; _GLFWextensionsupportedfun extensionSupported; _GLFWgetprocaddressfun getProcAddress; _GLFWdestroycontextfun destroy; From c8233ce7d39425ac8d6e24386a8937eb6341cb42 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 8 Dec 2019 17:39:19 +0100 Subject: [PATCH 5/6] EGL: Implement glfwGetBufferAge() This feature is provided by the EGL_EXT_buffer_age extension, otherwise always return 0. --- src/egl_context.c | 22 ++++++++++++++++++++++ src/egl_context.h | 5 +++++ 2 files changed, 27 insertions(+) diff --git a/src/egl_context.c b/src/egl_context.c index 91cfefe3e..ab9ae32d1 100644 --- a/src/egl_context.c +++ b/src/egl_context.c @@ -254,6 +254,24 @@ static void swapIntervalEGL(int interval) eglSwapInterval(_glfw.egl.display, interval); } +static int getBufferAgeEGL(_GLFWwindow* window) +{ + EGLint buffer_age; + + if (window != _glfwPlatformGetTls(&_glfw.contextSlot)) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "EGL: The context must be current on the calling thread when swapping buffers"); + return 0; + } + + if (!eglQuerySurface(_glfw.egl.display, window->context.egl.surface, + EGL_BUFFER_AGE_EXT, &buffer_age)) + return 0; + + return buffer_age; +} + static int extensionSupportedEGL(const char* extension) { const char* extensions = eglQueryString(_glfw.egl.display, EGL_EXTENSIONS); @@ -384,6 +402,8 @@ GLFWbool _glfwInitEGL(void) _glfw_dlsym(_glfw.egl.handle, "eglSwapInterval"); _glfw.egl.QueryString = (PFN_eglQueryString) _glfw_dlsym(_glfw.egl.handle, "eglQueryString"); + _glfw.egl.QuerySurface = (PFN_eglQuerySurface) + _glfw_dlsym(_glfw.egl.handle, "eglQuerySurface"); _glfw.egl.GetProcAddress = (PFN_eglGetProcAddress) _glfw_dlsym(_glfw.egl.handle, "eglGetProcAddress"); @@ -402,6 +422,7 @@ GLFWbool _glfwInitEGL(void) !_glfw.egl.SwapBuffers || !_glfw.egl.SwapInterval || !_glfw.egl.QueryString || + !_glfw.egl.QuerySurface || !_glfw.egl.GetProcAddress) { _glfwInputError(GLFW_PLATFORM_ERROR, @@ -719,6 +740,7 @@ GLFWbool _glfwCreateContextEGL(_GLFWwindow* window, window->context.swapBuffers = swapBuffersEGL; window->context.swapBuffersWithDamage = swapBuffersWithDamageEGL; window->context.swapInterval = swapIntervalEGL; + window->context.getBufferAge = getBufferAgeEGL; window->context.extensionSupported = extensionSupportedEGL; window->context.getProcAddress = getProcAddressEGL; window->context.destroy = destroyContextEGL; diff --git a/src/egl_context.h b/src/egl_context.h index 5c78ab27a..08176f76d 100644 --- a/src/egl_context.h +++ b/src/egl_context.h @@ -107,6 +107,8 @@ typedef struct wl_egl_window* EGLNativeWindowType; #define EGL_CONTEXT_RELEASE_BEHAVIOR_NONE_KHR 0 #define EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x2098 +#define EGL_BUFFER_AGE_EXT 0x313D + typedef int EGLint; typedef unsigned int EGLBoolean; typedef unsigned int EGLenum; @@ -131,6 +133,7 @@ typedef EGLBoolean (EGLAPIENTRY * PFN_eglMakeCurrent)(EGLDisplay,EGLSurface,EGLS typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapBuffers)(EGLDisplay,EGLSurface); typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapInterval)(EGLDisplay,EGLint); typedef const char* (EGLAPIENTRY * PFN_eglQueryString)(EGLDisplay,EGLint); +typedef EGLBoolean (EGLAPIENTRY * PFN_eglQuerySurface)(EGLDisplay,EGLSurface,EGLint,EGLint*); typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*); typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapBuffersWithDamageKHR)(EGLDisplay,EGLSurface,EGLint*,EGLint); #define eglGetConfigAttrib _glfw.egl.GetConfigAttrib @@ -148,6 +151,7 @@ typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapBuffersWithDamageKHR)(EGLDisplay,EG #define eglSwapBuffers _glfw.egl.SwapBuffers #define eglSwapInterval _glfw.egl.SwapInterval #define eglQueryString _glfw.egl.QueryString +#define eglQuerySurface _glfw.egl.QuerySurface #define eglGetProcAddress _glfw.egl.GetProcAddress #define eglSwapBuffersWithDamageKHR _glfw.egl.SwapBuffersWithDamageKHR @@ -199,6 +203,7 @@ typedef struct _GLFWlibraryEGL PFN_eglSwapBuffers SwapBuffers; PFN_eglSwapInterval SwapInterval; PFN_eglQueryString QueryString; + PFN_eglQuerySurface QuerySurface; PFN_eglGetProcAddress GetProcAddress; PFN_eglSwapBuffersWithDamageKHR SwapBuffersWithDamageKHR; From 8e51c4a577cad8c7b9f5128d6db7a24588ea0bbc Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 8 Dec 2019 16:14:59 +0100 Subject: [PATCH 6/6] Tests: Add damage tracking to MSAA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This lets compositors avoid re-rendering the entire buffer when only the outside of the squares changed. If glfwGetBufferAge() returns 0 for any reason (the buffer was just created, there was an error, or the underlying API doesn’t track buffer age), we swap the entire buffer again. --- tests/msaa.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/msaa.c b/tests/msaa.c index 33e2ccc3b..842104477 100644 --- a/tests/msaa.c +++ b/tests/msaa.c @@ -102,6 +102,19 @@ int main(int argc, char** argv) GLuint vertex_buffer, vertex_shader, fragment_shader, program; GLint mvp_location, vpos_location; + // Minimum static damage for both squares. + GLFWrect rects[8] = + {{ 25, 25, 350, 90}, + { 25, 285, 350, 90}, + { 25, 115, 90, 170}, + {285, 115, 90, 170}, + + {425, 25, 350, 90}, + {425, 285, 350, 90}, + {425, 115, 90, 170}, + {685, 115, 90, 170}, + }; + while ((ch = getopt(argc, argv, "hs:")) != -1) { switch (ch) @@ -178,11 +191,13 @@ int main(int argc, char** argv) while (!glfwWindowShouldClose(window)) { float ratio; + int buffer_age; int width, height; mat4x4 m, p, mvp; const double angle = glfwGetTime() * M_PI / 180.0; glfwGetFramebufferSize(window, &width, &height); + buffer_age = glfwGetBufferAge(window); ratio = width / (float) height; glViewport(0, 0, width, height); @@ -208,7 +223,12 @@ int main(int argc, char** argv) glEnable(GL_MULTISAMPLE); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - glfwSwapBuffers(window); + // If buffer_age is 0, we can’t assume anything about the previous buffer + // so swap the entire buffer. + if (buffer_age > 0) + glfwSwapBuffersWithDamage(window, rects, 8); + else + glfwSwapBuffers(window); glfwPollEvents(); }