From adfd461c3e5e104c0227adff445c7e535e8e072f Mon Sep 17 00:00:00 2001 From: Themaister Date: Sun, 13 Mar 2011 04:51:09 +0100 Subject: [PATCH] Start on XVideo --- Makefile | 5 + config.def.h | 10 ++ driver.c | 6 + driver.h | 2 + gfx/xvideo.c | 298 ++++++++++++++++++++++++++++++++++++++++++++ input/sdl.c | 2 +- input/x11_input.c | 174 ++++++++++++++++++++++++++ qb/config.libs.sh | 3 +- qb/config.params.sh | 1 + settings.c | 6 + 10 files changed, 505 insertions(+), 2 deletions(-) create mode 100644 gfx/xvideo.c create mode 100644 input/x11_input.c diff --git a/Makefile b/Makefile index 410490ce31..fece0c8ef1 100644 --- a/Makefile +++ b/Makefile @@ -72,6 +72,11 @@ else endif endif +ifeq ($(HAVE_XVIDEO), 1) + OBJ += gfx/xvideo.o input/x11_input.o + LIBS += -lXv -lX11 +endif + ifeq ($(HAVE_CG), 1) OBJ += gfx/shader_cg.o LIBS += -lCg -lCgGL diff --git a/config.def.h b/config.def.h index e5e3f2e135..d0067c618c 100644 --- a/config.def.h +++ b/config.def.h @@ -43,6 +43,7 @@ ///////////////// Drivers #define VIDEO_GL 0 +#define VIDEO_XVIDEO 11 //////////////////////// #define AUDIO_RSOUND 1 #define AUDIO_OSS 2 @@ -55,9 +56,14 @@ #define AUDIO_PULSE 10 //////////////////////// #define INPUT_SDL 7 +#define INPUT_X 12 //////////////////////// +#if defined(HAVE_SDL) #define VIDEO_DEFAULT_DRIVER VIDEO_GL +#elif defined(HAVE_XVIDEO) +#define VIDEO_DEFAULT_DRIVER VIDEO_XVIDEO +#endif #if defined(HAVE_ALSA) #define AUDIO_DEFAULT_DRIVER AUDIO_ALSA @@ -81,7 +87,11 @@ #error Need at least one audio driver! #endif +#if defined(HAVE_SDL) #define INPUT_DEFAULT_DRIVER INPUT_SDL +#elif defined(HAVE_XVIDEO) +#define INPUT_DEFAULT_DRIVER INPUT_X +#endif //////////////// diff --git a/driver.c b/driver.c index 2c372d509d..eeecdb3f37 100644 --- a/driver.c +++ b/driver.c @@ -61,12 +61,18 @@ static const video_driver_t *video_drivers[] = { #ifdef HAVE_SDL &video_gl, #endif +#ifdef HAVE_XVIDEO + &video_xvideo, +#endif }; static const input_driver_t *input_drivers[] = { #ifdef HAVE_SDL &input_sdl, #endif +//#ifdef HAVE_XVIDEO +// &input_x, +//#endif }; static void find_audio_driver(void) diff --git a/driver.h b/driver.h index 3324c14334..fc1b9b8c22 100644 --- a/driver.h +++ b/driver.h @@ -148,7 +148,9 @@ extern const audio_driver_t audio_sdl; extern const audio_driver_t audio_xa; extern const audio_driver_t audio_pulse; extern const video_driver_t video_gl; +extern const video_driver_t video_xvideo; extern const input_driver_t input_sdl; +extern const input_driver_t input_x; //////////////////////////////////////////////// #endif diff --git a/gfx/xvideo.c b/gfx/xvideo.c new file mode 100644 index 0000000000..88b087be01 --- /dev/null +++ b/gfx/xvideo.c @@ -0,0 +1,298 @@ +/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010-2011 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + +#include "driver.h" +#include "general.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct xv +{ + Display *display; + GC gc; + Window window; + Colormap colormap; + XShmSegmentInfo shminfo; + + int port; + int depth; + int visualid; + + XvImage *image; + + unsigned width; + unsigned height; + + uint8_t *ytable; + uint8_t *utable; + uint8_t *vtable; +} xv_t; + +static void init_yuv_tables(xv_t *xv) +{ + xv->ytable = malloc(0x10000); + xv->utable = malloc(0x10000); + xv->vtable = malloc(0x10000); + + for (unsigned i = 0; i < 0x10000; i++) + { + // Extract RGB555 color data from i + uint8_t r = (i >> 10) & 31, g = (i >> 5) & 31, b = (i) & 31; + r = (r << 3) | (r >> 2); //R5->R8 + g = (g << 3) | (g >> 2); //G5->G8 + b = (b << 3) | (b >> 2); //B5->B8 + + int y = (int)( +((double)r * 0.257) + ((double)g * 0.504) + ((double)b * 0.098) + 16.0 ); + int u = (int)( -((double)r * 0.148) - ((double)g * 0.291) + ((double)b * 0.439) + 128.0 ); + int v = (int)( +((double)r * 0.439) - ((double)g * 0.368) - ((double)b * 0.071) + 128.0 ); + + xv->ytable[i] = y < 0 ? 0 : y > 255 ? 255 : y; + xv->utable[i] = u < 0 ? 0 : u > 255 ? 255 : u; + xv->vtable[i] = v < 0 ? 0 : v > 255 ? 255 : v; + } +} + +static void render_yuy2(xv_t *xv, const uint16_t *input, unsigned width, unsigned height, unsigned pitch) +{ + uint16_t *output = (uint16_t*)xv->image->data; + + for (unsigned y = 0; y < height; y++) + { + for (unsigned x = 0; x < width >> 1; x++) + { + uint16_t p0 = *input++; + uint16_t p1 = *input++; + + uint8_t u = (xv->utable[p0] + xv->utable[p1]) >> 1; + uint8_t v = (xv->vtable[p0] + xv->vtable[p1]) >> 1; + + *output++ = (u << 8) | xv->ytable[p0]; + *output++ = (v << 8) | xv->ytable[p1]; + } + + input += (pitch >> 1) - width; + output += xv->width - width; + } +} + +static void* xv_init(video_info_t *video, const input_driver_t **input, void **input_data) +{ + xv_t *xv = calloc(1, sizeof(*xv)); + if (!xv) + return NULL; + + xv->display = XOpenDisplay(NULL); + + if (!XShmQueryExtension(xv->display)) + { + SSNES_ERR("XVideo: XShm extension not found.\n"); + goto error; + } + + // Find an appropriate Xv port. + xv->port = -1; + XvAdaptorInfo *adaptor_info; + unsigned adaptor_count = 0; + XvQueryAdaptors(xv->display, DefaultRootWindow(xv->display), &adaptor_count, &adaptor_info); + for (unsigned i = 0; i < adaptor_count; i++) + { + // Find adaptor that supports both input (memory->drawable) and image (drawable->screen) masks. + if (adaptor_info[i].num_formats < 1) continue; + if (!(adaptor_info[i].type & XvInputMask)) continue; + if (!(adaptor_info[i].type & XvImageMask)) continue; + + xv->port = adaptor_info[i].base_id; + xv->depth = adaptor_info[i].formats->depth; + xv->visualid = adaptor_info[i].formats->visual_id; + break; + } + XvFreeAdaptorInfo(adaptor_info); + + if (xv->port < 0) + { + SSNES_ERR("XVideo: Failed to find valid XvPort.\n"); + goto error; + } + + XVisualInfo visualtemplate; + visualtemplate.visualid = xv->visualid; + visualtemplate.screen = DefaultScreen(xv->display); + visualtemplate.depth = xv->depth; + visualtemplate.visual = 0; + int visualmatches = 0; + XVisualInfo *visualinfo = XGetVisualInfo(xv->display, VisualIDMask | VisualScreenMask | VisualDepthMask, &visualtemplate, &visualmatches); + if (visualmatches < 1 || !visualinfo->visual) + { + if (visualinfo) XFree(visualinfo); + SSNES_ERR("XVideo: Unable to find Xv-compatible visual.\n"); + goto error; + } + + xv->colormap = XCreateColormap(xv->display, DefaultRootWindow(xv->display), visualinfo->visual, AllocNone); + XSetWindowAttributes attributes; + attributes.colormap = xv->colormap; + attributes.border_pixel = 0; + attributes.event_mask = StructureNotifyMask; + xv->window = XCreateWindow(xv->display, DefaultRootWindow(xv->display), + /* x = */ 0, /* y = */ 0, video->width, video->height, + /* border_width = */ 0, xv->depth, InputOutput, visualinfo->visual, + CWColormap | CWBorderPixel | CWEventMask, &attributes); + XFree(visualinfo); + XSetWindowBackground(xv->display, xv->window, /* color = */ 0); + XMapWindow(xv->display, xv->window); + + xv->gc = XCreateGC(xv->display, xv->window, 0, 0); + + // Set colorkey to auto paint, so that Xv video output is always visible + Atom atom = XInternAtom(xv->display, "XV_AUTOPAINT_COLORKEY", true); + if(atom != None) XvSetPortAttribute(xv->display, xv->port, atom, 1); + + int format_count; + XvImageFormatValues *format = XvListImageFormats(xv->display, xv->port, &format_count); + + bool has_format = false; + uint32_t fourcc = 0; + for (int i = 0; i < format_count; i++) + { + if (format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked) + { + if (format[i].component_order[0] == 'Y' && format[i].component_order[1] == 'U' + && format[i].component_order[2] == 'Y' && format[i].component_order[3] == 'V') + { + has_format = true; + fourcc = format[i].id; + break; + } + } + } + + free(format); + if(!has_format) + { + SSNES_ERR("XVideo: unable to find a supported image format.\n"); + goto error; + } + + xv->width = 256 * video->input_scale; + xv->height = 256 * video->input_scale; + + xv->image = XvShmCreateImage(xv->display, xv->port, fourcc, 0, xv->width, xv->height, &xv->shminfo); + if(!xv->image) + { + SSNES_ERR("XVideo: XShmCreateImage failed.\n"); + goto error; + } + + xv->shminfo.shmid = shmget(IPC_PRIVATE, xv->image->data_size, IPC_CREAT | 0777); + xv->shminfo.shmaddr = xv->image->data = shmat(xv->shminfo.shmid, 0, 0); + xv->shminfo.readOnly = false; + if (!XShmAttach(xv->display, &xv->shminfo)) + { + SSNES_ERR("XVideo: XShmAttach failed.\n"); + goto error; + } + + void *xinput = input_x.init(); + if (xinput) + { + *input = &input_x; + *input_data = xinput; + } + else + *input = NULL; + + init_yuv_tables(xv); + return xv; + +error: + free(xv); + return NULL; +} + +static bool xv_frame(void *data, const void* frame, unsigned width, unsigned height, unsigned pitch, const char *msg) +{ + (void)msg; + xv_t *xv = data; + + XWindowAttributes target; + XGetWindowAttributes(xv->display, xv->window, &target); + render_yuy2(xv, frame, width, height, pitch); + XvShmPutImage(xv->display, xv->port, xv->window, xv->gc, xv->image, + 0, 0, width, height, + 0, 0, target.width, target.height, + true); + + return true; +} + +static bool xv_alive(void *data) +{ + return true; +} + +static bool xv_focus(void *data) +{ + return true; +} + +static void xv_set_nonblock_state(void *data, bool state) +{ + xv_t *xv = data; + Atom atom = XInternAtom(xv->display, "XV_SYNC_TO_VBLANK", true); + if (atom != None && xv->port >= 0) + XvSetPortAttribute(xv->display, xv->port, atom, !state); +} + +static void xv_free(void *data) +{ + xv_t *xv = data; + XShmDetach(xv->display, &xv->shminfo); + shmdt(xv->shminfo.shmaddr); + shmctl(xv->shminfo.shmid, IPC_RMID, NULL); + XFree(xv->image); + + if(xv->window) + XUnmapWindow(xv->display, xv->window); + if(xv->colormap) + XFreeColormap(xv->display, xv->colormap); + + XCloseDisplay(xv->display); + + free(xv->ytable); + free(xv->utable); + free(xv->vtable); + free(xv); +} + +const video_driver_t video_xvideo = { + .init = xv_init, + .frame = xv_frame, + .alive = xv_alive, + .set_nonblock_state = xv_set_nonblock_state, + .focus = xv_focus, + .free = xv_free, + .ident = "xvideo" +}; + diff --git a/input/sdl.c b/input/sdl.c index bd34fc71eb..d9b5699c59 100644 --- a/input/sdl.c +++ b/input/sdl.c @@ -248,7 +248,7 @@ static int16_t sdl_input_state(void *data, const struct snes_keybind **binds, bo switch (device) { case SNES_DEVICE_JOYPAD: - return sdl_joypad_device_state(data, binds, port == SNES_PORT_1 ? 0 : 1, device, index, id); + return sdl_joypad_device_state(data, binds, (port == SNES_PORT_1) ? 0 : 1, device, index, id); case SNES_DEVICE_MULTITAP: return sdl_joypad_device_state(data, binds, (port == SNES_PORT_2) ? 1 + index : 0, device, index, id); case SNES_DEVICE_MOUSE: diff --git a/input/x11_input.c b/input/x11_input.c new file mode 100644 index 0000000000..0e26901a52 --- /dev/null +++ b/input/x11_input.c @@ -0,0 +1,174 @@ +/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010-2011 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + +#include "driver.h" + +#include "SDL.h" +#include +#include "general.h" +#include +#include +#include "ssnes_sdl_input.h" + +#include +#include +#include + +typedef struct x11_input +{ + sdl_input_t *sdl; + Display *display; + char state[32]; +} x11_input_t; + +static void* x_input_init(void) +{ + x11_input_t *x11 = calloc(1, sizeof(*x11)); + if (!x11) + return NULL; + + x11->display = XOpenDisplay(NULL); + if (!x11->display) + { + free(x11); + return NULL; + } + + x11->sdl = input_sdl.init(); + if (!x11->sdl) + { + free(x11); + return NULL; + } + + x11->sdl->use_keyboard = false; + return x11; +} + +static const int sdl2xlut[] = { + 0, 0, 0, 0, 0, 0, 0, 0, // 0 + XK_BackSpace, XK_Tab, 0, 0, 0, XK_Return, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 16 + 0, 0, 0, XK_Escape, 0, 0, 0, 0, + XK_space, 0, 0, 0, 0, 0, 0, 0, // 32 + 0, 0, 0, 0, 0, 0, 0, 0, + XK_0, XK_1, XK_2, XK_3, XK_4, XK_5, XK_6, XK_7, // 48 + XK_8, XK_9, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 64 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 80 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 96 +}; + +static int sdl_to_xkeysym(int key) +{ + if (key < sizeof(sdl2xlut) / sizeof(int)) + return sdl2xlut[key]; + else + return 0; +} + +static bool x_key_pressed(x11_input_t *x11, int key) +{ + key = sdl_to_xkeysym(key); + + int keycode = XKeysymToKeycode(x11->display, key); + fprintf(stderr, "key: %d -> keycode: %d\n", key, keycode); + return x11->state[keycode >> 3] & (1 << (keycode & 7)); +} + +static bool x_is_pressed(x11_input_t *x11, const struct snes_keybind *binds, unsigned id) +{ + for (int i = 0; binds[i].id != -1; i++) + { + if (binds[i].id == id) + return x_key_pressed(x11, binds[i].key); + } + + return false; +} + +static bool x_bind_button_pressed(void *data, int key) +{ + x11_input_t *x11 = data; + const struct snes_keybind *binds = g_settings.input.binds[0]; + for (int i = 0; binds[i].id != -1; i++) + { + if (binds[i].id == key) + { + bool pressed = x_key_pressed(x11, binds[i].key); + if (!pressed) + return input_sdl.key_pressed(x11->sdl, key); + } + } + + return false; +} + +static int16_t x_input_state(void *data, const struct snes_keybind **binds, bool port, unsigned device, unsigned index, unsigned id) +{ + x11_input_t *x11 = data; + bool pressed = false; + + switch (device) + { + case SNES_DEVICE_JOYPAD: + pressed = x_is_pressed(x11, binds[(port == SNES_PORT_1) ? 0 : 1], id); + if (!pressed) + pressed = input_sdl.input_state(x11->sdl, binds, port, device, index, id); + return pressed; + + case SNES_DEVICE_MULTITAP: + pressed = x_is_pressed(x11, binds[(port == SNES_PORT_2) ? 1 + index : 0], id); + if (!pressed) + pressed = input_sdl.input_state(x11->sdl, binds, port, device, index, id); + return pressed; + + default: + return 0; + } +} + +static void x_input_free(void *data) +{ + x11_input_t *x11 = data; + input_sdl.free(x11->sdl); + XCloseDisplay(x11->display); + free(data); +} + +static void x_input_poll(void *data) +{ + x11_input_t *x11 = data; + XQueryKeymap(x11->display, x11->state); + //for (int i = 0; i < 32; i++) + //{ + // fprintf(stderr, "State %d: 0x%x\n", i, (unsigned)x11->state[i]); + //} + input_sdl.poll(x11->sdl); +} + +const input_driver_t input_x = { + .init = x_input_init, + .poll = x_input_poll, + .input_state = x_input_state, + .key_pressed = x_bind_button_pressed, + .free = x_input_free, + .ident = "x" +}; + diff --git a/qb/config.libs.sh b/qb/config.libs.sh index 3e6ead7961..9740a6038b 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -52,9 +52,10 @@ check_pkgconf SRC samplerate check_lib DYNAMIC -ldl dlopen check_pkgconf FREETYPE freetype2 +check_lib XVIDEO -lXv XvShmCreateImage # Creates config.mk and config.h. -VARS="ALSA OSS AL RSOUND ROAR JACK PULSE SDL FILTER CG XML DYNAMIC FFMPEG AVCODEC AVFORMAT AVCORE AVUTIL SWSCALE SRC CONFIGFILE FREETYPE" +VARS="ALSA OSS AL RSOUND ROAR JACK PULSE SDL FILTER CG XML DYNAMIC FFMPEG AVCODEC AVFORMAT AVCORE AVUTIL SWSCALE SRC CONFIGFILE FREETYPE XVIDEO" create_config_make config.mk $VARS create_config_header config.h $VARS diff --git a/qb/config.params.sh b/qb/config.params.sh index 5f82866470..957195a24f 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -23,3 +23,4 @@ add_command_line_enable AL "Enable OpenAL support" auto add_command_line_enable JACK "Enable JACK support" auto add_command_line_enable PULSE "Enable PulseAudio support" auto add_command_line_enable FREETYPE "Enable FreeType support" auto +add_command_line_enable XVIDEO "Enable XVideo support" auto diff --git a/settings.c b/settings.c index 0dd6d719bc..69f53166ff 100644 --- a/settings.c +++ b/settings.c @@ -44,6 +44,9 @@ static void set_defaults(void) case VIDEO_GL: def_video = "gl"; break; + case VIDEO_XVIDEO: + def_video = "xvideo"; + break; default: break; } @@ -83,6 +86,9 @@ static void set_defaults(void) case INPUT_SDL: def_input = "sdl"; break; + case INPUT_X: + def_input = "x"; + break; default: break; }