diff options
author | Niklas Haas <git@haasn.xyz> | 2017-09-14 08:04:55 +0200 |
---|---|---|
committer | Niklas Haas <git@haasn.xyz> | 2017-09-21 15:00:55 +0200 |
commit | 65979986a923a8f08019b257c3fe72cd5e8ecf68 (patch) | |
tree | b8f4b8c17d583594aef0ca509064f8b2ff7128d4 /video/out/opengl | |
parent | 20f958c9775652c3213588c2a0824f5353276adc (diff) | |
download | mpv-65979986a923a8f08019b257c3fe72cd5e8ecf68.tar.bz2 mpv-65979986a923a8f08019b257c3fe72cd5e8ecf68.tar.xz |
vo_opengl: refactor into vo_gpu
This is done in several steps:
1. refactor MPGLContext -> struct ra_ctx
2. move GL-specific stuff in vo_opengl into opengl/context.c
3. generalize context creation to support other APIs, and add --gpu-api
4. rename all of the --opengl- options that are no longer opengl-specific
5. move all of the stuff from opengl/* that isn't GL-specific into gpu/
(note: opengl/gl_utils.h became opengl/utils.h)
6. rename vo_opengl to vo_gpu
7. to handle window screenshots, the short-term approach was to just add
it to ra_swchain_fns. Long term (and for vulkan) this has to be moved to
ra itself (and vo_gpu altered to compensate), but this was a stop-gap
measure to prevent this commit from getting too big
8. move ra->fns->flush to ra_gl_ctx instead
9. some other minor changes that I've probably already forgotten
Note: This is one half of a major refactor, the other half of which is
provided by rossy's following commit. This commit enables support for
all linux platforms, while his version enables support for all non-linux
platforms.
Note 2: vo_opengl_cb.c also re-uses ra_gl_ctx so it benefits from the
--opengl- options like --opengl-early-flush, --opengl-finish etc. Should
be a strict superset of the old functionality.
Disclaimer: Since I have no way of compiling mpv on all platforms, some
of these ports were done blindly. Specifically, the blind ports included
context_mali_fbdev.c and context_rpi.c. Since they're both based on
egl_helpers, the port should have gone smoothly without any major
changes required. But if somebody complains about a compile error on
those platforms (assuming anybody actually uses them), you know where to
complain.
Diffstat (limited to 'video/out/opengl')
43 files changed, 1128 insertions, 10204 deletions
diff --git a/video/out/opengl/common.h b/video/out/opengl/common.h index 7b2e3ed497..b9f582b79f 100644 --- a/video/out/opengl/common.h +++ b/video/out/opengl/common.h @@ -26,10 +26,10 @@ #include "common/msg.h" #include "misc/bstr.h" -#include "video/out/vo.h" #include "video/csputils.h" - #include "video/mp_image.h" +#include "video/out/vo.h" +#include "video/out/gpu/ra.h" #include "gl_headers.h" diff --git a/video/out/opengl/context.c b/video/out/opengl/context.c index fe454e9741..d3cdcac3b7 100644 --- a/video/out/opengl/context.c +++ b/video/out/opengl/context.c @@ -1,10 +1,4 @@ /* - * common OpenGL routines - * - * copyleft (C) 2005-2010 Reimar Döffinger <Reimar.Doeffinger@gmx.de> - * Special thanks go to the xine team and Matthias Hopf, whose video_out_opengl.c - * gave me lots of good ideas. - * * This file is part of mpv. * * mpv is free software; you can redistribute it and/or @@ -21,73 +15,10 @@ * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ -#include <stddef.h> -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <stdbool.h> -#include <math.h> -#include <assert.h> - +#include "options/m_config.h" #include "context.h" -#include "common/common.h" -#include "options/options.h" -#include "options/m_option.h" - -extern const struct mpgl_driver mpgl_driver_x11; -extern const struct mpgl_driver mpgl_driver_x11egl; -extern const struct mpgl_driver mpgl_driver_x11_probe; -extern const struct mpgl_driver mpgl_driver_drm_egl; -extern const struct mpgl_driver mpgl_driver_drm; -extern const struct mpgl_driver mpgl_driver_cocoa; -extern const struct mpgl_driver mpgl_driver_wayland; -extern const struct mpgl_driver mpgl_driver_w32; -extern const struct mpgl_driver mpgl_driver_angle; -extern const struct mpgl_driver mpgl_driver_angle_es2; -extern const struct mpgl_driver mpgl_driver_dxinterop; -extern const struct mpgl_driver mpgl_driver_rpi; -extern const struct mpgl_driver mpgl_driver_mali; -extern const struct mpgl_driver mpgl_driver_vdpauglx; - -static const struct mpgl_driver *const backends[] = { -#if HAVE_RPI - &mpgl_driver_rpi, -#endif -#if HAVE_GL_COCOA - &mpgl_driver_cocoa, -#endif -#if HAVE_EGL_ANGLE_WIN32 - &mpgl_driver_angle, -#endif -#if HAVE_GL_WIN32 - &mpgl_driver_w32, -#endif -#if HAVE_GL_DXINTEROP - &mpgl_driver_dxinterop, -#endif -#if HAVE_GL_X11 - &mpgl_driver_x11_probe, -#endif -#if HAVE_EGL_X11 - &mpgl_driver_x11egl, -#endif -#if HAVE_GL_X11 - &mpgl_driver_x11, -#endif -#if HAVE_GL_WAYLAND - &mpgl_driver_wayland, -#endif -#if HAVE_EGL_DRM - &mpgl_driver_drm, - &mpgl_driver_drm_egl, -#endif -#if HAVE_MALI_FBDEV - &mpgl_driver_mali, -#endif -#if HAVE_VDPAU_GL_X11 - &mpgl_driver_vdpauglx, -#endif -}; +#include "ra_gl.h" +#include "utils.h" // 0-terminated list of desktop GL versions a backend should try to // initialize. The first entry is the most preferred version. @@ -103,140 +34,319 @@ const int mpgl_preferred_gl_versions[] = { 0 }; -int mpgl_find_backend(const char *name) +enum { + FLUSH_NO = 0, + FLUSH_YES, + FLUSH_AUTO, +}; + +enum { + GLES_AUTO = 0, + GLES_YES, + GLES_NO, +}; + +struct opengl_opts { + int use_glfinish; + int waitvsync; + int vsync_pattern[2]; + int swapinterval; + int early_flush; + int restrict_version; + int gles_mode; +}; + +#define OPT_BASE_STRUCT struct opengl_opts +const struct m_sub_options opengl_conf = { + .opts = (const struct m_option[]) { + OPT_FLAG("opengl-glfinish", use_glfinish, 0), + OPT_FLAG("opengl-waitvsync", waitvsync, 0), + OPT_INT("opengl-swapinterval", swapinterval, 0), + OPT_INTPAIR("opengl-check-pattern", vsync_pattern, 0), + OPT_INT("opengl-restrict", restrict_version, 0), + OPT_CHOICE("opengl-es", gles_mode, 0, + ({"auto", GLES_AUTO}, {"yes", GLES_YES}, {"no", GLES_NO})), + OPT_CHOICE("opengl-early-flush", early_flush, 0, + ({"no", FLUSH_NO}, {"yes", FLUSH_YES}, {"auto", FLUSH_AUTO})), + + OPT_REPLACED("opengl-debug", "gpu-debug"), + OPT_REPLACED("opengl-sw", "gpu-sw"), + OPT_REPLACED("opengl-vsync-fences", "swapchain-depth"), + OPT_REPLACED("opengl-backend", "gpu-context"), + {0}, + }, + .defaults = &(const struct opengl_opts) { + .swapinterval = 1, + }, + .size = sizeof(struct opengl_opts), +}; + +struct priv { + GL *gl; + struct mp_log *log; + struct ra_gl_ctx_params params; + struct opengl_opts *opts; + struct ra_swapchain_fns fns; + GLuint main_fb; + struct ra_tex *wrapped_fb; // corresponds to main_fb + // for debugging: + int frames_rendered; + unsigned int prev_sgi_sync_count; + // for gl_vsync_pattern + int last_pattern; + int matches, mismatches; + // for swapchain_depth simulation + GLsync *vsync_fences; + int num_vsync_fences; +}; + +bool ra_gl_ctx_test_version(struct ra_ctx *ctx, int version, bool es) { - if (name == NULL || strcmp(name, "auto") == 0) - return -1; - for (int n = 0; n < MP_ARRAY_SIZE(backends); n++) { - if (strcmp(backends[n]->name, name) == 0) - return n; + bool ret; + struct opengl_opts *opts; + void *tmp = talloc_new(NULL); + opts = mp_get_config_group(tmp, ctx->global, &opengl_conf); + + // Version too high + if (opts->restrict_version && version >= opts->restrict_version) { + ret = false; + goto done; } - return -2; -} -int mpgl_validate_backend_opt(struct mp_log *log, const struct m_option *opt, - struct bstr name, struct bstr param) -{ - if (bstr_equals0(param, "help")) { - mp_info(log, "OpenGL windowing backends:\n"); - mp_info(log, " auto (autodetect)\n"); - for (int n = 0; n < MP_ARRAY_SIZE(backends); n++) - mp_info(log, " %s\n", backends[n]->name); - return M_OPT_EXIT; + switch (opts->gles_mode) { + case GLES_YES: ret = es; goto done; + case GLES_NO: ret = !es; goto done; + case GLES_AUTO: ret = true; goto done; + default: abort(); } - char s[20]; - snprintf(s, sizeof(s), "%.*s", BSTR_P(param)); - return mpgl_find_backend(s) >= -1 ? 1 : M_OPT_INVALID; + +done: + talloc_free(tmp); + return ret; } -static void *get_native_display(void *pctx, const char *name) +static void *get_native_display(void *priv, const char *name) { - MPGLContext *ctx = pctx; - if (!ctx->native_display_type || !name) + struct priv *p = priv; + if (!p->params.native_display_type || !name) + return NULL; + if (strcmp(p->params.native_display_type, name) != 0) return NULL; - return strcmp(ctx->native_display_type, name) == 0 ? ctx->native_display : NULL; + + return p->params.native_display; } -static MPGLContext *init_backend(struct vo *vo, const struct mpgl_driver *driver, - bool probing, int vo_flags) +void ra_gl_ctx_uninit(struct ra_ctx *ctx) { - MPGLContext *ctx = talloc_ptrtype(NULL, ctx); - *ctx = (MPGLContext) { - .gl = talloc_zero(ctx, GL), - .vo = vo, - .global = vo->global, - .driver = driver, - .log = vo->log, + if (ctx->ra) + ctx->ra->fns->destroy(ctx->ra); + if (ctx->swapchain) { + talloc_free(ctx->swapchain); + ctx->swapchain = NULL; + } +} + +static const struct ra_swapchain_fns ra_gl_swapchain_fns; + +bool ra_gl_ctx_init(struct ra_ctx *ctx, GL *gl, struct ra_gl_ctx_params params) +{ + struct ra_swapchain *sw = ctx->swapchain = talloc_ptrtype(NULL, sw); + *sw = (struct ra_swapchain) { + .ctx = ctx, + .flip_v = !params.flipped, // OpenGL framebuffers are normally inverted }; - if (probing) - vo_flags |= VOFLAG_PROBING; - bool old_probing = vo->probing; - vo->probing = probing; // hack; kill it once backends are separate - MP_VERBOSE(vo, "Initializing OpenGL backend '%s'\n", ctx->driver->name); - ctx->priv = talloc_zero_size(ctx, ctx->driver->priv_size); - if (ctx->driver->init(ctx, vo_flags) < 0) { - vo->probing = old_probing; - talloc_free(ctx); - return NULL; + + struct priv *p = sw->priv = talloc_ptrtype(sw, p); + *p = (struct priv) { + .gl = gl, + .log = ctx->log, + .params = params, + .opts = mp_get_config_group(p, ctx->global, &opengl_conf), + .fns = ra_gl_swapchain_fns, + }; + + sw->fns = &p->fns; + + const struct ra_swapchain_fns *ext = p->params.external_swapchain; + if (ext) { + if (ext->color_depth) + p->fns.color_depth = ext->color_depth; + if (ext->screenshot) + p->fns.screenshot = ext->screenshot; + if (ext->start_frame) + p->fns.start_frame = ext->start_frame; + if (ext->submit_frame) + p->fns.submit_frame = ext->submit_frame; + if (ext->swap_buffers) + p->fns.swap_buffers = ext->swap_buffers; } - vo->probing = old_probing; - if (!ctx->gl->version && !ctx->gl->es) - goto cleanup; + if (!gl->version && !gl->es) + return false; - if (probing && ctx->gl->es && (vo_flags & VOFLAG_NO_GLES)) { - MP_VERBOSE(ctx->vo, "Skipping GLES backend.\n"); - goto cleanup; + if (gl->mpgl_caps & MPGL_CAP_SW) { + MP_WARN(p, "Suspected software renderer or indirect context.\n"); + if (ctx->opts.probing && !ctx->opts.allow_sw) + return false; } - if (ctx->gl->mpgl_caps & MPGL_CAP_SW) { - MP_WARN(ctx->vo, "Suspected software renderer or indirect context.\n"); - if (vo->probing && !(vo_flags & VOFLAG_SW)) - goto cleanup; + gl->debug_context = ctx->opts.debug; + gl->get_native_display_ctx = p; + gl->get_native_display = get_native_display; + + if (gl->SwapInterval) { + gl->SwapInterval(p->opts->swapinterval); + } else { + MP_VERBOSE(p, "GL_*_swap_control extension missing.\n"); } - ctx->gl->debug_context = !!(vo_flags & VOFLAG_GL_DEBUG); + ctx->ra = ra_create_gl(p->gl, ctx->log); + return !!ctx->ra; +} - ctx->gl->get_native_display_ctx = ctx; - ctx->gl->get_native_display = get_native_display; +void ra_gl_ctx_resize(struct ra_swapchain *sw, int w, int h, int fbo) +{ + struct priv *p = sw->priv; + if (p->main_fb == fbo && p->wrapped_fb && p->wrapped_fb->params.w == w + && p->wrapped_fb->params.h == h) + return; - return ctx; + if (p->wrapped_fb) + ra_tex_free(sw->ctx->ra, &p->wrapped_fb); -cleanup: - mpgl_uninit(ctx); - return NULL; + p->main_fb = fbo; + p->wrapped_fb = ra_create_wrapped_fb(sw->ctx->ra, fbo, w, h); } -// Create a VO window and create a GL context on it. -// vo_flags: passed to the backend's create window function -MPGLContext *mpgl_init(struct vo *vo, const char *backend_name, int vo_flags) +int ra_gl_ctx_color_depth(struct ra_swapchain *sw) { - MPGLContext *ctx = NULL; - int index = mpgl_find_backend(backend_name); - if (index == -1) { - for (int n = 0; n < MP_ARRAY_SIZE(backends); n++) { - ctx = init_backend(vo, backends[n], true, vo_flags); - if (ctx) - break; - } - // VO forced, but no backend is ok => force the first that works at all - if (!ctx && !vo->probing) { - for (int n = 0; n < MP_ARRAY_SIZE(backends); n++) { - ctx = init_backend(vo, backends[n], false, vo_flags); - if (ctx) - break; - } - } - } else if (index >= 0) { - ctx = init_backend(vo, backends[index], false, vo_flags); - } - return ctx; + struct priv *p = sw->priv; + GL *gl = p->gl; + + if (!p->wrapped_fb) + return 0; + + if ((gl->es < 300 && !gl->version) || !(gl->mpgl_caps & MPGL_CAP_FB)) + return 0; + + gl->BindFramebuffer(GL_FRAMEBUFFER, p->main_fb); + + GLenum obj = gl->version ? GL_BACK_LEFT : GL_BACK; + if (p->main_fb) + obj = GL_COLOR_ATTACHMENT0; + + GLint depth_g = 0; + + gl->GetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, obj, + GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE, &depth_g); + + gl->BindFramebuffer(GL_FRAMEBUFFER, 0); + + return depth_g; } -int mpgl_reconfig_window(struct MPGLContext *ctx) +struct mp_image *ra_gl_ctx_screenshot(struct ra_swapchain *sw) { - return ctx->driver->reconfig(ctx); + struct priv *p = sw->priv; + + assert(p->wrapped_fb); + struct mp_image *screen = gl_read_fbo_contents(p->gl, p->main_fb, + p->wrapped_fb->params.w, + p->wrapped_fb->params.h); + + // OpenGL FB is also read in flipped order, so we need to flip when the + // rendering is *not* flipped, which in our case is whenever + // p->params.flipped is true. I hope that made sense + if (p->params.flipped) + mp_image_vflip(screen); + + return screen; } -int mpgl_control(struct MPGLContext *ctx, int *events, int request, void *arg) +struct ra_tex *ra_gl_ctx_start_frame(struct ra_swapchain *sw) { - return ctx->driver->control(ctx, events, request, arg); + struct priv *p = sw->priv; + + return p->wrapped_fb; } -void mpgl_start_frame(struct MPGLContext *ctx) +bool ra_gl_ctx_submit_frame(struct ra_swapchain *sw, const struct vo_frame *frame) { - if (ctx->driver->start_frame) - ctx->driver->start_frame(ctx); + struct priv *p = sw->priv; + GL *gl = p->gl; + + if (p->opts->use_glfinish) + gl->Finish(); + + if (gl->FenceSync && !p->params.external_swapchain) { + GLsync fence = gl->FenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + if (fence) + MP_TARRAY_APPEND(p, p->vsync_fences, p->num_vsync_fences, fence); + } + + switch (p->opts->early_flush) { + case FLUSH_AUTO: + if (frame->display_synced) + break; + // fall through + case FLUSH_YES: + gl->Flush(); + } + + return true; } -void mpgl_swap_buffers(struct MPGLContext *ctx) +static void check_pattern(struct priv *p, int item) { - ctx->driver->swap_buffers(ctx); + int expected = p->opts->vsync_pattern[p->last_pattern]; + if (item == expected) { + p->last_pattern++; + if (p->last_pattern >= 2) + p->last_pattern = 0; + p->matches++; + } else { + p->mismatches++; + MP_WARN(p, "wrong pattern, expected %d got %d (hit: %d, mis: %d)\n", + expected, item, p->matches, p->mismatches); + } } -void mpgl_uninit(MPGLContext *ctx) +void ra_gl_ctx_swap_buffers(struct ra_swapchain *sw) { - if (ctx) - ctx->driver->uninit(ctx); - talloc_free(ctx); + struct priv *p = sw->priv; + GL *gl = p->gl; + + p->params.swap_buffers(sw->ctx); + p->frames_rendered++; + + if (p->frames_rendered > 5 && !sw->ctx->opts.debug) + ra_gl_set_debug(sw->ctx->ra, false); + + if ((p->opts->waitvsync || p->opts->vsync_pattern[0]) + && gl->GetVideoSync) + { + unsigned int n1 = 0, n2 = 0; + gl->GetVideoSync(&n1); + if (p->opts->waitvsync) + gl->WaitVideoSync(2, (n1 + 1) % 2, &n2); + int step = n1 - p->prev_sgi_sync_count; + p->prev_sgi_sync_count = n1; + MP_DBG(p, "Flip counts: %u->%u, step=%d\n", n1, n2, step); + if (p->opts->vsync_pattern[0]) + check_pattern(p, step); + } + + while (p->num_vsync_fences >= sw->ctx->opts.swapchain_depth) { + gl->ClientWaitSync(p->vsync_fences[0], GL_SYNC_FLUSH_COMMANDS_BIT, 1e9); + gl->DeleteSync(p->vsync_fences[0]); + MP_TARRAY_REMOVE_AT(p->vsync_fences, p->num_vsync_fences, 0); + } } + +static const struct ra_swapchain_fns ra_gl_swapchain_fns = { + .color_depth = ra_gl_ctx_color_depth, + .screenshot = ra_gl_ctx_screenshot, + .start_frame = ra_gl_ctx_start_frame, + .submit_frame = ra_gl_ctx_submit_frame, + .swap_buffers = ra_gl_ctx_swap_buffers, +}; diff --git a/video/out/opengl/context.h b/video/out/opengl/context.h index 229c5ef54f..bdf426b9b4 100644 --- a/video/out/opengl/context.h +++ b/video/out/opengl/context.h @@ -1,116 +1,56 @@ -/* - * common OpenGL routines - * - * copyleft (C) 2005-2010 Reimar Döffinger <Reimar.Doeffinger@gmx.de> - * Special thanks go to the xine team and Matthias Hopf, whose video_out_opengl.c - * gave me lots of good ideas. - * - * This file is part of mpv. - * - * mpv 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.1 of the License, or (at your option) any later version. - * - * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef MP_GL_CONTEXT_H_ -#define MP_GL_CONTEXT_H_ +#pragma once +#include "common/global.h" +#include "video/out/gpu/context.h" #include "common.h" -enum { - VOFLAG_GLES = 1 << 0, // Hint to create a GLES context - VOFLAG_NO_GLES = 1 << 1, // Hint to create a desktop GL context - VOFLAG_GL_DEBUG = 1 << 2, // Hint to request debug OpenGL context - VOFLAG_ALPHA = 1 << 3, // Hint to request alpha framebuffer - VOFLAG_SW = 1 << 4, // Hint to accept a software GL renderer - VOFLAG_PROBING = 1 << 6, // The backend is being auto-probed. - VOFLAG_GLES2 = 1 << 7, // Hint for GLESv2 (needs VOFLAG_GLES) -}; - extern const int mpgl_preferred_gl_versions[]; -struct MPGLContext; - -// A windowing backend (like X11, win32, ...), which provides OpenGL rendering. -struct mpgl_driver { - const char *name; - - // Size of the struct allocated for MPGLContext.priv - int priv_size; - - // Init the GL context and possibly the underlying VO backend. - // The created context should be compatible to GL 3.2 core profile, but - // some other GL versions are supported as well (e.g. GL 2.1 or GLES 2). - // Return 0 on success, negative value (-1) on error. - int (*init)(struct MPGLContext *ctx, int vo_flags); - - // Resize the window, or create a new window if there isn't one yet. - // Currently, there is an unfortunate interaction with ctx->vo, and - // display size etc. are determined by it. - // Return 0 on success, negative value (-1) on error. - int (*reconfig)(struct MPGLContext *ctx); - - // Called when rendering starts. The backend can map or resize the - // framebuffer, or update GL.main_fb. swap_buffers() ends the frame. - // Optional. - void (*start_frame)(struct MPGLContext *ctx); - - // Present the frame. - void (*swap_buffers)(struct MPGLContext *ctx); - - // This behaves exactly like vo_driver.control(). - int (*control)(struct MPGLContext *ctx, int *events, int request, void *arg); - - // These behave exactly like vo_driver.wakeup/wait_events. They are - // optional. - void (*wakeup)(struct MPGLContext *ctx); - void (*wait_events)(struct MPGLContext *ctx, int64_t until_time_us); - - // Destroy the GL context and possibly the underlying VO backend. - void (*uninit)(struct MPGLContext *ctx); -}; - -typedef struct MPGLContext { - GL *gl; - struct vo *vo; - const struct mpgl_driver *driver; - struct mpv_global *global; - struct mp_log *log; - - // For hwdec_vaegl.c. +// Returns whether or not a candidate GL version should be accepted or not +// (based on the --opengl opts). Implementations may call this before +// ra_gl_ctx_init if they wish to probe for multiple possible GL versions. +bool ra_gl_ctx_test_version(struct ra_ctx *ctx, int version, bool es); + +// These are a set of helpers for ra_ctx providers based on ra_gl. +// The init function also initializes ctx->ra and ctx->swapchain, so the user +// doesn't have to do this manually. (Similarly, the uninit function will +// clean them up) + +struct ra_gl_ctx_params { + // Set to the platform-specific function to swap buffers, like + // glXSwapBuffers, eglSwapBuffers etc. This will be called by + // ra_gl_ctx_swap_buffers. Required unless you either never call that + // function or if you override it yourself. + void (*swap_buffers)(struct ra_ctx *ctx); + + // Set to false if the implementation follows normal GL semantics, which is + // upside down. Set to true if it does *not*, i.e. if rendering is right + // side up + bool flipped; + + // If this is set to non-NULL, then the ra_gl_ctx will consider the GL + // implementation to be using an external swapchain, which disables the + // software simulation of --swapchain-depth. Any functions defined by this + // ra_swapchain_fns structs will entirely replace the equivalent ra_gl_ctx + // functions in the resulting ra_swapchain. + const struct ra_swapchain_fns *external_swapchain; + + // For |