diff --git a/Makefile b/Makefile index 94f52f093a..3972d52e75 100644 --- a/Makefile +++ b/Makefile @@ -2,43 +2,44 @@ include config.mk TARGET = ssnes -DEFINES = -OBJ = ssnes.o file.o driver.o -libsnes = -lsnes +OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o -LIBS = -lsamplerate $(libsnes) +LIBS = -lsamplerate -ifeq ($(BUILD_RSOUND), 1) +ifeq ($(HAVE_RSOUND), 1) OBJ += audio/rsound.o LIBS += -lrsound endif -ifeq ($(BUILD_OSS), 1) +ifeq ($(HAVE_OSS), 1) OBJ += audio/oss.o endif -ifeq ($(BUILD_ALSA), 1) +ifeq ($(HAVE_ALSA), 1) OBJ += audio/alsa.o LIBS += -lasound endif -ifeq ($(BUILD_ROAR), 1) +ifeq ($(HAVE_ROAR), 1) OBJ += audio/roar.o LIBS += -lroar endif -ifeq ($(BUILD_AL), 1) +ifeq ($(HAVE_AL), 1) OBJ += audio/openal.o LIBS += -lopenal endif +ifeq ($(HAVE_JACK),1) + OBJ += audio/jack.o + LIBS += -ljack +endif -ifeq ($(BUILD_OPENGL), 1) +ifeq ($(HAVE_GLFW), 1) OBJ += gfx/gl.o LIBS += -lglfw endif -ifeq ($(BUILD_CG), 1) +ifeq ($(HAVE_CG), 1) LIBS += -lCg -lCgGL - DEFINES += -DHAVE_CG endif -ifeq ($(BUILD_FILTER), 1) +ifeq ($(HAVE_FILTER), 1) OBJ += hqflt/hq.o OBJ += hqflt/grayscale.o OBJ += hqflt/bleed.o @@ -46,9 +47,19 @@ ifeq ($(BUILD_FILTER), 1) OBJ += hqflt/snes_ntsc/snes_ntsc.o endif -CFLAGS = -Wall -O3 -std=gnu99 -Wno-unused-variable -I. $(DEFINES) +ifeq ($(HAVE_DYNAMIC), 1) + LIBS += -ldl +else + LIBS += $(libsnes) +endif -all: $(TARGET) +CFLAGS = -Wall -O3 -g -std=gnu99 -I. + +all: $(TARGET) config.mk + +config.mk: configure qb/* + @echo "config.mk is outdated or non-existing. Run ./configure again." + @exit 1 ssnes: $(OBJ) $(CXX) -o $@ $(OBJ) $(LIBS) $(CFLAGS) @@ -57,14 +68,16 @@ ssnes: $(OBJ) $(CC) $(CFLAGS) -c -o $@ $< install: $(TARGET) - install -m755 $(TARGET) $(PREFIX)/bin + install -m755 $(TARGET) $(DESTDIR)/$(PREFIX)/bin + install -m644 ssnes.cfg $(DESTDIR)/etc/ssnes.cfg uninstall: $(TARGET) - rm -rf $(PREFIX)/bin/$(TARGET) + rm -rf $(DESTDIR)/$(PREFIX)/bin/$(TARGET) clean: rm -f *.o rm -f audio/*.o + rm -f conf/*.o rm -f gfx/*.o rm -f hqflt/*.o rm -f hqflt/snes_ntsc/*.o diff --git a/README.md b/README.md index ab32262042..a6ce0b1d0c 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,7 @@ This enables the possibility of custom front-ends for the emulator. # Philosophy SSNES attempts to be very small and lean, while still having all the useful core features expected from an emulator. -It is close in spirit to suckless' DWM, in that configuring the emulator requires a recompile. -The configuration is done through editing a C header file. -C programming skills are not necessary to configure it (no programming involved), but some basic programming experience might be needed. +It is used through command-line. # Dependencies @@ -33,6 +31,7 @@ SSNES needs one of these audio driver libraries: - RoarAudio - RSound - OpenAL + - JACK # Building libsnes @@ -46,17 +45,26 @@ SSNES needs one of these audio driver libraries: # Configuring -SSNES configuring is done through editing config.h and config.mk. -The default configs can be found in config.h.def and config.mk.def respectively. -Do note that you might have to edit config.mk if you edit driver and filter options! -By default, ALSA audio driver is assumed. +The default configuration is defined in config.def.h. +These can later be tweaked by using the ssnes config file. +A sample configuration file is installed to /etc/ssnes.cfg. +This is the system-wide config file. +Each user should create a config file in $XDG\_CONFIG\_HOME/ssnes/ssnes.cfg. +The users only need to configure a certain option if the desired value deviates from the value defined in config.def.h. -Most options in config.h should be self-explanatory. To configure joypads, start up jstest /dev/input/js0 to determine which joypad buttons (and axis) to use. # Compiling and installing -The good old make && sudo make install should do the trick :) +As most packages, SSNES is built using the standard ./configure && make && make install +Do note that the build system is not autotools based, but resembles it. + +Notable options for ./configure: +--with-libsnes=: Normally libsnes is located with -lsnes, however, this can be overridden. +--enable-dynamic: Do not link to libsnes at compile time, but load libsnes dynamically at runtime. libsnes\_path in config file defines which library to load. Useful for development. + +Do note that these two options are mutually exclusive. + # Filters and Cg shader support @@ -67,3 +75,5 @@ Cg shaders are compiled at run-time, and shaders could be dropped in. All shaders share a common interface to pass some essential arguments such as texture size and viewport size. (Common for pixel art scalers) Some Cg shaders are included in hqflt/cg/ and could be used as an example. +While these shaders are Cg, they closely resemble the GLSL shaders found in bSNES shader pack, so porting them is trivial. + diff --git a/audio/alsa.c b/audio/alsa.c index 5dece09311..4e6145141e 100644 --- a/audio/alsa.c +++ b/audio/alsa.c @@ -190,7 +190,8 @@ const audio_driver_t audio_alsa = { .stop = __alsa_stop, .start = __alsa_start, .set_nonblock_state = __alsa_set_nonblock_state, - .free = __alsa_free + .free = __alsa_free, + .ident = "alsa" }; diff --git a/audio/jack.c b/audio/jack.c new file mode 100644 index 0000000000..01ae1c9ef6 --- /dev/null +++ b/audio/jack.c @@ -0,0 +1,300 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - 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 +#include "general.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define FRAMES(x) (x / (sizeof(int16_t) * 2)) +#define SAMPLES(x) (x / sizeof(int16_t)) + +typedef struct jack +{ + jack_client_t *client; + jack_port_t *ports[2]; + jack_ringbuffer_t *buffer[2]; + volatile bool shutdown; + bool nonblock; + + pthread_cond_t cond; + pthread_mutex_t cond_lock; +} jack_t; + +static int process_cb(jack_nframes_t nframes, void *data) +{ + jack_t *jd = data; + if (nframes <= 0) + { + pthread_cond_signal(&jd->cond); + return 0; + } + + jack_nframes_t avail[2]; + avail[0] = jack_ringbuffer_read_space(jd->buffer[0]); + avail[1] = jack_ringbuffer_read_space(jd->buffer[1]); + jack_nframes_t min_avail = ((avail[0] < avail[1]) ? avail[0] : avail[1]) / sizeof(jack_default_audio_sample_t); + + if (min_avail > nframes) + min_avail = nframes; + + //static int underrun = 0; + //if (min_avail < nframes) + //{ + // SSNES_LOG("JACK: Underrun count: %d\n", underrun++); + // fprintf(stderr, "required %d frames, got %d.\n", (int)nframes, (int)min_avail); + //} + + for (int i = 0; i < 2; i++) + { + jack_default_audio_sample_t *out = jack_port_get_buffer(jd->ports[i], nframes); + assert(out); + jack_ringbuffer_read(jd->buffer[i], (char*)out, min_avail * sizeof(jack_default_audio_sample_t)); + + for (jack_nframes_t f = min_avail; f < nframes; f++) + { + out[f] = 0.0f; + } + } + pthread_cond_signal(&jd->cond); + return 0; +} + +static void shutdown_cb(void *data) +{ + jack_t *jd = data; + jd->shutdown = true; + pthread_cond_signal(&jd->cond); +} + +static inline void s16_to_float(jack_default_audio_sample_t * restrict out, const int16_t * restrict in, size_t samples) +{ + for (int i = 0; i < samples; i++) + out[i] = (float)in[i] / 0x8000; +} + +static void parse_ports(const char **dest_ports, const char **jports) +{ + int parsed = 0; + + const char *con = strtok(g_settings.audio.device, ","); + if (con) + dest_ports[parsed++] = con; + con = strtok(NULL, ","); + if (con) + dest_ports[parsed++] = con; + + for (int i = parsed; i < 2; i++) + dest_ports[i] = jports[i]; +} + +static void* __jack_init(const char* device, int rate, int latency) +{ + jack_t *jd = calloc(1, sizeof(jack_t)); + if ( jd == NULL ) + return NULL; + + const char **jports = NULL; + + jd->client = jack_client_open("SSNES", JackNullOption, NULL); + if (jd->client == NULL) + goto error; + + g_settings.audio.out_rate = jack_get_sample_rate(jd->client); + + jack_set_process_callback(jd->client, process_cb, jd); + jack_on_shutdown(jd->client, shutdown_cb, jd); + + jd->ports[0] = jack_port_register(jd->client, "left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + jd->ports[1] = jack_port_register(jd->client, "right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + if (jd->ports[0] == NULL || jd->ports[1] == NULL) + { + SSNES_ERR("Failed to register ports.\n"); + goto error; + } + + jack_nframes_t bufsize; + jack_nframes_t jack_bufsize = jack_get_buffer_size(jd->client); + + bufsize = (latency * g_settings.audio.out_rate / 1000) > jack_bufsize * 2 ? (latency * g_settings.audio.out_rate / 1000) : jack_bufsize * 2; + bufsize *= sizeof(jack_default_audio_sample_t); + + //fprintf(stderr, "jack buffer size: %d\n", (int)bufsize); + for (int i = 0; i < 2; i++) + { + jd->buffer[i] = jack_ringbuffer_create(bufsize); + if (jd->buffer[i] == NULL) + { + SSNES_ERR("Failed to create buffers.\n"); + goto error; + } + } + + const char *dest_ports[2]; + jports = jack_get_ports(jd->client, NULL, NULL, JackPortIsPhysical | JackPortIsInput); + if (jports == NULL) + { + SSNES_ERR("Failed to get ports.\n"); + goto error; + } + + parse_ports(dest_ports, jports); + + if (jack_activate(jd->client) < 0) + { + SSNES_ERR("Failed to activate Jack...\n"); + goto error; + } + + for (int i = 0; i < 2; i++) + { + if (jack_connect(jd->client, jack_port_name(jd->ports[i]), dest_ports[i])) + { + SSNES_ERR("Failed to connect to Jack port.\n"); + goto error; + } + } + + pthread_cond_init(&jd->cond, NULL); + pthread_mutex_init(&jd->cond_lock, NULL); + + + jack_free(jports); + return jd; + +error: + if (jports != NULL) + jack_free(jports); + return NULL; +} + +static size_t write_buffer(jack_t *jd, const void *buf, size_t size) +{ + //fprintf(stderr, "write_buffer: size: %zu\n", size); + // Convert our data to float, deinterleave and write. + jack_default_audio_sample_t out_buffer[size / sizeof(int16_t)]; + jack_default_audio_sample_t out_deinterleaved_buffer[2][FRAMES(size)]; + s16_to_float(out_buffer, buf, SAMPLES(size)); + + for (int i = 0; i < 2; i++) + for (size_t j = 0; j < FRAMES(size); j++) + out_deinterleaved_buffer[i][j] = out_buffer[j * 2 + i]; + + for(;;) + { + if (jd->shutdown) + return 0; + + size_t avail[2]; + avail[0] = jack_ringbuffer_write_space(jd->buffer[0]); + avail[1] = jack_ringbuffer_write_space(jd->buffer[1]); + size_t min_avail = avail[0] < avail[1] ? avail[0] : avail[1]; + + if (jd->nonblock) + { + if (min_avail < FRAMES(size) * sizeof(jack_default_audio_sample_t)) + size = min_avail * 2 * sizeof(int16_t) / sizeof(jack_default_audio_sample_t); + break; + } + + else + { + //fprintf(stderr, "Write avail is: %d\n", (int)min_avail); + if (min_avail >= FRAMES(size) * sizeof(jack_default_audio_sample_t)) + break; + } + + pthread_mutex_lock(&jd->cond_lock); + pthread_cond_wait(&jd->cond, &jd->cond_lock); + pthread_mutex_unlock(&jd->cond_lock); + } + + for (int i = 0; i < 2; i++) + jack_ringbuffer_write(jd->buffer[i], (const char*)out_deinterleaved_buffer[i], FRAMES(size) * sizeof(jack_default_audio_sample_t)); + return size; +} + +static ssize_t __jack_write(void* data, const void* buf, size_t size) +{ + jack_t *jd = data; + + return write_buffer(jd, buf, size); +} + +static bool __jack_stop(void *data) +{ + (void)data; + return true; +} + +static void __jack_set_nonblock_state(void *data, bool state) +{ + jack_t *jd = data; + jd->nonblock = state; +} + +static bool __jack_start(void *data) +{ + (void)data; + return true; +} + +static void __jack_free(void *data) +{ + jack_t *jd = data; + + jd->shutdown = true; + + if (jd->client != NULL) + { + jack_deactivate(jd->client); + jack_client_close(jd->client); + } + + for (int i = 0; i < 2; i++) + if (jd->buffer[i] != NULL) + jack_ringbuffer_free(jd->buffer[i]); + + pthread_mutex_destroy(&jd->cond_lock); + pthread_cond_destroy(&jd->cond); + free(jd); +} + +const audio_driver_t audio_jack = { + .init = __jack_init, + .write = __jack_write, + .stop = __jack_stop, + .start = __jack_start, + .set_nonblock_state = __jack_set_nonblock_state, + .free = __jack_free, + .ident = "jack" +}; + + + + + + diff --git a/audio/openal.c b/audio/openal.c index 75826541e7..431acba814 100644 --- a/audio/openal.c +++ b/audio/openal.c @@ -233,7 +233,8 @@ const audio_driver_t audio_openal = { .stop = __al_stop, .start = __al_start, .set_nonblock_state = __al_set_nonblock_state, - .free = __al_free + .free = __al_free, + .ident = "openal" }; diff --git a/audio/oss.c b/audio/oss.c index b12ddebad1..bf451905e6 100644 --- a/audio/oss.c +++ b/audio/oss.c @@ -136,7 +136,8 @@ const audio_driver_t audio_oss = { .stop = __oss_stop, .start = __oss_start, .set_nonblock_state = __oss_set_nonblock_state, - .free = __oss_free + .free = __oss_free, + .ident = "oss" }; diff --git a/audio/roar.c b/audio/roar.c index db94f0768b..89ab744560 100644 --- a/audio/roar.c +++ b/audio/roar.c @@ -107,7 +107,8 @@ const audio_driver_t audio_roar = { .stop = __roar_stop, .start = __roar_start, .set_nonblock_state = __roar_set_nonblock_state, - .free = __roar_free + .free = __roar_free, + .ident = "roar" }; diff --git a/audio/rsound.c b/audio/rsound.c index 181f0859c8..08040c2e65 100644 --- a/audio/rsound.c +++ b/audio/rsound.c @@ -135,7 +135,8 @@ const audio_driver_t audio_rsound = { .stop = __rsd_stop, .start = __rsd_start, .set_nonblock_state = __rsd_set_nonblock_state, - .free = __rsd_free + .free = __rsd_free, + .ident = "rsound" }; diff --git a/conf/config_file.c b/conf/config_file.c new file mode 100644 index 0000000000..441256f9cd --- /dev/null +++ b/conf/config_file.c @@ -0,0 +1,293 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - 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 "config_file.h" +#include +#include +#include +#include +#include "general.h" + +struct entry_list +{ + char *key; + char *value; + struct entry_list *next; +}; + +struct config_file +{ + struct entry_list *entries; +}; + +static char *getaline(FILE *file) +{ + char *newline = malloc(9); + size_t cur_size = 8; + size_t index = 0; + + int in = getc(file); + while (in != EOF && in != '\n') + { + if (index == cur_size) + { + cur_size *= 2; + newline = realloc(newline, cur_size + 1); + } + + newline[index++] = in; + in = getc(file); + } + newline[index] = '\0'; + return newline; +} + +static bool parse_line(struct entry_list *list, char *line) +{ + // Remove everything after comment. + char *comment = strchr(line, '#'); + if (comment) + *comment = '\0'; + + // Skips to first character. + while (isspace(*line)) + line++; + + char *key = malloc(9); + size_t cur_size = 8; + size_t index = 0; + + while (isgraph(*line)) + { + if (index == cur_size) + { + cur_size *= 2; + key = realloc(key, cur_size + 1); + } + + key[index++] = *line++; + } + key[index] = '\0'; + list->key = key; + + while (isspace(*line)) + line++; + + // If we don't have an equal sign here, we've got an invalid string... + if (*line != '=') + { + list->key = NULL; + free(key); + return false; + } + line++; + + while (isspace(*line)) + line++; + + // We have a full string. Read until next ". + if (*line == '"') + { + char *tok = strtok(line + 1, "\""); + if (tok == NULL) + { + list->key = NULL; + free(key); + return false; + } + list->value = strdup(tok); + } + else // We don't have that... Read till next space. + { + char *tok = strtok(line, " \t\f"); + if (tok == NULL) + { + list->key = NULL; + free(key); + return false; + } + list->value = strdup(tok); + } + + return true; +} + +static void print_config(config_file_t *conf) +{ + struct entry_list *tmp = conf->entries; + while (tmp != NULL) + { + printf("Key: \"%s\", Value: \"%s\"\n", tmp->key, tmp->value); + tmp = tmp->next; + } +} + +config_file_t *config_file_new(const char *path) +{ + FILE *file = fopen(path, "r"); + if (!file) + return NULL; + + struct config_file *conf = calloc(1, sizeof(*conf)); + if (conf == NULL) + return NULL; + + struct entry_list *tail = conf->entries; + + while (!feof(file)) + { + struct entry_list *list = calloc(1, sizeof(*list)); + char *line = getaline(file); + + if (line) + { + if (parse_line(list, line)) + { + if (conf->entries == NULL) + { + conf->entries = list; + tail = list; + } + else + { + tail->next = list; + tail = list; + } + } + free(line); + } + } + fclose(file); + + if (g_extern.verbose) + print_config(conf); + + return conf; +} + +void config_file_free(config_file_t *conf) +{ + if (conf != NULL) + { + struct entry_list *tmp = conf->entries; + struct entry_list *old = tmp; + while (tmp != NULL) + { + free(tmp->key); + free(tmp->value); + old = tmp; + tmp = tmp->next; + free(old); + } + free(conf); + } +} + +bool config_get_double(config_file_t *conf, const char *key, double *in) +{ + struct entry_list *list = conf->entries; + + while (list != NULL) + { + if (strcmp(key, list->key) == 0) + { + *in = strtod(list->value, NULL); + return true; + } + list = list->next; + } + return false; +} + +bool config_get_int(config_file_t *conf, const char *key, int *in) +{ + struct entry_list *list = conf->entries; + + while (list != NULL) + { + if (strcmp(key, list->key) == 0) + { + *in = strtol(list->value, NULL, 0); + return true; + } + list = list->next; + } + return false; +} + +bool config_get_char(config_file_t *conf, const char *key, char *in) +{ + struct entry_list *list = conf->entries; + + while (list != NULL) + { + if (strcmp(key, list->key) == 0) + { + if (strlen(list->value) > 1) + return false; + *in = *list->value; + return true; + } + list = list->next; + } + return false; +} + +bool config_get_string(config_file_t *conf, const char *key, char **str) +{ + struct entry_list *list = conf->entries; + + while (list != NULL) + { + if (strcmp(key, list->key) == 0) + { + *str = strdup(list->value); + return true; + } + list = list->next; + } + return false; +} + +bool config_get_bool(config_file_t *conf, const char *key, bool *in) +{ + struct entry_list *list = conf->entries; + + while (list != NULL) + { + if (strcmp(key, list->key) == 0) + { + if (strcasecmp(list->value, "true") == 0) + *in = true; + else if (strcasecmp(list->value, "1") == 0) + *in = true; + else if (strcasecmp(list->value, "false") == 0) + *in = false; + else if (strcasecmp(list->value, "0") == 0) + *in = false; + else + return false; + + return true; + } + list = list->next; + } + return false; +} + + diff --git a/conf/config_file.h b/conf/config_file.h new file mode 100644 index 0000000000..1992127564 --- /dev/null +++ b/conf/config_file.h @@ -0,0 +1,53 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - 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 . + */ + + +#ifndef __CONFIG_FILE_H +#define __CONFIG_FILE_H + +#include +#include + +typedef struct config_file config_file_t; + +///// +// Config file format +// - # are treated as comments. Rest of the line is ignored. +// - Format is: key = value. There can be as many spaces as you like in-between. +// - Value can be wrapped inside "" for multiword strings. (foo = "hai u") + +// Loads a config file. Returns NULL if file doesn't exist. +config_file_t *config_file_new(const char *path); +// Frees config file. +void config_file_free(config_file_t *conf); + +// All extract functions return true when value is valid and exists. Returns false otherwise. + +// Extracts a double from config file. +bool config_get_double(config_file_t *conf, const char *entry, double *in); +// Extracts an int from config file. +bool config_get_int(config_file_t *conf, const char *entry, int *in); +// Extracts a single char. If value consists of several chars, this is an error. +bool config_get_char(config_file_t *conf, const char *entry, char *in); +// Extracts an allocated string in *in. This must be free()-d if this function succeeds. +bool config_get_string(config_file_t *conf, const char *entry, char **in); +// Extracts a boolean from config. Valid boolean true are "true" and "1". Valid false are "false" and "0". Other values will be treated as an error. +bool config_get_bool(config_file_t *conf, const char *entry, bool *in); + + + +#endif diff --git a/config.h.def b/config.def.h similarity index 86% rename from config.h.def rename to config.def.h index 859daec4bb..d2ce444381 100644 --- a/config.h.def +++ b/config.def.h @@ -37,11 +37,11 @@ #define AUDIO_ALSA 3 #define AUDIO_ROAR 4 #define AUDIO_AL 5 +#define AUDIO_JACK 6 //////////////////////// -// Chooses which video and audio subsystem to use. Remember to update config.mk if you change these. -#define VIDEO_DRIVER VIDEO_GL -#define AUDIO_DRIVER AUDIO_ALSA +#define VIDEO_DEFAULT_DRIVER VIDEO_GL +#define AUDIO_DEFAULT_DRIVER AUDIO_ALSA //////////////// @@ -53,7 +53,7 @@ static const float xscale = 3.0; // Real x res = 296 * xscale static const float yscale = 3.0; // Real y res = 224 * yscale // Fullscreen -#define START_FULLSCREEN false; // To start in Fullscreen or not +static const bool fullscreen = false; // To start in Fullscreen or not static const unsigned fullscreen_x = 1280; static const unsigned fullscreen_y = 720; @@ -63,28 +63,9 @@ static const bool vsync = true; // Smooths picture static const bool video_smooth = true; -// Path to custom Cg shader. If using custom shaders, it is recommended to disable video_smooth. -#ifdef HAVE_CG -extern char cg_shader_path[]; -#define DEFAULT_CG_SHADER "hqflt/cg/quad.cg" -#endif - // On resize and fullscreen, rendering area will stay 4:3 static const bool force_aspect = true; -/////////// Video filters (CPU based) -#define FILTER_NONE 0 -#define FILTER_HQ2X 1 -#define FILTER_HQ4X 2 -#define FILTER_GRAYSCALE 3 -#define FILTER_BLEED 4 -#define FILTER_NTSC 5 -//////////////////////// - -// If you change this to something other than FILTER_NONE, make sure that you build the filter module in config.mk. -#define VIDEO_FILTER FILTER_NONE - - //////////////// // Audio //////////////// @@ -124,8 +105,6 @@ static const bool audio_sync = true; #define AXIS_NEG(x) ((uint32_t)(x << 16) | 0xFFFF) #define AXIS_POS(x) ((uint32_t)(x) | 0xFFFF0000U) -#define AXIS_NEG_GET(x) ((x >> 16) & 0xFFFF) -#define AXIS_POS_GET(x) (x & 0xFFFF) #define AXIS_NONE ((uint32_t)0xFFFFFFFFU) // To figure out which joypad buttons to use, check jstest or similar. diff --git a/config.mk.def b/config.mk.def deleted file mode 100644 index 9d677deeae..0000000000 --- a/config.mk.def +++ /dev/null @@ -1,13 +0,0 @@ - -BUILD_OPENGL = 1 -BUILD_CG = 0 -BUILD_FILTER = 0 - -BUILD_RSOUND = 0 -BUILD_OSS = 0 -BUILD_ALSA = 1 -BUILD_ROAR = 0 -BUILD_AL = 0 - - -PREFIX = /usr/local diff --git a/configure b/configure new file mode 100755 index 0000000000..d50a2b8eae --- /dev/null +++ b/configure @@ -0,0 +1,13 @@ +#!/bin/sh + +echo "" + +. qb/config.params.sh + +parse_input "$@" + +. qb/qb.comp.sh +. qb/config.libs.sh + + + diff --git a/driver.c b/driver.c index 5a4fc2f68b..4e694af930 100644 --- a/driver.c +++ b/driver.c @@ -17,9 +17,74 @@ #include "driver.h" -#include "config.h" #include "general.h" #include +#include +#include "hqflt/filters.h" +#include "config.h" + +static const audio_driver_t *audio_drivers[] = { +#ifdef HAVE_ALSA + &audio_alsa, +#endif +#ifdef HAVE_OSS + &audio_oss, +#endif +#ifdef HAVE_RSOUND + &audio_rsound, +#endif +#ifdef HAVE_AL + &audio_openal, +#endif +#ifdef HAVE_ROAR + &audio_roar, +#endif +#ifdef HAVE_JACK + &audio_jack, +#endif +}; + +static const video_driver_t *video_drivers[] = { +#ifdef HAVE_GLFW + &video_gl, +#endif +}; + +static void find_audio_driver(void) +{ + for (int i = 0; i < sizeof(audio_drivers) / sizeof(audio_driver_t*); i++) + { + if (strcasecmp(g_settings.audio.driver, audio_drivers[i]->ident) == 0) + { + driver.audio = audio_drivers[i]; + return; + } + } + SSNES_ERR("Couldn't find any audio driver named \"%s\"\n", g_settings.audio.driver); + fprintf(stderr, "Available audio drivers are:\n"); + for (int i = 0; i < sizeof(audio_drivers) / sizeof(audio_driver_t*); i++) + fprintf(stderr, "\t%s\n", audio_drivers[i]->ident); + + exit(1); +} + +static void find_video_driver(void) +{ + for (int i = 0; i < sizeof(video_drivers) / sizeof(video_driver_t*); i++) + { + if (strcasecmp(g_settings.video.driver, video_drivers[i]->ident) == 0) + { + driver.video = video_drivers[i]; + return; + } + } + SSNES_ERR("Couldn't find any video driver named \"%s\"\n", g_settings.video.driver); + fprintf(stderr, "Available video drivers are:\n"); + for (int i = 0; i < sizeof(video_drivers) / sizeof(video_driver_t*); i++) + fprintf(stderr, "\t%s\n", video_drivers[i]->ident); + + exit(1); +} void init_drivers(void) { @@ -35,73 +100,76 @@ void uninit_drivers(void) void init_audio(void) { - if (!audio_enable) + if (!g_settings.audio.enable) { - audio_active = false; + g_extern.audio_active = false; return; } - driver.audio_data = driver.audio->init(audio_device, out_rate, out_latency); - if ( driver.audio_data == NULL ) - audio_active = false; + find_audio_driver(); - if (!audio_sync && audio_active) + driver.audio_data = driver.audio->init(strlen(g_settings.audio.device) ? g_settings.audio.device : NULL, g_settings.audio.out_rate, g_settings.audio.latency); + if ( driver.audio_data == NULL ) + g_extern.audio_active = false; + + if (!g_settings.audio.sync && g_extern.audio_active) driver.audio->set_nonblock_state(driver.audio_data, true); int err; - source = src_new(SAMPLERATE_QUALITY, 2, &err); - if (!source) - audio_active = false; + g_extern.source = src_new(g_settings.audio.src_quality, 2, &err); + if (!g_extern.source) + g_extern.audio_active = false; } void uninit_audio(void) { - if (!audio_enable) + if (!g_settings.audio.enable) { - audio_active = false; + g_extern.audio_active = false; return; } if ( driver.audio_data && driver.audio ) driver.audio->free(driver.audio_data); - if ( source ) - src_delete(source); + if ( g_extern.source ) + src_delete(g_extern.source); } void init_video_input(void) { - int scale; + int scale = 2; + + find_video_driver(); // We multiply scales with 2 to allow for hi-res games. -#if VIDEO_FILTER == FILTER_NONE - scale = 2; -#elif VIDEO_FILTER == FILTER_HQ2X - scale = 4; -#elif VIDEO_FILTER == FILTER_HQ4X - scale = 8; -#elif VIDEO_FILTER == FILTER_NTSC - scale = 8; -#elif VIDEO_FILTER == FILTER_GRAYSCALE - scale = 2; -#elif VIDEO_FILTER == FILTER_BLEED - scale = 2; -#else - scale = 2; +#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 video_info_t video = { - .width = (fullscreen) ? fullscreen_x : (296 * xscale), - .height = (fullscreen) ? fullscreen_y : (224 * yscale), - .fullscreen = fullscreen, - .vsync = vsync, - .force_aspect = force_aspect, - .smooth = video_smooth, + .width = (g_settings.video.fullscreen) ? g_settings.video.fullscreen_x : (296 * g_settings.video.xscale), + .height = (g_settings.video.fullscreen) ? g_settings.video.fullscreen_y : (224 * g_settings.video.yscale), + .fullscreen = g_settings.video.fullscreen, + .vsync = g_settings.video.vsync, + .force_aspect = g_settings.video.force_aspect, + .smooth = g_settings.video.smooth, .input_scale = scale, }; const input_driver_t *tmp = driver.input; - driver.video_data = driver.video->init(&video, &(driver.input)); + driver.video_data = driver.video->init(&video, &driver.input); if ( driver.video_data == NULL ) { @@ -139,9 +207,9 @@ void uninit_video_input(void) driver.input->free(driver.input_data); } -bool video_active = true; -bool audio_active = true; +driver_t driver; +#if 0 driver_t driver = { #if VIDEO_DRIVER == VIDEO_GL .video = &video_gl, @@ -149,12 +217,12 @@ driver_t driver = { #error "Define a valid video driver in config.h" #endif -#if AUDIO_DRIVER == AUDIO_RSOUND +#if AUDIO_DRIVER == AUDIO_ALSA + .audio = &audio_alsa, +#elif AUDIO_DRIVER == AUDIO_RSOUND .audio = &audio_rsound, #elif AUDIO_DRIVER == AUDIO_OSS .audio = &audio_oss, -#elif AUDIO_DRIVER == AUDIO_ALSA - .audio = &audio_alsa, #elif AUDIO_DRIVER == AUDIO_ROAR .audio = &audio_roar, #elif AUDIO_DRIVER == AUDIO_AL @@ -163,4 +231,5 @@ driver_t driver = { #error "Define a valid audio driver in config.h" #endif }; +#endif diff --git a/driver.h b/driver.h index d5e215da9f..f695746cd3 100644 --- a/driver.h +++ b/driver.h @@ -54,14 +54,19 @@ typedef struct audio_driver bool (*start)(void* data); void (*set_nonblock_state)(void* data, bool toggle); // Should we care about blocking in audio thread? Fast forwarding. void (*free)(void* data); + const char *ident; } audio_driver_t; +#define AXIS_NEG_GET(x) ((x >> 16) & 0xFFFF) +#define AXIS_POS_GET(x) (x & 0xFFFF) +#define AXIS_NONE ((uint32_t)0xFFFFFFFFU) typedef struct input_driver { void* (*init)(void); void (*poll)(void* data); int16_t (*input_state)(void* data, const struct snes_keybind **snes_keybinds, bool port, unsigned device, unsigned index, unsigned id); void (*free)(void* data); + const char *ident; } input_driver_t; typedef struct video_driver @@ -71,6 +76,7 @@ typedef struct video_driver bool (*frame)(void* data, const uint16_t* frame, int width, int height, int pitch); void (*set_nonblock_state)(void* data, bool toggle); // Should we care about syncing to vblank? Fast forwarding. void (*free)(void* data); + const char *ident; } video_driver_t; @@ -92,8 +98,6 @@ void uninit_video_input(void); void init_audio(void); void uninit_audio(void); -extern bool video_active; -extern bool audio_active; extern driver_t driver; //////////////////////////////////////////////// Backends @@ -102,6 +106,7 @@ extern const audio_driver_t audio_oss; extern const audio_driver_t audio_alsa; extern const audio_driver_t audio_roar; extern const audio_driver_t audio_openal; +extern const audio_driver_t audio_jack; extern const video_driver_t video_gl; //////////////////////////////////////////////// diff --git a/dynamic.c b/dynamic.c new file mode 100644 index 0000000000..5c8ff14ceb --- /dev/null +++ b/dynamic.c @@ -0,0 +1,142 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - 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 "dynamic.h" +#include "general.h" +#include +#include "config.h" + +#ifdef HAVE_DYNAMIC +#include + +#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 + +#ifdef HAVE_DYNAMIC +static void *lib_handle = NULL; +#endif + +void (*psnes_init)(void); + +void (*psnes_set_video_refresh)(snes_video_refresh_t); +void (*psnes_set_audio_sample)(snes_audio_sample_t); +void (*psnes_set_input_poll)(snes_input_poll_t); +void (*psnes_set_input_state)(snes_input_state_t); + +void (*psnes_run)(void); + +unsigned (*psnes_library_revision_minor)(void); +unsigned (*psnes_library_revision_major)(void); + +bool (*psnes_load_cartridge_normal)(const char*, const uint8_t*, unsigned); + +unsigned (*psnes_serialize_size)(void); +bool (*psnes_serialize)(uint8_t*, unsigned); +bool (*psnes_unserialize)(const uint8_t*, unsigned); + +void (*psnes_set_cartridge_basename)(const char*); + +uint8_t* (*psnes_get_memory_data)(unsigned); +unsigned (*psnes_get_memory_size)(unsigned); + +void (*psnes_unload_cartridge)(void); +void (*psnes_term)(void); + +#ifdef HAVE_DYNAMIC +static void load_dynamic(void) +{ + SSNES_LOG("Loading dynamic libsnes from: \"%s\"\n", g_settings.libsnes); + lib_handle = dlopen(g_settings.libsnes, RTLD_LAZY); + if (!lib_handle) + { + SSNES_ERR("Failed to open dynamic library: \"%s\"\n", g_settings.libsnes); + exit(1); + } + + SYM(snes_init); + SYM(snes_set_video_refresh); + SYM(snes_set_audio_sample); + SYM(snes_set_input_poll); + SYM(snes_set_input_state); + SYM(snes_library_revision_minor); + SYM(snes_library_revision_major); + SYM(snes_run); + SYM(snes_load_cartridge_normal); + SYM(snes_serialize_size); + SYM(snes_serialize); + SYM(snes_unserialize); + SYM(snes_set_cartridge_basename); + SYM(snes_get_memory_data); + SYM(snes_get_memory_size); + SYM(snes_unload_cartridge); + SYM(snes_term); +} +#endif + +#define SSYM(x) do { \ + p##x = x; \ +} while(0) + +#ifndef HAVE_DYNAMIC +static void set_statics(void) +{ + SSYM(snes_init); + SSYM(snes_set_video_refresh); + SSYM(snes_set_audio_sample); + SSYM(snes_set_input_poll); + SSYM(snes_set_input_state); + SSYM(snes_library_revision_minor); + SSYM(snes_library_revision_major); + SSYM(snes_run); + SSYM(snes_load_cartridge_normal); + SSYM(snes_serialize_size); + SSYM(snes_serialize); + SSYM(snes_unserialize); + SSYM(snes_set_cartridge_basename); + SSYM(snes_get_memory_data); + SSYM(snes_get_memory_size); + SSYM(snes_unload_cartridge); + SSYM(snes_term); +} +#endif + +void init_dlsym(void) +{ +#ifdef HAVE_DYNAMIC + if (strlen(g_settings.libsnes) > 0) + load_dynamic(); + else + { + SSNES_ERR("This binary is built to use runtime dynamic binding of libsnes. Set libsnes_path in config to load a libsnes library dynamically.\n"); + exit(1); + } +#else + set_statics(); +#endif +} + +void uninit_dlsym(void) +{ +#ifdef HAVE_DYNAMIC + if (lib_handle) + dlclose(lib_handle); +#endif +} diff --git a/dynamic.h b/dynamic.h new file mode 100644 index 0000000000..a804da26bc --- /dev/null +++ b/dynamic.h @@ -0,0 +1,54 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - 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 . + */ + +#ifndef __DYNAMIC_H +#define __DYNAMIC_H + +#include +#include + +void init_dlsym(void); +void uninit_dlsym(void); + +extern void (*psnes_init)(void); + +extern void (*psnes_set_video_refresh)(snes_video_refresh_t); +extern void (*psnes_set_audio_sample)(snes_audio_sample_t); +extern void (*psnes_set_input_poll)(snes_input_poll_t); +extern void (*psnes_set_input_state)(snes_input_state_t); + +extern unsigned (*psnes_library_revision_minor)(void); +extern unsigned (*psnes_library_revision_major)(void); + +extern bool (*psnes_load_cartridge_normal)(const char*, const uint8_t*, unsigned); + +extern unsigned (*psnes_serialize_size)(void); +extern bool (*psnes_serialize)(uint8_t*, unsigned); +extern bool (*psnes_unserialize)(const uint8_t*, unsigned); + +extern void (*psnes_run)(void); + +extern void (*psnes_set_cartridge_basename)(const char*); + +extern uint8_t* (*psnes_get_memory_data)(unsigned); +extern unsigned (*psnes_get_memory_size)(unsigned); + +extern void (*psnes_unload_cartridge)(void); +extern void (*psnes_term)(void); + +#endif + diff --git a/file.c b/file.c index 6fc517592c..262eb4983e 100644 --- a/file.c +++ b/file.c @@ -21,6 +21,7 @@ #include #include #include +#include "dynamic.h" ssize_t read_file(FILE* file, void** buf) { @@ -101,7 +102,7 @@ void write_file(const char* path, uint8_t* data, size_t size) if ( file != NULL ) { SSNES_LOG("Saving state \"%s\". Size: %d bytes.\n", path, (int)size); - snes_serialize(data, size); + psnes_serialize(data, size); if ( fwrite(data, 1, size, file) != size ) SSNES_ERR("Did not save state properly.\n"); fclose(file); @@ -118,7 +119,7 @@ void load_state(const char* path, uint8_t* data, size_t size) if ( fread(data, 1, size, file) != size ) SSNES_ERR("Did not load state properly.\n"); fclose(file); - snes_unserialize(data, size); + psnes_unserialize(data, size); } else { @@ -136,8 +137,8 @@ void load_save_file(const char* path, int type) return; } - size_t size = snes_get_memory_size(type); - uint8_t *data = snes_get_memory_data(type); + size_t size = psnes_get_memory_size(type); + uint8_t *data = psnes_get_memory_data(type); if (size == 0 || !data) { @@ -158,8 +159,8 @@ void load_save_file(const char* path, int type) void save_file(const char* path, int type) { - size_t size = snes_get_memory_size(type); - uint8_t *data = snes_get_memory_data(type); + size_t size = psnes_get_memory_size(type); + uint8_t *data = psnes_get_memory_data(type); if ( data && size > 0 ) write_file(path, data, size); diff --git a/general.h b/general.h index 50b071add8..af97152d12 100644 --- a/general.h +++ b/general.h @@ -21,9 +21,75 @@ #include #include +#include "driver.h" +#include + + +#define MAX_PLAYERS 2 +#define MAX_BINDS 14 +struct settings +{ + struct + { + char driver[32]; + float xscale; + float yscale; + bool fullscreen; + unsigned fullscreen_x; + unsigned fullscreen_y; + bool vsync; + bool smooth; + bool force_aspect; + char cg_shader_path[256]; + unsigned filter; + } video; + + struct + { + char driver[32]; + bool enable; + unsigned out_rate; + unsigned in_rate; + char device[256]; + unsigned latency; + bool sync; + int src_quality; + } audio; + + struct + { + char driver[32]; + struct snes_keybind binds[MAX_PLAYERS][MAX_BINDS]; + int save_state_key; + int load_state_key; + int toggle_fullscreen_key; + int exit_emulator_key; + float axis_threshold; + } input; + + char libsnes[256]; +}; + +struct global +{ + bool verbose; + SRC_STATE *source; + bool audio_active; + bool video_active; + + FILE *rom_file; + char savefile_name_srm[256]; + char config_path[256]; + char basename[256]; +}; + +void parse_config(void); + +extern struct settings g_settings; +extern struct global g_extern; #define SSNES_LOG(msg, args...) do { \ - if (verbose) \ + if (g_extern.verbose) \ fprintf(stderr, "SSNES: " msg, ##args); \ } while(0) @@ -31,8 +97,4 @@ fprintf(stderr, "SSNES [ERROR] :: " msg, ##args); \ } while(0) -extern bool verbose; -extern SRC_STATE *source; -extern bool fullscreen; - #endif diff --git a/gfx/gl.c b/gfx/gl.c index 75c0e16f01..3e9b38c351 100644 --- a/gfx/gl.c +++ b/gfx/gl.c @@ -18,13 +18,15 @@ #define GL_GLEXT_PROTOTYPES #include "driver.h" -#include "config.h" #include #include #include #include "libsnes.hpp" #include #include +#include +#include "general.h" +#include "config.h" #ifdef HAVE_CG @@ -49,6 +51,7 @@ static const GLfloat tex_coords[] = { static bool keep_aspect = true; #ifdef HAVE_CG static CGparameter cg_mvp_matrix; +static bool cg_active = false; #endif static GLuint gl_width = 0, gl_height = 0; typedef struct gl @@ -65,6 +68,11 @@ typedef struct gl #endif GLuint texture; GLuint tex_filter; + + unsigned last_width; + unsigned last_height; + unsigned tex_w, tex_h; + GLfloat tex_coords[8]; } gl_t; @@ -116,9 +124,9 @@ static bool glfw_is_pressed(int port_num, const struct snes_keybind *key, unsign if (key->joyaxis != AXIS_NONE) { - if (AXIS_NEG_GET(key->joyaxis) < joypad_axes[port_num] && axes[AXIS_NEG_GET(key->joyaxis)] <= -AXIS_THRESHOLD) + if (AXIS_NEG_GET(key->joyaxis) < joypad_axes[port_num] && axes[AXIS_NEG_GET(key->joyaxis)] <= -g_settings.input.axis_threshold) return true; - if (AXIS_POS_GET(key->joyaxis) < joypad_axes[port_num] && axes[AXIS_POS_GET(key->joyaxis)] >= AXIS_THRESHOLD) + if (AXIS_POS_GET(key->joyaxis) < joypad_axes[port_num] && axes[AXIS_POS_GET(key->joyaxis)] >= g_settings.input.axis_threshold) return true; } return false; @@ -168,7 +176,8 @@ static void glfw_free_input(void *data) static const input_driver_t input_glfw = { .poll = glfw_input_poll, .input_state = glfw_input_state, - .free = glfw_free_input + .free = glfw_free_input, + .ident = "glfw" }; static void GLFWCALL resize(int width, int height) @@ -207,7 +216,8 @@ static void GLFWCALL resize(int width, int height) glMatrixMode(GL_MODELVIEW); glLoadIdentity(); #ifdef HAVE_CG - cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY); + if (cg_active) + cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY); #endif gl_width = out_width; gl_height = out_height; @@ -251,18 +261,43 @@ static bool gl_frame(void *data, const uint16_t* frame, int width, int height, i glClear(GL_COLOR_BUFFER_BIT); #if HAVE_CG - cgGLSetParameter2f(gl->cg_video_size, width, height); - cgGLSetParameter2f(gl->cg_texture_size, width, height); - cgGLSetParameter2f(gl->cg_output_size, gl_width, gl_height); + if (cg_active) + { + cgGLSetParameter2f(gl->cg_video_size, width, height); + cgGLSetParameter2f(gl->cg_texture_size, gl->tex_w, gl->tex_h); + cgGLSetParameter2f(gl->cg_output_size, gl_width, gl_height); - cgGLSetParameter2f(gl->cg_Vvideo_size, width, height); - cgGLSetParameter2f(gl->cg_Vtexture_size, width, height); - cgGLSetParameter2f(gl->cg_Voutput_size, gl_width, gl_height); + cgGLSetParameter2f(gl->cg_Vvideo_size, width, height); + cgGLSetParameter2f(gl->cg_Vtexture_size, gl->tex_w, gl->tex_h); + cgGLSetParameter2f(gl->cg_Voutput_size, gl_width, gl_height); + } #endif + if (width != gl->last_width || height != gl->last_height) // res change. need to clear out texture. + { + gl->last_width = width; + gl->last_height = height; + glPixelStorei(GL_UNPACK_ROW_LENGTH, width); + uint8_t *tmp = calloc(1, gl->tex_w * gl->tex_h * sizeof(uint16_t)); + glTexSubImage2D(GL_TEXTURE_2D, + 0, 0, 0, gl->tex_w, gl->tex_h, GL_BGRA, + GL_UNSIGNED_SHORT_1_5_5_5_REV, tmp); + free(tmp); + + gl->tex_coords[0] = 0; + gl->tex_coords[1] = (GLfloat)height / gl->tex_h; + gl->tex_coords[2] = 0; + gl->tex_coords[3] = 0; + gl->tex_coords[4] = (GLfloat)width / gl->tex_w; + gl->tex_coords[5] = 0; + gl->tex_coords[6] = (GLfloat)width / gl->tex_w; + gl->tex_coords[7] = (GLfloat)height / gl->tex_h; + } + + glPixelStorei(GL_UNPACK_ROW_LENGTH, pitch >> 1); - glTexImage2D(GL_TEXTURE_2D, - 0, GL_RGBA, width, height, 0, GL_BGRA, + glTexSubImage2D(GL_TEXTURE_2D, + 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); glDrawArrays(GL_QUADS, 0, 4); @@ -276,7 +311,8 @@ static void gl_free(void *data) { gl_t *gl = data; #ifdef HAVE_CG - cgDestroyContext(gl->cgCtx); + if (cg_active) + cgDestroyContext(gl->cgCtx); #endif glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); @@ -298,7 +334,7 @@ static void gl_set_nonblock_state(void *data, bool state) static void* gl_init(video_info_t *video, const input_driver_t **input) { - gl_t *gl = malloc(sizeof(gl_t)); + gl_t *gl = calloc(1, sizeof(gl_t)); if ( gl == NULL ) return NULL; @@ -350,47 +386,65 @@ static void* gl_init(video_info_t *video, const input_driver_t **input) glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glVertexPointer(3, GL_FLOAT, 3 * sizeof(GLfloat), vertexes); - glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), tex_coords); + + memcpy(gl->tex_coords, tex_coords, sizeof(tex_coords)); + glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), gl->tex_coords); + + 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(uint16_t)); + 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); + free(tmp); + gl->last_width = gl->tex_w; + gl->last_height = gl->tex_h; #ifdef HAVE_CG - gl->cgCtx = cgCreateContext(); - if (gl->cgCtx == NULL) + cg_active = false; + if (strlen(g_settings.video.cg_shader_path) > 0) { - fprintf(stderr, "Failed to create Cg context\n"); - goto error; - } - gl->cgFProf = cgGLGetLatestProfile(CG_GL_FRAGMENT); - gl->cgVProf = cgGLGetLatestProfile(CG_GL_VERTEX); - if (gl->cgFProf == CG_PROFILE_UNKNOWN || gl->cgVProf == CG_PROFILE_UNKNOWN) - { - fprintf(stderr, "Invalid profile type\n"); - goto error; - } - cgGLSetOptimalOptions(gl->cgFProf); - cgGLSetOptimalOptions(gl->cgVProf); - gl->cgFPrg = cgCreateProgramFromFile(gl->cgCtx, CG_SOURCE, cg_shader_path, gl->cgFProf, "main_fragment", 0); - gl->cgVPrg = cgCreateProgramFromFile(gl->cgCtx, CG_SOURCE, cg_shader_path, gl->cgVProf, "main_vertex", 0); - if (gl->cgFPrg == NULL || gl->cgVPrg == NULL) - { - CGerror err = cgGetError(); - fprintf(stderr, "CG error: %s\n", cgGetErrorString(err)); - goto error; - } - cgGLLoadProgram(gl->cgFPrg); - cgGLLoadProgram(gl->cgVPrg); - cgGLEnableProfile(gl->cgFProf); - cgGLEnableProfile(gl->cgVProf); - cgGLBindProgram(gl->cgFPrg); - cgGLBindProgram(gl->cgVPrg); + SSNES_LOG("Loading Cg file: %s\n", g_settings.video.cg_shader_path); + gl->cgCtx = cgCreateContext(); + if (gl->cgCtx == NULL) + { + fprintf(stderr, "Failed to create Cg context\n"); + goto error; + } + gl->cgFProf = cgGLGetLatestProfile(CG_GL_FRAGMENT); + gl->cgVProf = cgGLGetLatestProfile(CG_GL_VERTEX); + if (gl->cgFProf == CG_PROFILE_UNKNOWN || gl->cgVProf == CG_PROFILE_UNKNOWN) + { + fprintf(stderr, "Invalid profile type\n"); + goto error; + } + cgGLSetOptimalOptions(gl->cgFProf); + cgGLSetOptimalOptions(gl->cgVProf); + gl->cgFPrg = cgCreateProgramFromFile(gl->cgCtx, CG_SOURCE, g_settings.video.cg_shader_path, gl->cgFProf, "main_fragment", 0); + gl->cgVPrg = cgCreateProgramFromFile(gl->cgCtx, CG_SOURCE, g_settings.video.cg_shader_path, gl->cgVProf, "main_vertex", 0); + if (gl->cgFPrg == NULL || gl->cgVPrg == NULL) + { + CGerror err = cgGetError(); + fprintf(stderr, "CG error: %s\n", cgGetErrorString(err)); + goto error; + } + cgGLLoadProgram(gl->cgFPrg); + cgGLLoadProgram(gl->cgVPrg); + cgGLEnableProfile(gl->cgFProf); + cgGLEnableProfile(gl->cgVProf); + cgGLBindProgram(gl->cgFPrg); + cgGLBindProgram(gl->cgVPrg); - gl->cg_video_size = cgGetNamedParameter(gl->cgFPrg, "IN.video_size"); - gl->cg_texture_size = cgGetNamedParameter(gl->cgFPrg, "IN.texture_size"); - gl->cg_output_size = cgGetNamedParameter(gl->cgFPrg, "IN.output_size"); - gl->cg_Vvideo_size = cgGetNamedParameter(gl->cgVPrg, "IN.video_size"); - gl->cg_Vtexture_size = cgGetNamedParameter(gl->cgVPrg, "IN.texture_size"); - gl->cg_Voutput_size = cgGetNamedParameter(gl->cgVPrg, "IN.output_size"); - cg_mvp_matrix = cgGetNamedParameter(gl->cgVPrg, "modelViewProj"); - cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY); + gl->cg_video_size = cgGetNamedParameter(gl->cgFPrg, "IN.video_size"); + gl->cg_texture_size = cgGetNamedParameter(gl->cgFPrg, "IN.texture_size"); + gl->cg_output_size = cgGetNamedParameter(gl->cgFPrg, "IN.output_size"); + gl->cg_Vvideo_size = cgGetNamedParameter(gl->cgVPrg, "IN.video_size"); + gl->cg_Vtexture_size = cgGetNamedParameter(gl->cgVPrg, "IN.texture_size"); + gl->cg_Voutput_size = cgGetNamedParameter(gl->cgVPrg, "IN.output_size"); + cg_mvp_matrix = cgGetNamedParameter(gl->cgVPrg, "modelViewProj"); + cgGLSetStateMatrixParameter(cg_mvp_matrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY); + cg_active = true; + } #endif *input = &input_glfw; @@ -406,7 +460,8 @@ const video_driver_t video_gl = { .init = gl_init, .frame = gl_frame, .set_nonblock_state = gl_set_nonblock_state, - .free = gl_free + .free = gl_free, + .ident = "glfw" }; diff --git a/hqflt/cg/crt.cg b/hqflt/cg/crt.cg index ab89abc478..bee618ad38 100644 --- a/hqflt/cg/crt.cg +++ b/hqflt/cg/crt.cg @@ -52,7 +52,7 @@ output main_fragment(float2 texCoord : TEXCOORD0, uniform sampler2D decal : TEXU float2 rubyTextureSize = IN.texture_size; float2 xy = barrelDistortion(texCoord.xy); - float2 one = 0.999/rubyTextureSize; + float2 one = 1.0/rubyTextureSize; xy = xy + float2(0.0 , -0.5 * (phase + (1-phase) * rubyInputSize.y/rubyOutputSize.y) * one.y); float4 texels[8]; texels[0] = TEX2D(xy + float2(-one.x,0.0)); diff --git a/hqflt/filters.h b/hqflt/filters.h new file mode 100644 index 0000000000..439b3f3727 --- /dev/null +++ b/hqflt/filters.h @@ -0,0 +1,43 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - 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 . + */ + + +#ifndef __FILTERS_H +#define __FILTERS_H + +#include "config.h" + +#ifdef HAVE_FILTER + +#include "pastlib.h" +#include "grayscale.h" +#include "bleed.h" +#include "ntsc.h" + +#define FILTER_HQ2X 1 +#define FILTER_HQ4X 2 +#define FILTER_GRAYSCALE 3 +#define FILTER_BLEED 4 +#define FILTER_NTSC 5 +#define FILTER_HQ2X_STR "hq2x" +#define FILTER_HQ4X_STR "hq4x" +#define FILTER_GRAYSCALE_STR "grayscale" +#define FILTER_BLEED_STR "bleed" +#define FILTER_NTSC_STR "ntsc" +#endif + +#endif diff --git a/qb/conf.comp.sh b/qb/conf.comp.sh new file mode 100644 index 0000000000..cd894adfb6 --- /dev/null +++ b/qb/conf.comp.sh @@ -0,0 +1,2 @@ +USE_LANG_C="yes" +USE_LANG_CXX="yes" diff --git a/qb/config.libs.sh b/qb/config.libs.sh new file mode 100644 index 0000000000..a01eaf03d9 --- /dev/null +++ b/qb/config.libs.sh @@ -0,0 +1,32 @@ +. qb/qb.libs.sh + +check_switch_c C99 -std=gnu99 +check_critical C99 "Cannot find C99 compatible compiler." + +if [ $HAVE_DYNAMIC != yes ]; then + check_lib_cxx SNES $LIBSNES snes_init -ldl + check_critical SNES "Cannot find libsnes." + add_define_make libsnes $LIBSNES +fi + +check_lib ALSA -lasound snd_pcm_open +check_header OSS sys/soundcard.h +check_lib AL -lopenal alcOpenDevice +check_lib RSOUND -lrsound rsd_init +check_lib ROAR -lroar roar_vs_new +check_lib JACK -ljack jack_client_open + +check_lib GLFW -lglfw glfwInit +check_critical GLFW "Cannot find GLFW library." + +check_lib CG -lCg cgCreateContext + +check_lib SRC -lsamplerate src_callback_new + +check_lib DYNAMIC -ldl dlopen + +# Creates config.mk. +VARS="ALSA OSS AL RSOUND ROAR JACK GLFW FILTER CG DYNAMIC" +create_config_make config.mk $VARS +create_config_header config.h $VARS + diff --git a/qb/config.params.sh b/qb/config.params.sh new file mode 100644 index 0000000000..34dc9a88d2 --- /dev/null +++ b/qb/config.params.sh @@ -0,0 +1,19 @@ +. qb/qb.params.sh + +PACKAGE_NAME=ssnes +PACKAGE_VERSION=0.1 + +# Adds a command line opt to ./configure --help +# $1: Variable (HAVE_ALSA, HAVE_OSS, etc) +# $2: Comment +# $3: Default arg. auto implies that HAVE_ALSA will be set according to library checks later on. +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 FILTER "Disable CPU filter support" yes +add_command_line_enable CG "Enable CG shader support" auto +add_command_line_enable ALSA "Enable ALSA support" auto +add_command_line_enable OSS "Enable OSS support" auto +add_command_line_enable RSOUND "Enable RSound support" auto +add_command_line_enable ROAR "Enable RoarAudio support" auto +add_command_line_enable AL "Enable OpenAL support" auto +add_command_line_enable JACK "Enable JACK support" auto diff --git a/qb/qb.comp.sh b/qb/qb.comp.sh new file mode 100644 index 0000000000..12c75a6074 --- /dev/null +++ b/qb/qb.comp.sh @@ -0,0 +1,68 @@ +. qb/conf.comp.sh + +TEMP_C=.tmp.c +TEMP_CXX=.tmp.cxx +TEMP_EXE=.tmp + +echo -n "Checking operating system ... " +OS="Win32" # whatever ;D +unamestr="`uname -o`" +if [ ! -z "`echo $unamestr | grep -i Linux`" ]; then + OS="Linux" +elif [ ! -z "`echo $unamestr | grep -i Darwin`" ]; then + OS="Darwin" +elif [ ! -z "`echo $unamestr | grep -i BSD`" ]; then + OS="BSD" +elif [ ! -z "`echo $unamestr | grep -i NT`" ]; then + OS="Cygwin" +fi + +echo $OS + + +# Checking for working C compiler +if [ "$USE_LANG_C" = yes ]; then + echo "Checking for working C compiler ..." + if [ -z $CC ]; then + CC=`which gcc cc 2> /dev/null | grep ^/ | head -n 1` + fi + if [ -z $CC ]; then + echo "Could not find C compiler in path. Exiting ..." + exit 1 + fi + + echo -n "Checking if $CC is a suitable compiler ... " + answer=no + echo "#include " > $TEMP_C + echo "int main(void) { puts(\"Hai world!\"); return 0; }" >> $TEMP_C + $CC -o $TEMP_EXE $TEMP_C 2>/dev/null >/dev/null && answer=yes + echo $answer + + rm -rf $TEMP_C $TEMP_EXE + + [ $answer = no ] && echo "Can't find suitable C compiler. Exiting ..." && exit 1 +fi + +# Checking for working C++ compiler +if [ "$USE_LANG_CXX" = "yes" ]; then + echo "Checking for working C++ compiler ..." + if [ -z $CXX ]; then + CXX=`which g++ c++ 2> /dev/null | grep ^/ | head -n 1` + fi + if [ -z $CXX ]; then + echo "Could not find C compiler in path. Exiting ..." + exit 1 + fi + + echo -n "Checking if $CXX is a suitable compiler ... " + answer=no + echo "#include " > $TEMP_CXX + echo "int main() { std::cout << \"Hai guise\" << std::endl; return 0; }" >> $TEMP_CXX + $CXX -o $TEMP_EXE $TEMP_CXX 2>/dev/null >/dev/null && answer=yes + echo $answer + + rm -rf $TEMP_CXX $TEMP_EXE + + [ $answer = no ] && echo "Can't find suitable C++ compiler. Exiting ..." && exit 1 +fi + diff --git a/qb/qb.libs.sh b/qb/qb.libs.sh new file mode 100644 index 0000000000..ab8585fc83 --- /dev/null +++ b/qb/qb.libs.sh @@ -0,0 +1,313 @@ + +PKG_CONF_PATH="" +PKG_CONF_USED="" +CONFIG_DEFINES="" +MAKEFILE_DEFINES="" +INCLUDE_DIRS="" +LIBRARY_DIRS="" +[ -z "$PREFIX" ] && PREFIX="/usr/local" + +add_define_header() +{ + CONFIG_DEFINES="$CONFIG_DEFINES:@$1@$2@:" +} + +add_define_make() +{ + MAKEFILE_DEFINES="$MAKEFILE_DEFINES:@$1@$2@:" +} + +add_include_dirs() +{ + while [ ! -z "$1" ] + do + INCLUDE_DIRS="$INCLUDE_DIRS -I$1" + shift + done +} + +add_library_dirs() +{ + while [ ! -z "$1" ] + do + LIBRARY_DIRS="$LIBRARY_DIRS -L$1" + shift + done +} + +check_lib() +{ + tmpval="HAVE_$1" + eval tmpval=\$$tmpval + [ "$tmpval" = "no" ] && return 0 + + echo -n "Checking function $3 in $2 ... " + echo "void $3(void); int main(void) { $3(); return 0; }" > $TEMP_C + + + eval HAVE_$1=no + answer=no + + extralibs="$4" + + $CC -o $TEMP_EXE $TEMP_C $INCLUDE_DIRS $LIBRARY_DIRS $extralibs $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes + + echo $answer + + rm -rf $TEMP_C $TEMP_EXE + if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then + echo "Forced to build with library $2, but cannot locate. Exiting ..." + exit 1 + fi +} + +check_lib_cxx() +{ + tmpval="HAVE_$1" + eval tmpval=\$$tmpval + [ "$tmpval" = "no" ] && return 0 + + echo -n "Checking function $3 in $2 ... " + echo "extern \"C\" { void $3(void); } int main() { $3(); }" > $TEMP_CXX + + eval HAVE_$1=no + answer=no + + extralibs="$4" + + $CXX -o $TEMP_EXE $TEMP_CXX $INCLUDE_DIRS $LIBRARY_DIRS $extralibs $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes + + echo $answer + + rm -rf $TEMP_CXX $TEMP_EXE + if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then + echo "Forced to build with library $2, but cannot locate. Exiting ..." + exit 1 + fi +} + +locate_pkg_conf() +{ + echo -n "Checking for pkg-config ... " + PKG_CONF_PATH="`which pkg-config | grep ^/ | head -n1`" + if [ -z $PKG_CONF_PATH ]; then + echo "not found" + echo "Cannot locate pkg-config. Exiting ..." + exit 1 + fi + echo "$PKG_CONF_PATH" +} + +check_pkgconf() +{ + [ -z "$PKG_CONF_PATH" ] && locate_pkg_conf + + tmpval="HAVE_$1" + eval tmpval=\$$tmpval + [ "$tmpval" = "no" ] && return 0 + + echo -n "Checking presence of package $2 ... " + eval HAVE_$1=no + eval $1_CFLAGS="" + eval $1_LIBS="" + answer=no + minver=0.0 + [ ! -z $3 ] && minver=$3 + pkg-config --atleast-version=$minver --exists "$2" && eval HAVE_$1=yes && eval $1_CFLAGS='"`pkg-config $2 --cflags`"' && eval $1_LIBS='"`pkg-config $2 --libs`"' && answer=yes + echo $answer + + PKG_CONF_USED="$PKG_CONF_USED $1" + + if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then + echo "Forced to build with package $2, but cannot locate. Exiting ..." + exit 1 + fi +} + +check_header() +{ + tmpval="HAVE_$1" + eval tmpval=\$$tmpval + [ "$tmpval" = "no" ] && return 0 + + echo -n "Checking presence of header file $2 ... " + echo "#include<$2>" > $TEMP_C + echo "int main(void) { return 0; }" >> $TEMP_C + eval HAVE_$1=no + answer=no + + $CC -o $TEMP_EXE $TEMP_C $INCLUDE_DIRS 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes + + echo $answer + + rm -rf $TEMP_C $TEMP_EXE + if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then + echo "Build assumed that $2 exists, but cannot locate. Exiting ..." + exit 1 + fi +} + +check_switch_c() +{ + echo -n "Checking for availability of switch $2 in $CC ... " + if [ -z "$CC" ]; then + echo "No C compiler, cannot check ..." + exit 1 + fi + echo "int main(void) { return 0; }" > $TEMP_C + eval HAVE_$1=no + answer=no + $CC -o $TEMP_EXE $TEMP_C $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes + + echo $answer + + rm -rf $TEMP_C $TEMP_EXE +} + +check_switch_cxx() +{ + echo -n "Checking for availability of switch $2 in $CXX ... " + if [ -z "$CXX" ]; then + echo "No C++ compiler, cannot check ..." + exit 1 + fi + echo "int main() { return 0; }" > $TEMP_CXX + eval HAVE_$1=no + answer=no + $CXX -o $TEMP_EXE $TEMP_CXX $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes + + echo $answer + + rm -rf $TEMP_CXX $TEMP_EXE +} + +check_critical() +{ + val=HAVE_$1 + eval val=\$$val + if [ "$val" != "yes" ]; then + echo "$2" + exit 1 + fi +} + +output_define_header() +{ + arg1="`echo $2 | sed 's|^@\([^@]*\)@\([^@]*\)@$|\1|'`" + arg2="`echo $2 | sed 's|^@\([^@]*\)@\([^@]*\)@$|\2|'`" + + echo "#define $arg1 $arg2" >> "$outfile" +} + +create_config_header() +{ + outfile="$1" + shift + + echo "Creating config header: $outfile" + + name="`echo __$outfile | sed 's|[\./]|_|g' | tr '[a-z]' '[A-Z]'`" + echo "#ifndef $name" > "$outfile" + echo "#define $name" >> "$outfile" + echo "" >> "$outfile" + echo "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >> "$outfile" + echo "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >> "$outfile" + + while [ ! -z "$1" ] + do + tmpval="HAVE_$1" + eval tmpval=\$$tmpval + if [ "$tmpval" = "yes" ]; then + echo "#define HAVE_$1 1" >> "$outfile" + elif [ "$tmpval" = "no" ]; then + echo "/* #undef HAVE_$1 */" >> "$outfile" + fi + + shift + done + + echo "" >> "$outfile" + + tmpdefs="$CONFIG_DEFINES" + while [ ! -z "$tmpdefs" ] + do + subdefs="`echo $tmpdefs | sed 's|^:\(@[^@]*@[^@]*@\):.*$|\1|'`" + tmpdefs="`echo $tmpdefs | sed 's|^\W*$||'`" + tmpdefs="`echo $tmpdefs | sed 's|^:\(@[^@]*@[^@]*@\):||'`" + output_define_header "$outfile" "$subdefs" + done + + echo "#endif" >> "$outfile" +} + +output_define_make() +{ + arg1="`echo $2 | sed 's|^@\([^@]*\)@\([^@]*\)@$|\1|'`" + arg2="`echo $2 | sed 's|^@\([^@]*\)@\([^@]*\)@$|\2|'`" + + echo "$arg1 = $arg2" >> "$outfile" +} + +create_config_make() +{ + + outfile="$1" + shift + + echo "Creating make config: $outfile" + + rm -rf "$outfile" + touch "$outfile" + if [ "$USE_LANG_C" = "yes" ]; then + echo "CC = $CC" >> "$outfile" + echo "CFLAGS = $CFLAGS" >> "$outfile" + fi + if [ "$USE_LANG_CXX" = "yes" ]; then + echo "CXX = $CXX" >> "$outfile" + echo "CXXFLAGS = $CXXFLAGS" >> "$outfile" + fi + echo "LDFLAGS = $LDFLAGS" >> "$outfile" + echo "INCLUDE_DIRS = $INCLUDE_DIRS" >> "$outfile" + echo "LIBRARY_DIRS = $LIBRARY_DIRS" >> "$outfile" + echo "PACKAGE_NAME = $PACKAGE_NAME" >> "$outfile" + echo "PACKAGE_VERSION = $PACKAGE_VERSION" >> "$outfile" + echo "PREFIX = $PREFIX" >> "$outfile" + + while [ ! -z "$1" ] + do + tmpval="HAVE_$1" + eval tmpval=\$$tmpval + if [ "$tmpval" = yes ]; then + echo "HAVE_$1 = 1" >> "$outfile" + elif [ "$tmpval" = no ]; then + echo "HAVE_$1 = 0" >> "$outfile" + fi + + if [ ! -z "`echo $PKG_CONF_USED | grep $1`" ]; then + tmpval="$1_CFLAGS" + eval tmpval=\$$tmpval + echo "$1_CFLAGS = $tmpval" >> "$outfile" + + tmpval="$1_LIBS" + eval tmpval=\$$tmpval + echo "$1_LIBS = $tmpval" >> "$outfile" + fi + + + shift + done + + echo "" >> "$outfile" + + tmpdefs="$MAKEFILE_DEFINES" + while [ ! -z "$tmpdefs" ] + do + subdefs="`echo $tmpdefs | sed 's|^:\(@[^@]*@[^@]*@\):.*$|\1|'`" + tmpdefs="`echo $tmpdefs | sed 's|^\W*$||'`" + tmpdefs="`echo $tmpdefs | sed 's|^:\(@[^@]*@[^@]*@\):||'`" + output_define_make "$outfile" "$subdefs" + done + +} + + diff --git a/qb/qb.params.sh b/qb/qb.params.sh new file mode 100644 index 0000000000..231310d8e5 --- /dev/null +++ b/qb/qb.params.sh @@ -0,0 +1,150 @@ +COMMAND_LINE_OPTS_ENABLE="" + +add_command_line_enable() +{ + COMMAND_LINE_OPTS_ENABLE="$COMMAND_LINE_OPTS_ENABLE:\"$1\" \"$2\" \"$3\":" + eval HAVE_$1=$3 +} + +add_command_line_string() +{ + COMMAND_LINE_OPTS_STRINGS="$COMMAND_LINE_OPTS_STRINGS:\"$1\" \"$2\" \"$3\":" + eval $1=$3 +} + +## lvl. 43 regex dragon awaits thee. +print_help() +{ + echo "====================" + echo " Quickbuild script" + echo "====================" + echo "Package: $PACKAGE_NAME" + echo "Version: $PACKAGE_VERSION" + echo "" + echo "General environment variables:" + echo "CC: C compiler" + echo "CFLAGS: C compiler flags" + echo "CXX: C++ compiler" + echo "CXXFLAGS: C++ compiler flags" + echo "LDFLAGS: Linker flags" + echo "" + echo "General options:" + echo "--prefix=\$path: Install path prefix" + echo "--help: Show this help" + echo "" + echo "Custom options:" + + tmpopts="$COMMAND_LINE_OPTS_ENABLE" + while [ ! -z "$tmpopts" ] + do + subopts="`echo $tmpopts | sed 's|^:"\([^"]*\)"."\([^"]*\)"."\([^"]*\)":.*$|"\1":"\2":"\3"|'`" + tmpopts="`echo $tmpopts | sed 's|^\W*$||'`" + tmpopts="`echo $tmpopts | sed 's|^:"[^"]*"."[^"]*"."[^"]*":||'`" + print_sub_opt "$subopts" + done + + echo "" + + tmpopts="$COMMAND_LINE_OPTS_STRINGS" + while [ ! -z "$tmpopts" ] + do + subopts="`echo $tmpopts | sed 's|^:"\([^"]*\)"."\([^"]*\)"."\([^"]*\)":.*$|"\1":"\2":"\3"|'`" + tmpopts="`echo $tmpopts | sed 's|^\W*$||'`" + tmpopts="`echo $tmpopts | sed 's|^:"[^"]*"."[^"]*"."[^"]*":||'`" + print_sub_str_opt "$subopts" + done +} + +print_sub_opt() +{ + arg1="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\1|'`" + arg2="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\2|'`" + arg3="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\3|'`" + + lowertext="`echo $arg1 | tr '[A-Z]' '[a-z]'`" + + if [ "$arg3" = "auto" ]; then + echo -n "--enable-$lowertext: " + echo $arg2 + echo "--disable-$lowertext" + elif [ "$arg3" = "yes" ]; then + echo "--disable-$lowertext: $arg2" + elif [ "$arg3" = "no" ]; then + echo "--enable-$lowertext: $arg2" + fi +} + +print_sub_str_opt() +{ + arg1="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\1|'`" + arg2="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\2|'`" + arg3="`echo $1 | sed 's|^"\([^"]*\)":"\([^"]*\)":"\([^"]*\)"$|\3|'`" + + lowertext="`echo $arg1 | tr '[A-Z]' '[a-z]'`" + + echo "--with-$lowertext: $arg2 (Defaults: $arg3)" +} + +parse_input() +{ + ### Parse stuff :V + + while [ ! -z "$1" ] + do + + case "$1" in + + --prefix=*) + prefix="`echo $1 | sed -e 's|^--prefix=\(\S\S*\)$|\1|' -e 's|\(\S\S*\)/$|\1|'`" + + if [ "$prefix" != "$1" ]; then + PREFIX="$prefix" + fi + ;; + + --enable-*) + enable=`echo $1 | sed 's|^--enable-||'` + if [ -z "`echo $COMMAND_LINE_OPTS_ENABLE | grep -i $enable`" ]; then + print_help + exit 1 + fi + eval HAVE_`echo $enable | tr '[a-z]' '[A-Z]'`=yes + ;; + + --disable-*) + disable=`echo $1 | sed 's|^--disable-||'` + if [ -z "`echo $COMMAND_LINE_OPTS_ENABLE | grep -i $disable`" ]; then + print_help + exit 1 + fi + eval HAVE_`echo $disable | tr '[a-z]' '[A-Z]'`=no + ;; + + --with-*) + arg="`echo $1 | sed 's|^--with-\S\S*=||'`" + with=`echo $1 | sed 's|^--with-\(\S\S*\)=.*$|\1|'` + if [ -z "`echo $COMMAND_LINE_OPTS_STRINGS | grep -i $with`" ]; then + print_help + exit 1 + fi + eval "`echo $with | tr '[a-z]' '[A-Z]'`=\"$arg\"" + ;; + + + -h|--help) + print_help + exit 0 + ;; + *) + print_help + exit 1 + ;; + + esac + + shift + + done +} + + diff --git a/settings.c b/settings.c new file mode 100644 index 0000000000..0288dd3dd6 --- /dev/null +++ b/settings.c @@ -0,0 +1,461 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - 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 "general.h" +#include "conf/config_file.h" +#include "config.def.h" +#include +#include +#include "hqflt/filters.h" +#include "config.h" +#include + +struct settings g_settings; + +static void read_keybinds(config_file_t *conf); + +static void set_defaults(void) +{ + const char *def_video = NULL; + const char *def_audio = NULL; + + switch (VIDEO_DEFAULT_DRIVER) + { + case VIDEO_GL: + def_video = "glfw"; + break; + default: + break; + } + + switch (AUDIO_DEFAULT_DRIVER) + { + case AUDIO_RSOUND: + def_audio = "rsound"; + break; + case AUDIO_OSS: + def_audio = "oss"; + break; + case AUDIO_ALSA: + def_audio = "alsa"; + break; + case AUDIO_ROAR: + def_audio = "roar"; + break; + case AUDIO_AL: + def_audio = "openal"; + break; + default: + break; + } + + // No input atm ... It is in the GLFW driver. + + if (def_video) + strncpy(g_settings.video.driver, def_video, sizeof(g_settings.video.driver) - 1); + if (def_audio) + strncpy(g_settings.audio.driver, def_audio, sizeof(g_settings.audio.driver) - 1); + + g_settings.video.xscale = xscale; + g_settings.video.yscale = yscale; + g_settings.video.fullscreen = fullscreen; + g_settings.video.fullscreen_x = fullscreen_x; + g_settings.video.fullscreen_y = fullscreen_y; + g_settings.video.vsync = vsync; + g_settings.video.smooth = video_smooth; + g_settings.video.force_aspect = force_aspect; + + g_settings.audio.enable = audio_enable; + g_settings.audio.out_rate = out_rate; + g_settings.audio.in_rate = in_rate; + if (audio_device) + strncpy(g_settings.audio.device, audio_device, sizeof(g_settings.audio.device)); + g_settings.audio.latency = out_latency; + g_settings.audio.sync = audio_sync; + g_settings.audio.src_quality = SAMPLERATE_QUALITY; + + assert(sizeof(g_settings.input.binds[0]) >= sizeof(snes_keybinds_1)); + assert(sizeof(g_settings.input.binds[1]) >= sizeof(snes_keybinds_2)); + memcpy(g_settings.input.binds[0], snes_keybinds_1, sizeof(snes_keybinds_1)); + memcpy(g_settings.input.binds[1], snes_keybinds_2, sizeof(snes_keybinds_2)); + + g_settings.input.save_state_key = SAVE_STATE_KEY; + g_settings.input.load_state_key = LOAD_STATE_KEY; + g_settings.input.toggle_fullscreen_key = TOGGLE_FULLSCREEN; + g_settings.input.axis_threshold = AXIS_THRESHOLD; + g_settings.input.exit_emulator_key = GLFW_KEY_ESC; +} + +void parse_config(void) +{ + memset(&g_settings, 0, sizeof(struct settings)); + config_file_t *conf = NULL; + + if (strlen(g_extern.config_path) > 0) + { + conf = config_file_new(g_extern.config_path); + if (!conf) + { + SSNES_ERR("Couldn't find config at path: \"%s\"\n", g_extern.config_path); + exit(1); + } + } + else + { + const char *xdg = getenv("XDG_CONFIG_HOME"); + const char *home = getenv("HOME"); + if (xdg) + { + char conf_path[strlen(xdg) + strlen("/ssnes/ssnes.cfg ")]; + strcpy(conf_path, xdg); + strcat(conf_path, "/ssnes/ssnes.cfg"); + conf = config_file_new(conf_path); + } + else if (home) + { + char conf_path[strlen(home) + strlen("/.ssnesrc ")]; + strcpy(conf_path, xdg); + strcat(conf_path, "/.ssnesrc"); + conf = config_file_new(conf_path); + } + // Try this as a last chance... + if (!conf) + conf = config_file_new("/etc/ssnes.cfg"); + } + + set_defaults(); + if (conf == NULL) + return; + + int tmp_int; + double tmp_double; + bool tmp_bool; + char *tmp_str; + + // Video settings. + if (config_get_double(conf, "video_xscale", &tmp_double)) + g_settings.video.xscale = tmp_double; + + if (config_get_double(conf, "video_yscale", &tmp_double)) + g_settings.video.yscale = tmp_double; + + if (config_get_int(conf, "video_fullscreen_x", &tmp_int)) + g_settings.video.fullscreen_x = tmp_int; + + if (config_get_int(conf, "video_fullscreen_y", &tmp_int)) + g_settings.video.fullscreen_y = tmp_int; + + if (config_get_bool(conf, "video_fullscreen", &tmp_bool)) + g_settings.video.fullscreen = tmp_bool; + + if (config_get_bool(conf, "video_vsync", &tmp_bool)) + g_settings.video.vsync = tmp_bool; + + if (config_get_bool(conf, "video_smooth", &tmp_bool)) + g_settings.video.smooth = tmp_bool; + + if (config_get_bool(conf, "video_force_aspect", &tmp_bool)) + g_settings.video.force_aspect = tmp_bool; + + if (config_get_string(conf, "video_cg_shader", &tmp_str)) + { + strncpy(g_settings.video.cg_shader_path, tmp_str, sizeof(g_settings.video.cg_shader_path) - 1); + free(tmp_str); + } + +#ifdef HAVE_FILTER + if (config_get_string(conf, "video_filter", &tmp_str)) + { + unsigned filter = 0; + if (strcasecmp(FILTER_HQ2X_STR, tmp_str) == 0) + filter = FILTER_HQ2X; + else if (strcasecmp(FILTER_HQ4X_STR, tmp_str) == 0) + filter = FILTER_HQ4X; + else if (strcasecmp(FILTER_GRAYSCALE_STR, tmp_str) == 0) + filter = FILTER_GRAYSCALE; + else if (strcasecmp(FILTER_BLEED_STR, tmp_str) == 0) + filter = FILTER_BLEED; + else if (strcasecmp(FILTER_NTSC_STR, tmp_str) == 0) + filter = FILTER_NTSC; + else + { + SSNES_ERR( + "Invalid filter... Valid filters are:\n" + "\t%s\n" + "\t%s\n" + "\t%s\n" + "\t%s\n" + "\t%s\n", + FILTER_HQ2X_STR, FILTER_HQ4X_STR, FILTER_GRAYSCALE_STR, + FILTER_BLEED_STR, FILTER_NTSC_STR); + exit(1); + } + + free(tmp_str); + g_settings.video.filter = filter; + } +#endif + + // Input Settings. + if (config_get_double(conf, "input_axis_threshold", &tmp_double)) + g_settings.input.axis_threshold = tmp_double; + + // Audio settings. + if (config_get_bool(conf, "audio_enable", &tmp_bool)) + g_settings.audio.enable = tmp_bool; + + if (config_get_int(conf, "audio_out_rate", &tmp_int)) + g_settings.audio.out_rate = tmp_int; + + if (config_get_int(conf, "audio_in_rate", &tmp_int)) + g_settings.audio.in_rate = tmp_int; + + if (config_get_string(conf, "audio_device", &tmp_str)) + { + strncpy(g_settings.audio.device, tmp_str, sizeof(g_settings.audio.device) - 1); + free(tmp_str); + } + + if (config_get_int(conf, "audio_latency", &tmp_int)) + g_settings.audio.latency = tmp_int; + + if (config_get_bool(conf, "audio_sync", &tmp_bool)) + g_settings.audio.sync = tmp_bool; + + if (config_get_int(conf, "audio_src_quality", &tmp_int)) + { + int quals[] = { SRC_ZERO_ORDER_HOLD, SRC_LINEAR, SRC_SINC_FASTEST, + SRC_SINC_MEDIUM_QUALITY, SRC_SINC_BEST_QUALITY }; + + if (tmp_int > 0 && tmp_int < 6) + g_settings.audio.src_quality = quals[tmp_int]; + } + + if (config_get_string(conf, "video_driver", &tmp_str)) + { + strncpy(g_settings.video.driver, tmp_str, sizeof(g_settings.video.driver) - 1); + free(tmp_str); + } + if (config_get_string(conf, "audio_driver", &tmp_str)) + { + strncpy(g_settings.audio.driver, tmp_str, sizeof(g_settings.audio.driver) - 1); + free(tmp_str); + } + if (config_get_string(conf, "libsnes_path", &tmp_str)) + { + strncpy(g_settings.libsnes, tmp_str, sizeof(g_settings.libsnes) - 1); + free(tmp_str); + } + + read_keybinds(conf); + + // TODO: Keybinds. + + config_file_free(conf); +} + +struct bind_map +{ + const char *key; + const char *btn; + const char *axis; + int snes_key; +}; + +// Big and nasty bind map... :) +static const struct bind_map bind_maps[2][13] = { + { + { "input_player1_a", "input_player1_a_btn", NULL, SNES_DEVICE_ID_JOYPAD_A }, + { "input_player1_b", "input_player1_b_btn", NULL, SNES_DEVICE_ID_JOYPAD_B }, + { "input_player1_y", "input_player1_y_btn", NULL, SNES_DEVICE_ID_JOYPAD_Y }, + { "input_player1_x", "input_player1_x_btn", NULL, SNES_DEVICE_ID_JOYPAD_X }, + { "input_player1_start", "input_player1_start_btn", NULL, SNES_DEVICE_ID_JOYPAD_START }, + { "input_player1_select", "input_player1_select_btn", NULL, SNES_DEVICE_ID_JOYPAD_SELECT }, + { "input_player1_l", "input_player1_l_btn", NULL, SNES_DEVICE_ID_JOYPAD_L }, + { "input_player1_r", "input_player1_r_btn", NULL, SNES_DEVICE_ID_JOYPAD_R }, + { "input_player1_left", "input_player1_left_btn", "input_player1_left_axis", SNES_DEVICE_ID_JOYPAD_LEFT }, + { "input_player1_right", "input_player1_right_btn", "input_player1_right_axis", SNES_DEVICE_ID_JOYPAD_RIGHT }, + { "input_player1_up", "input_player1_up_btn", "input_player1_up_axis", SNES_DEVICE_ID_JOYPAD_UP }, + { "input_player1_down", "input_player1_down_btn", "input_player1_down_axis", SNES_DEVICE_ID_JOYPAD_DOWN }, + { "input_toggle_fast_forward", "input_toggle_fast_forward_btn", NULL, SNES_FAST_FORWARD_KEY } + }, + { + { "input_player2_a", "input_player2_a_btn", NULL, SNES_DEVICE_ID_JOYPAD_A }, + { "input_player2_b", "input_player2_b_btn", NULL, SNES_DEVICE_ID_JOYPAD_B }, + { "input_player2_y", "input_player2_y_btn", NULL, SNES_DEVICE_ID_JOYPAD_Y }, + { "input_player2_x", "input_player2_x_btn", NULL, SNES_DEVICE_ID_JOYPAD_X }, + { "input_player2_start", "input_player2_start_btn", NULL, SNES_DEVICE_ID_JOYPAD_START }, + { "input_player2_select", "input_player2_select_btn", NULL, SNES_DEVICE_ID_JOYPAD_SELECT }, + { "input_player2_l", "input_player2_l_btn", NULL, SNES_DEVICE_ID_JOYPAD_L }, + { "input_player2_r", "input_player2_r_btn", NULL, SNES_DEVICE_ID_JOYPAD_R }, + { "input_player2_left", "input_player2_left_btn", "input_player2_left_axis", SNES_DEVICE_ID_JOYPAD_LEFT }, + { "input_player2_right", "input_player2_right_btn", "input_player2_right_axis", SNES_DEVICE_ID_JOYPAD_RIGHT }, + { "input_player2_up", "input_player2_up_btn", "input_player2_up_axis", SNES_DEVICE_ID_JOYPAD_UP }, + { "input_player2_down", "input_player2_down_btn", "input_player2_down_axis", SNES_DEVICE_ID_JOYPAD_DOWN }, + { "input_toggle_fast_forward", "input_toggle_fast_forward_btn", NULL, SNES_FAST_FORWARD_KEY } + } +}; + +struct glfw_map +{ + const char *str; + int key; +}; + +// Edit: Not portable to different input systems atm. Might move this map into the driver itself or something. +static const struct glfw_map glfw_map[] = { + { "left", GLFW_KEY_LEFT }, + { "right", GLFW_KEY_RIGHT }, + { "up", GLFW_KEY_UP }, + { "down", GLFW_KEY_DOWN }, + { "enter", GLFW_KEY_ENTER }, + { "tab", GLFW_KEY_TAB }, + { "insert", GLFW_KEY_INSERT }, + { "del", GLFW_KEY_DEL }, + { "rshift", GLFW_KEY_RSHIFT }, + { "shift", GLFW_KEY_LSHIFT }, + { "ctrl", GLFW_KEY_LCTRL }, + { "alt", GLFW_KEY_LALT }, + { "space", GLFW_KEY_SPACE }, + { "escape", GLFW_KEY_ESC }, + { "f1", GLFW_KEY_F1 }, + { "f2", GLFW_KEY_F2 }, + { "f3", GLFW_KEY_F3 }, + { "f4", GLFW_KEY_F4 }, + { "f5", GLFW_KEY_F5 }, + { "f6", GLFW_KEY_F6 }, + { "f7", GLFW_KEY_F7 }, + { "f8", GLFW_KEY_F8 }, + { "f9", GLFW_KEY_F9 }, + { "f10", GLFW_KEY_F10 }, + { "f11", GLFW_KEY_F11 }, + { "f12", GLFW_KEY_F12 }, +}; + +static struct snes_keybind *find_snes_bind(unsigned port, int id) +{ + struct snes_keybind *binds = g_settings.input.binds[port]; + + for (int i = 0; binds[i].id != -1; i++) + { + if (id == binds[i].id) + return &binds[i]; + } + return NULL; +} + +static int find_glfw_bind(const char *str) +{ + for (int i = 0; i < sizeof(glfw_map)/sizeof(struct glfw_map); i++) + { + if (strcasecmp(glfw_map[i].str, str) == 0) + return glfw_map[i].key; + } + return -1; +} + +static int find_glfw_key(const char *str) +{ + // If the bind is a normal key-press ... + if (strlen(str) == 1 && isalpha(*str)) + return toupper(*str); + else // Check if we have a special mapping for it. + return find_glfw_bind(str); +} + +static void read_keybinds(config_file_t *conf) +{ + char *tmp_key = NULL; + int tmp_btn; + char *tmp_axis = NULL; + + for (int j = 0; j < 1; j++) + { + for (int i = 0; i < sizeof(bind_maps[j])/sizeof(struct bind_map); i++) + { + struct snes_keybind *bind = find_snes_bind(j, bind_maps[j][i].snes_key); + if (!bind) + continue; + + if (bind_maps[j][i].key && config_get_string(conf, bind_maps[j][i].key, &tmp_key)) + { + int key = find_glfw_key(tmp_key); + + if (key >= 0) + bind->key = key; + + free(tmp_key); + tmp_key = NULL; + } + + if (bind_maps[j][i].btn && config_get_int(conf, bind_maps[j][i].btn, &tmp_btn)) + { + if (tmp_btn >= 0) + bind->joykey = tmp_btn; + } + + if (bind_maps[j][i].axis && config_get_string(conf, bind_maps[j][i].axis, &tmp_axis)) + { + if (strlen(tmp_axis) >= 2 && (*tmp_axis == '+' || *tmp_axis == '-')) + { + int axis = strtol(tmp_axis + 1, NULL, 0); + if (*tmp_axis == '+') + bind->joyaxis = AXIS_POS(axis); + else + bind->joyaxis = AXIS_NEG(axis); + + } + free(tmp_axis); + tmp_axis = NULL; + } + } + } + + char *tmp_str; + if (config_get_string(conf, "input_toggle_fullscreen", &tmp_str)) + { + int key = find_glfw_key(tmp_str); + if (key >= 0) + g_settings.input.toggle_fullscreen_key = key; + free(tmp_str); + } + if (config_get_string(conf, "input_save_state", &tmp_str)) + { + int key = find_glfw_key(tmp_str); + if (key >= 0) + g_settings.input.save_state_key = key; + free(tmp_str); + } + if (config_get_string(conf, "input_load_state", &tmp_str)) + { + int key = find_glfw_key(tmp_str); + if (key >= 0) + g_settings.input.load_state_key = key; + free(tmp_str); + } + if (config_get_string(conf, "input_exit_emulator", &tmp_str)) + { + int key = find_glfw_key(tmp_str); + if (key >= 0) + g_settings.input.exit_emulator_key = key; + free(tmp_str); + } +} + + + + diff --git a/ssnes.c b/ssnes.c index 7ed812dd08..cc0c518b10 100644 --- a/ssnes.c +++ b/ssnes.c @@ -24,15 +24,16 @@ #include #include #include -#include "config.h" #include "driver.h" #include "file.h" -#include "hqflt/pastlib.h" -#include "hqflt/grayscale.h" -#include "hqflt/bleed.h" -#include "hqflt/ntsc.h" +#include "hqflt/filters.h" #include "general.h" +#include "dynamic.h" +struct global g_extern = { + .video_active = true, + .audio_active = true, +}; // To avoid continous switching if we hold the button down, we require that the button must go from pressed, unpressed back to pressed to be able to toggle between then. @@ -46,10 +47,10 @@ void set_fast_forward_button(bool new_button_state) if (new_button_state && !old_button_state) { syncing_state = !syncing_state; - if (video_active) + if (g_extern.video_active) driver.video->set_nonblock_state(driver.video_data, syncing_state); - if (audio_active) - driver.audio->set_nonblock_state(driver.audio_data, (audio_sync) ? syncing_state : true); + if (g_extern.audio_active) + driver.audio->set_nonblock_state(driver.audio_data, (g_settings.audio.sync) ? syncing_state : true); if (syncing_state) audio_chunk_size = AUDIO_CHUNK_SIZE_NONBLOCKING; else @@ -58,7 +59,7 @@ void set_fast_forward_button(bool new_button_state) old_button_state = new_button_state; } -#if VIDEO_FILTER != FILTER_NONE +#ifdef HAVE_FILTER static inline void process_frame (uint16_t * restrict out, const uint16_t * restrict in, unsigned width, unsigned height) { int pitch = 1024; @@ -79,53 +80,54 @@ static inline void process_frame (uint16_t * restrict out, const uint16_t * rest // Format received is 16-bit 0RRRRRGGGGGBBBBB static void video_frame(const uint16_t *data, unsigned width, unsigned height) { - if ( !video_active ) + if ( !g_extern.video_active ) return; -#if VIDEO_FILTER == FILTER_HQ2X - uint16_t outputHQ2x[width * height * 2 * 2]; -#elif VIDEO_FILTER == FILTER_HQ4X - uint16_t outputHQ4x[width * height * 4 * 4]; -#elif VIDEO_FILTER == FILTER_NTSC - uint16_t output_ntsc[SNES_NTSC_OUT_WIDTH(width) * height]; -#endif - -#if VIDEO_FILTER != FILTER_NONE +#ifdef HAVE_FILTER + uint16_t output_filter[width * height * 4 * 4]; uint16_t output[width * height]; process_frame(output, data, width, height); -#endif -#if VIDEO_FILTER == FILTER_HQ2X - ProcessHQ2x(output, outputHQ2x); - if ( !driver.video->frame(driver.video_data, outputHQ2x, width << 1, height << 1, width << 2) ) - video_active = false; -#elif VIDEO_FILTER == FILTER_HQ4X - ProcessHQ4x(output, outputHQ4x); - if ( !driver.video->frame(driver.video_data, outputHQ4x, width << 2, height << 2, width << 3) ) - video_active = false; -#elif VIDEO_FILTER == FILTER_GRAYSCALE - grayscale_filter(output, width, height); - if ( !driver.video->frame(driver.video_data, output, width, height, width << 1) ) - video_active = false; -#elif VIDEO_FILTER == FILTER_BLEED - bleed_filter(output, width, height); - if ( !driver.video->frame(driver.video_data, output, width, height, width << 1) ) - video_active = false; -#elif VIDEO_FILTER == FILTER_NTSC - ntsc_filter(output_ntsc, output, width, height); - if ( !driver.video->frame(driver.video_data, output_ntsc, SNES_NTSC_OUT_WIDTH(width), height, SNES_NTSC_OUT_WIDTH(width) << 1) ) - video_active = false; + switch (g_settings.video.filter) + { + case FILTER_HQ2X: + ProcessHQ2x(output, output_filter); + if ( !driver.video->frame(driver.video_data, output_filter, width << 1, height << 1, width << 2) ) + g_extern.video_active = false; + break; + case FILTER_HQ4X: + ProcessHQ4x(output, output_filter); + if ( !driver.video->frame(driver.video_data, output_filter, width << 2, height << 2, width << 3) ) + g_extern.video_active = false; + break; + case FILTER_GRAYSCALE: + grayscale_filter(output, width, height); + if ( !driver.video->frame(driver.video_data, output, width, height, width << 1) ) + g_extern.video_active = false; + break; + case FILTER_BLEED: + bleed_filter(output, width, height); + if ( !driver.video->frame(driver.video_data, output, width, height, width << 1) ) + g_extern.video_active = false; + break; + case FILTER_NTSC: + ntsc_filter(output_filter, output, width, height); + if ( !driver.video->frame(driver.video_data, output_filter, SNES_NTSC_OUT_WIDTH(width), height, SNES_NTSC_OUT_WIDTH(width) << 1) ) + g_extern.video_active = false; + break; + default: + if ( !driver.video->frame(driver.video_data, data, width, height, (height == 448 || height == 478) ? 1024 : 2048) ) + g_extern.video_active = false; + } #else if ( !driver.video->frame(driver.video_data, data, width, height, (height == 448 || height == 478) ? 1024 : 2048) ) - video_active = false; + g_extern.video_active = false; #endif - } -SRC_STATE* source = NULL; static void audio_sample(uint16_t left, uint16_t right) { - if ( !audio_active ) + if ( !g_extern.audio_active ) return; static float data[AUDIO_CHUNK_SIZE_NONBLOCKING]; @@ -146,16 +148,16 @@ static void audio_sample(uint16_t left, uint16_t right) src_data.input_frames = audio_chunk_size / 2; src_data.output_frames = audio_chunk_size * 8; src_data.end_of_input = 0; - src_data.src_ratio = (double)out_rate / (double)in_rate; + src_data.src_ratio = (double)g_settings.audio.out_rate / (double)g_settings.audio.in_rate; - src_process(source, &src_data); + src_process(g_extern.source, &src_data); src_float_to_short_array(outsamples, temp_outsamples, src_data.output_frames_gen * 2); if ( driver.audio->write(driver.audio_data, temp_outsamples, src_data.output_frames_gen * 4) < 0 ) { fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n"); - audio_active = false; + g_extern.audio_active = false; } data_ptr = 0; @@ -169,7 +171,7 @@ static void input_poll(void) static int16_t input_state(bool port, unsigned device, unsigned index, unsigned id) { - const struct snes_keybind *binds[] = { snes_keybinds_1, snes_keybinds_2 }; + const struct snes_keybind *binds[] = { g_settings.input.binds[0], g_settings.input.binds[1] }; return driver.input->input_state(driver.input_data, binds, port, device, index, id); } @@ -193,20 +195,10 @@ static void print_help(void) puts("Usage: ssnes [rom file] [-h/--help | -s/--save]"); puts("\t-h/--help: Show this help message"); puts("\t-s/--save: Path for save file (*.srm). Required when rom is input from stdin"); -#ifdef HAVE_CG - puts("\t-f/--shader: Path to Cg shader. Will be compiled at runtime.\n"); -#endif + puts("\t-c/--config: Path for config file. Defaults to $XDG_CONFIG_HOME/ssnes/ssnes.cfg"); puts("\t-v/--verbose: Verbose logging"); } -bool fullscreen = START_FULLSCREEN; -static FILE* rom_file = NULL; -static char savefile_name_srm[256] = {0}; -bool verbose = false; -#ifdef HAVE_CG -char cg_shader_path[256] = DEFAULT_CG_SHADER; -#endif - static void parse_input(int argc, char *argv[]) { if (argc < 2) @@ -219,18 +211,12 @@ static void parse_input(int argc, char *argv[]) { "help", 0, NULL, 'h' }, { "save", 1, NULL, 's' }, { "verbose", 0, NULL, 'v' }, -#ifdef HAVE_CG - { "shader", 1, NULL, 'f' }, -#endif + { "config", 0, NULL, 'c' }, { NULL, 0, NULL, 0 } }; int option_index = 0; -#ifdef HAVE_CG - char optstring[] = "hs:vf:"; -#else - char optstring[] = "hs:v"; -#endif + char optstring[] = "hs:vc:"; for(;;) { int c = getopt_long(argc, argv, optstring, opts, &option_index); @@ -245,18 +231,16 @@ static void parse_input(int argc, char *argv[]) exit(0); case 's': - strncpy(savefile_name_srm, optarg, sizeof(savefile_name_srm)); - savefile_name_srm[sizeof(savefile_name_srm)-1] = '\0'; + strncpy(g_extern.savefile_name_srm, optarg, sizeof(g_extern.savefile_name_srm)); + g_extern.savefile_name_srm[sizeof(g_extern.savefile_name_srm)-1] = '\0'; break; -#ifdef HAVE_CG - case 'f': - strncpy(cg_shader_path, optarg, sizeof(cg_shader_path) - 1); - break; -#endif - case 'v': - verbose = true; + g_extern.verbose = true; + break; + + case 'c': + strncpy(g_extern.config_path, optarg, sizeof(g_extern.config_path) - 1); break; case '?': @@ -275,24 +259,20 @@ static void parse_input(int argc, char *argv[]) strcpy(tmp, argv[optind]); char *dst = strrchr(tmp, '.'); if (dst) - { *dst = '\0'; - snes_set_cartridge_basename(tmp); - } - else - snes_set_cartridge_basename(tmp); + strncpy(g_extern.basename, tmp, sizeof(g_extern.basename) - 1); SSNES_LOG("Opening file: \"%s\"\n", argv[optind]); - rom_file = fopen(argv[optind], "rb"); - if (rom_file == NULL) + g_extern.rom_file = fopen(argv[optind], "rb"); + if (g_extern.rom_file == NULL) { SSNES_ERR("Could not open file: \"%s\"\n", optarg); exit(1); } - if (strlen(savefile_name_srm) == 0) - fill_pathname(savefile_name_srm, argv[optind], ".srm"); + if (strlen(g_extern.savefile_name_srm) == 0) + fill_pathname(g_extern.savefile_name_srm, argv[optind], ".srm"); } - else if (strlen(savefile_name_srm) == 0) + else if (strlen(g_extern.savefile_name_srm) == 0) { SSNES_ERR("Need savefile argument when reading rom from stdin.\n"); print_help(); @@ -302,35 +282,41 @@ static void parse_input(int argc, char *argv[]) int main(int argc, char *argv[]) { - snes_init(); parse_input(argc, argv); + parse_config(); + init_dlsym(); + psnes_init(); + if (strlen(g_extern.basename) > 0) + psnes_set_cartridge_basename(g_extern.basename); + + SSNES_LOG("Version of libsnes API: %u.%u\n", psnes_library_revision_major(), psnes_library_revision_minor()); void *rom_buf; ssize_t rom_len = 0; - if ((rom_len = read_file(rom_file, &rom_buf)) == -1) + if ((rom_len = read_file(g_extern.rom_file, &rom_buf)) == -1) { SSNES_ERR("Could not read ROM file.\n"); exit(1); } SSNES_LOG("ROM size: %zi bytes\n", rom_len); - if (rom_file != NULL) - fclose(rom_file); + if (g_extern.rom_file != NULL) + fclose(g_extern.rom_file); - char statefile_name[strlen(savefile_name_srm)+strlen(".state")+1]; - char savefile_name_rtc[strlen(savefile_name_srm)+strlen(".rtc")+1]; + char statefile_name[strlen(g_extern.savefile_name_srm)+strlen(".state")+1]; + char savefile_name_rtc[strlen(g_extern.savefile_name_srm)+strlen(".rtc")+1]; fill_pathname(statefile_name, argv[1], ".state"); fill_pathname(savefile_name_rtc, argv[1], ".rtc"); init_drivers(); - snes_set_video_refresh(video_frame); - snes_set_audio_sample(audio_sample); - snes_set_input_poll(input_poll); - snes_set_input_state(input_state); + psnes_set_video_refresh(video_frame); + psnes_set_audio_sample(audio_sample); + psnes_set_input_poll(input_poll); + psnes_set_input_state(input_state); - if (!snes_load_cartridge_normal(NULL, rom_buf, rom_len)) + if (!psnes_load_cartridge_normal(NULL, rom_buf, rom_len)) { SSNES_ERR("ROM file is not valid!\n"); goto error; @@ -338,7 +324,7 @@ int main(int argc, char *argv[]) free(rom_buf); - unsigned serial_size = snes_serialize_size(); + unsigned serial_size = psnes_serialize_size(); uint8_t *serial_data = malloc(serial_size); if (serial_data == NULL) { @@ -346,49 +332,51 @@ int main(int argc, char *argv[]) goto error; } - load_save_file(savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM); + load_save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM); load_save_file(savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC); ///// TODO: Modular friendly!!! for(;;) { - bool quitting = glfwGetKey(GLFW_KEY_ESC) || !glfwGetWindowParam(GLFW_OPENED); + bool quitting = glfwGetKey(g_settings.input.exit_emulator_key) || !glfwGetWindowParam(GLFW_OPENED); if ( quitting ) break; - if ( glfwGetKey( SAVE_STATE_KEY )) + if ( glfwGetKey( g_settings.input.save_state_key )) { write_file(statefile_name, serial_data, serial_size); } - else if ( glfwGetKey( LOAD_STATE_KEY ) ) + else if ( glfwGetKey( g_settings.input.load_state_key ) ) load_state(statefile_name, serial_data, serial_size); - else if ( glfwGetKey( TOGGLE_FULLSCREEN ) ) + else if ( glfwGetKey( g_settings.input.toggle_fullscreen_key ) ) { - fullscreen = !fullscreen; + g_settings.video.fullscreen = !g_settings.video.fullscreen; uninit_drivers(); init_drivers(); } - snes_run(); + psnes_run(); } - save_file(savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM); + save_file(g_extern.savefile_name_srm, SNES_MEMORY_CARTRIDGE_RAM); save_file(savefile_name_rtc, SNES_MEMORY_CARTRIDGE_RTC); - snes_unload_cartridge(); - snes_term(); + psnes_unload_cartridge(); + psnes_term(); uninit_drivers(); free(serial_data); + uninit_dlsym(); return 0; error: - snes_unload_cartridge(); - snes_term(); + psnes_unload_cartridge(); + psnes_term(); uninit_drivers(); + uninit_dlsym(); return 1; } diff --git a/ssnes.cfg b/ssnes.cfg new file mode 100644 index 0000000000..b9d5f9aaba --- /dev/null +++ b/ssnes.cfg @@ -0,0 +1,148 @@ +##### Config file for SSNES + +## If enabled, load libsnes from a dynamic location. +# libsnes_path = "/path/to/libsnes.so" + +#### Video + +# Windowed xscale and yscale (Real x res: 296 * xscale, real y scale: 224 * xscale) +# video_xscale = 3.0 +# video_yscale = 3.0 + +# Fullscreen resolution +# video_fullscreen_x = 1280 +# video_fullscreen_y = 720 + +# Start in fullscreen. Can be changed at runtime. +# video_fullscreen = false + +# Video vsync. +# video_vsync = true + +# Smoothens picture with bilinear filtering. Should be disabled if using Cg shaders. +# video_smooth = true + +# Forces rendering area to stay 4:3. +# video_force_aspect = true + +# Path to Cg shader. If enabled +# video_cg_shader = "/path/to/cg/shader.cg" + +# CPU-based filter. Valid ones are: hq2x, hq4x, grayscale, bleed, ntsc. +# video_filter = ntsc + +#### Audio + +# Enable audio. +# audio_enable = true + +# Audio output samplerate. +# audio_out_rate = 48000 + +# Audio input samplerate from libsnes. +# Lower this (slightly) if you are experiencing frequent audio dropouts while vsync is enabled. +# Conversely, increase this slightly if you are experiencing good audio, +# but lots of dropped frames. Reasonable values for this is 32000 +/- 100 Hz. +# audio_in_rate = 31950 + +# Audio driver backend. Depending on configuration possible candidates are: alsa, oss, rsound, roar, openal +# audio_driver = alsa + +# Override the default audio device the audio_driver uses. +# audio_device = + +# Will sync (block) on audio. Recommended. +# audio_sync = true + +# Desired audio latency in milliseconds. Might not be honored if driver can't provide given latency. +# audio_latency = 64 + +# libsamplerate quality. Valid values are from 1 to 5. These values map to zero_order_hold, linear, sinc_fastest, sinc_medium and sinc_best. +# audio_src_quality = + +### Input + +# Defines axis threshold. Possible values are [0.0, 1.0] +# input_axis_threshold = 0.6 + +# Keyboard input. Will recognize normal keypresses and special keys like "left", "right", and so on. +# input_player1_a = x +# input_player1_b = z +# input_player1_y = a +# input_player1_x = s +# input_player1_start = enter +# input_player1_select = rshift +# input_player1_l = q +# input_player1_r = w +# input_player1_left = left +# input_player1_right = right +# input_player1_up = up +# input_player1_down = down + +# Joypad buttons. Figure these out by looking at jstest /dev/input/js0 output. +# input_player1_a_btn = 1 +# input_player1_b_btn = 0 +# input_player1_y_btn = 2 +# input_player1_x_btn = 3 +# input_player1_start_btn = 7 +# input_player1_select_btn = 6 +# input_player1_l_btn = 4 +# input_player1_r_btn = 5 +# input_player1_left_btn = 11 +# input_player1_right_btn = 12 +# input_player1_up_btn = 13 +# input_player1_down_btn = 14 + +# Axis for DPAD. Needs to be either '+' or '-' in the first character signaling either positive or negative direction of the axis, then the axis number. +# input_player1_left_axis = -0 +# input_player1_right_axis = +0 +# input_player1_up_axis = +1 +# input_player1_down_axis = -1 + +# Same stuff, just for player two. +# input_player2_a = +# input_player2_b = +# input_player2_y = +# input_player2_x = +# input_player2_start = +# input_player2_select = +# input_player2_l = +# input_player2_r = +# input_player2_left = +# input_player2_right = +# input_player2_up = +# input_player2_down = + +# input_player2_a_btn = 1 +# input_player2_b_btn = 0 +# input_player2_y_btn = 2 +# input_player2_x_btn = 3 +# input_player2_start_btn = 7 +# input_player2_select_btn = 6 +# input_player2_l_btn = 4 +# input_player2_r_btn = 5 +# input_player2_left_btn = 11 +# input_player2_right_btn = 12 +# input_player2_up_btn = 13 +# input_player2_down_btn = 14 + + +# input_player2_left_axis = -0 +# input_player2_right_axis = +0 +# input_player2_up_axis = +1 +# input_player2_down_axis = -1 + +# Toggles fullscreen. +# input_toggle_fullscreen = f +# Saves state. +# input_save_state = f2 +# Loads state. +# input_load_state = f4 + +# Toggles between fast-forwarding and normal speed. +# input_toggle_fast_forward = space +# Same, just mapping to a joypad button. +# input_toggle_fast_forward_btn = 10 + +# Key to exit emulator cleanly. +# input_exit_emulator = escape