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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <SDL.h>
+
+#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
