An initial take on bsnes CPU filters.

This commit is contained in:
Themaister 2011-03-07 19:12:14 +01:00
parent 96b978d55a
commit f94dc125e5
11 changed files with 190 additions and 108 deletions

View File

@ -84,11 +84,7 @@ ifeq ($(HAVE_XML), 1)
endif
ifeq ($(HAVE_FILTER), 1)
OBJ += hqflt/hq.o
OBJ += hqflt/grayscale.o
OBJ += hqflt/bleed.o
OBJ += hqflt/ntsc.o
OBJ += hqflt/snes_ntsc/snes_ntsc.o
LIBS += -ldl
endif
ifeq ($(HAVE_FREETYPE), 1)

View File

@ -51,19 +51,6 @@ static void sdl_audio_cb(void *data, Uint8 *stream, int len)
memset(stream + write_size, 0, len - write_size);
}
// Interesting hack from http://www-graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
static inline uint32_t next_pow2(uint32_t v)
{
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
static inline int find_num_frames(int rate, int latency)
{
int frames = (rate * latency) / 1000;

View File

@ -67,19 +67,6 @@ static bool init_lib(void)
return true;
}
// Interesting hack from http://www-graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
static inline uint32_t next_pow2(uint32_t v)
{
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
static void* __xa_init(const char* device, int rate, int latency)
{
if (!init_lib())

View File

@ -20,8 +20,8 @@
#include "general.h"
#include <stdio.h>
#include <string.h>
#include "hqflt/filters.h"
#include <assert.h>
#include <math.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
@ -212,28 +212,90 @@ void uninit_audio(void)
free(g_extern.audio_data.conv_outsamples); g_extern.audio_data.conv_outsamples = NULL;
}
#ifdef HAVE_FILTER
static void init_filter(void)
{
if (g_extern.filter.active)
return;
if (*g_settings.video.filter_path == '\0')
return;
g_extern.filter.lib = dylib_load(g_settings.video.filter_path);
if (!g_extern.filter.lib)
{
SSNES_ERR("Failed to load filter \"%s\"\n", g_settings.video.filter_path);
return;
}
g_extern.filter.psize = dylib_proc(g_extern.filter.lib, "video_size");
g_extern.filter.prender = dylib_proc(g_extern.filter.lib, "video_render");
if (!g_extern.filter.psize || !g_extern.filter.prender)
{
SSNES_ERR("Failed to find functions in filter...\n");
dylib_close(g_extern.filter.lib);
g_extern.filter.lib = NULL;
return;
}
g_extern.filter.active = true;
unsigned width = 512;
unsigned height = 512;
g_extern.filter.psize(&width, &height);
unsigned pow2_x = next_pow2(ceil(width));
unsigned pow2_y = next_pow2(ceil(height));
unsigned maxsize = pow2_x > pow2_y ? pow2_x : pow2_y;
g_extern.filter.scale = maxsize / 256;
g_extern.filter.buffer = malloc(256 * 256 * g_extern.filter.scale * g_extern.filter.scale * sizeof(uint32_t));
g_extern.filter.pitch = 256 * g_extern.filter.scale * sizeof(uint32_t);
assert(g_extern.filter.buffer);
g_extern.filter.colormap = malloc(32768 * sizeof(uint32_t));
assert(g_extern.filter.colormap);
// TODO: Taken from bSNES source. WTF does this do?
for (int i = 0; i < 32768; i++)
{
unsigned r = (i >> 10) & 31;
unsigned g = (i >> 5) & 31;
unsigned b = (i >> 0) & 31;
r = (r << 3) | (r >> 2);
g = (g << 3) | (g >> 2);
b = (b << 3) | (b >> 2);
g_extern.filter.colormap[i] = (r << 16) | (g << 8) | (b << 0);
}
}
#endif
static void deinit_filter(void)
{
if (g_extern.filter.active)
{
g_extern.filter.active = false;
dylib_close(g_extern.filter.lib);
g_extern.filter.lib = NULL;
free(g_extern.filter.buffer);
free(g_extern.filter.colormap);
}
}
void init_video_input(void)
{
int scale = 2;
#ifdef HAVE_FILTER
init_filter();
#endif
// We use at least 512x512 textures to accomodate for hi-res games.
unsigned scale = 2;
find_video_driver();
find_input_driver();
// We multiply scales with 2 to allow for hi-res games.
#if HAVE_FILTER
switch (g_settings.video.filter)
{
case FILTER_HQ2X:
scale = 4;
break;
case FILTER_HQ4X:
case FILTER_NTSC:
scale = 8;
break;
default:
break;
}
#endif
if (g_extern.filter.active)
scale = g_extern.filter.scale;
video_info_t video = {
.width = (g_settings.video.fullscreen) ? g_settings.video.fullscreen_x : (296 * g_settings.video.xscale),

View File

@ -25,31 +25,24 @@
#include <libsnes.hpp>
#ifdef HAVE_DYNAMIC
#ifdef _WIN32
#include <windows.h>
#define SYM(x) do { \
p##x = ((void*)GetProcAddress(lib_handle, #x)); \
if (p##x == NULL) { SSNES_ERR("Failed to load symbol: \"%s\"\n", #x); exit(1); } \
} while(0)
#else
#include <dlfcn.h>
#define SYM(x) do { \
p##x = dlsym(lib_handle, #x); \
if (p##x == NULL) { SSNES_ERR("Failed to load symbol: \"%s\"\n", #x); exit(1); } \
} while(0)
#endif // _WIN32
#endif // HAVE_DYNAMIC
#endif
#ifdef HAVE_DYNAMIC
#ifdef _WIN32
static HMODULE lib_handle;
#else
static void *lib_handle = NULL;
#endif
#define DLSYM(lib, x) dylib_proc(lib, #x)
#define SYM(x) do { \
p##x = DLSYM(lib_handle, x); \
if (p##x == NULL) { SSNES_ERR("Failed to load symbol: \"%s\"\n", #x); exit(1); } \
} while(0)
static dylib_t lib_handle = NULL;
#endif
void (*psnes_init)(void);
void (*psnes_set_video_refresh)(snes_video_refresh_t);
@ -97,11 +90,7 @@ void (*psnes_term)(void);
static void load_dynamic(void)
{
SSNES_LOG("Loading dynamic libsnes from: \"%s\"\n", g_settings.libsnes);
#ifdef _WIN32
lib_handle = LoadLibrary(g_settings.libsnes);
#else
lib_handle = dlopen(g_settings.libsnes, RTLD_LAZY);
#endif
lib_handle = dylib_load(g_settings.libsnes);
if (!lib_handle)
{
SSNES_ERR("Failed to open dynamic library: \"%s\"\n", g_settings.libsnes);
@ -186,12 +175,35 @@ void uninit_dlsym(void)
{
#ifdef HAVE_DYNAMIC
if (lib_handle)
{
#ifdef _WIN32
FreeLibrary(lib_handle);
#else
dlclose(lib_handle);
#endif
}
dylib_close(lib_handle);
#endif
}
// Platform independent dylib loading.
dylib_t dylib_load(const char *path)
{
#ifdef _WIN32
return LoadLibrary(path);
#else
return dlopen(path, RTLD_LAZY);
#endif
}
void* dylib_proc(dylib_t lib, const char *proc)
{
#ifdef _WIN32
return GetProcAddress(lib, proc);
#else
return dlsym(lib, proc);
#endif
}
void dylib_close(dylib_t lib)
{
#ifdef _WIN32
FreeLibrary(lib);
#else
dlclose(lib);
#endif
}

View File

@ -24,6 +24,16 @@
void init_dlsym(void);
void uninit_dlsym(void);
#ifdef _WIN32
typedef HMODULE dylib_t;
#else
typedef void* dylib_t;
#endif
dylib_t dylib_load(const char *path);
void dylib_close(dylib_t lib);
void* dylib_proc(dylib_t lib, const char *proc);
extern void (*psnes_init)(void);
extern void (*psnes_set_video_refresh)(snes_video_refresh_t);

View File

@ -28,6 +28,7 @@
#include "movie.h"
#include "autosave.h"
#include "netplay.h"
#include "dynamic.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
@ -184,6 +185,20 @@ struct global
int16_t *conv_outsamples;
} audio_data;
struct
{
bool active;
uint32_t *buffer;
uint32_t *colormap;
unsigned pitch;
dylib_t lib;
unsigned scale;
void (*psize)(unsigned *width, unsigned *height);
void (*prender)(uint32_t *colormap, uint32_t *output, unsigned outpitch,
const uint16_t *input, unsigned pitch, unsigned width, unsigned height);
} filter;
msg_queue_t *msg_queue;
// Rewind support.
@ -240,4 +255,18 @@ extern struct global g_extern;
fflush(stderr); \
} while(0)
static inline uint32_t next_pow2(uint32_t v)
{
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
#endif

View File

@ -141,6 +141,10 @@ typedef struct gl
GLfloat tex_coords[8];
GLfloat fbo_tex_coords[8];
GLenum texture_type; // XBGR1555 or RGBA
GLenum texture_fmt;
unsigned base_size; // 2 or 4
#ifdef HAVE_FREETYPE
font_renderer_t *font;
GLuint font_tex;
@ -263,18 +267,6 @@ static inline void gl_init_font(gl_t *gl, const char *font_path, unsigned font_s
#endif
}
static inline unsigned next_pow_2(unsigned v)
{
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
static void gl_init_fbo(gl_t *gl, unsigned width, unsigned height)
{
if (!g_settings.video.render_to_texture)
@ -288,8 +280,8 @@ static void gl_init_fbo(gl_t *gl, unsigned width, unsigned height)
float scale_x = g_settings.video.fbo_scale_x;
float scale_y = g_settings.video.fbo_scale_y;
unsigned xscale = next_pow_2(ceil(scale_x));
unsigned yscale = next_pow_2(ceil(scale_y));
unsigned xscale = next_pow2(ceil(scale_x));
unsigned yscale = next_pow2(ceil(scale_y));
SSNES_LOG("Internal FBO scale: (%u, %u)\n", xscale, yscale);
gl->fbo_width = width * xscale;
@ -485,7 +477,7 @@ static void show_fps(void)
frames++;
}
static bool gl_frame(void *data, const uint16_t* frame, unsigned width, unsigned height, unsigned pitch, const char *msg)
static bool gl_frame(void *data, const void* frame, unsigned width, unsigned height, unsigned pitch, const char *msg)
{
gl_t *gl = data;
@ -517,10 +509,11 @@ static bool gl_frame(void *data, const uint16_t* frame, unsigned width, unsigned
gl->last_height = height;
glPixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(pitch));
glPixelStorei(GL_UNPACK_ROW_LENGTH, gl->tex_w);
uint8_t *tmp = calloc(1, gl->tex_w * gl->tex_h * sizeof(uint16_t));
void *tmp = calloc(1, gl->tex_w * gl->tex_h * gl->base_size);
glTexSubImage2D(GL_TEXTURE_2D,
0, 0, 0, gl->tex_w, gl->tex_h, GL_BGRA,
GL_UNSIGNED_SHORT_1_5_5_5_REV, tmp);
0, 0, 0, gl->tex_w, gl->tex_h, gl->texture_type,
gl->texture_fmt, tmp);
free(tmp);
gl->tex_coords[0] = 0;
@ -534,10 +527,10 @@ static bool gl_frame(void *data, const uint16_t* frame, unsigned width, unsigned
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, pitch >> 1);
glPixelStorei(GL_UNPACK_ROW_LENGTH, pitch / gl->base_size);
glTexSubImage2D(GL_TEXTURE_2D,
0, 0, 0, width, height, GL_BGRA,
GL_UNSIGNED_SHORT_1_5_5_5_REV, frame);
0, 0, 0, width, height, gl->texture_type,
gl->texture_fmt, frame);
glFlush();
glDrawArrays(GL_QUADS, 0, 4);
@ -681,6 +674,10 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
else
gl->tex_filter = GL_NEAREST;
gl->texture_type = video->rgb32 ? GL_RGBA : GL_BGRA;
gl->texture_fmt = video->rgb32 ? GL_UNSIGNED_INT_8_8_8_8 : GL_UNSIGNED_SHORT_1_5_5_5_REV;
gl->base_size = video->rgb32 ? sizeof(uint32_t) : sizeof(uint16_t);
glEnable(GL_TEXTURE_2D);
glDisable(GL_DITHER);
glDisable(GL_DEPTH_TEST);
@ -710,11 +707,13 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
gl->tex_w = 256 * video->input_scale;
gl->tex_h = 256 * video->input_scale;
uint8_t *tmp = calloc(1, gl->tex_w * gl->tex_h * sizeof(uint32_t));
void *tmp = calloc(1, gl->tex_w * gl->tex_h * gl->base_size);
glTexImage2D(GL_TEXTURE_2D,
0, GL_RGBA, gl->tex_w, gl->tex_h, 0, GL_BGRA,
GL_UNSIGNED_SHORT_1_5_5_5_REV, tmp);
0, GL_RGBA, gl->tex_w, gl->tex_h, 0, gl->texture_type,
gl->texture_fmt, tmp);
free(tmp);
gl->last_width = gl->tex_w;
gl->last_height = gl->tex_h;

View File

@ -10,7 +10,7 @@ PACKAGE_VERSION=0.3.1
add_command_line_enable DYNAMIC "Enable dynamic loading of libsnes library." no
add_command_line_string LIBSNES "libsnes library used" "-lsnes"
add_command_line_enable FFMPEG "Enable FFmpeg recording support" no
add_command_line_enable FILTER "Enable CPU filter support" no
add_command_line_enable FILTER "Disable CPU filter support" yes
add_command_line_enable SRC "Enable libsamplerate support" no
add_command_line_enable CONFIGFILE "Disable support for config file" yes
add_command_line_enable CG "Enable Cg shader support" auto

View File

@ -19,7 +19,6 @@
#include "conf/config_file.h"
#include <assert.h>
#include <string.h>
#include "hqflt/filters.h"
#include "config.def.h"
#ifdef HAVE_CONFIG_H

11
ssnes.c
View File

@ -25,7 +25,6 @@
#include <time.h>
#include "driver.h"
#include "file.h"
#include "filters.h"
#include "general.h"
#include "dynamic.h"
#include "record/ffemu.h"
@ -100,12 +99,14 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
const char *msg = msg_queue_pull(g_extern.msg_queue);
#ifdef HAVE_FILTER
unsigned owidth = width;
unsigned oheight = height;
video_filter_size(&owidth, &oheight);
if (g_extern.filter.active)
{
video_filter_render(g_extern.filter.buffer, g_extern.filter.pitch >> 2, data, (height == 448 || height == 478) ? 512 : 1024, width, height);
unsigned owidth = width;
unsigned oheight = height;
g_extern.filter.psize(&owidth, &oheight);
g_extern.filter.prender(g_extern.filter.colormap, g_extern.filter.buffer,
g_extern.filter.pitch >> 2, data, (height == 448 || height == 478) ? 512 : 1024, width, height);
if (!driver.video->frame(driver.video_data, g_extern.filter.buffer, owidth, oheight, g_extern.filter.pitch, msg))
g_extern.video_active = false;
}