summaryrefslogtreecommitdiffstats
path: root/video/out/vo_sdl.c
diff options
context:
space:
mode:
Diffstat (limited to 'video/out/vo_sdl.c')
-rw-r--r--video/out/vo_sdl.c992
1 files changed, 992 insertions, 0 deletions
diff --git a/video/out/vo_sdl.c b/video/out/vo_sdl.c
new file mode 100644
index 0000000..5f4c027
--- /dev/null
+++ b/video/out/vo_sdl.c
@@ -0,0 +1,992 @@
+/*
+ * video output driver for SDL 2.0+
+ *
+ * Copyright (C) 2012 Rudolf Polzer <divVerent@xonotic.org>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <SDL.h>
+
+#include "input/input.h"
+#include "input/keycodes.h"
+#include "input/input.h"
+#include "common/msg.h"
+#include "options/m_config.h"
+#include "options/options.h"
+
+#include "osdep/timer.h"
+
+#include "sub/osd.h"
+
+#include "video/mp_image.h"
+
+#include "win_state.h"
+#include "vo.h"
+
+struct formatmap_entry {
+ Uint32 sdl;
+ unsigned int mpv;
+ int is_rgba;
+};
+const struct formatmap_entry formats[] = {
+ {SDL_PIXELFORMAT_YV12, IMGFMT_420P, 0},
+ {SDL_PIXELFORMAT_IYUV, IMGFMT_420P, 0},
+ {SDL_PIXELFORMAT_UYVY, IMGFMT_UYVY, 0},
+ //{SDL_PIXELFORMAT_YVYU, IMGFMT_YVYU, 0},
+#if BYTE_ORDER == BIG_ENDIAN
+ {SDL_PIXELFORMAT_RGB888, IMGFMT_0RGB, 0}, // RGB888 means XRGB8888
+ {SDL_PIXELFORMAT_RGBX8888, IMGFMT_RGB0, 0}, // has no alpha -> bad for OSD
+ {SDL_PIXELFORMAT_BGR888, IMGFMT_0BGR, 0}, // BGR888 means XBGR8888
+ {SDL_PIXELFORMAT_BGRX8888, IMGFMT_BGR0, 0}, // has no alpha -> bad for OSD
+ {SDL_PIXELFORMAT_ARGB8888, IMGFMT_ARGB, 1}, // matches SUBBITMAP_BGRA
+ {SDL_PIXELFORMAT_RGBA8888, IMGFMT_RGBA, 1},
+ {SDL_PIXELFORMAT_ABGR8888, IMGFMT_ABGR, 1},
+ {SDL_PIXELFORMAT_BGRA8888, IMGFMT_BGRA, 1},
+#else
+ {SDL_PIXELFORMAT_RGB888, IMGFMT_BGR0, 0}, // RGB888 means XRGB8888
+ {SDL_PIXELFORMAT_RGBX8888, IMGFMT_0BGR, 0}, // has no alpha -> bad for OSD
+ {SDL_PIXELFORMAT_BGR888, IMGFMT_RGB0, 0}, // BGR888 means XBGR8888
+ {SDL_PIXELFORMAT_BGRX8888, IMGFMT_0RGB, 0}, // has no alpha -> bad for OSD
+ {SDL_PIXELFORMAT_ARGB8888, IMGFMT_BGRA, 1}, // matches SUBBITMAP_BGRA
+ {SDL_PIXELFORMAT_RGBA8888, IMGFMT_ABGR, 1},
+ {SDL_PIXELFORMAT_ABGR8888, IMGFMT_RGBA, 1},
+ {SDL_PIXELFORMAT_BGRA8888, IMGFMT_ARGB, 1},
+#endif
+ {SDL_PIXELFORMAT_RGB24, IMGFMT_RGB24, 0},
+ {SDL_PIXELFORMAT_BGR24, IMGFMT_BGR24, 0},
+ {SDL_PIXELFORMAT_RGB565, IMGFMT_RGB565, 0},
+};
+
+struct keymap_entry {
+ SDL_Keycode sdl;
+ int mpv;
+};
+const struct keymap_entry keys[] = {
+ {SDLK_RETURN, MP_KEY_ENTER},
+ {SDLK_ESCAPE, MP_KEY_ESC},
+ {SDLK_BACKSPACE, MP_KEY_BACKSPACE},
+ {SDLK_TAB, MP_KEY_TAB},
+ {SDLK_PRINTSCREEN, MP_KEY_PRINT},
+ {SDLK_PAUSE, MP_KEY_PAUSE},
+ {SDLK_INSERT, MP_KEY_INSERT},
+ {SDLK_HOME, MP_KEY_HOME},
+ {SDLK_PAGEUP, MP_KEY_PAGE_UP},
+ {SDLK_DELETE, MP_KEY_DELETE},
+ {SDLK_END, MP_KEY_END},
+ {SDLK_PAGEDOWN, MP_KEY_PAGE_DOWN},
+ {SDLK_RIGHT, MP_KEY_RIGHT},
+ {SDLK_LEFT, MP_KEY_LEFT},
+ {SDLK_DOWN, MP_KEY_DOWN},
+ {SDLK_UP, MP_KEY_UP},
+ {SDLK_KP_ENTER, MP_KEY_KPENTER},
+ {SDLK_KP_1, MP_KEY_KP1},
+ {SDLK_KP_2, MP_KEY_KP2},
+ {SDLK_KP_3, MP_KEY_KP3},
+ {SDLK_KP_4, MP_KEY_KP4},
+ {SDLK_KP_5, MP_KEY_KP5},
+ {SDLK_KP_6, MP_KEY_KP6},
+ {SDLK_KP_7, MP_KEY_KP7},
+ {SDLK_KP_8, MP_KEY_KP8},
+ {SDLK_KP_9, MP_KEY_KP9},
+ {SDLK_KP_0, MP_KEY_KP0},
+ {SDLK_KP_PERIOD, MP_KEY_KPDEC},
+ {SDLK_POWER, MP_KEY_POWER},
+ {SDLK_MENU, MP_KEY_MENU},
+ {SDLK_STOP, MP_KEY_STOP},
+ {SDLK_MUTE, MP_KEY_MUTE},
+ {SDLK_VOLUMEUP, MP_KEY_VOLUME_UP},
+ {SDLK_VOLUMEDOWN, MP_KEY_VOLUME_DOWN},
+ {SDLK_KP_COMMA, MP_KEY_KPDEC},
+ {SDLK_AUDIONEXT, MP_KEY_NEXT},
+ {SDLK_AUDIOPREV, MP_KEY_PREV},
+ {SDLK_AUDIOSTOP, MP_KEY_STOP},
+ {SDLK_AUDIOPLAY, MP_KEY_PLAY},
+ {SDLK_AUDIOMUTE, MP_KEY_MUTE},
+ {SDLK_F1, MP_KEY_F + 1},
+ {SDLK_F2, MP_KEY_F + 2},
+ {SDLK_F3, MP_KEY_F + 3},
+ {SDLK_F4, MP_KEY_F + 4},
+ {SDLK_F5, MP_KEY_F + 5},
+ {SDLK_F6, MP_KEY_F + 6},
+ {SDLK_F7, MP_KEY_F + 7},
+ {SDLK_F8, MP_KEY_F + 8},
+ {SDLK_F9, MP_KEY_F + 9},
+ {SDLK_F10, MP_KEY_F + 10},
+ {SDLK_F11, MP_KEY_F + 11},
+ {SDLK_F12, MP_KEY_F + 12},
+ {SDLK_F13, MP_KEY_F + 13},
+ {SDLK_F14, MP_KEY_F + 14},
+ {SDLK_F15, MP_KEY_F + 15},
+ {SDLK_F16, MP_KEY_F + 16},
+ {SDLK_F17, MP_KEY_F + 17},
+ {SDLK_F18, MP_KEY_F + 18},
+ {SDLK_F19, MP_KEY_F + 19},
+ {SDLK_F20, MP_KEY_F + 20},
+ {SDLK_F21, MP_KEY_F + 21},
+ {SDLK_F22, MP_KEY_F + 22},
+ {SDLK_F23, MP_KEY_F + 23},
+ {SDLK_F24, MP_KEY_F + 24}
+};
+
+struct mousemap_entry {
+ Uint8 sdl;
+ int mpv;
+};
+const struct mousemap_entry mousebtns[] = {
+ {SDL_BUTTON_LEFT, MP_MBTN_LEFT},
+ {SDL_BUTTON_MIDDLE, MP_MBTN_MID},
+ {SDL_BUTTON_RIGHT, MP_MBTN_RIGHT},
+ {SDL_BUTTON_X1, MP_MBTN_BACK},
+ {SDL_BUTTON_X2, MP_MBTN_FORWARD},
+};
+
+struct priv {
+ SDL_Window *window;
+ SDL_Renderer *renderer;
+ int renderer_index;
+ SDL_RendererInfo renderer_info;
+ SDL_Texture *tex;
+ int tex_swapped;
+ struct mp_image_params params;
+ struct mp_rect src_rect;
+ struct mp_rect dst_rect;
+ struct mp_osd_res osd_res;
+ struct formatmap_entry osd_format;
+ struct osd_bitmap_surface {
+ int change_id;
+ struct osd_target {
+ SDL_Rect source;
+ SDL_Rect dest;
+ SDL_Texture *tex;
+ SDL_Texture *tex2;
+ } *targets;
+ int num_targets;
+ int targets_size;
+ } osd_surfaces[MAX_OSD_PARTS];
+ double osd_pts;
+ Uint32 wakeup_event;
+ bool screensaver_enabled;
+ struct m_config_cache *opts_cache;
+
+ // options
+ bool allow_sw;
+ bool switch_mode;
+ bool vsync;
+};
+
+static bool lock_texture(struct vo *vo, struct mp_image *texmpi)
+{
+ struct priv *vc = vo->priv;
+ *texmpi = (struct mp_image){0};
+ mp_image_set_size(texmpi, vc->params.w, vc->params.h);
+ mp_image_setfmt(texmpi, vc->params.imgfmt);
+ switch (texmpi->num_planes) {
+ case 1:
+ case 3:
+ break;
+ default:
+ MP_ERR(vo, "Invalid plane count\n");
+ return false;
+ }
+ void *pixels;
+ int pitch;
+ if (SDL_LockTexture(vc->tex, NULL, &pixels, &pitch)) {
+ MP_ERR(vo, "SDL_LockTexture failed\n");
+ return false;
+ }
+ texmpi->planes[0] = pixels;
+ texmpi->stride[0] = pitch;
+ if (texmpi->num_planes == 3) {
+ if (vc->tex_swapped) {
+ texmpi->planes[2] =
+ ((Uint8 *) texmpi->planes[0] + texmpi->h * pitch);
+ texmpi->stride[2] = pitch / 2;
+ texmpi->planes[1] =
+ ((Uint8 *) texmpi->planes[2] + (texmpi->h * pitch) / 4);
+ texmpi->stride[1] = pitch / 2;
+ } else {
+ texmpi->planes[1] =
+ ((Uint8 *) texmpi->planes[0] + texmpi->h * pitch);
+ texmpi->stride[1] = pitch / 2;
+ texmpi->planes[2] =
+ ((Uint8 *) texmpi->planes[1] + (texmpi->h * pitch) / 4);
+ texmpi->stride[2] = pitch / 2;
+ }
+ }
+ return true;
+}
+
+static bool is_good_renderer(SDL_RendererInfo *ri,
+ const char *driver_name_wanted, bool allow_sw,
+ struct formatmap_entry *osd_format)
+{
+ if (driver_name_wanted && driver_name_wanted[0])
+ if (strcmp(driver_name_wanted, ri->name))
+ return false;
+
+ if (!allow_sw &&
+ !(ri->flags & SDL_RENDERER_ACCELERATED))
+ return false;
+
+ int i, j;
+ for (i = 0; i < ri->num_texture_formats; ++i)
+ for (j = 0; j < sizeof(formats) / sizeof(formats[0]); ++j)
+ if (ri->texture_formats[i] == formats[j].sdl)
+ if (formats[j].is_rgba) {
+ if (osd_format)
+ *osd_format = formats[j];
+ return true;
+ }
+
+ return false;
+}
+
+static void destroy_renderer(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+
+ // free ALL the textures
+ if (vc->tex) {
+ SDL_DestroyTexture(vc->tex);
+ vc->tex = NULL;
+ }
+
+ int i, j;
+ for (i = 0; i < MAX_OSD_PARTS; ++i) {
+ for (j = 0; j < vc->osd_surfaces[i].targets_size; ++j) {
+ if (vc->osd_surfaces[i].targets[j].tex) {
+ SDL_DestroyTexture(vc->osd_surfaces[i].targets[j].tex);
+ vc->osd_surfaces[i].targets[j].tex = NULL;
+ }
+ if (vc->osd_surfaces[i].targets[j].tex2) {
+ SDL_DestroyTexture(vc->osd_surfaces[i].targets[j].tex2);
+ vc->osd_surfaces[i].targets[j].tex2 = NULL;
+ }
+ }
+ }
+
+ if (vc->renderer) {
+ SDL_DestroyRenderer(vc->renderer);
+ vc->renderer = NULL;
+ }
+}
+
+static bool try_create_renderer(struct vo *vo, int i, const char *driver)
+{
+ struct priv *vc = vo->priv;
+
+ // first probe
+ SDL_RendererInfo ri;
+ if (SDL_GetRenderDriverInfo(i, &ri))
+ return false;
+ if (!is_good_renderer(&ri, driver, vc->allow_sw, NULL))
+ return false;
+
+ vc->renderer = SDL_CreateRenderer(vc->window, i, 0);
+ if (!vc->renderer) {
+ MP_ERR(vo, "SDL_CreateRenderer failed\n");
+ return false;
+ }
+
+ if (SDL_GetRendererInfo(vc->renderer, &vc->renderer_info)) {
+ MP_ERR(vo, "SDL_GetRendererInfo failed\n");
+ destroy_renderer(vo);
+ return false;
+ }
+
+ if (!is_good_renderer(&vc->renderer_info, NULL, vc->allow_sw,
+ &vc->osd_format)) {
+ MP_ERR(vo, "Renderer '%s' does not fulfill "
+ "requirements on this system\n",
+ vc->renderer_info.name);
+ destroy_renderer(vo);
+ return false;
+ }
+
+ if (vc->renderer_index != i) {
+ MP_INFO(vo, "Using %s\n", vc->renderer_info.name);
+ vc->renderer_index = i;
+ }
+
+ return true;
+}
+
+static int init_renderer(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+
+ int n = SDL_GetNumRenderDrivers();
+ int i;
+
+ if (vc->renderer_index >= 0)
+ if (try_create_renderer(vo, vc->renderer_index, NULL))
+ return 0;
+
+ for (i = 0; i < n; ++i)
+ if (try_create_renderer(vo, i, SDL_GetHint(SDL_HINT_RENDER_DRIVER)))
+ return 0;
+
+ for (i = 0; i < n; ++i)
+ if (try_create_renderer(vo, i, NULL))
+ return 0;
+
+ MP_ERR(vo, "No supported renderer\n");
+ return -1;
+}
+
+static void resize(struct vo *vo, int w, int h)
+{
+ struct priv *vc = vo->priv;
+ vo->dwidth = w;
+ vo->dheight = h;
+ vo_get_src_dst_rects(vo, &vc->src_rect, &vc->dst_rect,
+ &vc->osd_res);
+ SDL_RenderSetLogicalSize(vc->renderer, w, h);
+ vo->want_redraw = true;
+ vo_wakeup(vo);
+}
+
+static void force_resize(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ int w, h;
+ SDL_GetWindowSize(vc->window, &w, &h);
+ resize(vo, w, h);
+}
+
+static void check_resize(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ int w, h;
+ SDL_GetWindowSize(vc->window, &w, &h);
+ if (vo->dwidth != w || vo->dheight != h)
+ resize(vo, w, h);
+}
+
+static inline void set_screensaver(bool enabled)
+{
+ if (!!enabled == !!SDL_IsScreenSaverEnabled())
+ return;
+
+ if (enabled)
+ SDL_EnableScreenSaver();
+ else
+ SDL_DisableScreenSaver();
+}
+
+static void set_fullscreen(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ struct mp_vo_opts *opts = vc->opts_cache->opts;
+ int fs = opts->fullscreen;
+ SDL_bool prev_screensaver_state = SDL_IsScreenSaverEnabled();
+
+ Uint32 fs_flag;
+ if (vc->switch_mode)
+ fs_flag = SDL_WINDOW_FULLSCREEN;
+ else
+ fs_flag = SDL_WINDOW_FULLSCREEN_DESKTOP;
+
+ Uint32 old_flags = SDL_GetWindowFlags(vc->window);
+ int prev_fs = !!(old_flags & fs_flag);
+ if (fs == prev_fs)
+ return;
+
+ Uint32 flags = 0;
+ if (fs)
+ flags |= fs_flag;
+
+ if (SDL_SetWindowFullscreen(vc->window, flags)) {
+ MP_ERR(vo, "SDL_SetWindowFullscreen failed\n");
+ return;
+ }
+
+ // toggling fullscreen might recreate the window, so better guard for this
+ set_screensaver(prev_screensaver_state);
+
+ force_resize(vo);
+}
+
+static void update_screeninfo(struct vo *vo, struct mp_rect *screenrc)
+{
+ struct priv *vc = vo->priv;
+ SDL_DisplayMode mode;
+ if (SDL_GetCurrentDisplayMode(SDL_GetWindowDisplayIndex(vc->window),
+ &mode)) {
+ MP_ERR(vo, "SDL_GetCurrentDisplayMode failed\n");
+ return;
+ }
+ *screenrc = (struct mp_rect){0, 0, mode.w, mode.h};
+}
+
+static int reconfig(struct vo *vo, struct mp_image_params *params)
+{
+ struct priv *vc = vo->priv;
+
+ struct vo_win_geometry geo;
+ struct mp_rect screenrc;
+
+ update_screeninfo(vo, &screenrc);
+ vo_calc_window_geometry(vo, &screenrc, &geo);
+ vo_apply_window_geometry(vo, &geo);
+
+ int win_w = vo->dwidth;
+ int win_h = vo->dheight;
+
+ SDL_SetWindowSize(vc->window, win_w, win_h);
+ if (geo.flags & VO_WIN_FORCE_POS)
+ SDL_SetWindowPosition(vc->window, geo.win.x0, geo.win.y0);
+
+ if (vc->tex)
+ SDL_DestroyTexture(vc->tex);
+ Uint32 texfmt = SDL_PIXELFORMAT_UNKNOWN;
+ int i, j;
+ for (i = 0; i < vc->renderer_info.num_texture_formats; ++i)
+ for (j = 0; j < sizeof(formats) / sizeof(formats[0]); ++j)
+ if (vc->renderer_info.texture_formats[i] == formats[j].sdl)
+ if (params->imgfmt == formats[j].mpv)
+ texfmt = formats[j].sdl;
+ if (texfmt == SDL_PIXELFORMAT_UNKNOWN) {
+ MP_ERR(vo, "Invalid pixel format\n");
+ return -1;
+ }
+
+ vc->tex_swapped = texfmt == SDL_PIXELFORMAT_YV12;
+ vc->tex = SDL_CreateTexture(vc->renderer, texfmt,
+ SDL_TEXTUREACCESS_STREAMING,
+ params->w, params->h);
+ if (!vc->tex) {
+ MP_ERR(vo, "Could not create a texture\n");
+ return -1;
+ }
+
+ vc->params = *params;
+
+ struct mp_image tmp;
+ if (!lock_texture(vo, &tmp)) {
+ SDL_DestroyTexture(vc->tex);
+ vc->tex = NULL;
+ return -1;
+ }
+ mp_image_clear(&tmp, 0, 0, tmp.w, tmp.h);
+ SDL_UnlockTexture(vc->tex);
+
+ resize(vo, win_w, win_h);
+
+ set_screensaver(vc->screensaver_enabled);
+ set_fullscreen(vo);
+
+ SDL_ShowWindow(vc->window);
+
+ check_resize(vo);
+
+ return 0;
+}
+
+static void flip_page(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ SDL_RenderPresent(vc->renderer);
+}
+
+static void wakeup(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ SDL_Event event = {.type = vc->wakeup_event};
+ // Note that there is no context - SDL is a singleton.
+ SDL_PushEvent(&event);
+}
+
+static void wait_events(struct vo *vo, int64_t until_time_ns)
+{
+ int64_t wait_ns = until_time_ns - mp_time_ns();
+ // Round-up to 1ms for short timeouts (100us, 1000us]
+ if (wait_ns > MP_TIME_US_TO_NS(100))
+ wait_ns = MPMAX(wait_ns, MP_TIME_MS_TO_NS(1));
+ int timeout_ms = MPCLAMP(wait_ns / MP_TIME_MS_TO_NS(1), 0, 10000);
+ SDL_Event ev;
+
+ while (SDL_WaitEventTimeout(&ev, timeout_ms)) {
+ timeout_ms = 0;
+ switch (ev.type) {
+ case SDL_WINDOWEVENT:
+ switch (ev.window.event) {
+ case SDL_WINDOWEVENT_EXPOSED:
+ vo->want_redraw = true;
+ break;
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ check_resize(vo);
+ vo_event(vo, VO_EVENT_RESIZE);
+ break;
+ case SDL_WINDOWEVENT_ENTER:
+ mp_input_put_key(vo->input_ctx, MP_KEY_MOUSE_ENTER);
+ break;
+ case SDL_WINDOWEVENT_LEAVE:
+ mp_input_put_key(vo->input_ctx, MP_KEY_MOUSE_LEAVE);
+ break;
+ }
+ break;
+ case SDL_QUIT:
+ mp_input_put_key(vo->input_ctx, MP_KEY_CLOSE_WIN);
+ break;
+ case SDL_TEXTINPUT: {
+ int sdl_mod = SDL_GetModState();
+ int mpv_mod = 0;
+ // we ignore KMOD_LSHIFT, KMOD_RSHIFT and KMOD_RALT (if
+ // mp_input_use_alt_gr() is true) because these are already
+ // factored into ev.text.text
+ if (sdl_mod & (KMOD_LCTRL | KMOD_RCTRL))
+ mpv_mod |= MP_KEY_MODIFIER_CTRL;
+ if ((sdl_mod & KMOD_LALT) ||
+ ((sdl_mod & KMOD_RALT) && !mp_input_use_alt_gr(vo->input_ctx)))
+ mpv_mod |= MP_KEY_MODIFIER_ALT;
+ if (sdl_mod & (KMOD_LGUI | KMOD_RGUI))
+ mpv_mod |= MP_KEY_MODIFIER_META;
+ struct bstr t = {
+ ev.text.text, strlen(ev.text.text)
+ };
+ mp_input_put_key_utf8(vo->input_ctx, mpv_mod, t);
+ break;
+ }
+ case SDL_KEYDOWN: {
+ // Issue: we don't know in advance whether this keydown event
+ // will ALSO cause a SDL_TEXTINPUT event
+ // So we're conservative, and only map non printable keycodes
+ // (e.g. function keys, arrow keys, etc.)
+ // However, this does lose some keypresses at least on X11
+ // (e.g. Ctrl-A generates SDL_KEYDOWN only, but the key is
+ // 'a'... and 'a' is normally also handled by SDL_TEXTINPUT).
+ // The default config does not use Ctrl, so this is fine...
+ int keycode = 0;
+ int i;
+ for (i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i)
+ if (keys[i].sdl == ev.key.keysym.sym) {
+ keycode = keys[i].mpv;
+ break;
+ }
+ if (keycode) {
+ if (ev.key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT))
+ keycode |= MP_KEY_MODIFIER_SHIFT;
+ if (ev.key.keysym.mod & (KMOD_LCTRL | KMOD_RCTRL))
+ keycode |= MP_KEY_MODIFIER_CTRL;
+ if (ev.key.keysym.mod & (KMOD_LALT | KMOD_RALT))
+ keycode |= MP_KEY_MODIFIER_ALT;
+ if (ev.key.keysym.mod & (KMOD_LGUI | KMOD_RGUI))
+ keycode |= MP_KEY_MODIFIER_META;
+ mp_input_put_key(vo->input_ctx, keycode);
+ }
+ break;
+ }
+ case SDL_MOUSEMOTION:
+ mp_input_set_mouse_pos(vo->input_ctx, ev.motion.x, ev.motion.y);
+ break;
+ case SDL_MOUSEBUTTONDOWN: {
+ int i;
+ for (i = 0; i < sizeof(mousebtns) / sizeof(mousebtns[0]); ++i)
+ if (mousebtns[i].sdl == ev.button.button) {
+ mp_input_put_key(vo->input_ctx, mousebtns[i].mpv | MP_KEY_STATE_DOWN);
+ break;
+ }
+ break;
+ }
+ case SDL_MOUSEBUTTONUP: {
+ int i;
+ for (i = 0; i < sizeof(mousebtns) / sizeof(mousebtns[0]); ++i)
+ if (mousebtns[i].sdl == ev.button.button) {
+ mp_input_put_key(vo->input_ctx, mousebtns[i].mpv | MP_KEY_STATE_UP);
+ break;
+ }
+ break;
+ }
+ case SDL_MOUSEWHEEL: {
+#if SDL_VERSION_ATLEAST(2, 0, 4)
+ double multiplier = ev.wheel.direction == SDL_MOUSEWHEEL_FLIPPED ? -1 : 1;
+#else
+ double multiplier = 1;
+#endif
+ int y_code = ev.wheel.y > 0 ? MP_WHEEL_UP : MP_WHEEL_DOWN;
+ mp_input_put_wheel(vo->input_ctx, y_code, abs(ev.wheel.y) * multiplier);
+ int x_code = ev.wheel.x > 0 ? MP_WHEEL_RIGHT : MP_WHEEL_LEFT;
+ mp_input_put_wheel(vo->input_ctx, x_code, abs(ev.wheel.x) * multiplier);
+ break;
+ }
+ }
+ }
+}
+
+static void uninit(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ destroy_renderer(vo);
+ SDL_DestroyWindow(vc->window);
+ vc->window = NULL;
+ SDL_QuitSubSystem(SDL_INIT_VIDEO);
+ talloc_free(vc);
+}
+
+static inline void upload_to_texture(struct vo *vo, SDL_Texture *tex,
+ int w, int h, void *bitmap, int stride)
+{
+ struct priv *vc = vo->priv;
+
+ if (vc->osd_format.sdl == SDL_PIXELFORMAT_ARGB8888) {
+ // NOTE: this optimization is questionable, because SDL docs say
+ // that this way is slow.
+ // It did measure up faster, though...
+ SDL_UpdateTexture(tex, NULL, bitmap, stride);
+ return;
+ }
+
+ void *pixels;
+ int pitch;
+ if (SDL_LockTexture(tex, NULL, &pixels, &pitch)) {
+ MP_ERR(vo, "Could not lock texture\n");
+ } else {
+ SDL_ConvertPixels(w, h, SDL_PIXELFORMAT_ARGB8888,
+ bitmap, stride,
+ vc->osd_format.sdl,
+ pixels, pitch);
+ SDL_UnlockTexture(tex);
+ }
+}
+
+static inline void subbitmap_to_texture(struct vo *vo, SDL_Texture *tex,
+ struct sub_bitmap *bmp,
+ uint32_t ormask)
+{
+ if (ormask == 0) {
+ upload_to_texture(vo, tex, bmp->w, bmp->h,
+ bmp->bitmap, bmp->stride);
+ } else {
+ uint32_t *temppixels;
+ temppixels = talloc_array(vo, uint32_t, bmp->w * bmp->h);
+
+ int x, y;
+ for (y = 0; y < bmp->h; ++y) {
+ const uint32_t *src =
+ (const uint32_t *) ((const char *) bmp->bitmap + y * bmp->stride);
+ uint32_t *dst = temppixels + y * bmp->w;
+ for (x = 0; x < bmp->w; ++x)
+ dst[x] = src[x] | ormask;
+ }
+
+ upload_to_texture(vo, tex, bmp->w, bmp->h,
+ temppixels, sizeof(uint32_t) * bmp->w);
+
+ talloc_free(temppixels);
+ }
+}
+
+static void generate_osd_part(struct vo *vo, struct sub_bitmaps *imgs)
+{
+ struct priv *vc = vo->priv;
+ struct osd_bitmap_surface *sfc = &vc->osd_surfaces[imgs->render_index];
+
+ if (imgs->format == SUBBITMAP_EMPTY || imgs->num_parts == 0)
+ return;
+
+ if (imgs->change_id == sfc->change_id)
+ return;
+
+ if (imgs->num_parts > sfc->targets_size) {
+ sfc->targets = talloc_realloc(vc, sfc->targets,
+ struct osd_target, imgs->num_parts);
+ memset(&sfc->targets[sfc->targets_size], 0, sizeof(struct osd_target) *
+ (imgs->num_parts - sfc->targets_size));
+ sfc->targets_size = imgs->num_parts;
+ }
+ sfc->num_targets = imgs->num_parts;
+
+ for (int i = 0; i < imgs->num_parts; i++) {
+ struct osd_target *target = sfc->targets + i;
+ struct sub_bitmap *bmp = imgs->parts + i;
+
+ target->source = (SDL_Rect){
+ 0, 0, bmp->w, bmp->h
+ };
+ target->dest = (SDL_Rect){
+ bmp->x, bmp->y, bmp->dw, bmp->dh
+ };
+
+ // tex: alpha blended texture
+ if (target->tex) {
+ SDL_DestroyTexture(target->tex);
+ target->tex = NULL;
+ }
+ if (!target->tex)
+ target->tex = SDL_CreateTexture(vc->renderer,
+ vc->osd_format.sdl, SDL_TEXTUREACCESS_STREAMING,
+ bmp->w, bmp->h);
+ if (!target->tex) {
+ MP_ERR(vo, "Could not create texture\n");
+ }
+ if (target->tex) {
+ SDL_SetTextureBlendMode(target->tex,
+ SDL_BLENDMODE_BLEND);
+ SDL_SetTextureColorMod(target->tex, 0, 0, 0);
+ subbitmap_to_texture(vo, target->tex, bmp, 0); // RGBA -> 000A
+ }
+
+ // tex2: added texture
+ if (target->tex2) {
+ SDL_DestroyTexture(target->tex2);
+ target->tex2 = NULL;
+ }
+ if (!target->tex2)
+ target->tex2 = SDL_CreateTexture(vc->renderer,
+ vc->osd_format.sdl, SDL_TEXTUREACCESS_STREAMING,
+ bmp->w, bmp->h);
+ if (!target->tex2) {
+ MP_ERR(vo, "Could not create texture\n");
+ }
+ if (target->tex2) {
+ SDL_SetTextureBlendMode(target->tex2,
+ SDL_BLENDMODE_ADD);
+ subbitmap_to_texture(vo, target->tex2, bmp,
+ 0xFF000000); // RGBA -> RGB1
+ }
+ }
+
+ sfc->change_id = imgs->change_id;
+}
+
+static void draw_osd_part(struct vo *vo, int index)
+{
+ struct priv *vc = vo->priv;
+ struct osd_bitmap_surface *sfc = &vc->osd_surfaces[index];
+ int i;
+
+ for (i = 0; i < sfc->num_targets; i++) {
+ struct osd_target *target = sfc->targets + i;
+ if (target->tex)
+ SDL_RenderCopy(vc->renderer, target->tex,
+ &target->source, &target->dest);
+ if (target->tex2)
+ SDL_RenderCopy(vc->renderer, target->tex2,
+ &target->source, &target->dest);
+ }
+}
+
+static void draw_osd_cb(void *ctx, struct sub_bitmaps *imgs)
+{
+ struct vo *vo = ctx;
+ generate_osd_part(vo, imgs);
+ draw_osd_part(vo, imgs->render_index);
+}
+
+static void draw_osd(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+
+ static const bool osdformats[SUBBITMAP_COUNT] = {
+ [SUBBITMAP_BGRA] = true,
+ };
+
+ osd_draw(vo->osd, vc->osd_res, vc->osd_pts, 0, osdformats, draw_osd_cb, vo);
+}
+
+static int preinit(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+
+ if (SDL_WasInit(SDL_INIT_EVENTS)) {
+ MP_ERR(vo, "Another component is using SDL already.\n");
+ return -1;
+ }
+
+ vc->opts_cache = m_config_cache_alloc(vc, vo->global, &vo_sub_opts);
+
+ // predefine SDL defaults (SDL env vars shall override)
+ SDL_SetHintWithPriority(SDL_HINT_RENDER_SCALE_QUALITY, "1",
+ SDL_HINT_DEFAULT);
+ SDL_SetHintWithPriority(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0",
+ SDL_HINT_DEFAULT);
+
+ // predefine MPV options (SDL env vars shall be overridden)
+ SDL_SetHintWithPriority(SDL_HINT_RENDER_VSYNC, vc->vsync ? "1" : "0",
+ SDL_HINT_OVERRIDE);
+
+ if (SDL_InitSubSystem(SDL_INIT_VIDEO)) {
+ MP_ERR(vo, "SDL_Init failed\n");
+ return -1;
+ }
+
+ // then actually try
+ vc->window = SDL_CreateWindow("MPV", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
+ 640, 480, SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN);
+ if (!vc->window) {
+ MP_ERR(vo, "SDL_CreateWindow failed\n");
+ return -1;
+ }
+
+ // try creating a renderer (this also gets the renderer_info data
+ // for query_format to use!)
+ if (init_renderer(vo) != 0) {
+ SDL_DestroyWindow(vc->window);
+ vc->window = NULL;
+ return -1;
+ }
+
+ vc->wakeup_event = SDL_RegisterEvents(1);
+ if (vc->wakeup_event == (Uint32)-1)
+ MP_ERR(vo, "SDL_RegisterEvents() failed.\n");
+
+ MP_WARN(vo, "Warning: this legacy VO has bad performance. Consider fixing "
+ "your graphics drivers, or not forcing the sdl VO.\n");
+
+ return 0;
+}
+
+static int query_format(struct vo *vo, int format)
+{
+ struct priv *vc = vo->priv;
+ int i, j;
+ for (i = 0; i < vc->renderer_info.num_texture_formats; ++i)
+ for (j = 0; j < sizeof(formats) / sizeof(formats[0]); ++j)
+ if (vc->renderer_info.texture_formats[i] == formats[j].sdl)
+ if (format == formats[j].mpv)
+ return 1;
+ return 0;
+}
+
+static void draw_frame(struct vo *vo, struct vo_frame *frame)
+{
+ struct priv *vc = vo->priv;
+
+ // typically this runs in parallel with the following mp_image_copy call
+ SDL_SetRenderDrawColor(vc->renderer, 0, 0, 0, 255);
+ SDL_RenderClear(vc->renderer);
+
+ SDL_SetTextureBlendMode(vc->tex, SDL_BLENDMODE_NONE);
+
+ if (frame->current) {
+ vc->osd_pts = frame->current->pts;
+
+ mp_image_t texmpi;
+ if (!lock_texture(vo, &texmpi))
+ return;
+
+ mp_image_copy(&texmpi, frame->current);
+
+ SDL_UnlockTexture(vc->tex);
+ }
+
+ SDL_Rect src, dst;
+ src.x = vc->src_rect.x0;
+ src.y = vc->src_rect.y0;
+ src.w = vc->src_rect.x1 - vc->src_rect.x0;
+ src.h = vc->src_rect.y1 - vc->src_rect.y0;
+ dst.x = vc->dst_rect.x0;
+ dst.y = vc->dst_rect.y0;
+ dst.w = vc->dst_rect.x1 - vc->dst_rect.x0;
+ dst.h = vc->dst_rect.y1 - vc->dst_rect.y0;
+
+ SDL_RenderCopy(vc->renderer, vc->tex, &src, &dst);
+
+ draw_osd(vo);
+}
+
+static struct mp_image *get_window_screenshot(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ struct mp_image *image = mp_image_alloc(vc->osd_format.mpv, vo->dwidth,
+ vo->dheight);
+ if (!image)
+ return NULL;
+ if (SDL_RenderReadPixels(vc->renderer, NULL, vc->osd_format.sdl,
+ image->planes[0], image->stride[0])) {
+ MP_ERR(vo, "SDL_RenderReadPixels failed\n");
+ talloc_free(image);
+ return NULL;
+ }
+ return image;
+}
+
+static int control(struct vo *vo, uint32_t request, void *data)
+{
+ struct priv *vc = vo->priv;
+
+ switch (request) {
+ case VOCTRL_VO_OPTS_CHANGED: {
+ void *opt;
+ while (m_config_cache_get_next_changed(vc->opts_cache, &opt)) {
+ struct mp_vo_opts *opts = vc->opts_cache->opts;
+ if (&opts->fullscreen == opt)
+ set_fullscreen(vo);
+ }
+ return 1;
+ }
+ case VOCTRL_SET_PANSCAN:
+ force_resize(vo);
+ return VO_TRUE;
+ case VOCTRL_SCREENSHOT_WIN:
+ *(struct mp_image **)data = get_window_screenshot(vo);
+ return true;
+ case VOCTRL_SET_CURSOR_VISIBILITY:
+ SDL_ShowCursor(*(bool *)data);
+ return true;
+ case VOCTRL_KILL_SCREENSAVER:
+ vc->screensaver_enabled = false;
+ set_screensaver(vc->screensaver_enabled);
+ return VO_TRUE;
+ case VOCTRL_RESTORE_SCREENSAVER:
+ vc->screensaver_enabled = true;
+ set_screensaver(vc->screensaver_enabled);
+ return VO_TRUE;
+ case VOCTRL_UPDATE_WINDOW_TITLE:
+ SDL_SetWindowTitle(vc->window, (char *)data);
+ return true;
+ }
+ return VO_NOTIMPL;
+}
+
+#define OPT_BASE_STRUCT struct priv
+
+const struct vo_driver video_out_sdl = {
+ .description = "SDL 2.0 Renderer",
+ .name = "sdl",
+ .priv_size = sizeof(struct priv),
+ .priv_defaults = &(const struct priv) {
+ .renderer_index = -1,
+ .vsync = true,
+ },
+ .options = (const struct m_option []){
+ {"sw", OPT_BOOL(allow_sw)},
+ {"switch-mode", OPT_BOOL(switch_mode)},
+ {"vsync", OPT_BOOL(vsync)},
+ {NULL}
+ },
+ .preinit = preinit,
+ .query_format = query_format,
+ .reconfig = reconfig,
+ .control = control,
+ .draw_frame = draw_frame,
+ .uninit = uninit,
+ .flip_page = flip_page,
+ .wait_events = wait_events,
+ .wakeup = wakeup,
+ .options_prefix = "sdl",
+};