Start implementing some minor extensions to libsnes.

This commit is contained in:
Themaister 2011-10-27 23:40:34 +02:00
parent fad89f9d2d
commit 0522a2d6a0
8 changed files with 169 additions and 65 deletions

View File

@ -111,7 +111,6 @@
// Windowed
static const float xscale = 3.0; // Real x res = aspect * base_size * xscale
static const float yscale = 3.0; // Real y res = base_size * yscale
static const unsigned base_size = 224; // Base size for windowed mode.
// Fullscreen
static const bool fullscreen = false; // To start in Fullscreen or not

View File

@ -330,17 +330,17 @@ static void init_filter(void)
g_extern.filter.active = true;
unsigned width = 512;
unsigned height = 512;
unsigned width = g_extern.system.geom.max_width;
unsigned height = g_extern.system.geom.max_height;
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.scale = maxsize / SSNES_SCALE_BASE;
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);
g_extern.filter.buffer = malloc(SSNES_SCALE_BASE * SSNES_SCALE_BASE * g_extern.filter.scale * g_extern.filter.scale * sizeof(uint32_t));
g_extern.filter.pitch = SSNES_SCALE_BASE * g_extern.filter.scale * sizeof(uint32_t);
assert(g_extern.filter.buffer);
g_extern.filter.colormap = malloc(32768 * sizeof(uint32_t));
@ -412,8 +412,9 @@ void init_video_input(void)
init_shader_dir();
#endif
// We use at least 512x512 textures to accomodate for hi-res games.
unsigned scale = 2;
unsigned max_dim = max(g_extern.system.geom.max_width, g_extern.system.geom.max_height);
unsigned scale = max_dim / SSNES_SCALE_BASE;
scale = max(scale, 1);
find_video_driver();
find_input_driver();
@ -432,16 +433,18 @@ void init_video_input(void)
{
if (g_settings.video.force_aspect)
{
width = roundf(g_settings.video.base_size * g_settings.video.xscale * g_settings.video.aspect_ratio);
height = roundf(g_settings.video.base_size * g_settings.video.yscale);
width = roundf(g_extern.system.geom.base_height * g_settings.video.xscale * g_settings.video.aspect_ratio);
height = roundf(g_extern.system.geom.base_height * g_settings.video.yscale);
}
else
{
width = roundf(8.0f / 7.0f * g_settings.video.base_size * g_settings.video.xscale); // Assume 8:7 aspect.
height = roundf(g_settings.video.base_size * g_settings.video.yscale);
width = roundf(g_extern.system.geom.base_width * g_settings.video.xscale);
height = roundf(g_extern.system.geom.base_height * g_settings.video.yscale);
}
}
SSNES_LOG("Video @ %ux%u\n", width, height);
video_info_t video = {
.width = width,
.height = height,

View File

@ -96,6 +96,8 @@ unsigned (*psnes_get_memory_size)(unsigned);
void (*psnes_unload_cartridge)(void);
void (*psnes_term)(void);
static void set_environment(void);
#ifdef HAVE_DYNAMIC
static void load_dynamic(void)
{
@ -181,30 +183,23 @@ static void set_statics(void)
void init_dlsym(void)
{
// Guarantee that we can do "dirty" casting. Every OS that this program supports should pass this ...
// Guarantee that we can do "dirty" casting.
// Every OS that this program supports should pass this ...
assert(sizeof(void*) == sizeof(void (*)(void)));
#ifdef HAVE_DYNAMIC
#ifndef _WIN32 // Try to verify that -lsnes was not linked in from other modules
// Try to verify that -lsnes was not linked in from other modules
// since loading it dynamically and with -l will fail hard.
void *lib = dlopen(NULL, RTLD_LAZY);
if (lib)
{
void *sym = dlsym(lib, "snes_init");
function_t sym = dylib_proc(NULL, "snes_init");
if (sym)
{
SSNES_ERR("Serious problem! SSNES wants to load libsnes dyamically, but it is already linked!\n");
SSNES_ERR("This could happen if other modules SSNES depends on link against libsnes directly.\n");
SSNES_ERR("Proceeding could cause a crash! Aborting ...\n");
dlclose(lib);
exit(1);
}
dlclose(lib);
}
#endif
if (!*g_settings.libsnes)
{
#if defined(_WIN32)
@ -220,6 +215,8 @@ void init_dlsym(void)
#else
set_statics();
#endif
set_environment();
}
void uninit_dlsym(void)
@ -243,16 +240,26 @@ dylib_t dylib_load(const char *path)
function_t dylib_proc(dylib_t lib, const char *proc)
{
#ifdef _WIN32
function_t sym = (function_t)GetProcAddress(lib, proc);
function_t sym = (function_t)GetProcAddress(lib ? lib : GetModuleHandle(NULL), proc);
#else
void *ptr_sym = NULL;
if (lib)
ptr_sym = dlsym(lib, proc);
else
{
void *handle = dlopen(NULL, RTLD_LAZY);
if (handle)
{
ptr_sym = dlsym(handle, proc);
dlclose(handle);
}
}
// Dirty hack to workaround the non-legality of void* -> fn-pointer casts.
void *ptr_sym = dlsym(lib, proc);
function_t sym;
memcpy(&sym, &ptr_sym, sizeof(void*));
#endif
if (!sym)
SSNES_WARN("Failed to load symbol \"%s\"\n", proc);
return sym;
}
@ -265,3 +272,52 @@ void dylib_close(dylib_t lib)
#endif
}
static bool environment_cb(unsigned cmd, void *data)
{
switch (cmd)
{
case SNES_ENVIRONMENT_GET_FULLPATH:
*(const char**)data = g_extern.system.fullpath;
break;
case SNES_ENVIRONMENT_SET_GEOMETRY:
g_extern.system.geom = *(const struct snes_geometry*)data;
g_extern.system.geom.max_width = next_pow2(g_extern.system.geom.max_width);
g_extern.system.geom.max_height = next_pow2(g_extern.system.geom.max_height);
break;
case SNES_ENVIRONMENT_SET_PITCH:
g_extern.system.pitch = *(const unsigned*)data;
break;
default:
return false;
}
return true;
}
// Assume SNES as defaults.
static void set_environment_defaults(void)
{
g_extern.system.pitch = 0; // 0 is classic libsnes semantics.
g_extern.system.geom = (struct snes_geometry) {
.base_width = 256,
.base_height = 224,
.max_width = 512,
.max_height = 512,
};
}
// SSNES extension hooks. Totally optional 'n shizz :)
static void set_environment(void)
{
void (*psnes_set_environment)(snes_environment_t) =
(void (*)(snes_environment_t))dylib_proc(lib_handle, "snes_set_environment");
if (psnes_set_environment)
psnes_set_environment(environment_cb);
set_environment_defaults();
}

View File

@ -74,7 +74,6 @@ struct settings
char driver[32];
float xscale;
float yscale;
unsigned base_size;
bool fullscreen;
unsigned fullscreen_x;
unsigned fullscreen_y;
@ -208,6 +207,13 @@ struct global
unsigned state_slot;
struct
{
struct snes_geometry geom;
unsigned pitch; // If 0, has classic libsnes semantics.
char fullpath[MAXPATHLEN];
} system;
struct
{
hermite_resampler_t *source;
@ -329,6 +335,16 @@ extern struct global g_extern;
fflush(stderr); \
} while (0)
#ifndef max
#define max(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif
#define SSNES_SCALE_BASE 256
static inline uint32_t next_pow2(uint32_t v)
{
v--;

View File

@ -66,11 +66,29 @@ extern "C" {
#define SNES_MEMORY_OAM 103
#define SNES_MEMORY_CGRAM 104
// SSNES extension. Not required to be implemented for a working implementation.
#define SNES_ENVIRONMENT_GET_FULLPATH 0 // const char **
#define SNES_ENVIRONMENT_SET_GEOMETRY 1 // const struct snes_geometry *
#define SNES_ENVIRONMENT_SET_PITCH 2 // const unsigned *
struct snes_geometry
{
unsigned base_width; // Nominal video width of system.
unsigned base_height; // Nominal video height of system.
unsigned max_width; // Maximum possible width of system.
unsigned max_height; // Maximum possible height of system.
};
typedef bool (*snes_environment_t)(unsigned cmd, void *data);
void snes_set_environment(snes_environment_t);
////
typedef void (*snes_video_refresh_t)(const uint16_t *data, unsigned width, unsigned height);
typedef void (*snes_audio_sample_t)(uint16_t left, uint16_t right);
typedef void (*snes_input_poll_t)(void);
typedef int16_t (*snes_input_state_t)(bool port, unsigned device, unsigned index, unsigned id);
unsigned snes_library_revision_major(void);
unsigned snes_library_revision_minor(void);

View File

@ -120,7 +120,6 @@ static void set_defaults(void)
g_settings.video.xscale = xscale;
g_settings.video.yscale = yscale;
g_settings.video.base_size = base_size;
g_settings.video.fullscreen = g_extern.force_fullscreen ? true : fullscreen;
g_settings.video.fullscreen_x = fullscreen_x;
g_settings.video.fullscreen_y = fullscreen_y;
@ -302,7 +301,6 @@ static void parse_config_file(void)
CONFIG_GET_DOUBLE(video.xscale, "video_xscale");
CONFIG_GET_DOUBLE(video.yscale, "video_yscale");
CONFIG_GET_INT(video.base_size, "video_base_size");
CONFIG_GET_INT(video.fullscreen_x, "video_fullscreen_x");
CONFIG_GET_INT(video.fullscreen_y, "video_fullscreen_y");

69
ssnes.c
View File

@ -85,6 +85,14 @@ static void set_fast_forward_button(bool new_button_state, bool new_hold_button_
old_hold_button_state = new_hold_button_state;
}
static inline unsigned lines_to_pitch(unsigned height)
{
if (g_extern.system.pitch == 0) // SNES semantics
return ((height == 448) || (height == 478)) ? 1024 : 2048;
else
return g_extern.system.pitch;
}
static void video_cached_frame(void);
static void take_screenshot(void)
{
@ -100,7 +108,7 @@ static void take_screenshot(void)
ret = screenshot_dump(g_settings.screenshot_directory,
data,
width, height,
(height == 448 || height == 478) ? 1024 : 2048);
lines_to_pitch(height));
}
const char *msg = NULL;
@ -126,6 +134,25 @@ static void take_screenshot(void)
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
}
static inline void adjust_crop(const uint16_t **data, unsigned *height)
{
// Rather SNES specific.
if (g_settings.video.crop_overscan)
{
if (*height == 239)
{
*data += 7 * 1024; // Skip 7 top scanlines.
*height = 224;
}
else if (*height == 478)
{
*data += 15 * 512; // Skip 15 top scanlines.
*height = 448;
}
}
}
// libsnes: 0.065
// Format received is 16-bit 0RRRRRGGGGGBBBBB
static void video_frame(const uint16_t *data, unsigned width, unsigned height)
@ -133,19 +160,7 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
if (!g_extern.video_active)
return;
if (g_settings.video.crop_overscan)
{
if (height == 239)
{
data += 7 * 1024; // Skip 7 top scanlines.
height = 224;
}
else if (height == 478)
{
data += 15 * 512; // Skip 15 top scanlines.
height = 448;
}
}
adjust_crop(&data, &height);
// Slightly messy code,
// but we really need to do processing before blocking on VSync for best possible scheduling.
@ -154,7 +169,7 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
{
struct ffemu_video_data ffemu_data = {
.data = data,
.pitch = height == 448 || height == 478 ? 1024 : 2048,
.pitch = lines_to_pitch(height),
.width = width,
.height = height,
};
@ -171,7 +186,7 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
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, data, (height == 448 || height == 478) ? 1024 : 2048, width, height);
g_extern.filter.pitch, data, lines_to_pitch(height), width, height);
#ifdef HAVE_FFMPEG
if (g_extern.recording && g_settings.video.post_filter_record)
@ -189,10 +204,10 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
if (!driver.video->frame(driver.video_data, g_extern.filter.buffer, owidth, oheight, g_extern.filter.pitch, msg))
g_extern.video_active = false;
}
else if (!driver.video->frame(driver.video_data, data, width, height, (height == 448 || height == 478) ? 1024 : 2048, msg))
else if (!driver.video->frame(driver.video_data, data, width, height, lines_to_pitch(height), msg))
g_extern.video_active = false;
#else
if (!driver.video->frame(driver.video_data, data, width, height, (height == 448 || height == 478) ? 1024 : 2048, msg))
if (!driver.video->frame(driver.video_data, data, width, height, lines_to_pitch(height), msg))
g_extern.video_active = false;
#endif
@ -457,6 +472,8 @@ static void print_help(void)
static void set_basename(const char *path)
{
strlcpy(g_extern.system.fullpath, path, sizeof(g_extern.system.fullpath));
strlcpy(g_extern.basename, path, sizeof(g_extern.basename));
char *dst = strrchr(g_extern.basename, '.');
if (dst)
@ -921,10 +938,10 @@ static void init_recording(void)
struct ffemu_rational ntsc_fps = {60000, 1000};
struct ffemu_rational pal_fps = {50000, 1000};
struct ffemu_params params = {
.out_width = 256,
.out_height = 224,
.fb_width = 512,
.fb_height = 512,
.out_width = g_extern.system.geom.base_width,
.out_height = g_extern.system.geom.base_height,
.fb_width = g_extern.system.geom.max_width,
.fb_height = g_extern.system.geom.max_height,
.channels = 2,
.samplerate = 32000,
.filename = g_extern.record_path,
@ -939,8 +956,8 @@ static void init_recording(void)
}
else if (g_settings.video.hires_record)
{
params.out_width = 512;
params.out_height = 448;
params.out_width *= 2;
params.out_height *= 2;
}
if (g_settings.video.force_aspect)
@ -953,8 +970,8 @@ static void init_recording(void)
g_extern.filter.psize(&params.out_width, &params.out_height);
params.rgb32 = true;
unsigned max_width = 512;
unsigned max_height = 512;
unsigned max_width = params.fb_width;
unsigned max_height = params.fb_height;
g_extern.filter.psize(&max_width, &max_height);
params.fb_width = next_pow2(max_width);
params.fb_height = next_pow2(max_height);

View File

@ -24,9 +24,6 @@
# video_xscale = 3.0
# video_yscale = 3.0
# Base size for windowed mode calculations. Generally 224 for SNES, but can be modified.
# video_base_size = 224
# Fullscreen resolution. Resolution of 0 uses the resolution of the desktop.
# video_fullscreen_x = 0
# video_fullscreen_y = 0