diff --git a/video/out/opengl/context_sdl.c b/video/out/opengl/context_sdl.c
new file mode 100644
index 0000000000..bfe46e03bf
--- /dev/null
+++ b/video/out/opengl/context_sdl.c
@@ -0,0 +1,244 @@
+/*
+ * 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 .
+ */
+
+#include
+
+#include "common/common.h"
+#include "common/msg.h"
+#include "video/mp_image.h"
+
+#include "context.h"
+#include "ra_gl.h"
+
+struct priv {
+ GL gl;
+ SDL_Window *window;
+ SDL_GLContext glcontext;
+ int w, h;
+};
+
+static void sdl_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ ra_gl_ctx_uninit(ctx);
+
+ if (p->glcontext) {
+ SDL_GL_DeleteContext(p->glcontext);
+ p->glcontext = NULL;
+ }
+
+ if (p->window) {
+ SDL_DestroyWindow(p->window);
+ p->window = NULL;
+ }
+
+ SDL_Quit();
+}
+
+static void sdl_swap_buffers(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ SDL_GL_SwapWindow(p->window);
+}
+
+static bool sdl_gl_create_window(struct ra_ctx *ctx, int gl_version)
+{
+ struct priv *p = ctx->priv;
+ struct vo *vo = ctx->vo;
+
+ // For maximum compatibility, don't request specific GL version
+ // Let SDL/system provide whatever OpenGL is available
+ // This is especially important for macOS 10.6 which doesn't support Core profile
+
+ // Reset all GL attributes to defaults first
+ SDL_GL_ResetAttributes();
+
+ SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
+ SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
+ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+
+ // Create window
+ int w = vo->dwidth;
+ int h = vo->dheight;
+
+ Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
+
+ // Use vo_get_window_title if available, otherwise use "mpv"
+ const char *title = "mpv";
+
+ p->window = SDL_CreateWindow(title,
+ SDL_WINDOWPOS_CENTERED,
+ SDL_WINDOWPOS_CENTERED,
+ w, h, flags);
+ if (!p->window) {
+ MP_FATAL(vo, "Failed to create SDL window: %s\n", SDL_GetError());
+ return false;
+ }
+
+ p->w = w;
+ p->h = h;
+
+ // Create OpenGL context
+ p->glcontext = SDL_GL_CreateContext(p->window);
+ if (!p->glcontext) {
+ MP_FATAL(vo, "Failed to create OpenGL context: %s\n", SDL_GetError());
+ SDL_DestroyWindow(p->window);
+ p->window = NULL;
+ return false;
+ }
+
+ // Make context current
+ if (SDL_GL_MakeCurrent(p->window, p->glcontext) < 0) {
+ MP_FATAL(vo, "Failed to make OpenGL context current: %s\n", SDL_GetError());
+ return false;
+ }
+
+ // Get actual GL version
+ int actual_major = 0, actual_minor = 0;
+ SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &actual_major);
+ SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &actual_minor);
+
+ MP_VERBOSE(vo, "OpenGL context created: %d.%d (auto-detected)\n",
+ actual_major, actual_minor);
+
+ return true;
+}
+
+static bool sdl_reconfig(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ struct vo *vo = ctx->vo;
+
+ int w = vo->dwidth;
+ int h = vo->dheight;
+
+ if (p->window) {
+ SDL_SetWindowSize(p->window, w, h);
+ p->w = w;
+ p->h = h;
+ }
+
+ ra_gl_ctx_resize(ctx->swapchain, w, h, 0);
+ return true;
+}
+
+static int sdl_control(struct ra_ctx *ctx, int *events, int request, void *arg)
+{
+ struct priv *p = ctx->priv;
+
+ switch (request) {
+ case VOCTRL_CHECK_EVENTS: {
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ case SDL_QUIT:
+ // Signal window close
+ *events |= VO_EVENT_EXPOSE;
+ break;
+ case SDL_WINDOWEVENT:
+ if (event.window.event == SDL_WINDOWEVENT_RESIZED ||
+ event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
+ p->w = event.window.data1;
+ p->h = event.window.data2;
+ *events |= VO_EVENT_RESIZE;
+ } else if (event.window.event == SDL_WINDOWEVENT_EXPOSED) {
+ *events |= VO_EVENT_EXPOSE;
+ }
+ break;
+ }
+ }
+ return VO_TRUE;
+ }
+ case VOCTRL_SET_CURSOR_VISIBILITY:
+ if (p->window)
+ SDL_ShowCursor(*(bool *)arg ? SDL_ENABLE : SDL_DISABLE);
+ return VO_TRUE;
+ case VOCTRL_UPDATE_WINDOW_TITLE:
+ if (p->window)
+ SDL_SetWindowTitle(p->window, (const char *)arg);
+ return VO_TRUE;
+ }
+
+ return VO_NOTIMPL;
+}
+
+static void sdl_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info)
+{
+ // SDL doesn't provide detailed vsync info
+ info->vsync_duration = 0;
+ info->skipped_vsyncs = 0;
+ info->last_queue_display_time = 0;
+}
+
+static bool sdl_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ struct vo *vo = ctx->vo;
+ GL *gl = &p->gl;
+
+ if (SDL_Init(SDL_INIT_VIDEO) != 0) {
+ MP_FATAL(vo, "SDL_Init failed: %s\n", SDL_GetError());
+ return false;
+ }
+
+ // Try to create OpenGL context (auto-detect version)
+ if (!sdl_gl_create_window(ctx, 0)) {
+ MP_FATAL(vo, "Failed to create SDL window with OpenGL context\n");
+ SDL_Quit();
+ return false;
+ }
+
+ mpgl_load_functions(gl, (void *)SDL_GL_GetProcAddress, NULL, vo->log);
+
+ // Set vsync
+ SDL_GL_SetSwapInterval(1);
+
+ struct ra_ctx_params params = {
+ .swap_buffers = sdl_swap_buffers,
+ .get_vsync = sdl_get_vsync,
+ };
+
+ if (!ra_gl_ctx_init(ctx, gl, params)) {
+ MP_FATAL(vo, "Failed to initialize rendering abstraction\n");
+ if (p->glcontext) {
+ SDL_GL_DeleteContext(p->glcontext);
+ p->glcontext = NULL;
+ }
+ if (p->window) {
+ SDL_DestroyWindow(p->window);
+ p->window = NULL;
+ }
+ SDL_Quit();
+ return false;
+ }
+
+ return true;
+}
+
+const struct ra_ctx_fns ra_ctx_sdl = {
+ .type = "opengl",
+ .name = "sdl",
+ .description = "SDL2 OpenGL",
+ .reconfig = sdl_reconfig,
+ .control = sdl_control,
+ .init = sdl_init,
+ .uninit = sdl_uninit,
+};
diff --git a/meson.build b/meson.build
index 5ffe559768..a9ecac43fe 100644
--- a/meson.build
+++ b/meson.build
@@ -999,8 +999,10 @@ sdl2_video = get_option('sdl2-video').require(
)
features += {'sdl2-video': sdl2_video.allowed()}
if features['sdl2-video']
- sources += files('video/out/vo_sdl.c')
+ sources += files('video/out/vo_sdl.c',
+ 'video/out/opengl/context_sdl.c')
dependencies += sdl2
+ features += {'gl': true}
endif
shaderc = dependency('shaderc', required:
diff --git a/video/out/gpu/context.c b/video/out/gpu/context.c
index 60c0641a70..a4d64ba155 100644
--- a/video/out/gpu/context.c
+++ b/video/out/gpu/context.c
@@ -42,6 +42,7 @@ extern const struct ra_ctx_fns ra_ctx_wgl;
extern const struct ra_ctx_fns ra_ctx_angle;
extern const struct ra_ctx_fns ra_ctx_dxgl;
extern const struct ra_ctx_fns ra_ctx_android;
+extern const struct ra_ctx_fns ra_ctx_sdl;
/* Vulkan */
extern const struct ra_ctx_fns ra_ctx_vulkan_wayland;
@@ -120,6 +121,9 @@ static const struct ra_ctx_fns *const contexts[] = {
#if HAVE_GL_X11
&ra_ctx_glx,
#endif
+#if HAVE_SDL2_VIDEO
+ &ra_ctx_sdl,
+#endif
#if HAVE_EGL_DRM
&ra_ctx_drm_egl,
#endif