mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 17:28:49 -10:00
Compare commits
170
Commits
v0.4-final
...
v0.5.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f79eafe449 | ||
|
|
111b159373 | ||
|
|
d744167865 | ||
|
|
a2fea86c92 | ||
|
|
5385806e74 | ||
|
|
99e85684db | ||
|
|
45727bf9f5 | ||
|
|
2a0abb0618 | ||
|
|
192e3303a1 | ||
|
|
b6051add6a | ||
|
|
24d0cda5fd | ||
|
|
d0ba59f8b5 | ||
|
|
99a1784aaf | ||
|
|
b6ff98cf18 | ||
|
|
2dcccc7cda | ||
|
|
d03855c655 | ||
|
|
69227bc1ca | ||
|
|
851debf1e4 | ||
|
|
8c1db1c407 | ||
|
|
576894e0e8 | ||
|
|
631c3ca393 | ||
|
|
fd70a1c875 | ||
|
|
30cb2e4c07 | ||
|
|
7d87cc2d24 | ||
|
|
0342dcb169 | ||
|
|
f339560550 | ||
|
|
11b58d8c69 | ||
|
|
3717f7442c | ||
|
|
e02d9f41e8 | ||
|
|
f62120861a | ||
|
|
884e2881a0 | ||
|
|
a605628dff | ||
|
|
4d96fbba6a | ||
|
|
9bfeb88e24 | ||
|
|
712f463022 | ||
|
|
713e8624a0 | ||
|
|
5c8a549125 | ||
|
|
c3e7a35b62 | ||
|
|
5bc9ca216e | ||
|
|
838a85a719 | ||
|
|
b880039541 | ||
|
|
c646166fe1 | ||
|
|
44c52ca4f8 | ||
|
|
9c9eee5d01 | ||
|
|
7bbaa6ee31 | ||
|
|
f24aed9358 | ||
|
|
02344f9048 | ||
|
|
200d2b598b | ||
|
|
3213bb13ea | ||
|
|
53fd0b3751 | ||
|
|
9b6e6a1215 | ||
|
|
e5a9d78b70 | ||
|
|
f22e964cc0 | ||
|
|
9fb827d238 | ||
|
|
24f802023a | ||
|
|
ef2b755e17 | ||
|
|
7cc8c01a1e | ||
|
|
ca509c5b9f | ||
|
|
12a39e160b | ||
|
|
7c5f36be61 | ||
|
|
aec44b093e | ||
|
|
ee83c31183 | ||
|
|
f2ad94f1c3 | ||
|
|
3aed471d57 | ||
|
|
3ee19ec75f | ||
|
|
b0d1a98cd1 | ||
|
|
94ff67ee15 | ||
|
|
8d2a7e4593 | ||
|
|
4e7a003497 | ||
|
|
380384654c | ||
|
|
2144ef84fd | ||
|
|
83d913b6d2 | ||
|
|
2782c9da91 | ||
|
|
2f539cd326 | ||
|
|
f4af518fb4 | ||
|
|
eebe0ae5d0 | ||
|
|
f9c5c7c8a5 | ||
|
|
8bc3d3ac5c | ||
|
|
6a3386ad1a | ||
|
|
5dced40c98 | ||
|
|
3f58932c2b | ||
|
|
373b9dcc37 | ||
|
|
993bdfb75f | ||
|
|
70000607a2 | ||
|
|
1b64e4b902 | ||
|
|
2166db91e2 | ||
|
|
4f50b6a749 | ||
|
|
676f659306 | ||
|
|
2f858b7948 | ||
|
|
36d9adbc0f | ||
|
|
9473b392df | ||
|
|
3bf050d5aa | ||
|
|
8a0ccc99df | ||
|
|
c938f3d8de | ||
|
|
d1c7b774f0 | ||
|
|
6b6877afc1 | ||
|
|
c370cba5b5 | ||
|
|
05d62a93c9 | ||
|
|
24cb2e6844 | ||
|
|
7f1cd62c8a | ||
|
|
5f5013871a | ||
|
|
aebe6bcd23 | ||
|
|
ac21fb81e2 | ||
|
|
74afe7f95b | ||
|
|
c8161a31d6 | ||
|
|
628217c6ae | ||
|
|
efed98d5b6 | ||
|
|
29addd605e | ||
|
|
0c121571b3 | ||
|
|
6244671b04 | ||
|
|
21b046cdd1 | ||
|
|
b32224d406 | ||
|
|
5d5865cfd8 | ||
|
|
4a78f2fef1 | ||
|
|
b521becd3c | ||
|
|
f10ed12b74 | ||
|
|
8d051ab413 | ||
|
|
4cc17a74f7 | ||
|
|
5ae4235545 | ||
|
|
8ceb8225ad | ||
|
|
20a3087d1c | ||
|
|
1f45dc512b | ||
|
|
13dee59fc8 | ||
|
|
5f78063449 | ||
|
|
ea6bd8c5c5 | ||
|
|
bdc85f2def | ||
|
|
efa731fc6d | ||
|
|
80d50b046d | ||
|
|
1855a7ea0d | ||
|
|
f5c11071be | ||
|
|
4b9e46ff06 | ||
|
|
75d618140b | ||
|
|
59c6de69b0 | ||
|
|
dc20c58189 | ||
|
|
c6eecf706a | ||
|
|
f2ee22d259 | ||
|
|
b48322222a | ||
|
|
bbf5a1ee72 | ||
|
|
c0851877bf | ||
|
|
51d96676af | ||
|
|
9f3b86e893 | ||
|
|
ac85037fc1 | ||
|
|
fa8e948e3e | ||
|
|
252cce07a6 | ||
|
|
17581106ba | ||
|
|
1bd8424fb4 | ||
|
|
23db525296 | ||
|
|
d679f8b24f | ||
|
|
d2f2ab6a9d | ||
|
|
3e1e08ca9a | ||
|
|
bb93f20be9 | ||
|
|
54dc289cde | ||
|
|
fcebad2589 | ||
|
|
4b60f98603 | ||
|
|
d304960c21 | ||
|
|
8da13b716a | ||
|
|
a6c08a24d7 | ||
|
|
6713272809 | ||
|
|
8d7cf23b40 | ||
|
|
d317a9773f | ||
|
|
e8806c48c9 | ||
|
|
abd7bab43a | ||
|
|
66464c2cce | ||
|
|
092fa9d531 | ||
|
|
1005d7fa9b | ||
|
|
079549cbd2 | ||
|
|
0f0a4d0a5e | ||
|
|
f0bbeb901d | ||
|
|
8027a25016 | ||
|
|
a1426c96f8 |
No files matched your search
@@ -1,4 +1,6 @@
|
||||
*.o
|
||||
*.so
|
||||
*.dll
|
||||
ssnes
|
||||
/config.h
|
||||
/config.mk
|
||||
|
||||
@@ -2,7 +2,7 @@ include config.mk
|
||||
|
||||
TARGET = ssnes tools/ssnes-joyconfig
|
||||
|
||||
OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o ups.o strl.o
|
||||
OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o ups.o strl.o screenshot.o
|
||||
JOYCONFIG_OBJ = tools/ssnes-joyconfig.o conf/config_file.o
|
||||
HEADERS = $(wildcard */*.h) $(wildcard *.h)
|
||||
|
||||
@@ -67,7 +67,7 @@ ifeq ($(HAVE_PULSE), 1)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL), 1)
|
||||
OBJ += gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o
|
||||
OBJ += gfx/sdl.o gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o
|
||||
DEFINES += $(SDL_CFLAGS)
|
||||
LIBS += $(SDL_LIBS)
|
||||
ifeq ($(OSX),1)
|
||||
@@ -77,13 +77,9 @@ else
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_FBO), 1)
|
||||
DEFINES += -DHAVE_FBO
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XVIDEO), 1)
|
||||
OBJ += gfx/xvideo.o input/x11_input.o
|
||||
LIBS += -lXv -lX11
|
||||
LIBS += -lX11 -lXext -lXv
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_CG), 1)
|
||||
@@ -92,12 +88,13 @@ ifeq ($(HAVE_CG), 1)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XML), 1)
|
||||
OBJ += gfx/shader_glsl.o
|
||||
OBJ += gfx/shader_glsl.o gfx/image.o gfx/snes_state.o sha256.o cheats.o
|
||||
LIBS += $(XML_LIBS)
|
||||
DEFINES += $(XML_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_FILTER), 1)
|
||||
ifeq ($(HAVE_DYLIB), 1)
|
||||
OBJ += gfx/ext.o audio/ext.o
|
||||
LIBS += -ldl
|
||||
endif
|
||||
|
||||
@@ -107,6 +104,11 @@ ifeq ($(HAVE_FREETYPE), 1)
|
||||
DEFINES += $(FREETYPE_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL_IMAGE), 1)
|
||||
LIBS += $(SDL_IMAGE_LIBS)
|
||||
DEFINES += $(SDL_IMAGE_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_FFMPEG), 1)
|
||||
OBJ += record/ffemu.o
|
||||
LIBS += $(AVCODEC_LIBS) $(AVCORE_LIBS) $(AVFORMAT_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS)
|
||||
@@ -123,11 +125,17 @@ ifeq ($(HAVE_STRL), 1)
|
||||
DEFINES += -DHAVE_STRL
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_PYTHON), 1)
|
||||
DEFINES += $(PYTHON_CFLAGS) -Wno-unused-parameter
|
||||
LIBS += $(PYTHON_LIBS)
|
||||
OBJ += gfx/py_state/py_state.o
|
||||
endif
|
||||
|
||||
ifneq ($(V),1)
|
||||
Q := @
|
||||
endif
|
||||
|
||||
CFLAGS += -Wall -O3 -g -I.
|
||||
CFLAGS += -Wall -O3 -g -I. -pedantic
|
||||
ifneq ($(findstring icc,$(CC)),)
|
||||
CFLAGS += -std=c99
|
||||
else
|
||||
@@ -153,9 +161,9 @@ tools/ssnes-joyconfig: $(JOYCONFIG_OBJ)
|
||||
@$(if $(Q), $(shell echo echo CC $<),)
|
||||
|
||||
install: $(TARGET)
|
||||
mkdir -p $(DESTDIR)$(PREFIX)/bin
|
||||
mkdir -p $(DESTDIR)/etc
|
||||
mkdir -p $(DESTDIR)$(PREFIX)/share/man/man1
|
||||
mkdir -p $(DESTDIR)$(PREFIX)/bin 2>/dev/null || /bin/true
|
||||
mkdir -p $(DESTDIR)/etc 2>/dev/null || /bin/true
|
||||
mkdir -p $(DESTDIR)$(PREFIX)/share/man/man1 2>/dev/null || /bin/true
|
||||
install -m755 $(TARGET) $(DESTDIR)$(PREFIX)/bin
|
||||
install -m644 ssnes.cfg $(DESTDIR)/etc/ssnes.cfg
|
||||
install -m644 docs/ssnes.1 $(DESTDIR)$(PREFIX)/share/man/man1
|
||||
@@ -163,18 +171,20 @@ install: $(TARGET)
|
||||
install -m755 ssnes-zip $(DESTDIR)$(PREFIX)/bin
|
||||
|
||||
uninstall:
|
||||
rm -f $(DESTDIR)$(PREFIX)/bin/{ssnes,ssnes-joyconfig,ssnes-zip}
|
||||
rm -f $(DESTDIR)$(PREFIX)/bin/ssnes
|
||||
rm -f $(DESTDIR)$(PREFIX)/bin/ssnes-joyconfig
|
||||
rm -f $(DESTDIR)$(PREFIX)/bin/ssnes-zip
|
||||
rm -f $(DESTDIR)/etc/ssnes.cfg
|
||||
rm -f $(DESTDIR)$(PREFIX)/share/man/man1/{ssnes,ssnes-joyconfig}.1
|
||||
rm -f $(DESTDIR)$(PREFIX)/share/man/man1/ssnes.1
|
||||
rm -f $(DESTDIR)$(PREFIX)/share/man/man1/ssnes-joyconfig.1
|
||||
|
||||
clean:
|
||||
rm -f *.o
|
||||
rm -f audio/*.o
|
||||
rm -f conf/*.o
|
||||
rm -f gfx/*.o
|
||||
rm -f gfx/py_state/*.o
|
||||
rm -f record/*.o
|
||||
rm -f hqflt/*.o
|
||||
rm -f hqflt/snes_ntsc/*.o
|
||||
rm -f input/*.o
|
||||
rm -f tools/*.o
|
||||
rm -f $(TARGET)
|
||||
|
||||
+39
-7
@@ -1,20 +1,24 @@
|
||||
TARGET = ssnes.exe
|
||||
JTARGET = ssnes-joyconfig.exe
|
||||
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o ups.o strl.o
|
||||
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o ups.o strl.o screenshot.o
|
||||
JOBJ = conf/config_file.o tools/main-stub.o tools/ssnes-joyconfig.o
|
||||
|
||||
CC = gcc
|
||||
CXX = g++
|
||||
|
||||
HAVE_DINPUT = 1
|
||||
HAVE_SRC = 0
|
||||
HAVE_SDL = 1
|
||||
HAVE_SDL_IMAGE = 1
|
||||
HAVE_XML = 1
|
||||
HAVE_FREETYPE = 1
|
||||
HAVE_XAUDIO = 1
|
||||
HAVE_RSOUND = 1
|
||||
HAVE_FILTER = 1
|
||||
HAVE_DYLIB = 1
|
||||
HAVE_NETPLAY = 1
|
||||
HAVE_FBO = 1
|
||||
HAVE_CG = 1
|
||||
HAVE_PYTHON = 1
|
||||
libsnes ?= -lsnes
|
||||
|
||||
LIBS = -lm
|
||||
@@ -33,11 +37,22 @@ else
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL), 1)
|
||||
OBJ += gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o
|
||||
OBJ += gfx/sdl.o gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o
|
||||
LIBS += $(SDL_LIBS) -lopengl32
|
||||
DEFINES += $(SDL_CFLAGS) -DHAVE_SDL
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL_IMAGE), 1)
|
||||
LIBS += -lSDL_image
|
||||
DEFINES += -DHAVE_SDL_IMAGE
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_CG), 1)
|
||||
OBJ += gfx/shader_cg.o
|
||||
LIBS += -lcg -lcgGL
|
||||
DEFINES += -DHAVE_CG
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XAUDIO), 1)
|
||||
OBJ += audio/xaudio.o
|
||||
DEFINES += -DHAVE_XAUDIO
|
||||
@@ -50,13 +65,13 @@ ifeq ($(HAVE_RSOUND), 1)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XML), 1)
|
||||
OBJ += gfx/shader_glsl.o
|
||||
OBJ += gfx/shader_glsl.o gfx/image.o gfx/snes_state.o sha256.o cheats.o
|
||||
DEFINES += $(XML_CFLAGS) -DHAVE_XML
|
||||
LIBS += -lxml2
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_FILTER), 1)
|
||||
DEFINES += -DHAVE_FILTER
|
||||
ifeq ($(HAVE_DYLIB), 1)
|
||||
DEFINES += -DHAVE_DYLIB
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_NETPLAY), 1)
|
||||
@@ -81,6 +96,22 @@ ifeq ($(HAVE_FBO), 1)
|
||||
DEFINES += -DHAVE_FBO
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DYLIB), 1)
|
||||
OBJ += gfx/ext.o audio/ext.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_PYTHON), 1)
|
||||
LIBS += -lpython32
|
||||
DEFINES += -DHAVE_PYTHON -Ipython
|
||||
OBJ += gfx/py_state/py_state.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DINPUT), 1)
|
||||
LIBS += -ldinput8 -ldxguid -lole32
|
||||
DEFINES += -DHAVE_DINPUT
|
||||
OBJ += input/dinput.o
|
||||
endif
|
||||
|
||||
ifneq ($(V),1)
|
||||
Q := @
|
||||
endif
|
||||
@@ -107,6 +138,7 @@ clean:
|
||||
rm -f audio/*.o
|
||||
rm -f conf/*.o
|
||||
rm -f gfx/*.o
|
||||
rm -f gfx/py_state/*.o
|
||||
rm -f record/*.o
|
||||
rm -f hqflt/*.o
|
||||
rm -f input/*.o
|
||||
@@ -116,7 +148,7 @@ clean:
|
||||
rm -f tools/*.o
|
||||
|
||||
dist: all
|
||||
zip -r ssnes-win32-0.4-beta.zip $(TARGET) ssnes.cfg snes.dll libxml2.dll iconv.dll zlib1.dll SDL.dll freetype6.dll xaudio-c.dll rsound.dll pthreadGC2.dll README.win32.txt $(JTARGET)
|
||||
zip -r ssnes-win32-0.5.2.zip $(TARGET) ssnes.cfg snes.dll libxml2.dll iconv.dll zlib1.dll SDL.dll freetype6.dll xaudio-c.dll rsound.dll pthreadGC2.dll cg.dll cgGL.dll libjpeg-8.dll libpng15-15.dll python32.dll SDL_image.dll $(JTARGET)
|
||||
|
||||
libs:
|
||||
wget https://github.com/downloads/Themaister/SSNES/SSNES-win32-libs.zip --no-check-certificate
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
TARGET = ssnes.exe
|
||||
JTARGET = ssnes-joyconfig.exe
|
||||
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o ups.o strl.o screenshot.o
|
||||
JOBJ = conf/config_file.o tools/main-stub.o tools/ssnes-joyconfig.o
|
||||
|
||||
CC = gcc
|
||||
CXX = g++
|
||||
|
||||
HAVE_DINPUT = 1
|
||||
HAVE_SRC = 0
|
||||
HAVE_SDL = 1
|
||||
HAVE_XML = 1
|
||||
HAVE_FREETYPE = 1
|
||||
HAVE_XAUDIO = 1
|
||||
HAVE_RSOUND = 0
|
||||
HAVE_DYLIB = 1
|
||||
HAVE_NETPLAY = 1
|
||||
HAVE_FBO = 1
|
||||
HAVE_CG = 0
|
||||
HAVE_PYTHON = 0
|
||||
libsnes ?= -lsnes
|
||||
|
||||
LIBS = -lm
|
||||
DEFINES = -I. -DHAVE_CONFIGFILE
|
||||
LDFLAGS = -L. -static-libgcc -s
|
||||
|
||||
SRC_LIBS = -lsamplerate-0
|
||||
SDL_LIBS = -lmingw32 -lSDL -lSDLmain -lwinmm -mwindows -lm -luser32 -lgdi32 -lwinmm -ldxguid
|
||||
SDL_CFLAGS = -ISDL
|
||||
|
||||
ifeq ($(HAVE_SRC), 1)
|
||||
LIBS += $(SRC_LIBS)
|
||||
DEFINES += $(SRC_CFLAGS) -DHAVE_SRC
|
||||
else
|
||||
OBJ += audio/hermite.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL), 1)
|
||||
OBJ += gfx/sdl.o gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o
|
||||
LIBS += $(SDL_LIBS) -lopengl32
|
||||
DEFINES += $(SDL_CFLAGS) -DHAVE_SDL
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_CG), 1)
|
||||
OBJ += gfx/shader_cg.o
|
||||
LIBS += -lcg -lcgGL
|
||||
DEFINES += -DHAVE_CG
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XAUDIO), 1)
|
||||
OBJ += audio/xaudio.o
|
||||
DEFINES += -DHAVE_XAUDIO
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_RSOUND), 1)
|
||||
OBJ += audio/rsound.o
|
||||
DEFINES += -DHAVE_RSOUND
|
||||
LIBS += -lrsound
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XML), 1)
|
||||
OBJ += gfx/shader_glsl.o gfx/image.o gfx/snes_state.o sha256.o cheats.o
|
||||
DEFINES += $(XML_CFLAGS) -DHAVE_XML -DLIBXML_STATIC
|
||||
LIBS += -lxml2 -lz -lws2_32
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DYLIB), 1)
|
||||
DEFINES += -DHAVE_DYLIB
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_NETPLAY), 1)
|
||||
DEFINES += -DHAVE_NETPLAY
|
||||
OBJ += netplay.o
|
||||
LIBS += -lws2_32
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_FREETYPE), 1)
|
||||
OBJ += gfx/fonts.o
|
||||
DEFINES += -DHAVE_FREETYPE -Ifreetype2
|
||||
LIBS += -lz -lfreetype6
|
||||
endif
|
||||
|
||||
ifeq ($(DYNAMIC), 1)
|
||||
DEFINES += -DHAVE_DYNAMIC
|
||||
else
|
||||
LIBS += $(libsnes)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_FBO), 1)
|
||||
DEFINES += -DHAVE_FBO
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DYLIB), 1)
|
||||
OBJ += gfx/ext.o audio/ext.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_PYTHON), 1)
|
||||
LIBS += -lpython32
|
||||
DEFINES += -DHAVE_PYTHON -Ipython
|
||||
OBJ += gfx/py_state/py_state.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DINPUT), 1)
|
||||
LIBS += -ldinput8 -ldxguid -lole32
|
||||
DEFINES += -DHAVE_DINPUT
|
||||
OBJ += input/dinput.o
|
||||
endif
|
||||
|
||||
|
||||
ifneq ($(V),1)
|
||||
Q := @
|
||||
endif
|
||||
|
||||
|
||||
CFLAGS = -Wall -O3 -std=gnu99 -I.
|
||||
|
||||
all: $(TARGET) $(JTARGET)
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
$(Q)$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS)
|
||||
@$(if $(Q), $(shell echo echo LD $@),)
|
||||
|
||||
%.o: %.c
|
||||
$(Q)$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $<
|
||||
@$(if $(Q), $(shell echo echo CC $<),)
|
||||
|
||||
$(JTARGET): $(JOBJ)
|
||||
$(Q)$(CC) -o $@ $(JOBJ) $(SDL_LIBS) $(LDFLAGS)
|
||||
@$(if $(Q), $(shell echo echo LD $@),)
|
||||
|
||||
clean:
|
||||
rm -f *.o
|
||||
rm -f audio/*.o
|
||||
rm -f conf/*.o
|
||||
rm -f gfx/*.o
|
||||
rm -f record/*.o
|
||||
rm -f hqflt/*.o
|
||||
rm -f input/*.o
|
||||
rm -f hqflt/snes_ntsc/*.o
|
||||
rm -f $(TARGET)
|
||||
rm -f ssnes-joyconfig.exe
|
||||
rm -f tools/*.o
|
||||
|
||||
dist: all
|
||||
zip -r ssnes-win64-0.5.zip $(TARGET) ssnes.cfg snes.dll xaudio-c.dll README.win32.txt $(JTARGET)
|
||||
|
||||
libs:
|
||||
wget https://github.com/downloads/Themaister/SSNES/ssnes-win64-libs.zip --no-check-certificate
|
||||
unzip ssnes-win64-libs.zip
|
||||
|
||||
.PHONY: all install uninstall clean dist libs
|
||||
+1
-1
@@ -49,7 +49,7 @@ static bool find_float_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
|
||||
return false;
|
||||
}
|
||||
|
||||
static void* __alsa_init(const char* device, int rate, int latency)
|
||||
static void* __alsa_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
alsa_t *alsa = calloc(1, sizeof(alsa_t));
|
||||
if ( alsa == NULL )
|
||||
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ext/ssnes_audio.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include "driver.h"
|
||||
#include "dynamic.h"
|
||||
#include "general.h"
|
||||
#include <sys/types.h>
|
||||
|
||||
typedef struct audio_ext
|
||||
{
|
||||
dylib_t lib;
|
||||
const ssnes_audio_driver_t *driver;
|
||||
void *handle;
|
||||
bool is_float;
|
||||
} audio_ext_t;
|
||||
|
||||
static void audio_ext_free(void *data)
|
||||
{
|
||||
audio_ext_t *ext = data;
|
||||
if (ext)
|
||||
{
|
||||
if (ext->driver && ext->handle)
|
||||
ext->driver->free(ext->handle);
|
||||
if (ext->lib)
|
||||
dylib_close(ext->lib);
|
||||
free(ext);
|
||||
}
|
||||
}
|
||||
|
||||
static void* audio_ext_init(const char *device, unsigned rate, unsigned latency)
|
||||
{
|
||||
if (!(*g_settings.audio.external_driver))
|
||||
{
|
||||
SSNES_ERR("Please define an external audio driver.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
audio_ext_t *ext = calloc(1, sizeof(*ext));
|
||||
if (!ext)
|
||||
return NULL;
|
||||
|
||||
ext->lib = dylib_load(g_settings.audio.external_driver);
|
||||
if (!ext->lib)
|
||||
{
|
||||
SSNES_ERR("Failed to load external library \"%s\"\n", g_settings.audio.external_driver);
|
||||
goto error;
|
||||
}
|
||||
|
||||
const ssnes_audio_driver_t* (*plugin_load)(void) = (const ssnes_audio_driver_t* (*)(void))dylib_proc(ext->lib, "ssnes_audio_driver_init");
|
||||
|
||||
if (!plugin_load)
|
||||
{
|
||||
SSNES_ERR("Failed to find symbol \"ssnes_audio_driver_init\" in plugin.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ext->driver = plugin_load();
|
||||
if (!ext->driver)
|
||||
{
|
||||
SSNES_ERR("Received invalid driver from plugin.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
SSNES_LOG("Loaded external audio driver: \"%s\"\n", ext->driver->ident ? ext->driver->ident : "Unknown");
|
||||
|
||||
if (ext->driver->api_version != SSNES_AUDIO_API_VERSION)
|
||||
{
|
||||
SSNES_ERR("API mismatch in external audio plugin. SSNES: %d, Plugin: %d ...\n", SSNES_AUDIO_API_VERSION, ext->driver->api_version);
|
||||
goto error;
|
||||
}
|
||||
|
||||
ssnes_audio_driver_info_t info = {
|
||||
.device = device,
|
||||
.sample_rate = rate,
|
||||
.latency = latency
|
||||
};
|
||||
|
||||
ext->handle = ext->driver->init(&info);
|
||||
if (!ext->handle)
|
||||
{
|
||||
SSNES_ERR("Failed to init audio driver.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (ext->driver->sample_rate)
|
||||
g_settings.audio.out_rate = ext->driver->sample_rate(ext->handle);
|
||||
|
||||
if (!g_settings.audio.sync)
|
||||
ext->driver->set_nonblock_state(ext->handle, SSNES_TRUE);
|
||||
|
||||
return ext;
|
||||
|
||||
error:
|
||||
audio_ext_free(ext);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ssize_t audio_ext_write(void *data, const void *buf, size_t size)
|
||||
{
|
||||
audio_ext_t *ext = data;
|
||||
unsigned frame_size = ext->is_float ? (2 * sizeof(float)) : (2 * sizeof(int16_t));
|
||||
size /= frame_size;
|
||||
|
||||
int ret = ext->driver->write(ext->handle, buf, size);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
return ret * frame_size;
|
||||
}
|
||||
|
||||
static bool audio_ext_start(void *data)
|
||||
{
|
||||
audio_ext_t *ext = data;
|
||||
return ext->driver->start(ext->handle);
|
||||
}
|
||||
|
||||
static bool audio_ext_stop(void *data)
|
||||
{
|
||||
audio_ext_t *ext = data;
|
||||
return ext->driver->stop(ext->handle);
|
||||
}
|
||||
|
||||
static void audio_ext_set_nonblock_state(void *data, bool toggle)
|
||||
{
|
||||
audio_ext_t *ext = data;
|
||||
ext->driver->set_nonblock_state(ext->handle, toggle);
|
||||
}
|
||||
|
||||
static bool audio_ext_use_float(void *data)
|
||||
{
|
||||
audio_ext_t *ext = data;
|
||||
ext->is_float = ext->driver->use_float(ext->handle);
|
||||
return ext->is_float;
|
||||
}
|
||||
|
||||
|
||||
const audio_driver_t audio_ext = {
|
||||
.init = audio_ext_init,
|
||||
.write = audio_ext_write,
|
||||
.stop = audio_ext_stop,
|
||||
.start = audio_ext_start,
|
||||
.set_nonblock_state = audio_ext_set_nonblock_state,
|
||||
.use_float = audio_ext_use_float,
|
||||
.free = audio_ext_free,
|
||||
.ident = "ext"
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
TARGET = ssnes_iir_filter.so
|
||||
OBJ = ssnes_iir_filter.o
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
$(CC) -o $@ -shared $(OBJ) -lm
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c -o $@ $< -g -std=c99 -fPIC -O3 -march=native $(CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET)
|
||||
rm -f $(OBJ)
|
||||
|
||||
.PHONY: clean
|
||||
@@ -0,0 +1,16 @@
|
||||
TARGET = iir_filter
|
||||
OBJ = ssnes_iir_filter.o
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
$(CC) -o $@ $(OBJ) -lm
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c -o $@ $< -g -std=c99 -O3 -march=native $(CFLAGS) -DTEST -DLOG
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET)
|
||||
rm -f $(OBJ)
|
||||
|
||||
.PHONY: clean
|
||||
@@ -0,0 +1,128 @@
|
||||
/////
|
||||
// API header for external SSNES audio driver plugins.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef __SSNES_AUDIO_DRIVER_PLUGIN_H
|
||||
#define __SSNES_AUDIO_DRIVER_PLUGIN_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef SSNES_DLL_IMPORT
|
||||
#define SSNES_API_EXPORT __declspec(dllimport)
|
||||
#else
|
||||
#define SSNES_API_EXPORT __declspec(dllexport)
|
||||
#endif
|
||||
#define SSNES_API_CALLTYPE __cdecl
|
||||
#else
|
||||
#define SSNES_API_EXPORT
|
||||
#define SSNES_API_CALLTYPE
|
||||
#endif
|
||||
|
||||
#ifndef SSNES_TRUE
|
||||
#define SSNES_TRUE 1
|
||||
#endif
|
||||
|
||||
#ifndef SSNES_FALSE
|
||||
#define SSNES_FALSE 0
|
||||
#endif
|
||||
|
||||
#ifndef SSNES_OK
|
||||
#define SSNES_OK 1
|
||||
#endif
|
||||
|
||||
#ifndef SSNES_ERROR
|
||||
#define SSNES_ERROR 0
|
||||
#endif
|
||||
|
||||
#define SSNES_AUDIO_API_VERSION 2
|
||||
|
||||
typedef struct ssnes_audio_driver_info
|
||||
{
|
||||
// A hint for a subdevice of the audio driver.
|
||||
// This is driver independent, and not relevant for all
|
||||
// audio drivers. I.e. ALSA driver might use "hw:0",
|
||||
// OSS "/dev/audio", etc.
|
||||
const char *device;
|
||||
|
||||
// Audio sample rate.
|
||||
unsigned sample_rate;
|
||||
|
||||
// Maximum audio latency requested for output,
|
||||
// measured in milliseconds.
|
||||
// If driver is not able to provide this latency, it can
|
||||
// be disregarded.
|
||||
unsigned latency;
|
||||
} ssnes_audio_driver_info_t;
|
||||
|
||||
typedef struct ssnes_audio_driver
|
||||
{
|
||||
// Initializes the device.
|
||||
void* (*init)(const ssnes_audio_driver_info_t *info);
|
||||
|
||||
// Write data in buffer to audio driver.
|
||||
// A frame here is defined as one combined sample of left and right
|
||||
// channels. (I.e. 44.1kHz, 16-bit stereo has 88.2k samples/s, and
|
||||
// 44.1k frames/s.)
|
||||
//
|
||||
// Samples are interleaved in format LRLRLRLRLR ...
|
||||
// If the driver returns true in use_float(), a floating point
|
||||
// format will be used, with range [-1.0, 1.0].
|
||||
// If not, signed 16-bit samples in native byte ordering will be used.
|
||||
//
|
||||
// This function returns the number of frames successfully written.
|
||||
// If an error occurs, -1 should be returned.
|
||||
// Note that non-blocking behavior that cannot write at this time
|
||||
// should return 0 as returning -1 will terminate the driver.
|
||||
//
|
||||
// Unless said otherwise with set_nonblock_state(), all writes
|
||||
// are blocking, and it should block till it has written all frames.
|
||||
int (*write)(void *data, const void *buffer, unsigned frames);
|
||||
|
||||
// Temporarily pauses the audio driver.
|
||||
int (*stop)(void *data);
|
||||
|
||||
// Resumes audio driver from the paused state.
|
||||
int (*start)(void *data);
|
||||
|
||||
// If state is true, nonblocking operation is assumed.
|
||||
// This is typically used for fast-forwarding. If driver cannot
|
||||
// implement nonblocking writes, this can be disregarded, but should
|
||||
// log a message to stderr.
|
||||
void (*set_nonblock_state)(void *data, int state);
|
||||
|
||||
// Stops and frees the audio driver.
|
||||
void (*free)(void *data);
|
||||
|
||||
// If true is returned, the audio driver is capable of using
|
||||
// floating point data. This will likely increase performance as the
|
||||
// resampler unit uses floating point. The sample range is
|
||||
// [-1.0, 1.0].
|
||||
int (*use_float)(void *data);
|
||||
|
||||
// The driver might be forced to use a certain output frequency
|
||||
// (i.e. Jack), and thus to avoid double resampling, the driver
|
||||
// can request SSNES to resample to a different sample rate.
|
||||
// This function can be set to NULL if the driver does not
|
||||
// desire to override the sample rate.
|
||||
unsigned (*sample_rate)(void *data);
|
||||
|
||||
// Human readable identification string for the driver.
|
||||
const char *ident;
|
||||
|
||||
// Must be set to SSNES_AUDIO_API_VERSION.
|
||||
// Used for detecting API mismatch.
|
||||
int api_version;
|
||||
} ssnes_audio_driver_t;
|
||||
|
||||
SSNES_API_EXPORT const ssnes_audio_driver_t* SSNES_API_CALLTYPE
|
||||
ssnes_audio_driver_init(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,124 @@
|
||||
/////
|
||||
// API header for external SSNES DSP plugins.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef __SSNES_DSP_PLUGIN_H
|
||||
#define __SSNES_DSP_PLUGIN_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef SSNES_DLL_IMPORT
|
||||
#define SSNES_API_EXPORT __declspec(dllimport)
|
||||
#else
|
||||
#define SSNES_API_EXPORT __declspec(dllexport)
|
||||
#endif
|
||||
#define SSNES_API_CALLTYPE __cdecl
|
||||
#else
|
||||
#define SSNES_API_EXPORT
|
||||
#define SSNES_API_CALLTYPE
|
||||
#endif
|
||||
|
||||
#ifndef SSNES_FALSE
|
||||
#define SSNES_FALSE 0
|
||||
#endif
|
||||
|
||||
#ifndef SSNES_TRUE
|
||||
#define SSNES_TRUE 1
|
||||
#endif
|
||||
|
||||
#define SSNES_DSP_API_VERSION 2
|
||||
|
||||
typedef struct ssnes_dsp_info
|
||||
{
|
||||
// Input sample rate that the DSP plugin receives. This is generally ~32kHz.
|
||||
// Some small variance is allowed due to syncing behavior.
|
||||
float input_rate;
|
||||
|
||||
// SSNES requests that the DSP plugin resamples the
|
||||
// input to a certain frequency.
|
||||
//
|
||||
// However, the plugin might ignore this
|
||||
// using the resample field in ssnes_dsp_output_t (see below).
|
||||
float output_rate;
|
||||
} ssnes_dsp_info_t;
|
||||
|
||||
typedef struct ssnes_dsp_output
|
||||
{
|
||||
// The DSP plugin has to provide the buffering for the output samples.
|
||||
// This is for performance reasons to avoid redundant copying of data.
|
||||
// The samples are laid out in interleaving order: LRLRLRLR
|
||||
// The range of the samples are [-1.0, 1.0].
|
||||
// This range cannot be exceeded without horrible audio glitches.
|
||||
const float *samples;
|
||||
|
||||
// Frames which the DSP plugin outputted for the current process.
|
||||
// One frame is here defined as a combined sample of
|
||||
// left and right channels.
|
||||
// (I.e. 44.1kHz, 16bit stereo will have
|
||||
// 88.2k samples/sec and 44.1k frames/sec.)
|
||||
unsigned frames;
|
||||
|
||||
// If true, the DSP plugin did not resample the input audio,
|
||||
// and requests resampling to the proper frequency to be
|
||||
// performed outside the plugin.
|
||||
// If false,
|
||||
// it is assumed that the output has the same sample rate as given
|
||||
// in output_rate.
|
||||
int should_resample;
|
||||
} ssnes_dsp_output_t;
|
||||
|
||||
typedef struct ssnes_dsp_input
|
||||
{
|
||||
// Input data for the DSP. The samples are interleaved in order: LRLRLRLR
|
||||
const float *samples;
|
||||
|
||||
// Number of frames for input data.
|
||||
// One frame is here defined as a combined sample of
|
||||
// left and right channels.
|
||||
// (I.e. 44.1kHz, 16bit stereo will have
|
||||
// 88.2k samples/sec and 44.1k frames/sec.)
|
||||
unsigned frames;
|
||||
} ssnes_dsp_input_t;
|
||||
|
||||
typedef struct ssnes_dsp_plugin
|
||||
{
|
||||
// Creates a handle of the plugin. Returns NULL if failed.
|
||||
void* (*init)(const ssnes_dsp_info_t *info);
|
||||
|
||||
// Processes input data.
|
||||
// The plugin is allowed to return variable sizes for output data.
|
||||
void (*process)(void *data, ssnes_dsp_output_t *output,
|
||||
const ssnes_dsp_input_t *input);
|
||||
|
||||
// Frees the handle.
|
||||
void (*free)(void *data);
|
||||
|
||||
// API version used to compile the plugin.
|
||||
// Used to detect mismatches in API.
|
||||
// Must be set to SSNES_DSP_API_VERSION on compile.
|
||||
int api_version;
|
||||
|
||||
// Signal plugin that it may open a configuring window or
|
||||
// something similiar. The behavior of this function
|
||||
// is thus plugin dependent. Implementing this is optional,
|
||||
// and can be set to NULL.
|
||||
void (*config)(void *data);
|
||||
|
||||
// Human readable identification string.
|
||||
const char *ident;
|
||||
} ssnes_dsp_plugin_t;
|
||||
|
||||
// Called by SSNES at startup to get the callback struct.
|
||||
// This is NOT dynamically allocated!
|
||||
SSNES_API_EXPORT const ssnes_dsp_plugin_t* SSNES_API_CALLTYPE
|
||||
ssnes_dsp_plugin_init(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,418 @@
|
||||
#include "ssnes_dsp.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <complex.h>
|
||||
|
||||
// Simple IIR/EQ filter implementation, optimized for SSE3.
|
||||
|
||||
#ifdef __SSE3__ // Build with -march=native or -msse3 to let this be detected! D:
|
||||
#define USE_SSE3
|
||||
#endif
|
||||
|
||||
#ifdef USE_SSE3
|
||||
#include <pmmintrin.h>
|
||||
#endif
|
||||
|
||||
// Make a test build with standalone main()
|
||||
//#define TEST
|
||||
|
||||
#ifdef TEST
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#endif
|
||||
|
||||
// Log filter coeffs for debugging
|
||||
//#define LOG
|
||||
|
||||
#define min(x, y) ((x) < (y) ? (x) : (y))
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265
|
||||
#endif
|
||||
|
||||
// Taps needs to be power-of-two.
|
||||
#define TAPS (2048)
|
||||
|
||||
static const float lp_freq = 5000.0;
|
||||
static const float hp_freq = 500.0;
|
||||
static const float bp_freq = 300.0;
|
||||
static const unsigned bp_q = 500;
|
||||
|
||||
#ifdef USE_SSE3
|
||||
typedef union
|
||||
{
|
||||
__m128 vec[TAPS / 4 + 1]; // Wraparound for unaligned reads.
|
||||
float f[TAPS + 4];
|
||||
} vec_filt;
|
||||
#else
|
||||
typedef float vec_filt[TAPS + 4];
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
vec_filt buffer[2];
|
||||
vec_filt iir_buffer[2];
|
||||
vec_filt fir_coeff;
|
||||
vec_filt iir_coeff;
|
||||
|
||||
float out_buffer[4096];
|
||||
unsigned buf_ptr;
|
||||
} dsp_state;
|
||||
|
||||
// Convolve polynoms.
|
||||
static void conv(float * restrict a, const float * restrict b, unsigned a_size, unsigned b_size)
|
||||
{
|
||||
float output[a_size + b_size - 1];
|
||||
|
||||
// Lead-in
|
||||
for (int i = 0; i < b_size - 1; i++)
|
||||
{
|
||||
output[i] = 0.0;
|
||||
for (int j = 0; j < b_size; j++)
|
||||
{
|
||||
if ((i - j) >= 0)
|
||||
output[i] += a[i - j] * b[j];
|
||||
}
|
||||
}
|
||||
|
||||
// Mid-section
|
||||
for (unsigned i = b_size - 1; i < a_size; i++)
|
||||
{
|
||||
output[i] = 0.0;
|
||||
for (unsigned j = 0; j < b_size; j++)
|
||||
output[i] += a[i - j] * b[j];
|
||||
}
|
||||
|
||||
// Lead-out
|
||||
for (unsigned i = a_size; i < a_size + b_size - 1; i++)
|
||||
{
|
||||
output[i] = 0.0;
|
||||
for (unsigned j = 0; j < b_size; j++)
|
||||
{
|
||||
if ((i - j) < a_size)
|
||||
output[i] += a[i - j] * b[j];
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(a, output, sizeof(output));
|
||||
}
|
||||
|
||||
#ifdef LOG
|
||||
static void print_coeff(const char *key, const float *coeff, unsigned elem)
|
||||
{
|
||||
for (unsigned i = 0; i < elem; i++)
|
||||
fprintf(stderr, "%4s [%3u] = %9.5f\n", key, i, coeff[i]);
|
||||
}
|
||||
|
||||
static void print_filter_coeffs(const float *fir, const float *iir, unsigned elem)
|
||||
{
|
||||
print_coeff("FIR", fir, elem);
|
||||
print_coeff("IIR", iir, elem);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Simulate plain 1-pole RC circuit.
|
||||
// dVc/dt = (Vin - Vc) / RC =>
|
||||
// y[n] = y[n - 1] * (1 - Ts/RC) + x[n] * Ts/RC
|
||||
// RCw = 1 => RC = 1/(2*pi*fc)
|
||||
static void generate_low_pass(float fir_out[1], float iir_out[2], float freq, float input_rate)
|
||||
{
|
||||
float ts = 1.0 / input_rate;
|
||||
float t = 1.0 / (2 * M_PI * freq);
|
||||
fir_out[0] = ts / t;
|
||||
iir_out[0] = 1.0;
|
||||
iir_out[1] = -(1.0 - ts / t);
|
||||
|
||||
#ifdef LOG
|
||||
print_coeff("FLOW", fir_out, 1);
|
||||
print_coeff("ILOW", iir_out, 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
// 1-pole RC circuit (Vo taken over R).
|
||||
static void generate_high_pass(float fir_out[2], float iir_out[2], float freq, float input_rate)
|
||||
{
|
||||
float ts = 1.0 / input_rate;
|
||||
float t = 1.0 / (2 * M_PI * freq);
|
||||
fir_out[0] = 1.0;
|
||||
fir_out[1] = -1.0;
|
||||
|
||||
iir_out[0] = 1.0;
|
||||
iir_out[1] = -(1.0 - ts / t);
|
||||
|
||||
#ifdef LOG
|
||||
print_coeff("FHI", fir_out, 2);
|
||||
print_coeff("IHI", iir_out, 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void generate_band_pass(float *fir_out, unsigned num_fir, float freq, float input_rate)
|
||||
{
|
||||
float w_norm = 2.0 * M_PI * freq / input_rate;
|
||||
for (unsigned i = 0; i < num_fir; i++)
|
||||
fir_out[i] = cosf(i * w_norm);
|
||||
|
||||
complex float amp = 0.0;
|
||||
for (unsigned i = 0; i < num_fir; i++)
|
||||
amp += cosf(i * w_norm) * cexp(-I * w_norm * i);
|
||||
|
||||
float abs = cabs(amp);
|
||||
for (unsigned i = 0; i < num_fir; i++)
|
||||
fir_out[i] /= abs;
|
||||
}
|
||||
|
||||
// Generic low-pass. Differential equation.
|
||||
static void generate_filter_coeffs(float * restrict fir_out, float * restrict iir_out, float input_rate)
|
||||
{
|
||||
fir_out[0] = 1.0;
|
||||
iir_out[0] = 1.0;
|
||||
|
||||
unsigned fir_length = 1;
|
||||
unsigned iir_length = 1;
|
||||
|
||||
/*
|
||||
// Low pass
|
||||
float lp_fir[1];
|
||||
float lp_iir[2];
|
||||
generate_low_pass(lp_fir, lp_iir, lp_freq, input_rate);
|
||||
conv(fir_out, lp_fir, fir_length, 1);
|
||||
conv(iir_out, lp_iir, iir_length, 2);
|
||||
fir_length += 0;
|
||||
iir_length += 1;
|
||||
|
||||
// High pass
|
||||
float hp_fir[2];
|
||||
float hp_iir[2];
|
||||
generate_high_pass(hp_fir, hp_iir, hp_freq, input_rate);
|
||||
conv(fir_out, hp_fir, fir_length, 2);
|
||||
conv(iir_out, hp_iir, iir_length, 2);
|
||||
fir_length += 1;
|
||||
iir_length += 1;
|
||||
*/
|
||||
|
||||
// Band pass
|
||||
float bp_fir[bp_q];
|
||||
generate_band_pass(bp_fir, bp_q, bp_freq, input_rate);
|
||||
conv(fir_out, bp_fir, fir_length, bp_q);
|
||||
fir_length += bp_q - 1;
|
||||
iir_length += 0;
|
||||
|
||||
// Adjust from Z rational polynomial model to discrete domain.
|
||||
// We don't care about first param as it's implicitly 1.
|
||||
for (unsigned i = 0; i < TAPS; i++)
|
||||
iir_out[i] = -1.0 * iir_out[i + 1];
|
||||
}
|
||||
|
||||
|
||||
static void* dsp_init(const ssnes_dsp_info_t *info)
|
||||
{
|
||||
(void)info;
|
||||
dsp_state *state = calloc(1, sizeof(*state));
|
||||
if (!state)
|
||||
return NULL;
|
||||
|
||||
fprintf(stderr, "Input rate = %.2f\n", (float)info->input_rate);
|
||||
|
||||
#ifdef USE_SSE3
|
||||
generate_filter_coeffs(state->fir_coeff.f, state->iir_coeff.f, info->input_rate);
|
||||
#ifdef LOG
|
||||
print_filter_coeffs(state->fir_coeff.f, state->iir_coeff.f, 3);
|
||||
#endif
|
||||
#else
|
||||
generate_filter_coeffs(state->fir_coeff, state->iir_coeff, info->input_rate);
|
||||
#ifdef LOG
|
||||
print_filter_coeffs(state->fir_coeff, state->iir_coeff, 3);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef USE_SSE3
|
||||
memcpy(state->fir_coeff.f + TAPS, state->fir_coeff.f, 4 * sizeof(float));
|
||||
memcpy(state->iir_coeff.f + TAPS, state->iir_coeff.f, 4 * sizeof(float));
|
||||
#endif
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
#ifdef USE_SSE3
|
||||
static void calculate_iir(float *out, dsp_state *dsp)
|
||||
{
|
||||
unsigned buf_ptr = (dsp->buf_ptr - 1) & (TAPS - 1);
|
||||
unsigned iir_ptr = (TAPS - dsp->buf_ptr) & (TAPS - 1);
|
||||
|
||||
const float * restrict samples_left = dsp->buffer[0].f;
|
||||
const float * restrict samples_right = dsp->buffer[1].f;
|
||||
const float * restrict iir_left = dsp->iir_buffer[0].f;
|
||||
const float * restrict iir_right = dsp->iir_buffer[1].f;
|
||||
|
||||
__m128 sum_left = _mm_setzero_ps();
|
||||
__m128 sum_right = _mm_setzero_ps();
|
||||
|
||||
for (unsigned i = 0; i < TAPS; i += 4)
|
||||
{
|
||||
__m128 left = _mm_load_ps(samples_left + i);
|
||||
__m128 right = _mm_load_ps(samples_right + i);
|
||||
const __m128 ileft = _mm_load_ps(iir_left + i);
|
||||
const __m128 iright = _mm_load_ps(iir_right + i);
|
||||
|
||||
// Need unaligned reads here.
|
||||
const __m128 fir = _mm_loadu_ps(dsp->fir_coeff.f + iir_ptr);
|
||||
const __m128 iir = _mm_loadu_ps(dsp->iir_coeff.f + iir_ptr);
|
||||
|
||||
const __m128 fir_res_left = _mm_mul_ps(fir, left);
|
||||
const __m128 fir_res_right = _mm_mul_ps(fir, right);
|
||||
const __m128 iir_res_left = _mm_mul_ps(iir, ileft);
|
||||
const __m128 iir_res_right = _mm_mul_ps(iir, iright);
|
||||
|
||||
left = _mm_add_ps(fir_res_left, iir_res_left);
|
||||
right = _mm_add_ps(fir_res_right, iir_res_right);
|
||||
|
||||
sum_left = _mm_add_ps(sum_left, left);
|
||||
sum_right = _mm_add_ps(sum_right, right);
|
||||
|
||||
iir_ptr = (iir_ptr + 4) & (TAPS - 1);
|
||||
}
|
||||
|
||||
__m128 res = _mm_hadd_ps(sum_left, sum_right);
|
||||
res = _mm_hadd_ps(res, res);
|
||||
|
||||
union
|
||||
{
|
||||
__m128 vec;
|
||||
float f[4];
|
||||
} u;
|
||||
u.vec = res;
|
||||
|
||||
out[0] = u.f[0];
|
||||
out[1] = u.f[1];
|
||||
dsp->iir_buffer[0].f[buf_ptr] = u.f[0];
|
||||
dsp->iir_buffer[1].f[buf_ptr] = u.f[1];
|
||||
}
|
||||
#else
|
||||
static void calculate_iir(float *out, dsp_state *dsp)
|
||||
{
|
||||
const float * restrict samples_left = dsp->buffer[0];
|
||||
const float * restrict samples_right = dsp->buffer[1];
|
||||
const float * restrict iir_left = dsp->iir_buffer[0];
|
||||
const float * restrict iir_right = dsp->iir_buffer[1];
|
||||
const float * restrict fir = dsp->fir_coeff;
|
||||
const float * restrict iir = dsp->iir_coeff;
|
||||
|
||||
unsigned iir_ptr = 0;
|
||||
unsigned sample_ptr = dsp->buf_ptr;
|
||||
|
||||
float sum[2] = { 0.0f, 0.0f };
|
||||
for (unsigned i = 0; i < TAPS; i++)
|
||||
{
|
||||
sum[0] += fir[iir_ptr] * samples_left[sample_ptr] + iir[iir_ptr] * iir_left[sample_ptr];
|
||||
sum[1] += fir[iir_ptr] * samples_right[sample_ptr] + iir[iir_ptr] * iir_right[sample_ptr];
|
||||
iir_ptr++;
|
||||
sample_ptr = (sample_ptr + 1) & (TAPS - 1);
|
||||
}
|
||||
|
||||
// Stick our output value in the IIR buffer.
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
out[i] = sum[i];
|
||||
dsp->iir_buffer[i][(dsp->buf_ptr - 1) & (TAPS - 1)] = sum[i];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void dsp_process(void *data, ssnes_dsp_output_t *output, const ssnes_dsp_input_t *input)
|
||||
{
|
||||
dsp_state *dsp = data;
|
||||
output->should_resample = SSNES_TRUE;
|
||||
output->frames = input->frames;
|
||||
output->samples = dsp->out_buffer;
|
||||
|
||||
for (unsigned i = 0; i < input->frames; i++)
|
||||
{
|
||||
#ifdef USE_SSE3
|
||||
dsp->buffer[0].f[dsp->buf_ptr] = input->samples[(i << 1) + 0];
|
||||
dsp->buffer[1].f[dsp->buf_ptr] = input->samples[(i << 1) + 1];
|
||||
#else
|
||||
dsp->buffer[0][dsp->buf_ptr] = input->samples[(i << 1) + 0];
|
||||
dsp->buffer[1][dsp->buf_ptr] = input->samples[(i << 1) + 1];
|
||||
#endif
|
||||
|
||||
calculate_iir(&dsp->out_buffer[i << 1], dsp);
|
||||
dsp->buf_ptr = (dsp->buf_ptr - 1) & (TAPS - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void dsp_config(void *data)
|
||||
{
|
||||
(void)data;
|
||||
// Normally we unhide a GUI window or something,
|
||||
// but we're just going to print to the log instead.
|
||||
fprintf(stderr, "DSP_CONFIG\n");
|
||||
}
|
||||
|
||||
static void dsp_free(void *data)
|
||||
{
|
||||
free(data);
|
||||
}
|
||||
|
||||
const ssnes_dsp_plugin_t dsp_plug = {
|
||||
.init = dsp_init,
|
||||
.process = dsp_process,
|
||||
.free = dsp_free,
|
||||
.config = dsp_config,
|
||||
.api_version = SSNES_DSP_API_VERSION,
|
||||
.ident = "IIR filter"
|
||||
};
|
||||
|
||||
SSNES_API_EXPORT const ssnes_dsp_plugin_t*
|
||||
SSNES_API_CALLTYPE ssnes_dsp_plugin_init(void)
|
||||
{
|
||||
return &dsp_plug;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
int main(void)
|
||||
{
|
||||
const ssnes_dsp_plugin_t *plug = ssnes_dsp_plugin_init();
|
||||
assert(plug);
|
||||
|
||||
ssnes_dsp_info_t info = {
|
||||
.input_rate = 44100,
|
||||
.output_rate = 44100
|
||||
};
|
||||
void *handle = plug->init(&info); // Info isn't used.
|
||||
assert(handle);
|
||||
|
||||
int16_t buf[64];
|
||||
float fbuf[64];
|
||||
|
||||
for (;;)
|
||||
{
|
||||
size_t rd = fread(buf, sizeof(int16_t), 64, stdin);
|
||||
for (unsigned i = 0; i < rd; i++)
|
||||
fbuf[i] = (float)buf[i];
|
||||
|
||||
ssnes_dsp_input_t input = {
|
||||
.samples = fbuf,
|
||||
.frames = rd >> 1
|
||||
};
|
||||
ssnes_dsp_output_t output;
|
||||
plug->process(handle, &output, &input);
|
||||
|
||||
for (unsigned i = 0; i < output.frames << 1; i++)
|
||||
{
|
||||
int32_t sample = (int32_t)output.samples[i];
|
||||
buf[i] = (sample > 0x7fff) ? 0x7fff : ((sample < -0x8000) ? -0x8000 : (int16_t)sample);
|
||||
}
|
||||
|
||||
fwrite(buf, sizeof(int16_t), output.frames << 1, stdout);
|
||||
|
||||
if (rd < 64)
|
||||
break;
|
||||
}
|
||||
|
||||
plug->free(handle);
|
||||
}
|
||||
#endif
|
||||
+1
-1
@@ -124,7 +124,7 @@ static size_t find_buffersize(jack_t *jd, int latency)
|
||||
return buffer_frames * sizeof(jack_default_audio_sample_t);
|
||||
}
|
||||
|
||||
static void* __jack_init(const char* device, int rate, int latency)
|
||||
static void* __jack_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
jack_t *jd = calloc(1, sizeof(jack_t));
|
||||
if ( jd == NULL )
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ typedef struct al
|
||||
|
||||
} al_t;
|
||||
|
||||
static void* __al_init(const char* device, int rate, int latency)
|
||||
static void* __al_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
(void)device;
|
||||
al_t *al = calloc(1, sizeof(al_t));
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static void* __oss_init(const char* device, int rate, int latency)
|
||||
static void* __oss_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
int *fd = calloc(1, sizeof(int));
|
||||
if ( fd == NULL )
|
||||
|
||||
+1
-1
@@ -115,7 +115,7 @@ static void stream_latency_update_cb(pa_stream *s, void *data)
|
||||
pa_threaded_mainloop_signal(pa->mainloop, 0);
|
||||
}
|
||||
|
||||
static void* __pulse_init(const char* device, int rate, int latency)
|
||||
static void* __pulse_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
pa_t *pa = calloc(1, sizeof(*pa));
|
||||
if (!pa)
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ typedef struct
|
||||
bool nonblocking;
|
||||
} roar_t;
|
||||
|
||||
static void* __roar_init(const char* device, int rate, int latency)
|
||||
static void* __roar_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
int err;
|
||||
roar_t *roar = calloc(1, sizeof(roar_t));
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ static void err_cb(void *userdata)
|
||||
SDL_CondSignal(rsd->cond);
|
||||
}
|
||||
|
||||
static void* __rsd_init(const char* device, int rate, int latency)
|
||||
static void* __rsd_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
rsd_t *rsd = calloc(1, sizeof(rsd_t));
|
||||
if ( rsd == NULL )
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ static inline int find_num_frames(int rate, int latency)
|
||||
return next_pow2(frames);
|
||||
}
|
||||
|
||||
static void* sdl_audio_init(const char* device, int rate, int latency)
|
||||
static void* sdl_audio_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
(void)device;
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
|
||||
|
||||
+4
-1
@@ -67,11 +67,14 @@ static bool init_lib(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void* __xa_init(const char* device, int rate, int latency)
|
||||
static void* __xa_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
if (!init_lib())
|
||||
return NULL;
|
||||
|
||||
if (latency < 8)
|
||||
latency = 8; // Do not allow shenanigans.
|
||||
|
||||
xa_t *xa = calloc(1, sizeof(xa_t));
|
||||
if (xa == NULL)
|
||||
return NULL;
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "cheats.h"
|
||||
#include "sha256.h"
|
||||
#include "dynamic.h"
|
||||
#include "general.h"
|
||||
#include "strl.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libxml/parser.h>
|
||||
#include <libxml/tree.h>
|
||||
|
||||
struct cheat
|
||||
{
|
||||
char *desc;
|
||||
bool state;
|
||||
char *code;
|
||||
};
|
||||
|
||||
struct cheat_manager
|
||||
{
|
||||
struct cheat *cheats;
|
||||
unsigned ptr;
|
||||
unsigned size;
|
||||
unsigned buf_size;
|
||||
};
|
||||
|
||||
static char* strcat_alloc(char *dest, const char *input)
|
||||
{
|
||||
size_t dest_len = dest ? strlen(dest) : 0;
|
||||
size_t input_len = strlen(input);
|
||||
size_t required_len = dest_len + input_len + 1;
|
||||
|
||||
char *output = realloc(dest, required_len);
|
||||
assert(output);
|
||||
|
||||
if (dest)
|
||||
strlcat(output, input, required_len);
|
||||
else
|
||||
strlcpy(output, input, required_len);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static void xml_grab_cheat(struct cheat *cht, xmlNodePtr ptr)
|
||||
{
|
||||
memset(cht, 0, sizeof(struct cheat));
|
||||
bool first = true;
|
||||
for (xmlNodePtr node = ptr; node; node = node->next)
|
||||
{
|
||||
if (strcmp((const char*)node->name, "description") == 0)
|
||||
{
|
||||
cht->desc = (char*)xmlNodeGetContent(node);
|
||||
}
|
||||
else if (strcmp((const char*)node->name, "code") == 0)
|
||||
{
|
||||
if (!first)
|
||||
cht->code = strcat_alloc(cht->code, "+");
|
||||
|
||||
xmlChar *code = xmlNodeGetContent(node);
|
||||
assert(code);
|
||||
|
||||
cht->code = strcat_alloc(cht->code, (const char*)code);
|
||||
xmlFree(code);
|
||||
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void xml_grab_cheats(cheat_manager_t *handle, xmlNodePtr ptr)
|
||||
{
|
||||
xmlNodePtr node = NULL;
|
||||
for (node = ptr; node; node = node->next)
|
||||
{
|
||||
if (strcmp((const char*)node->name, "name") == 0)
|
||||
{
|
||||
xmlChar *name = xmlNodeGetContent(node);
|
||||
if (name)
|
||||
{
|
||||
SSNES_LOG("Found cheat for game: \"%s\"\n", name);
|
||||
xmlFree(name);
|
||||
}
|
||||
}
|
||||
else if (strcmp((const char*)node->name, "cheat") == 0)
|
||||
{
|
||||
if (handle->size == handle->buf_size)
|
||||
{
|
||||
handle->buf_size *= 2;
|
||||
handle->cheats = realloc(handle->cheats, handle->buf_size * sizeof(struct cheat));
|
||||
assert(handle->cheats);
|
||||
}
|
||||
|
||||
xml_grab_cheat(&handle->cheats[handle->size++], node->children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cheat_manager_t* cheat_manager_new(const char *path)
|
||||
{
|
||||
psnes_cheat_reset();
|
||||
|
||||
xmlParserCtxtPtr ctx = NULL;
|
||||
xmlDocPtr doc = NULL;
|
||||
cheat_manager_t *handle = calloc(1, sizeof(struct cheat_manager));
|
||||
if (!handle)
|
||||
return NULL;
|
||||
|
||||
handle->buf_size = 32;
|
||||
handle->cheats = malloc(handle->buf_size * sizeof(struct cheat));
|
||||
if (!handle->cheats)
|
||||
{
|
||||
handle->buf_size = 0;
|
||||
goto error;
|
||||
}
|
||||
|
||||
ctx = xmlNewParserCtxt();
|
||||
if (!ctx)
|
||||
goto error;
|
||||
|
||||
doc = xmlCtxtReadFile(ctx, path, NULL, 0);
|
||||
if (!doc)
|
||||
{
|
||||
SSNES_ERR("Failed to parse XML file: %s\n", path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (ctx->valid == 0)
|
||||
{
|
||||
SSNES_ERR("Cannot validate XML file: %s\n", path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
xmlNodePtr head = xmlDocGetRootElement(doc);
|
||||
xmlNodePtr cur = NULL;
|
||||
for (cur = head; cur; cur = cur->next)
|
||||
{
|
||||
if (cur->type == XML_ELEMENT_NODE && strcmp((const char*)cur->name, "database") == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!cur)
|
||||
goto error;
|
||||
|
||||
for (cur = cur->children; cur; cur = cur->next)
|
||||
{
|
||||
if (cur->type != XML_ELEMENT_NODE)
|
||||
continue;
|
||||
|
||||
if (strcmp((const char*)cur->name, "cartridge") == 0)
|
||||
{
|
||||
xmlChar *sha256 = xmlGetProp(cur, (const xmlChar*)"sha256");
|
||||
if (!sha256)
|
||||
continue;
|
||||
|
||||
if (memcmp(sha256, g_extern.sha256, 64) == 0)
|
||||
{
|
||||
xmlFree(sha256);
|
||||
break;
|
||||
}
|
||||
|
||||
xmlFree(sha256);
|
||||
}
|
||||
}
|
||||
|
||||
if (!cur)
|
||||
goto error;
|
||||
|
||||
xml_grab_cheats(handle, cur->children);
|
||||
|
||||
xmlFreeDoc(doc);
|
||||
xmlFreeParserCtxt(ctx);
|
||||
return handle;
|
||||
|
||||
error:
|
||||
cheat_manager_free(handle);
|
||||
if (doc)
|
||||
xmlFreeDoc(doc);
|
||||
if (ctx)
|
||||
xmlFreeParserCtxt(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void cheat_manager_free(cheat_manager_t *handle)
|
||||
{
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
if (handle->cheats)
|
||||
{
|
||||
for (unsigned i = 0; i < handle->size; i++)
|
||||
{
|
||||
xmlFree(handle->cheats[i].desc);
|
||||
free(handle->cheats[i].code);
|
||||
}
|
||||
|
||||
free(handle->cheats);
|
||||
}
|
||||
|
||||
free(handle);
|
||||
}
|
||||
|
||||
static void cheat_manager_update(cheat_manager_t *handle)
|
||||
{
|
||||
msg_queue_clear(g_extern.msg_queue);
|
||||
char msg[256];
|
||||
snprintf(msg, sizeof(msg), "Cheat: #%u [%s]: %s", handle->ptr, handle->cheats[handle->ptr].state ? "ON" : "OFF", handle->cheats[handle->ptr].desc);
|
||||
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
|
||||
SSNES_LOG("%s\n", msg);
|
||||
}
|
||||
|
||||
void cheat_manager_toggle(cheat_manager_t *handle)
|
||||
{
|
||||
handle->cheats[handle->ptr].state ^= true;
|
||||
|
||||
unsigned index = 0;
|
||||
psnes_cheat_reset();
|
||||
for (unsigned i = 0; i < handle->size; i++)
|
||||
{
|
||||
if (handle->cheats[handle->ptr].state)
|
||||
psnes_cheat_set(index++, true, handle->cheats[handle->ptr].code);
|
||||
}
|
||||
|
||||
cheat_manager_update(handle);
|
||||
}
|
||||
|
||||
void cheat_manager_index_next(cheat_manager_t *handle)
|
||||
{
|
||||
handle->ptr = (handle->ptr + 1) % handle->size;
|
||||
cheat_manager_update(handle);
|
||||
}
|
||||
|
||||
void cheat_manager_index_prev(cheat_manager_t *handle)
|
||||
{
|
||||
if (handle->ptr == 0)
|
||||
handle->ptr = handle->size - 1;
|
||||
else
|
||||
handle->ptr--;
|
||||
|
||||
cheat_manager_update(handle);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __SSNES_CHEATS_H
|
||||
#define __SSNES_CHEATS_H
|
||||
|
||||
typedef struct cheat_manager cheat_manager_t;
|
||||
|
||||
cheat_manager_t* cheat_manager_new(const char *path);
|
||||
void cheat_manager_free(cheat_manager_t *handle);
|
||||
|
||||
void cheat_manager_index_next(cheat_manager_t *handle);
|
||||
void cheat_manager_index_prev(cheat_manager_t *handle);
|
||||
void cheat_manager_toggle(cheat_manager_t *handle);
|
||||
|
||||
#endif
|
||||
@@ -223,6 +223,22 @@ bool config_get_int(config_file_t *conf, const char *key, int *in)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool config_get_hex(config_file_t *conf, const char *key, unsigned *in)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list != NULL)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
{
|
||||
*in = strtol(list->value, NULL, 16);
|
||||
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;
|
||||
|
||||
@@ -42,6 +42,8 @@ void config_file_free(config_file_t *conf);
|
||||
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 an int from config file. (Hexadecimal)
|
||||
bool config_get_hex(config_file_t *conf, const char *entry, unsigned *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.
|
||||
|
||||
+54
-84
@@ -33,7 +33,7 @@
|
||||
#ifdef HAVE_SDL
|
||||
#include "SDL.h"
|
||||
#else
|
||||
#error HAVE_SDL is not defined!
|
||||
#error "HAVE_SDL is not defined!"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SRC
|
||||
@@ -44,6 +44,8 @@
|
||||
///////////////// Drivers
|
||||
#define VIDEO_GL 0
|
||||
#define VIDEO_XVIDEO 11
|
||||
#define VIDEO_SDL 13
|
||||
#define VIDEO_EXT 14
|
||||
////////////////////////
|
||||
#define AUDIO_RSOUND 1
|
||||
#define AUDIO_OSS 2
|
||||
@@ -54,6 +56,7 @@
|
||||
#define AUDIO_SDL 8
|
||||
#define AUDIO_XAUDIO 9
|
||||
#define AUDIO_PULSE 10
|
||||
#define AUDIO_EXT 15
|
||||
////////////////////////
|
||||
#define INPUT_SDL 7
|
||||
#define INPUT_X 12
|
||||
@@ -63,6 +66,12 @@
|
||||
#define VIDEO_DEFAULT_DRIVER VIDEO_GL
|
||||
#elif defined(HAVE_XVIDEO)
|
||||
#define VIDEO_DEFAULT_DRIVER VIDEO_XVIDEO
|
||||
#elif defined(HAVE_SDL)
|
||||
#define VIDEO_DEFAULT_DRIVER VIDEO_SDL
|
||||
#elif defined(HAVE_DYLIB)
|
||||
#define VIDEO_DEFAULT_DRIVER VIDEO_EXT
|
||||
#else
|
||||
#error "Need at least one video driver!"
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_ALSA)
|
||||
@@ -83,14 +92,18 @@
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_RSOUND
|
||||
#elif defined(HAVE_ROAR)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_ROAR
|
||||
#elif defined(HAVE_DYLIB)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_EXT
|
||||
#else
|
||||
#error Need at least one audio driver!
|
||||
#error "Need at least one audio driver!"
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_SDL)
|
||||
#define INPUT_DEFAULT_DRIVER INPUT_SDL
|
||||
#elif defined(HAVE_XVIDEO)
|
||||
#define INPUT_DEFAULT_DRIVER INPUT_X
|
||||
#else
|
||||
#error "Need at least one input driver!"
|
||||
#endif
|
||||
|
||||
|
||||
@@ -107,6 +120,9 @@ static const bool fullscreen = false; // To start in Fullscreen or not
|
||||
static const unsigned fullscreen_x = 0; // Fullscreen resolution. A value of 0 uses the desktop resolution.
|
||||
static const unsigned fullscreen_y = 0;
|
||||
|
||||
// Force 16-bit colors.
|
||||
static const bool force_16bit = false;
|
||||
|
||||
// Video VSYNC (recommended)
|
||||
static const bool vsync = true;
|
||||
|
||||
@@ -116,6 +132,9 @@ static const bool video_smooth = true;
|
||||
// On resize and fullscreen, rendering area will stay 4:3
|
||||
static const bool force_aspect = true;
|
||||
|
||||
// Crop overscanned frames (7/8 or 15/15 for interlaced frames).
|
||||
static const bool crop_overscan = false;
|
||||
|
||||
// Font size for on-screen messages.
|
||||
static const unsigned font_size = 48;
|
||||
|
||||
@@ -196,19 +215,20 @@ static const bool netplay_client_swap_input = false;
|
||||
// Player 1
|
||||
static const struct snes_keybind snes_keybinds_1[] = {
|
||||
// SNES button | keyboard key | js btn | js axis |
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_x, 1, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_z, 0, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_s, 3, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_a, 2, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_q, 4, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_w, 5, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_LEFT, 11, AXIS_NEG(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_RIGHT, 12, AXIS_POS(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_UP, 13, AXIS_NEG(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_DOWN, 14, AXIS_POS(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_RETURN, 7, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_RSHIFT, 6, AXIS_NONE },
|
||||
{ SSNES_FAST_FORWARD_KEY, SDLK_SPACE, 10, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_x, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_z, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_s, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_a, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_q, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_w, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_LEFT, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_RIGHT, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_UP, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_DOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_RETURN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_RSHIFT, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_FAST_FORWARD_KEY, SDLK_SPACE, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_FAST_FORWARD_HOLD_KEY, SDLK_l, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_SAVE_STATE_KEY, SDLK_F2, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_LOAD_STATE_KEY, SDLK_F4, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_FULLSCREEN_TOGGLE_KEY, SDLK_f, NO_BTN, AXIS_NONE },
|
||||
@@ -223,78 +243,28 @@ static const struct snes_keybind snes_keybinds_1[] = {
|
||||
{ SSNES_RESET, SDLK_h, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_SHADER_NEXT, SDLK_m, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_SHADER_PREV, SDLK_n, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_CHEAT_INDEX_PLUS, SDLK_y, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_CHEAT_INDEX_MINUS, SDLK_t, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_CHEAT_TOGGLE, SDLK_u, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_SCREENSHOT, SDLK_PRINT, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_DSP_CONFIG, SDLK_c, NO_BTN, AXIS_NONE },
|
||||
{ -1 }
|
||||
};
|
||||
|
||||
// Player 2
|
||||
static const struct snes_keybind snes_keybinds_2[] = {
|
||||
// SNES button | keyboard key | js btn | js axis |
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_b, 1, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_v, 0, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_g, 3, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_f, 2, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_r, 4, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_NEG(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_POS(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE },
|
||||
{ -1 }
|
||||
};
|
||||
|
||||
// Player 3
|
||||
static const struct snes_keybind snes_keybinds_3[] = {
|
||||
// SNES button | keyboard key | js btn | js axis |
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_b, 1, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_v, 0, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_g, 3, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_f, 2, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_r, 4, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_NEG(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_POS(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE },
|
||||
{ -1 }
|
||||
};
|
||||
|
||||
// Player 4
|
||||
static const struct snes_keybind snes_keybinds_4[] = {
|
||||
// SNES button | keyboard key | js btn | js axis |
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_b, 1, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_v, 0, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_g, 3, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_f, 2, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_r, 4, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_NEG(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_POS(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE },
|
||||
{ -1 }
|
||||
};
|
||||
|
||||
// Player 5
|
||||
static const struct snes_keybind snes_keybinds_5[] = {
|
||||
// SNES button | keyboard key | js btn | js axis |
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_b, 1, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_v, 0, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_g, 3, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_f, 2, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_r, 4, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_t, 5, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_j, 11, AXIS_NEG(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_l, 12, AXIS_POS(0) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_i, 13, AXIS_NEG(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_k, 14, AXIS_POS(1) },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_p, 6, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_o, 7, AXIS_NONE },
|
||||
// Player 2-5
|
||||
static const struct snes_keybind snes_keybinds_rest[] = {
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SDLK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ -1 }
|
||||
};
|
||||
|
||||
|
||||
+15
-3
@@ -62,10 +62,10 @@ static const bool _xaudio_supp = true;
|
||||
static const bool _xaudio_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FILTER
|
||||
static const bool _filter_supp = true;
|
||||
#ifdef HAVE_DYLIB
|
||||
static const bool _dylib_supp = true;
|
||||
#else
|
||||
static const bool _filter_supp = false;
|
||||
static const bool _dylib_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CG
|
||||
@@ -80,6 +80,12 @@ static const bool _xml_supp = true;
|
||||
static const bool _xml_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SDL_IMAGE
|
||||
static const bool _sdl_image_supp = true;
|
||||
#else
|
||||
static const bool _sdl_image_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
static const bool _fbo_supp = true;
|
||||
#else
|
||||
@@ -122,4 +128,10 @@ static const bool _netplay_supp = true;
|
||||
static const bool _netplay_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
static const bool _python_supp = true;
|
||||
#else
|
||||
static const bool _python_supp = false;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -131,5 +131,9 @@ Network port used for netplay. This defaults to 55435. This option affects both
|
||||
Attempts to apply an UPS patch to the current ROM image. No files are altered.
|
||||
If this flag is not specified, SSNES will look for a .ups file with same basename as ROM specified.
|
||||
|
||||
.TP
|
||||
\fB-D, --detach\fR
|
||||
Detach from the current console. This is currently only relevant for Microsoft Windows.
|
||||
|
||||
.SH "SEE ALSO"
|
||||
\fBssnes-joyconfig\fR(1)
|
||||
@@ -56,24 +56,31 @@ static const audio_driver_t *audio_drivers[] = {
|
||||
#ifdef HAVE_PULSE
|
||||
&audio_pulse,
|
||||
#endif
|
||||
#ifdef HAVE_DYLIB
|
||||
&audio_ext,
|
||||
#endif
|
||||
};
|
||||
|
||||
static const video_driver_t *video_drivers[] = {
|
||||
#ifdef HAVE_SDL
|
||||
&video_gl,
|
||||
&video_sdl,
|
||||
#endif
|
||||
#ifdef HAVE_XVIDEO
|
||||
&video_xvideo,
|
||||
#endif
|
||||
#ifdef HAVE_DYLIB
|
||||
&video_ext,
|
||||
#endif
|
||||
};
|
||||
|
||||
static const input_driver_t *input_drivers[] = {
|
||||
#ifdef HAVE_SDL
|
||||
&input_sdl,
|
||||
#endif
|
||||
//#ifdef HAVE_XVIDEO
|
||||
// &input_x,
|
||||
//#endif
|
||||
#ifdef HAVE_XVIDEO
|
||||
&input_x,
|
||||
#endif
|
||||
};
|
||||
|
||||
static void find_audio_driver(void)
|
||||
@@ -125,7 +132,7 @@ static void find_input_driver(void)
|
||||
SSNES_ERR("Couldn't find any input driver named \"%s\"\n", g_settings.input.driver);
|
||||
fprintf(stderr, "Available input drivers are:\n");
|
||||
for (int i = 0; i < sizeof(input_drivers) / sizeof(input_driver_t*); i++)
|
||||
fprintf(stderr, "\t%s\n", video_drivers[i]->ident);
|
||||
fprintf(stderr, "\t%s\n", input_drivers[i]->ident);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
@@ -142,6 +149,71 @@ void uninit_drivers(void)
|
||||
uninit_audio();
|
||||
}
|
||||
|
||||
static void init_dsp_plugin(void)
|
||||
{
|
||||
if (!(*g_settings.audio.dsp_plugin))
|
||||
return;
|
||||
|
||||
g_extern.audio_data.dsp_lib = dylib_load(g_settings.audio.dsp_plugin);
|
||||
if (!g_extern.audio_data.dsp_lib)
|
||||
{
|
||||
SSNES_ERR("Failed to open DSP plugin: \"%s\" ...\n", g_settings.audio.dsp_plugin);
|
||||
return;
|
||||
}
|
||||
|
||||
const ssnes_dsp_plugin_t* (*SSNES_API_CALLTYPE plugin_init)(void) =
|
||||
(const ssnes_dsp_plugin_t *(*)(void))dylib_proc(g_extern.audio_data.dsp_lib, "ssnes_dsp_plugin_init");
|
||||
if (!plugin_init)
|
||||
{
|
||||
SSNES_ERR("Failed to find symbol \"ssnes_dsp_plugin_init\" in DSP plugin.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
g_extern.audio_data.dsp_plugin = plugin_init();
|
||||
if (!g_extern.audio_data.dsp_plugin)
|
||||
{
|
||||
SSNES_ERR("Failed to get a valid DSP plugin.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (g_extern.audio_data.dsp_plugin->api_version != SSNES_DSP_API_VERSION)
|
||||
{
|
||||
SSNES_ERR("DSP plugin API mismatch! SSNES: %d, Plugin: %d\n", SSNES_DSP_API_VERSION, g_extern.audio_data.dsp_plugin->api_version);
|
||||
goto error;
|
||||
}
|
||||
|
||||
SSNES_LOG("Loaded DSP plugin: \"%s\"\n", g_extern.audio_data.dsp_plugin->ident ? g_extern.audio_data.dsp_plugin->ident : "Unknown");
|
||||
|
||||
ssnes_dsp_info_t info = {
|
||||
.input_rate = g_settings.audio.in_rate,
|
||||
.output_rate = g_settings.audio.out_rate
|
||||
};
|
||||
|
||||
g_extern.audio_data.dsp_handle = g_extern.audio_data.dsp_plugin->init(&info);
|
||||
if (!g_extern.audio_data.dsp_handle)
|
||||
{
|
||||
SSNES_ERR("Failed to init DSP plugin.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
if (g_extern.audio_data.dsp_lib)
|
||||
dylib_close(g_extern.audio_data.dsp_lib);
|
||||
g_extern.audio_data.dsp_plugin = NULL;
|
||||
g_extern.audio_data.dsp_lib = NULL;
|
||||
}
|
||||
|
||||
static void deinit_dsp_plugin(void)
|
||||
{
|
||||
if (g_extern.audio_data.dsp_lib && g_extern.audio_data.dsp_plugin)
|
||||
{
|
||||
g_extern.audio_data.dsp_plugin->free(g_extern.audio_data.dsp_handle);
|
||||
dylib_close(g_extern.audio_data.dsp_lib);
|
||||
}
|
||||
}
|
||||
|
||||
#define AUDIO_CHUNK_SIZE_BLOCKING 64
|
||||
#define AUDIO_CHUNK_SIZE_NONBLOCKING 2048 // So we don't get complete line-noise when fast-forwarding audio.
|
||||
#define AUDIO_MAX_RATIO 16
|
||||
@@ -192,6 +264,8 @@ void init_audio(void)
|
||||
g_extern.audio_data.data_ptr = 0;
|
||||
assert((g_extern.audio_data.outsamples = malloc(max_bufsamples * sizeof(float) * AUDIO_MAX_RATIO)));
|
||||
assert((g_extern.audio_data.conv_outsamples = malloc(max_bufsamples * sizeof(int16_t) * AUDIO_MAX_RATIO)));
|
||||
|
||||
init_dsp_plugin();
|
||||
}
|
||||
|
||||
void uninit_audio(void)
|
||||
@@ -217,9 +291,11 @@ void uninit_audio(void)
|
||||
free(g_extern.audio_data.data); g_extern.audio_data.data = NULL;
|
||||
free(g_extern.audio_data.outsamples); g_extern.audio_data.outsamples = NULL;
|
||||
free(g_extern.audio_data.conv_outsamples); g_extern.audio_data.conv_outsamples = NULL;
|
||||
|
||||
deinit_dsp_plugin();
|
||||
}
|
||||
|
||||
#ifdef HAVE_FILTER
|
||||
#ifdef HAVE_DYLIB
|
||||
static void init_filter(void)
|
||||
{
|
||||
if (g_extern.filter.active)
|
||||
@@ -235,8 +311,12 @@ static void init_filter(void)
|
||||
return;
|
||||
}
|
||||
|
||||
g_extern.filter.psize = dylib_proc(g_extern.filter.lib, "filter_size");
|
||||
g_extern.filter.prender = dylib_proc(g_extern.filter.lib, "filter_render");
|
||||
g_extern.filter.psize =
|
||||
(void (*)(unsigned*, unsigned*))dylib_proc(g_extern.filter.lib, "filter_size");
|
||||
g_extern.filter.prender =
|
||||
(void (*)(uint32_t*, uint32_t*,
|
||||
unsigned, const uint16_t*,
|
||||
unsigned, unsigned, unsigned))dylib_proc(g_extern.filter.lib, "filter_render");
|
||||
if (!g_extern.filter.psize || !g_extern.filter.prender)
|
||||
{
|
||||
SSNES_ERR("Failed to find functions in filter...\n");
|
||||
@@ -263,7 +343,7 @@ static void init_filter(void)
|
||||
g_extern.filter.colormap = malloc(32768 * sizeof(uint32_t));
|
||||
assert(g_extern.filter.colormap);
|
||||
|
||||
// Set up conversion map from 16-bit XBGR1555 to 32-bit RGBA.
|
||||
// Set up conversion map from 16-bit XRGB1555 to 32-bit ARGB.
|
||||
for (int i = 0; i < 32768; i++)
|
||||
{
|
||||
unsigned r = (i >> 10) & 31;
|
||||
@@ -273,7 +353,7 @@ static void init_filter(void)
|
||||
r = (r << 3) | (r >> 2);
|
||||
g = (g << 3) | (g >> 2);
|
||||
b = (b << 3) | (b >> 2);
|
||||
g_extern.filter.colormap[i] = (r << 24) | (g << 16) | (b << 8);
|
||||
g_extern.filter.colormap[i] = (r << 16) | (g << 8) | (b << 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -318,7 +398,7 @@ static void deinit_shader_dir(void)
|
||||
|
||||
void init_video_input(void)
|
||||
{
|
||||
#ifdef HAVE_FILTER
|
||||
#ifdef HAVE_DYLIB
|
||||
init_filter();
|
||||
#endif
|
||||
|
||||
@@ -335,9 +415,30 @@ void init_video_input(void)
|
||||
if (g_extern.filter.active)
|
||||
scale = g_extern.filter.scale;
|
||||
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
if (g_settings.video.fullscreen)
|
||||
{
|
||||
width = g_settings.video.fullscreen_x;
|
||||
height = g_settings.video.fullscreen_y;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (g_settings.video.force_aspect)
|
||||
{
|
||||
width = 224 * g_settings.video.xscale * g_settings.video.aspect_ratio;
|
||||
height = 224 * g_settings.video.yscale;
|
||||
}
|
||||
else
|
||||
{
|
||||
width = 256 * g_settings.video.xscale;
|
||||
height = 224 * g_settings.video.yscale;
|
||||
}
|
||||
}
|
||||
|
||||
video_info_t video = {
|
||||
.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),
|
||||
.width = width,
|
||||
.height = height,
|
||||
.fullscreen = g_settings.video.fullscreen,
|
||||
.vsync = g_settings.video.vsync,
|
||||
.force_aspect = g_settings.video.force_aspect,
|
||||
@@ -351,7 +452,7 @@ void init_video_input(void)
|
||||
|
||||
if ( driver.video_data == NULL )
|
||||
{
|
||||
SSNES_ERR("Cannot open video driver... Exiting ...\n");
|
||||
SSNES_ERR("Cannot open video driver ... Exiting ...\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
enum
|
||||
{
|
||||
SSNES_FAST_FORWARD_KEY = 0x666, // Hurr, durr
|
||||
SSNES_FAST_FORWARD_HOLD_KEY,
|
||||
SSNES_LOAD_STATE_KEY,
|
||||
SSNES_SAVE_STATE_KEY,
|
||||
SSNES_FULLSCREEN_TOGGLE_KEY,
|
||||
@@ -42,6 +43,11 @@ enum
|
||||
SSNES_RESET,
|
||||
SSNES_SHADER_NEXT,
|
||||
SSNES_SHADER_PREV,
|
||||
SSNES_CHEAT_INDEX_PLUS,
|
||||
SSNES_CHEAT_INDEX_MINUS,
|
||||
SSNES_CHEAT_TOGGLE,
|
||||
SSNES_SCREENSHOT,
|
||||
SSNES_DSP_CONFIG
|
||||
};
|
||||
|
||||
|
||||
@@ -55,8 +61,8 @@ struct snes_keybind
|
||||
|
||||
typedef struct video_info
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
bool fullscreen;
|
||||
bool vsync;
|
||||
bool force_aspect;
|
||||
@@ -67,7 +73,7 @@ typedef struct video_info
|
||||
|
||||
typedef struct audio_driver
|
||||
{
|
||||
void* (*init)(const char* device, int rate, int latency);
|
||||
void* (*init)(const char* device, unsigned rate, unsigned latency);
|
||||
ssize_t (*write)(void* data, const void* buf, size_t size);
|
||||
bool (*stop)(void* data);
|
||||
bool (*start)(void* data);
|
||||
@@ -108,7 +114,7 @@ typedef struct input_driver
|
||||
|
||||
typedef struct video_driver
|
||||
{
|
||||
void* (*init)(video_info_t *video, const input_driver_t **input, void **input_data);
|
||||
void* (*init)(const video_info_t *video, const input_driver_t **input, void **input_data);
|
||||
// Should the video driver act as an input driver as well? :) The video init might preinitialize an input driver to override the settings in case the video driver relies on input driver for event handling, e.g.
|
||||
bool (*frame)(void* data, const void* frame, unsigned width, unsigned height, unsigned pitch, const char *msg); // msg is for showing a message on the screen along with the video frame.
|
||||
void (*set_nonblock_state)(void* data, bool toggle); // Should we care about syncing to vblank? Fast forwarding.
|
||||
@@ -151,8 +157,11 @@ extern const audio_driver_t audio_jack;
|
||||
extern const audio_driver_t audio_sdl;
|
||||
extern const audio_driver_t audio_xa;
|
||||
extern const audio_driver_t audio_pulse;
|
||||
extern const audio_driver_t audio_ext;
|
||||
extern const video_driver_t video_gl;
|
||||
extern const video_driver_t video_xvideo;
|
||||
extern const video_driver_t video_sdl;
|
||||
extern const video_driver_t video_ext;
|
||||
extern const input_driver_t input_sdl;
|
||||
extern const input_driver_t input_x;
|
||||
////////////////////////////////////////////////
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "dynamic.h"
|
||||
#include "general.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
@@ -34,13 +35,13 @@
|
||||
#ifdef HAVE_DYNAMIC
|
||||
#define DLSYM(lib, x) dylib_proc(lib, #x)
|
||||
|
||||
#define SYM(x) do { \
|
||||
p##x = DLSYM(lib_handle, x); \
|
||||
#define SYM(type, x) do { \
|
||||
p##x = (type)DLSYM(lib_handle, x); \
|
||||
if (p##x == NULL) { SSNES_ERR("Failed to load symbol: \"%s\"\n", #x); exit(1); } \
|
||||
} while(0)
|
||||
|
||||
#define OPT_SYM(x) do { \
|
||||
p##x = DLSYM(lib_handle, x); \
|
||||
#define OPT_SYM(type, x) do { \
|
||||
p##x = (type)DLSYM(lib_handle, x); \
|
||||
} while(0)
|
||||
|
||||
static dylib_t lib_handle = NULL;
|
||||
@@ -57,6 +58,9 @@ void (*psnes_set_input_state)(snes_input_state_t);
|
||||
void (*psnes_reset)(void);
|
||||
void (*psnes_run)(void);
|
||||
|
||||
void (*psnes_cheat_reset)(void);
|
||||
void (*psnes_cheat_set)(unsigned, bool, const char*);
|
||||
|
||||
const char *(*psnes_library_id)(void) = NULL;
|
||||
unsigned (*psnes_library_revision_minor)(void);
|
||||
unsigned (*psnes_library_revision_major)(void);
|
||||
@@ -103,31 +107,38 @@ static void load_dynamic(void)
|
||||
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);
|
||||
OPT_SYM(snes_library_id);
|
||||
SYM(snes_library_revision_minor);
|
||||
SYM(snes_library_revision_major);
|
||||
SYM(snes_reset);
|
||||
SYM(snes_run);
|
||||
SYM(snes_get_region);
|
||||
SYM(snes_load_cartridge_normal);
|
||||
SYM(snes_load_cartridge_super_game_boy);
|
||||
SYM(snes_load_cartridge_bsx);
|
||||
SYM(snes_load_cartridge_bsx_slotted);
|
||||
SYM(snes_load_cartridge_sufami_turbo);
|
||||
SYM(snes_set_controller_port_device);
|
||||
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);
|
||||
SYM(void (*)(void), snes_init);
|
||||
SYM(void (*)(snes_video_refresh_t), snes_set_video_refresh);
|
||||
SYM(void (*)(snes_audio_sample_t), snes_set_audio_sample);
|
||||
SYM(void (*)(snes_input_poll_t), snes_set_input_poll);
|
||||
SYM(void (*)(snes_input_state_t), snes_set_input_state);
|
||||
OPT_SYM(const char *(*)(void), snes_library_id);
|
||||
SYM(unsigned (*)(void), snes_library_revision_minor);
|
||||
SYM(unsigned (*)(void), snes_library_revision_major);
|
||||
SYM(void (*)(void), snes_cheat_reset);
|
||||
SYM(void (*)(unsigned, bool, const char*), snes_cheat_set);
|
||||
SYM(void (*)(void), snes_reset);
|
||||
SYM(void (*)(void), snes_run);
|
||||
SYM(bool (*)(void), snes_get_region);
|
||||
SYM(bool (*)(const char*, const uint8_t*, unsigned), snes_load_cartridge_normal);
|
||||
SYM(bool (*)(const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned), snes_load_cartridge_super_game_boy);
|
||||
SYM(bool (*)(const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned), snes_load_cartridge_bsx);
|
||||
SYM(bool (*)(const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned), snes_load_cartridge_bsx_slotted);
|
||||
SYM(bool (*)(const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned), snes_load_cartridge_sufami_turbo);
|
||||
SYM(void (*)(bool, unsigned), snes_set_controller_port_device);
|
||||
SYM(unsigned (*)(void), snes_serialize_size);
|
||||
SYM(bool (*)(uint8_t*, unsigned), snes_serialize);
|
||||
SYM(bool (*)(const uint8_t*, unsigned), snes_unserialize);
|
||||
SYM(void (*)(const char*), snes_set_cartridge_basename);
|
||||
SYM(uint8_t *(*)(unsigned), snes_get_memory_data);
|
||||
SYM(unsigned (*)(unsigned), snes_get_memory_size);
|
||||
SYM(void (*)(void), snes_unload_cartridge);
|
||||
SYM(void (*)(void), snes_term);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -145,6 +156,8 @@ static void set_statics(void)
|
||||
SSYM(snes_set_input_state);
|
||||
SSYM(snes_library_revision_minor);
|
||||
SSYM(snes_library_revision_major);
|
||||
SSYM(snes_cheat_reset);
|
||||
SSYM(snes_cheat_set);
|
||||
SSYM(snes_reset);
|
||||
SSYM(snes_run);
|
||||
SSYM(snes_get_region);
|
||||
@@ -167,6 +180,9 @@ static void set_statics(void)
|
||||
|
||||
void init_dlsym(void)
|
||||
{
|
||||
// Guarantee that we can do "dirty" casting. Every OS that this program supports should pass this ...
|
||||
assert(sizeof(void*) == sizeof(void (*)(void)));
|
||||
|
||||
#ifdef HAVE_DYNAMIC
|
||||
if (strlen(g_settings.libsnes) > 0)
|
||||
load_dynamic();
|
||||
@@ -198,12 +214,15 @@ dylib_t dylib_load(const char *path)
|
||||
#endif
|
||||
}
|
||||
|
||||
void* dylib_proc(dylib_t lib, const char *proc)
|
||||
function_t dylib_proc(dylib_t lib, const char *proc)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
void *sym = (void*)GetProcAddress(lib, proc);
|
||||
function_t sym = (function_t)GetProcAddress(lib, proc);
|
||||
#else
|
||||
void *sym = dlsym(lib, proc);
|
||||
// Dirty hack to workaround the non-legality of void* -> fn-pointer casts.
|
||||
void *ptr_sym = dlsym(lib, proc);
|
||||
function_t sym;
|
||||
memcpy(&sym, &ptr_sym, sizeof(void*));
|
||||
#endif
|
||||
|
||||
if (!sym)
|
||||
|
||||
@@ -25,10 +25,11 @@ void init_dlsym(void);
|
||||
void uninit_dlsym(void);
|
||||
|
||||
typedef void *dylib_t;
|
||||
typedef void (*function_t)(void);
|
||||
|
||||
dylib_t dylib_load(const char *path);
|
||||
void dylib_close(dylib_t lib);
|
||||
void* dylib_proc(dylib_t lib, const char *proc);
|
||||
function_t dylib_proc(dylib_t lib, const char *proc);
|
||||
|
||||
extern void (*psnes_init)(void);
|
||||
|
||||
@@ -37,6 +38,9 @@ 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 void (*psnes_cheat_reset)(void);
|
||||
extern void (*psnes_cheat_set)(unsigned, bool, const char*);
|
||||
|
||||
extern const char* (*psnes_library_id)(void);
|
||||
extern unsigned (*psnes_library_revision_minor)(void);
|
||||
extern unsigned (*psnes_library_revision_major)(void);
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
#include "ups.h"
|
||||
#include "strl.h"
|
||||
|
||||
#ifdef HAVE_XML
|
||||
#include "sha256.h"
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
@@ -36,7 +40,7 @@
|
||||
#endif
|
||||
|
||||
// Generic file loader.
|
||||
static ssize_t read_file(const char *path, void **buf)
|
||||
ssize_t read_file(const char *path, void **buf)
|
||||
{
|
||||
void *rom_buf = NULL;
|
||||
FILE *file = fopen(path, "rb");
|
||||
@@ -175,6 +179,10 @@ static ssize_t read_rom_file(FILE* file, void** buf)
|
||||
}
|
||||
|
||||
g_extern.cart_crc = crc32_calculate(ret_buf, ret);
|
||||
#ifdef HAVE_XML
|
||||
sha256_hash(g_extern.sha256, ret_buf, ret);
|
||||
SSNES_LOG("SHA256 sum: %s\n", g_extern.sha256);
|
||||
#endif
|
||||
*buf = ret_buf;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include <sys/types.h>
|
||||
#include "general.h"
|
||||
|
||||
ssize_t read_file(const char *path, void **buf);
|
||||
|
||||
bool load_state(const char* path);
|
||||
bool save_state(const char* path);
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
#include "autosave.h"
|
||||
#include "netplay.h"
|
||||
#include "dynamic.h"
|
||||
#include "cheats.h"
|
||||
#include "audio/ext/ssnes_dsp.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
@@ -42,7 +44,7 @@
|
||||
|
||||
|
||||
#define MAX_PLAYERS 5
|
||||
#define MAX_BINDS 28 // Needs to be increased every time there are new binds added.
|
||||
#define MAX_BINDS 34 // Needs to be increased every time there are new binds added.
|
||||
#define SSNES_NO_JOYPAD 0xFFFF
|
||||
|
||||
enum ssnes_shader_type
|
||||
@@ -67,6 +69,7 @@ struct settings
|
||||
bool vsync;
|
||||
bool smooth;
|
||||
bool force_aspect;
|
||||
bool crop_overscan;
|
||||
float aspect_ratio;
|
||||
char cg_shader_path[256];
|
||||
char bsnes_shader_path[256];
|
||||
@@ -85,6 +88,9 @@ struct settings
|
||||
float msg_pos_x;
|
||||
float msg_pos_y;
|
||||
|
||||
bool force_16bit;
|
||||
|
||||
char external_driver[256];
|
||||
} video;
|
||||
|
||||
struct
|
||||
@@ -98,6 +104,9 @@ struct settings
|
||||
unsigned latency;
|
||||
bool sync;
|
||||
int src_quality;
|
||||
|
||||
char dsp_plugin[256];
|
||||
char external_driver[256];
|
||||
} audio;
|
||||
|
||||
struct
|
||||
@@ -110,6 +119,9 @@ struct settings
|
||||
} input;
|
||||
|
||||
char libsnes[256];
|
||||
char cheat_database[256];
|
||||
|
||||
char screenshot_directory[256];
|
||||
|
||||
bool rewind_enable;
|
||||
unsigned rewind_buffer_size;
|
||||
@@ -185,6 +197,10 @@ struct global
|
||||
|
||||
float *outsamples;
|
||||
int16_t *conv_outsamples;
|
||||
|
||||
dylib_t dsp_lib;
|
||||
const ssnes_dsp_plugin_t *dsp_plugin;
|
||||
void *dsp_handle;
|
||||
} audio_data;
|
||||
|
||||
struct
|
||||
@@ -243,6 +259,14 @@ struct global
|
||||
size_t size;
|
||||
size_t ptr;
|
||||
} shader_dir;
|
||||
|
||||
char sha256[64 + 1];
|
||||
|
||||
bool do_screenshot;
|
||||
|
||||
#ifdef HAVE_XML
|
||||
cheat_manager_t *cheat;
|
||||
#endif
|
||||
};
|
||||
|
||||
void parse_config(void);
|
||||
@@ -250,19 +274,19 @@ void parse_config(void);
|
||||
extern struct settings g_settings;
|
||||
extern struct global g_extern;
|
||||
|
||||
#define SSNES_LOG(msg, args...) do { \
|
||||
#define SSNES_LOG(...) do { \
|
||||
if (g_extern.verbose) \
|
||||
fprintf(stderr, "SSNES: " msg, ##args); \
|
||||
fprintf(stderr, "SSNES: " __VA_ARGS__); \
|
||||
fflush(stderr); \
|
||||
} while(0)
|
||||
|
||||
#define SSNES_ERR(msg, args...) do { \
|
||||
fprintf(stderr, "SSNES [ERROR] :: " msg, ##args); \
|
||||
#define SSNES_ERR(...) do { \
|
||||
fprintf(stderr, "SSNES [ERROR] :: " __VA_ARGS__); \
|
||||
fflush(stderr); \
|
||||
} while(0)
|
||||
|
||||
#define SSNES_WARN(msg, args...) do { \
|
||||
fprintf(stderr, "SSNES [WARN] :: " msg, ##args); \
|
||||
#define SSNES_WARN(...) do { \
|
||||
fprintf(stderr, "SSNES [WARN] :: " __VA_ARGS__); \
|
||||
fflush(stderr); \
|
||||
} while(0)
|
||||
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Loader for external API plugins.
|
||||
|
||||
#define SSNES_DLL_IMPORT
|
||||
#include "ext/ssnes_video.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include "dynamic.h"
|
||||
#include "general.h"
|
||||
|
||||
static bool g_input_dead = true;
|
||||
static bool g_video_dead = true;
|
||||
static dylib_t g_lib = NULL;
|
||||
|
||||
/////////// Input hook
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const ssnes_input_driver_t *driver;
|
||||
void *handle;
|
||||
} input_ext_t;
|
||||
|
||||
static void* input_ext_init(void)
|
||||
{
|
||||
g_input_dead = false;
|
||||
return calloc(1, sizeof(input_ext_t));
|
||||
}
|
||||
|
||||
static void input_ext_poll(void *data)
|
||||
{
|
||||
input_ext_t *ext = data;
|
||||
ext->driver->poll(ext->handle);
|
||||
}
|
||||
|
||||
static int16_t input_ext_input_state(void *data, const struct snes_keybind **snes_keybinds, bool port, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
input_ext_t *ext = data;
|
||||
|
||||
unsigned player = 0;
|
||||
if (device == SNES_DEVICE_MULTITAP)
|
||||
player = (port == SNES_PORT_1) ? 1 : index + 2;
|
||||
else
|
||||
player = (port == SNES_PORT_1) ? 1 : 2;
|
||||
|
||||
const struct snes_keybind *ssnes_bind = NULL;
|
||||
|
||||
for (unsigned i = 0; g_settings.input.binds[player - 1][i].id != -1; i++)
|
||||
{
|
||||
if (g_settings.input.binds[player - 1][i].id == id)
|
||||
{
|
||||
ssnes_bind = &g_settings.input.binds[player - 1][i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ssnes_bind)
|
||||
{
|
||||
struct ssnes_keybind bind = {
|
||||
.key = ssnes_bind->key,
|
||||
.joykey = ssnes_bind->joykey,
|
||||
.joyaxis = ssnes_bind->joyaxis
|
||||
};
|
||||
|
||||
return ext->driver->input_state(ext->handle, &bind, player);
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool input_ext_key_pressed(void *data, int key)
|
||||
{
|
||||
input_ext_t *ext = data;
|
||||
|
||||
const struct snes_keybind *ssnes_bind = NULL;
|
||||
for (unsigned i = 0; g_settings.input.binds[0][i].id != -1; i++)
|
||||
{
|
||||
if (g_settings.input.binds[0][i].id == key)
|
||||
{
|
||||
ssnes_bind = &g_settings.input.binds[0][i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ssnes_bind)
|
||||
{
|
||||
struct ssnes_keybind bind = {
|
||||
.key = ssnes_bind->key,
|
||||
.joykey = ssnes_bind->joykey,
|
||||
.joyaxis = ssnes_bind->joyaxis
|
||||
};
|
||||
|
||||
return ext->driver->input_state(ext->handle, &bind, 1);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static void input_ext_free(void *data)
|
||||
{
|
||||
input_ext_t *ext = data;
|
||||
if (ext)
|
||||
{
|
||||
if (ext->driver && ext->handle)
|
||||
ext->driver->free(ext->handle);
|
||||
|
||||
if (g_video_dead)
|
||||
{
|
||||
dylib_close(g_lib);
|
||||
g_lib = NULL;
|
||||
}
|
||||
|
||||
g_input_dead = true;
|
||||
|
||||
free(ext);
|
||||
}
|
||||
}
|
||||
|
||||
static const input_driver_t input_ext = {
|
||||
.init = input_ext_init,
|
||||
.poll = input_ext_poll,
|
||||
.input_state = input_ext_input_state,
|
||||
.key_pressed = input_ext_key_pressed,
|
||||
.free = input_ext_free,
|
||||
.ident = "ext"
|
||||
};
|
||||
|
||||
|
||||
//////////// Video hook
|
||||
typedef struct
|
||||
{
|
||||
const ssnes_video_driver_t *driver;
|
||||
void *handle;
|
||||
} ext_t;
|
||||
|
||||
static void video_ext_free(void *data)
|
||||
{
|
||||
ext_t *ext = data;
|
||||
if (ext)
|
||||
{
|
||||
if (ext->driver && ext->handle)
|
||||
ext->driver->free(ext->handle);
|
||||
|
||||
if (g_input_dead)
|
||||
{
|
||||
dylib_close(g_lib);
|
||||
g_lib = NULL;
|
||||
}
|
||||
|
||||
g_video_dead = true;
|
||||
|
||||
free(ext);
|
||||
}
|
||||
}
|
||||
|
||||
static bool video_ext_focus(void *data)
|
||||
{
|
||||
ext_t *ext = data;
|
||||
return ext->driver->focus(ext->handle);
|
||||
}
|
||||
|
||||
static bool video_ext_alive(void *data)
|
||||
{
|
||||
ext_t *ext = data;
|
||||
return ext->driver->alive(ext->handle);
|
||||
}
|
||||
|
||||
static void video_ext_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
ext_t *ext = data;
|
||||
ext->driver->set_nonblock_state(ext->handle, state);
|
||||
}
|
||||
|
||||
static bool video_ext_frame(void *data, const void *frame, unsigned width, unsigned height, unsigned pitch, const char *msg)
|
||||
{
|
||||
ext_t *ext = data;
|
||||
return ext->driver->frame(ext->handle, frame, width, height, pitch, msg);
|
||||
}
|
||||
|
||||
static void* setup_input(ext_t *ext, const ssnes_input_driver_t *driver)
|
||||
{
|
||||
int joypad_index[5];
|
||||
for (unsigned i = 0; i < 5; i++)
|
||||
joypad_index[i] = g_settings.input.joypad_map[i] == SSNES_NO_JOYPAD ? -1 : g_settings.input.joypad_map[i];
|
||||
|
||||
void *handle = driver->init(joypad_index, g_settings.input.axis_threshold);
|
||||
if (!handle)
|
||||
return NULL;
|
||||
|
||||
input_ext_t *wrap_handle = input_ext.init();
|
||||
if (!wrap_handle)
|
||||
return NULL;
|
||||
|
||||
wrap_handle->handle = handle;
|
||||
wrap_handle->driver = driver;
|
||||
|
||||
return wrap_handle;
|
||||
}
|
||||
|
||||
static bool setup_video(ext_t *ext, const video_info_t *video, const input_driver_t **input, void **input_data)
|
||||
{
|
||||
SSNES_LOG("Loaded driver: \"%s\"\n", ext->driver->ident ? ext->driver->ident : "Unknown");
|
||||
|
||||
if (SSNES_GRAPHICS_API_VERSION != ext->driver->api_version)
|
||||
{
|
||||
SSNES_ERR("API version mismatch detected!\n");
|
||||
SSNES_ERR("Required API version: %d, Library version: %d\n", SSNES_GRAPHICS_API_VERSION, ext->driver->api_version);
|
||||
return false;
|
||||
}
|
||||
|
||||
ssnes_video_info_t info = {
|
||||
.width = video->width,
|
||||
.height = video->height,
|
||||
.fullscreen = video->fullscreen,
|
||||
.vsync = video->vsync,
|
||||
.force_aspect = video->force_aspect,
|
||||
.aspect_ratio = g_settings.video.aspect_ratio,
|
||||
.smooth = video->smooth,
|
||||
.input_scale = video->input_scale,
|
||||
.color_format = video->rgb32 ? SSNES_COLOR_FORMAT_ARGB8888 : SSNES_COLOR_FORMAT_XRGB1555,
|
||||
.xml_shader = g_settings.video.bsnes_shader_path,
|
||||
.cg_shader = g_settings.video.cg_shader_path,
|
||||
.ttf_font = *g_settings.video.font_path ? g_settings.video.font_path : NULL,
|
||||
.ttf_font_size = g_settings.video.font_size
|
||||
};
|
||||
|
||||
const ssnes_input_driver_t *input_driver = NULL;
|
||||
ext->handle = ext->driver->init(&info, &input_driver);
|
||||
if (!ext->handle)
|
||||
return false;
|
||||
|
||||
*input = input_driver ? &input_ext : NULL;
|
||||
if (input_driver)
|
||||
*input_data = setup_input(ext, input_driver);
|
||||
else
|
||||
*input_data = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void* video_ext_init(const video_info_t *video, const input_driver_t **input, void **input_data)
|
||||
{
|
||||
ext_t *ext = calloc(1, sizeof(*ext));
|
||||
if (!ext)
|
||||
return NULL;
|
||||
|
||||
if (!(*g_settings.video.external_driver))
|
||||
{
|
||||
SSNES_ERR("External driver needs video_external_driver path to be set!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
g_lib = dylib_load(g_settings.video.external_driver);
|
||||
if (!g_lib)
|
||||
{
|
||||
SSNES_ERR("Failed to open library: \"%s\"\n", g_settings.video.external_driver);
|
||||
goto error;
|
||||
}
|
||||
|
||||
const ssnes_video_driver_t* (*video_init)(void) =
|
||||
(const ssnes_video_driver_t *(*)(void))dylib_proc(g_lib, "ssnes_video_init");
|
||||
if (!video_init)
|
||||
{
|
||||
SSNES_ERR("Couldn't find function ssnes_video_init in library ...\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ext->driver = video_init();
|
||||
if (!ext->driver)
|
||||
{
|
||||
SSNES_ERR("External driver returned invalid driver handle.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!setup_video(ext, video, input, input_data))
|
||||
{
|
||||
SSNES_ERR("Failed to start driver.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
g_video_dead = false;
|
||||
return ext;
|
||||
|
||||
error:
|
||||
video_ext_free(ext);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const video_driver_t video_ext = {
|
||||
.init = video_ext_init,
|
||||
.frame = video_ext_frame,
|
||||
.alive = video_ext_alive,
|
||||
.set_nonblock_state = video_ext_set_nonblock_state,
|
||||
.focus = video_ext_focus,
|
||||
.free = video_ext_free,
|
||||
.ident = "ext"
|
||||
};
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
/////
|
||||
// API header for external SSNES video and input plugins.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef __SSNES_VIDEO_DRIVER_H
|
||||
#define __SSNES_VIDEO_DRIVER_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef SSNES_DLL_IMPORT
|
||||
#define SSNES_API_EXPORT __declspec(dllimport)
|
||||
#else
|
||||
#define SSNES_API_EXPORT __declspec(dllexport)
|
||||
#endif
|
||||
#define SSNES_API_CALLTYPE __cdecl
|
||||
#else
|
||||
#define SSNES_API_EXPORT
|
||||
#define SSNES_API_CALLTYPE
|
||||
#endif
|
||||
|
||||
#define SSNES_GRAPHICS_API_VERSION 1
|
||||
|
||||
// Since we don't want to rely on C++ or C99 for a proper boolean type,
|
||||
// and make sure return semantics are perfectly clear ... ;)
|
||||
|
||||
#ifndef SSNES_OK
|
||||
#define SSNES_OK 1
|
||||
#endif
|
||||
|
||||
#ifndef SSNES_ERROR
|
||||
#define SSNES_ERROR 0
|
||||
#endif
|
||||
|
||||
#ifndef SSNES_TRUE
|
||||
#define SSNES_TRUE 1
|
||||
#endif
|
||||
|
||||
#ifndef SSNES_FALSE
|
||||
#define SSNES_FALSE 0
|
||||
#endif
|
||||
|
||||
#define SSNES_COLOR_FORMAT_XRGB1555 0
|
||||
#define SSNES_COLOR_FORMAT_ARGB8888 1
|
||||
|
||||
typedef struct ssnes_video_info
|
||||
{
|
||||
// Width of window.
|
||||
// If fullscreen mode is requested,
|
||||
// a width of 0 means the resolution of the desktop should be used.
|
||||
unsigned width;
|
||||
|
||||
// Height of window.
|
||||
// If fullscreen mode is requested,
|
||||
// a height of 0 means the resolutiof the desktop should be used.
|
||||
unsigned height;
|
||||
|
||||
// If true, start the window in fullscreen mode.
|
||||
int fullscreen;
|
||||
|
||||
// If true, VSync should be enabled.
|
||||
int vsync;
|
||||
|
||||
// If true, the output image should have the aspect ratio
|
||||
// as set in aspect_ratio.
|
||||
int force_aspect;
|
||||
|
||||
// Aspect ratio. Only takes effect if force_aspect is enabled.
|
||||
float aspect_ratio;
|
||||
|
||||
// Requests that the image is smoothed,
|
||||
// using bilinear filtering or otherwise.
|
||||
// If this cannot be implemented efficiently, this can be disregarded.
|
||||
// If smooth is false, nearest-neighbor scaling is requested.
|
||||
int smooth;
|
||||
|
||||
// input_scale defines the maximum size of the picture that will
|
||||
// ever be used with the frame callback.
|
||||
// The maximum resolution is a multiple of 256x256 size,
|
||||
// so an input scale of 2
|
||||
// means you should allocate a texture or of 512x512.
|
||||
// It is normally set to 2 due to the possibility of
|
||||
// pseudo-hires on the SNES.
|
||||
// CPU filters or otherwise might increase this value.
|
||||
unsigned input_scale;
|
||||
|
||||
// Defines the coloring format used of the input frame.
|
||||
// XRGB1555 format is 16-bit and has byte ordering: 0RRRRRGGGGGBBBBB,
|
||||
// in native endian.
|
||||
// ARGB8888 is AAAAAAAARRRRRRRRGGGGGGGGBBBBBBBB, native endian.
|
||||
// Alpha channel should be disregarded.
|
||||
int color_format;
|
||||
|
||||
// If non-NULL, requests the use of an XML shader.
|
||||
// Can be disregarded.
|
||||
const char *xml_shader;
|
||||
// If non-NULL, requests the use of a Cg shader.
|
||||
// Can be disregarded.
|
||||
// If both are non-NULL,
|
||||
// Cg or XML could be used at the discretion of the plugin.
|
||||
const char *cg_shader;
|
||||
|
||||
// Requestes that a certain
|
||||
// TTF font is used for rendering messages to the screen.
|
||||
// Can be disregarded.
|
||||
const char *ttf_font;
|
||||
unsigned ttf_font_size;
|
||||
|
||||
} ssnes_video_info_t;
|
||||
|
||||
// Some convenience macros.
|
||||
// Extract which axes to test for in negative or positive direction.
|
||||
// May be equal to SSNES_NO_AXIS, which means testing should not occur.
|
||||
#define SSNES_AXIS_NEG_GET(x) (((unsigned)(x) >> 16) & 0xFFFFU)
|
||||
#define SSNES_AXIS_POS_GET(x) ((unsigned)(x) & 0xFFFFU)
|
||||
|
||||
// I hope no joypad will ever have this many buttons or axes ... ;)
|
||||
// If joykey is this value, do not check that button.
|
||||
#define SSNES_NO_AXIS ((unsigned short)0xFFFFU)
|
||||
#define SSNES_NO_BTN ((unsigned short)0xFFFFU)
|
||||
|
||||
// Masks to test on joykey which hat direction is to be tested for.
|
||||
#define SSNES_HAT_UP_MASK (1 << 15)
|
||||
#define SSNES_HAT_DOWN_MASK (1 << 14)
|
||||
#define SSNES_HAT_LEFT_MASK (1 << 13)
|
||||
#define SSNES_HAT_RIGHT_MASK (1 << 12)
|
||||
#define SSNES_HAT_MAP(x, hat) ((x & ((1 << 12) - 1)) | hat)
|
||||
|
||||
#define SSNES_HAT_MASK (SSNES_HAT_UP_MASK | SSNES_HAT_DOWN_MASK | \
|
||||
SSNES_HAT_LEFT_MASK | SSNES_HAT_RIGHT_MASK)
|
||||
|
||||
// Test this on the joykey. If true, we want to test for a joypad hat
|
||||
// rather than a button.
|
||||
#define SSNES_GET_HAT_DIR(x) (x & SSNES_HAT_MASK)
|
||||
|
||||
// Gets the joypad hat to be tested for.
|
||||
// Only valid when SSNES_GET_HAT_DIR() returns true.
|
||||
#define SSNES_GET_HAT(x) (x & (~SSNES_HAT_MASK))
|
||||
|
||||
// key, joykey and joyaxis are all checked at the same time.
|
||||
// If any one of these are pressed, return 1 in state callback.
|
||||
struct ssnes_keybind
|
||||
{
|
||||
// If analog_x is true, we request an analog device to be polled
|
||||
// rather than normal keys.
|
||||
// The returned value should be the delta of
|
||||
// last frame and current frame in the X-axis.
|
||||
int analog_x;
|
||||
// If analog_y is true, we request an analog device to be polled
|
||||
// rather than normal keys.
|
||||
// The returned value should be the delta of
|
||||
// last frame and current frame in the Y-axis.
|
||||
int analog_y;
|
||||
|
||||
// Keyboard key. The key values use the SDL keysyms,
|
||||
// which probably need to be transformed to the native format.
|
||||
unsigned short key;
|
||||
|
||||
// Joypad key. Joypad POV (hats) are embedded into this key as well.
|
||||
unsigned short joykey;
|
||||
|
||||
// Joypad axis. Negative and positive axes are embedded into this variable.
|
||||
unsigned joyaxis;
|
||||
};
|
||||
|
||||
typedef struct ssnes_input_driver
|
||||
{
|
||||
// Inits input driver.
|
||||
// Joypad index denotes which joypads are desired for the various players.
|
||||
// Should an entry be negative,
|
||||
// do not open joypad for that player.
|
||||
// Threshold states the minimum offset that a joypad axis
|
||||
// has to be held for it to be registered.
|
||||
// The range of this is [0, 1],
|
||||
// where 0 means any displacement will register,
|
||||
// and 1 means the axis has to be pressed all the way to register.
|
||||
void* (*init)(const int joypad_index[5], float axis_threshold);
|
||||
|
||||
// Polls input. Called once every frame.
|
||||
void (*poll)(void* data);
|
||||
|
||||
// Queries input state for a certain key on a certain player.
|
||||
// Players are 1 - 5.
|
||||
// For digital inputs, pressed key is 1, not pressed key is 0.
|
||||
// Analog values have same range as a signed 16-bit integer.
|
||||
int (*input_state)(void* data, const struct ssnes_keybind *bind,
|
||||
unsigned player);
|
||||
|
||||
// Frees the input struct.
|
||||
void (*free)(void* data);
|
||||
|
||||
// Human readable indentification string.
|
||||
const char *ident;
|
||||
} ssnes_input_driver_t;
|
||||
|
||||
typedef struct ssnes_video_driver
|
||||
{
|
||||
// Inits the video driver. Returns an opaque handle pointer to the driver.
|
||||
// Returns NULL on error.
|
||||
//
|
||||
// Should the video driver request that a certain input driver is used,
|
||||
// it is possible to set the driver to *input.
|
||||
// If no certain driver is desired, set *input to NULL.
|
||||
void* (*init)(const ssnes_video_info_t *video,
|
||||
const ssnes_input_driver_t **input);
|
||||
|
||||
// Updates frame on the screen.
|
||||
// Frame can be either XRGB1555 or ARGB32 format
|
||||
// depending on rgb32 setting in ssnes_video_info_t.
|
||||
// Pitch is the distance in bytes between two scanlines in memory.
|
||||
//
|
||||
// When msg is non-NULL,
|
||||
// it's a message that should be displayed to the user.
|
||||
int (*frame)(void* data, const void* frame,
|
||||
unsigned width, unsigned height, unsigned pitch, const char *msg);
|
||||
|
||||
// Requests nonblocking operation.
|
||||
// True = VSync is turned off.
|
||||
// False = VSync is turned on.
|
||||
void (*set_nonblock_state)(void* data, int toggle);
|
||||
|
||||
// This must return false when the user exits the emulator.
|
||||
int (*alive)(void *data);
|
||||
|
||||
// Does the window have focus?
|
||||
int (*focus)(void *data);
|
||||
|
||||
// Frees the video driver.
|
||||
void (*free)(void* data);
|
||||
|
||||
// A human-readable identification of the video driver.
|
||||
const char *ident;
|
||||
|
||||
// Needs to be defined to SSNES_GRAPHICS_API_VERSION.
|
||||
// This is used to detect API mismatches.
|
||||
int api_version;
|
||||
} ssnes_video_driver_t;
|
||||
|
||||
// Called by SSNES on startup to get a driver handle.
|
||||
// This is NOT dynamically allocated.
|
||||
SSNES_API_EXPORT const ssnes_video_driver_t* SSNES_API_CALLTYPE
|
||||
ssnes_video_init(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+1
-1
@@ -35,7 +35,7 @@ bool gfx_window_title(char *buf, size_t size)
|
||||
if (frames == 0)
|
||||
{
|
||||
gettimeofday(&tv, NULL);
|
||||
snprintf(buf, size, g_extern.title_buf);
|
||||
snprintf(buf, size, "%s", g_extern.title_buf);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,13 +52,23 @@
|
||||
#include "fonts.h"
|
||||
#endif
|
||||
|
||||
static const GLfloat vertexes[] = {
|
||||
// Used for the last pass when rendering to the back buffer.
|
||||
static const GLfloat vertexes_flipped[] = {
|
||||
0, 0,
|
||||
0, 1,
|
||||
1, 1,
|
||||
1, 0
|
||||
};
|
||||
|
||||
// Used when rendering to an FBO.
|
||||
// Texture coords have to be aligned with vertex coordinates.
|
||||
static const GLfloat vertexes[] = {
|
||||
0, 1,
|
||||
0, 0,
|
||||
1, 0,
|
||||
1, 1
|
||||
};
|
||||
|
||||
static const GLfloat tex_coords[] = {
|
||||
0, 1,
|
||||
0, 0,
|
||||
@@ -66,12 +76,6 @@ static const GLfloat tex_coords[] = {
|
||||
1, 1
|
||||
};
|
||||
|
||||
static const GLfloat fbo_tex_coords[] = {
|
||||
0, 0,
|
||||
0, 1,
|
||||
1, 1,
|
||||
1, 0
|
||||
};
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
#ifdef _WIN32
|
||||
@@ -81,7 +85,7 @@ static PFNGLFRAMEBUFFERTEXTURE2DPROC pglFramebufferTexture2D = NULL;
|
||||
static PFNGLCHECKFRAMEBUFFERSTATUSPROC pglCheckFramebufferStatus = NULL;
|
||||
static PFNGLDELETEFRAMEBUFFERSPROC pglDeleteFramebuffers = NULL;
|
||||
|
||||
#define LOAD_SYM(sym) if (!p##sym) p##sym = ((void*)SDL_GL_GetProcAddress(#sym))
|
||||
#define LOAD_SYM(sym) if (!p##sym) { SDL_SYM_WRAP(p##sym, #sym) }
|
||||
static bool load_fbo_proc(void)
|
||||
{
|
||||
LOAD_SYM(glGenFramebuffers);
|
||||
@@ -103,6 +107,19 @@ static bool load_fbo_proc(void) { return true; }
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (defined(HAVE_XML) || defined(HAVE_CG)) && defined(_WIN32)
|
||||
PFNGLCLIENTACTIVETEXTUREPROC pglClientActiveTexture = NULL;
|
||||
PFNGLACTIVETEXTUREPROC pglActiveTexture = NULL;
|
||||
static inline bool load_gl_proc(void)
|
||||
{
|
||||
LOAD_SYM(glClientActiveTexture);
|
||||
LOAD_SYM(glActiveTexture);
|
||||
return pglClientActiveTexture && pglActiveTexture;
|
||||
}
|
||||
#else
|
||||
static inline bool load_gl_proc(void) { return true; }
|
||||
#endif
|
||||
|
||||
#define MAX_SHADERS 16
|
||||
|
||||
typedef struct gl
|
||||
@@ -111,6 +128,10 @@ typedef struct gl
|
||||
GLuint texture;
|
||||
GLuint tex_filter;
|
||||
|
||||
void *empty_buf;
|
||||
|
||||
unsigned frame_count;
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
// Render-to-texture, multipass shaders
|
||||
GLuint fbo[MAX_SHADERS];
|
||||
@@ -140,7 +161,7 @@ typedef struct gl
|
||||
GLfloat fbo_tex_coords[8];
|
||||
#endif
|
||||
|
||||
GLenum texture_type; // XBGR1555 or RGBA
|
||||
GLenum texture_type; // XBGR1555 or ARGB
|
||||
GLenum texture_fmt;
|
||||
unsigned base_size; // 2 or 4
|
||||
|
||||
@@ -231,14 +252,23 @@ static void gl_shader_set_proj_matrix(void)
|
||||
|
||||
static void gl_shader_set_params(unsigned width, unsigned height,
|
||||
unsigned tex_width, unsigned tex_height,
|
||||
unsigned out_width, unsigned out_height)
|
||||
unsigned out_width, unsigned out_height,
|
||||
unsigned frame_count,
|
||||
const struct gl_tex_info *info,
|
||||
const struct gl_tex_info *fbo_info, unsigned fbo_info_cnt)
|
||||
{
|
||||
#ifdef HAVE_CG
|
||||
gl_cg_set_params(width, height, tex_width, tex_height, out_width, out_height);
|
||||
gl_cg_set_params(width, height,
|
||||
tex_width, tex_height,
|
||||
out_width, out_height,
|
||||
frame_count, info, fbo_info, fbo_info_cnt);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_XML
|
||||
gl_glsl_set_params(width, height, tex_width, tex_height, out_width, out_height);
|
||||
gl_glsl_set_params(width, height,
|
||||
tex_width, tex_height,
|
||||
out_width, out_height,
|
||||
frame_count, info, fbo_info, fbo_info_cnt);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -622,17 +652,65 @@ static void gl_render_msg(gl_t *gl, const char *msg)
|
||||
|
||||
// Go back to old rendering path.
|
||||
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), gl->tex_coords);
|
||||
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), vertexes);
|
||||
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), vertexes_flipped);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
glDisable(GL_BLEND);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void set_lut_texture_coords(const GLfloat *coords)
|
||||
{
|
||||
#if defined(HAVE_XML) || defined(HAVE_CG)
|
||||
// For texture images.
|
||||
pglClientActiveTexture(GL_TEXTURE1);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), coords);
|
||||
pglClientActiveTexture(GL_TEXTURE0);
|
||||
#else
|
||||
(void)coords;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void set_texture_coords(GLfloat *coords, GLfloat xamt, GLfloat yamt)
|
||||
{
|
||||
coords[1] = yamt;
|
||||
coords[4] = xamt;
|
||||
coords[6] = xamt;
|
||||
coords[7] = yamt;
|
||||
}
|
||||
|
||||
static void check_window(gl_t *gl)
|
||||
{
|
||||
SDL_Event event;
|
||||
|
||||
// Search for events we care about ...
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
gl->quitting = true;
|
||||
break;
|
||||
|
||||
case SDL_VIDEORESIZE:
|
||||
gl->should_resize = true;
|
||||
gl->win_width = event.resize.w;
|
||||
gl->win_height = event.resize.h;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool gl_frame(void *data, const void* frame, unsigned width, unsigned height, unsigned pitch, const char *msg)
|
||||
{
|
||||
gl_t *gl = data;
|
||||
check_window(gl);
|
||||
|
||||
gl_shader_use(1);
|
||||
gl->frame_count++;
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
// Render to texture in first pass.
|
||||
@@ -745,39 +823,56 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
#endif
|
||||
}
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
gl_shader_set_params(width, height, gl->tex_w, gl->tex_h, gl->vp_width, gl->vp_height);
|
||||
|
||||
if (width != gl->last_width || height != gl->last_height) // Res change. need to clear out texture.
|
||||
if ((width != gl->last_width || height != gl->last_height) && gl->empty_buf) // Res change. need to clear out texture.
|
||||
{
|
||||
gl->last_width = width;
|
||||
gl->last_height = height;
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(pitch));
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, gl->tex_w);
|
||||
|
||||
// Can we pass NULL here, hmm?
|
||||
void *tmp = calloc(1, gl->tex_w * gl->tex_h * gl->base_size);
|
||||
glTexSubImage2D(GL_TEXTURE_2D,
|
||||
0, 0, 0, gl->tex_w, gl->tex_h, gl->texture_type,
|
||||
gl->texture_fmt, tmp);
|
||||
free(tmp);
|
||||
gl->texture_fmt, gl->empty_buf);
|
||||
|
||||
GLfloat x = (GLfloat)width / gl->tex_w;
|
||||
GLfloat y = (GLfloat)height / gl->tex_h;
|
||||
GLfloat xamt = (GLfloat)width / gl->tex_w;
|
||||
GLfloat yamt = (GLfloat)height / gl->tex_h;
|
||||
|
||||
gl->tex_coords[1] = y;
|
||||
gl->tex_coords[4] = x;
|
||||
gl->tex_coords[6] = x;
|
||||
gl->tex_coords[7] = y;
|
||||
|
||||
//SSNES_LOG("Setting last rect: %ux%u\n", width, height);
|
||||
set_texture_coords(gl->tex_coords, xamt, yamt);
|
||||
}
|
||||
|
||||
// Work around a certain issue a Cg where not using TEXUNIT0
|
||||
// in shader causes cgGLEnableTextureParameter() causes it
|
||||
// to bind to TEXUNIT0, to avoid really funny bugs, rebind
|
||||
// our texture.
|
||||
#ifdef HAVE_CG
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
// Need to preserve the "flipped" state when in FBO too to have
|
||||
// consistent texture coordinates.
|
||||
if (gl->render_to_tex)
|
||||
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), vertexes);
|
||||
#endif
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, pitch / gl->base_size);
|
||||
glTexSubImage2D(GL_TEXTURE_2D,
|
||||
0, 0, 0, width, height, gl->texture_type,
|
||||
gl->texture_fmt, frame);
|
||||
|
||||
struct gl_tex_info tex_info = {
|
||||
.tex = gl->texture,
|
||||
.input_size = {width, height},
|
||||
.tex_size = {gl->tex_w, gl->tex_h}
|
||||
};
|
||||
struct gl_tex_info fbo_tex_info[MAX_SHADERS];
|
||||
unsigned fbo_tex_info_cnt = 0;
|
||||
memcpy(tex_info.coord, gl->tex_coords, sizeof(gl->tex_coords));
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
gl_shader_set_params(width, height, gl->tex_w, gl->tex_h, gl->vp_width, gl->vp_height, gl->frame_count,
|
||||
&tex_info, fbo_tex_info, fbo_tex_info_cnt);
|
||||
|
||||
glDrawArrays(GL_QUADS, 0, 4);
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
@@ -789,20 +884,26 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
// It's kinda handy ... :)
|
||||
const struct gl_fbo_rect *prev_rect;
|
||||
const struct gl_fbo_rect *rect;
|
||||
struct gl_tex_info *fbo_info;
|
||||
|
||||
// Calculate viewports, texture coordinates etc, and render all passes from FBOs, to another FBO.
|
||||
for (int i = 1; i < gl->fbo_pass; i++)
|
||||
{
|
||||
prev_rect = &gl->fbo_rect[i - 1];
|
||||
rect = &gl->fbo_rect[i];
|
||||
fbo_info = &fbo_tex_info[i - 1];
|
||||
|
||||
GLfloat xamt = (GLfloat)prev_rect->img_width / prev_rect->width;
|
||||
GLfloat yamt = (GLfloat)prev_rect->img_height / prev_rect->height;
|
||||
|
||||
gl->fbo_tex_coords[3] = yamt;
|
||||
gl->fbo_tex_coords[4] = xamt;
|
||||
gl->fbo_tex_coords[5] = yamt;
|
||||
gl->fbo_tex_coords[6] = xamt;
|
||||
set_texture_coords(gl->fbo_tex_coords, xamt, yamt);
|
||||
|
||||
fbo_info->tex = gl->fbo_texture[i - 1];
|
||||
fbo_info->input_size[0] = prev_rect->img_width;
|
||||
fbo_info->input_size[1] = prev_rect->img_height;
|
||||
fbo_info->tex_size[0] = prev_rect->width;
|
||||
fbo_info->tex_size[1] = prev_rect->height;
|
||||
memcpy(fbo_info->coord, gl->fbo_tex_coords, sizeof(gl->fbo_tex_coords));
|
||||
|
||||
pglBindFramebuffer(GL_FRAMEBUFFER, gl->fbo[i]);
|
||||
gl_shader_use(i + 1);
|
||||
@@ -812,8 +913,14 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
|
||||
// Render to FBO with certain size.
|
||||
set_viewport(gl, rect->img_width, rect->img_height, true);
|
||||
gl_shader_set_params(prev_rect->img_width, prev_rect->img_height, prev_rect->width, prev_rect->height, gl->vp_width, gl->vp_height);
|
||||
gl_shader_set_params(prev_rect->img_width, prev_rect->img_height,
|
||||
prev_rect->width, prev_rect->height,
|
||||
gl->vp_width, gl->vp_height, gl->frame_count,
|
||||
&tex_info, fbo_tex_info, fbo_tex_info_cnt);
|
||||
|
||||
glDrawArrays(GL_QUADS, 0, 4);
|
||||
|
||||
fbo_tex_info_cnt++;
|
||||
}
|
||||
|
||||
// Render our last FBO texture directly to screen.
|
||||
@@ -821,22 +928,25 @@ static bool gl_frame(void *data, const void* frame, unsigned width, unsigned hei
|
||||
GLfloat xamt = (GLfloat)prev_rect->img_width / prev_rect->width;
|
||||
GLfloat yamt = (GLfloat)prev_rect->img_height / prev_rect->height;
|
||||
|
||||
gl->fbo_tex_coords[3] = yamt;
|
||||
gl->fbo_tex_coords[4] = xamt;
|
||||
gl->fbo_tex_coords[5] = yamt;
|
||||
gl->fbo_tex_coords[6] = xamt;
|
||||
set_texture_coords(gl->fbo_tex_coords, xamt, yamt);
|
||||
|
||||
// Render our FBO texture to back buffer.
|
||||
pglBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
gl_shader_use(gl->fbo_pass + 1);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, gl->fbo_texture[gl->fbo_pass - 1]);
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
gl->render_to_tex = false;
|
||||
set_viewport(gl, gl->win_width, gl->win_height, false);
|
||||
gl_shader_set_params(prev_rect->img_width, prev_rect->img_height, prev_rect->width, prev_rect->height, gl->vp_width, gl->vp_height);
|
||||
glBindTexture(GL_TEXTURE_2D, gl->fbo_texture[gl->fbo_pass - 1]);
|
||||
gl_shader_set_params(prev_rect->img_width, prev_rect->img_height,
|
||||
prev_rect->width, prev_rect->height,
|
||||
gl->vp_width, gl->vp_height, gl->frame_count,
|
||||
&tex_info, fbo_tex_info, fbo_tex_info_cnt);
|
||||
|
||||
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), vertexes_flipped);
|
||||
glDrawArrays(GL_QUADS, 0, 4);
|
||||
|
||||
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), gl->tex_coords);
|
||||
}
|
||||
#endif
|
||||
@@ -871,6 +981,9 @@ static void gl_free(void *data)
|
||||
#endif
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
|
||||
if (gl->empty_buf)
|
||||
free(gl->empty_buf);
|
||||
|
||||
free(gl);
|
||||
}
|
||||
|
||||
@@ -882,18 +995,27 @@ static void gl_set_nonblock_state(void *data, bool state)
|
||||
SSNES_LOG("GL VSync => %s\n", state ? "off" : "on");
|
||||
#ifdef _WIN32
|
||||
static BOOL (APIENTRY *wgl_swap_interval)(int) = NULL;
|
||||
if (!wgl_swap_interval) wgl_swap_interval = (BOOL (APIENTRY*)(int)) SDL_GL_GetProcAddress("wglSwapIntervalEXT");
|
||||
if (!wgl_swap_interval)
|
||||
{
|
||||
SDL_SYM_WRAP(wgl_swap_interval, "wglSwapIntervalEXT");
|
||||
}
|
||||
if (wgl_swap_interval) wgl_swap_interval(state ? 0 : 1);
|
||||
#else
|
||||
static int (*glx_swap_interval)(int) = NULL;
|
||||
if (!glx_swap_interval) glx_swap_interval = (int (*)(int))SDL_GL_GetProcAddress("glXSwapIntervalSGI");
|
||||
if (!glx_swap_interval) glx_swap_interval = (int (*)(int))SDL_GL_GetProcAddress("glXSwapIntervalMESA");
|
||||
if (!glx_swap_interval)
|
||||
{
|
||||
SDL_SYM_WRAP(glx_swap_interval, "glXSwapIntervalSGI");
|
||||
}
|
||||
if (!glx_swap_interval)
|
||||
{
|
||||
SDL_SYM_WRAP(glx_swap_interval, "glXSwapIntervalMESA");
|
||||
}
|
||||
if (glx_swap_interval) glx_swap_interval(state ? 0 : 1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static void* gl_init(video_info_t *video, const input_driver_t **input, void **input_data)
|
||||
static void* gl_init(const video_info_t *video, const input_driver_t **input, void **input_data)
|
||||
{
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
return NULL;
|
||||
@@ -907,27 +1029,43 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, video->vsync ? 1 : 0);
|
||||
|
||||
if (!SDL_SetVideoMode(video->width, video->height, 0, SDL_OPENGL | SDL_RESIZABLE | (video->fullscreen ? SDL_FULLSCREEN : 0)))
|
||||
if (!SDL_SetVideoMode(video->width, video->height, g_settings.video.force_16bit ? 16 : 0, SDL_OPENGL | SDL_RESIZABLE | (video->fullscreen ? SDL_FULLSCREEN : 0)))
|
||||
return NULL;
|
||||
|
||||
// Remove that ugly mouse :D
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
#if (defined(HAVE_XML) || defined(HAVE_CG)) && defined(_WIN32)
|
||||
// Win32 GL lib doesn't have some functions needed for XML shaders.
|
||||
// Need to load dynamically :(
|
||||
if (!load_gl_proc())
|
||||
{
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
gl_t *gl = calloc(1, sizeof(gl_t));
|
||||
if (!gl)
|
||||
{
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gl->vsync = video->vsync;
|
||||
int attr = 0;
|
||||
SDL_GL_GetAttribute(SDL_GL_SWAP_CONTROL, &attr);
|
||||
if (attr <= 0 && video->vsync)
|
||||
SSNES_WARN("GL VSync has not been enabled!\n");
|
||||
{
|
||||
SSNES_WARN("SDL failed to setup VSync, attempting to recover using native calls!\n");
|
||||
gl_set_nonblock_state(gl, false);
|
||||
}
|
||||
attr = 0;
|
||||
SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &attr);
|
||||
if (attr <= 0)
|
||||
SSNES_WARN("GL double buffer has not been enabled!\n");
|
||||
|
||||
|
||||
gl_t *gl = calloc(1, sizeof(gl_t));
|
||||
if (!gl)
|
||||
return NULL;
|
||||
|
||||
gl->full_x = full_x;
|
||||
gl->full_y = full_y;
|
||||
|
||||
@@ -952,15 +1090,16 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SSNES_LOG("GL: Loaded %u programs(s).\n", gl_shader_num());
|
||||
SSNES_LOG("GL: Loaded %u program(s).\n", gl_shader_num());
|
||||
|
||||
// Set up render to texture.
|
||||
gl_init_fbo(gl, 256 * video->input_scale, 256 * video->input_scale);
|
||||
|
||||
gl->vsync = video->vsync;
|
||||
|
||||
gl->keep_aspect = video->force_aspect;
|
||||
|
||||
// Apparently need to set viewport for passes when we aren't using FBOs.
|
||||
gl_shader_use(0);
|
||||
set_viewport(gl, gl->win_width, gl->win_height, false);
|
||||
gl_shader_use(1);
|
||||
set_viewport(gl, gl->win_width, gl->win_height, false);
|
||||
|
||||
@@ -970,8 +1109,8 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
|
||||
else
|
||||
gl->tex_filter = video->smooth ? GL_LINEAR : GL_NEAREST;
|
||||
|
||||
gl->texture_type = video->rgb32 ? GL_RGBA : GL_BGRA;
|
||||
gl->texture_fmt = video->rgb32 ? GL_UNSIGNED_INT_8_8_8_8 : GL_UNSIGNED_SHORT_1_5_5_5_REV;
|
||||
gl->texture_type = GL_BGRA;
|
||||
gl->texture_fmt = video->rgb32 ? GL_UNSIGNED_INT_8_8_8_8_REV : GL_UNSIGNED_SHORT_1_5_5_5_REV;
|
||||
gl->base_size = video->rgb32 ? sizeof(uint32_t) : sizeof(uint16_t);
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
@@ -999,11 +1138,13 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), vertexes);
|
||||
glVertexPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), vertexes_flipped);
|
||||
|
||||
memcpy(gl->tex_coords, tex_coords, sizeof(tex_coords));
|
||||
glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), gl->tex_coords);
|
||||
|
||||
set_lut_texture_coords(tex_coords);
|
||||
|
||||
gl->tex_w = 256 * video->input_scale;
|
||||
gl->tex_h = 256 * video->input_scale;
|
||||
|
||||
@@ -1011,6 +1152,9 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
|
||||
0, GL_RGBA, gl->tex_w, gl->tex_h, 0, gl->texture_type,
|
||||
gl->texture_fmt, NULL);
|
||||
|
||||
// Empty buffer that we use to clear out the texture with on res change.
|
||||
gl->empty_buf = calloc(gl->base_size, gl->tex_w * gl->tex_h);
|
||||
|
||||
gl->last_width = gl->tex_w;
|
||||
gl->last_height = gl->tex_h;
|
||||
|
||||
@@ -1018,10 +1162,6 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
|
||||
sdl_input_t *sdl_input = input_sdl.init();
|
||||
if (sdl_input)
|
||||
{
|
||||
sdl_input->quitting = &gl->quitting;
|
||||
sdl_input->should_resize = &gl->should_resize;
|
||||
sdl_input->new_width = &gl->win_width;
|
||||
sdl_input->new_height = &gl->win_height;
|
||||
*input = &input_sdl;
|
||||
*input_data = sdl_input;
|
||||
}
|
||||
@@ -1029,7 +1169,7 @@ static void* gl_init(video_info_t *video, const input_driver_t **input, void **i
|
||||
*input = NULL;
|
||||
|
||||
gl_init_font(gl, g_settings.video.font_path, g_settings.video.font_size);
|
||||
|
||||
|
||||
if (!gl_check_error())
|
||||
{
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
@@ -1056,7 +1196,13 @@ static bool gl_focus(void *data)
|
||||
static bool gl_xml_shader(void *data, const char *path)
|
||||
{
|
||||
gl_t *gl = data;
|
||||
gl_shader_deinit();
|
||||
|
||||
//if (!gl_check_error())
|
||||
// SSNES_WARN("Error happened before deinit!\n");
|
||||
|
||||
|
||||
//if (!gl_check_error())
|
||||
// SSNES_WARN("Error happened in deinit!\n");
|
||||
|
||||
#ifdef HAVE_FBO
|
||||
if (gl->fbo_inited)
|
||||
@@ -1069,13 +1215,15 @@ static bool gl_xml_shader(void *data, const char *path)
|
||||
gl->render_to_tex = false;
|
||||
gl->fbo_pass = 0;
|
||||
|
||||
if (!gl_check_error())
|
||||
SSNES_WARN("Failed to deinit FBO properly!\n");
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, gl->texture);
|
||||
}
|
||||
#endif
|
||||
|
||||
gl_shader_deinit();
|
||||
|
||||
//if (!gl_check_error())
|
||||
// SSNES_WARN("Failed to deinit rendering path properly!\n");
|
||||
|
||||
if (!gl_glsl_init(path))
|
||||
return false;
|
||||
|
||||
@@ -1083,6 +1231,8 @@ static bool gl_xml_shader(void *data, const char *path)
|
||||
gl_init_fbo(gl, gl->tex_w, gl->tex_h);
|
||||
|
||||
// Apparently need to set viewport for passes when we aren't using FBOs.
|
||||
gl_shader_use(0);
|
||||
set_viewport(gl, gl->win_width, gl->win_height, false);
|
||||
gl_shader_use(1);
|
||||
set_viewport(gl, gl->win_width, gl->win_height, false);
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
#define __GL_COMMON_H
|
||||
|
||||
#include "general.h"
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/gl.h>
|
||||
@@ -93,4 +98,28 @@ struct gl_fbo_scale
|
||||
bool valid;
|
||||
};
|
||||
|
||||
struct gl_tex_info
|
||||
{
|
||||
GLuint tex;
|
||||
float input_size[2];
|
||||
float tex_size[2];
|
||||
float coord[8];
|
||||
};
|
||||
|
||||
// Not legal to cast void* to fn-pointer. Need dirty hack to be compilant.
|
||||
// sizeof(void*) == sizeof(void (*)(void)) is asserted earlier.
|
||||
#define SDL_SYM_WRAP(sym, symbol) { \
|
||||
void *sym__ = SDL_GL_GetProcAddress(symbol); \
|
||||
memcpy(&(sym), &sym__, sizeof(void*)); \
|
||||
}
|
||||
|
||||
// Windows ... <_<
|
||||
#if (defined(HAVE_XML) || defined(HAVE_CG)) && defined(_WIN32)
|
||||
extern PFNGLCLIENTACTIVETEXTUREPROC pglClientActiveTexture;
|
||||
extern PFNGLACTIVETEXTUREPROC pglActiveTexture;
|
||||
#else
|
||||
#define pglClientActiveTexture glClientActiveTexture
|
||||
#define pglActiveTexture glActiveTexture
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "image.h"
|
||||
#include "file.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include "general.h"
|
||||
|
||||
#ifdef HAVE_SDL_IMAGE
|
||||
|
||||
#include "SDL_image.h"
|
||||
bool texture_image_load(const char *path, struct texture_image *out_img)
|
||||
{
|
||||
SDL_Surface *img = IMG_Load(path);
|
||||
if (!img)
|
||||
return false;
|
||||
|
||||
out_img->width = img->w;
|
||||
out_img->height = img->h;
|
||||
|
||||
size_t size = out_img->width * out_img->height * sizeof(uint32_t);
|
||||
out_img->pixels = malloc(size);
|
||||
if (!out_img->pixels)
|
||||
{
|
||||
SDL_FreeSurface(img);
|
||||
return false;
|
||||
}
|
||||
|
||||
const SDL_PixelFormat *fmt = img->format;
|
||||
|
||||
SSNES_LOG("SDL_image: %dx%d @ %d bpp\n", img->w, img->h, img->format->BitsPerPixel);
|
||||
if (img->format->BitsPerPixel == 32)
|
||||
{
|
||||
for (unsigned y = 0; y < img->h; y++)
|
||||
{
|
||||
uint32_t *dst = out_img->pixels + y * img->w;
|
||||
const uint32_t *src = (const uint32_t*)img->pixels + y * img->pitch / sizeof(uint32_t);
|
||||
|
||||
for (unsigned x = 0; x < img->w; x++)
|
||||
{
|
||||
uint32_t r = (src[x] & fmt->Rmask) >> fmt->Rshift;
|
||||
uint32_t g = (src[x] & fmt->Gmask) >> fmt->Gshift;
|
||||
uint32_t b = (src[x] & fmt->Bmask) >> fmt->Bshift;
|
||||
uint32_t a = (src[x] & fmt->Amask) >> fmt->Ashift;
|
||||
dst[x] = (r << 24) | (g << 16) | (b << 8) | a;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (img->format->BitsPerPixel == 24)
|
||||
{
|
||||
for (unsigned y = 0; y < img->h; y++)
|
||||
{
|
||||
uint32_t *dst = out_img->pixels + y * img->w;
|
||||
const uint8_t *src = (const uint8_t*)img->pixels + y * img->pitch;
|
||||
|
||||
for (unsigned x = 0; x < img->w; x++)
|
||||
{
|
||||
// Correct?
|
||||
uint32_t color = 0;
|
||||
color |= src[3 * x + 0] << 0;
|
||||
color |= src[3 * x + 1] << 8;
|
||||
color |= src[3 * x + 2] << 16;
|
||||
uint32_t r = (color & fmt->Rmask) >> fmt->Rshift;
|
||||
uint32_t g = (color & fmt->Gmask) >> fmt->Gshift;
|
||||
uint32_t b = (color & fmt->Bmask) >> fmt->Bshift;
|
||||
dst[x] = (r << 24) | (g << 16) | (b << 8) | 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SSNES_ERR("8-bit and 16-bit image support are not implemented.\n");
|
||||
SDL_FreeSurface(img);
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_FreeSurface(img);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
bool texture_image_load(const char *path, struct texture_image *out_img)
|
||||
{
|
||||
// TODO: Check more gracefully.
|
||||
if (!strstr(path, ".tga"))
|
||||
return false;
|
||||
|
||||
void *raw_buf = NULL;
|
||||
ssize_t len = read_file(path, &raw_buf);
|
||||
if (len < 0)
|
||||
return false;
|
||||
|
||||
uint8_t *buf = raw_buf;
|
||||
|
||||
if (buf[2] != 2) // Uncompressed RGB
|
||||
{
|
||||
free(buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
|
||||
uint8_t info[6];
|
||||
memcpy(info, buf + 12, 6);
|
||||
|
||||
width = info[0] + ((unsigned)info[1] * 256);
|
||||
height = info[2] + ((unsigned)info[3] * 256);
|
||||
unsigned bits = info[4];
|
||||
|
||||
SSNES_LOG("Loaded TGA: (%ux%u @ %u bpp)\n", width, height, bits);
|
||||
|
||||
unsigned size = width * height * sizeof(uint32_t);
|
||||
out_img->pixels = malloc(size);
|
||||
out_img->width = width;
|
||||
out_img->height = height;
|
||||
if (!out_img->pixels)
|
||||
{
|
||||
free(buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t *tmp = buf + 18;
|
||||
if (bits == 32)
|
||||
{
|
||||
for (unsigned i = 0; i < width * height; i++)
|
||||
{
|
||||
uint32_t r = tmp[i * 4 + 2];
|
||||
uint32_t g = tmp[i * 4 + 1];
|
||||
uint32_t b = tmp[i * 4 + 0];
|
||||
uint32_t a = tmp[i * 4 + 3];
|
||||
|
||||
out_img->pixels[i] = (r << 24) | (g << 16) | (b << 8) | a;
|
||||
}
|
||||
}
|
||||
else if (bits == 24)
|
||||
{
|
||||
for (unsigned i = 0; i < width * height; i++)
|
||||
{
|
||||
uint32_t r = tmp[i * 3 + 2];
|
||||
uint32_t g = tmp[i * 3 + 1];
|
||||
uint32_t b = tmp[i * 3 + 0];
|
||||
uint32_t a = 0xff;
|
||||
|
||||
out_img->pixels[i] = (r << 24) | (g << 16) | (b << 8) | a;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
free(buf);
|
||||
free(out_img->pixels);
|
||||
out_img->pixels = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
free(buf);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __SSNES_IMAGE_H
|
||||
#define __SSNES_IMAGE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct texture_image
|
||||
{
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
uint32_t *pixels;
|
||||
};
|
||||
|
||||
bool texture_image_load(const char *path, struct texture_image* img);
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,288 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Python.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "dynamic.h"
|
||||
#include "libsnes.hpp"
|
||||
#include "py_state.h"
|
||||
#include "general.h"
|
||||
#include "strl.h"
|
||||
|
||||
#define PY_READ_FUNC_DECL(RAMTYPE) py_read_##RAMTYPE
|
||||
#define PY_READ_FUNC(RAMTYPE) \
|
||||
static PyObject* PY_READ_FUNC_DECL(RAMTYPE) (PyObject *self, PyObject *args) \
|
||||
{ \
|
||||
(void)self; \
|
||||
\
|
||||
const uint8_t *data = psnes_get_memory_data(SNES_MEMORY_##RAMTYPE); \
|
||||
if (!data) \
|
||||
{ \
|
||||
Py_INCREF(Py_None); \
|
||||
return Py_None; \
|
||||
} \
|
||||
unsigned max = psnes_get_memory_size(SNES_MEMORY_##RAMTYPE); \
|
||||
\
|
||||
unsigned addr; \
|
||||
if (!PyArg_ParseTuple(args, "I", &addr)) \
|
||||
return NULL; \
|
||||
\
|
||||
if (addr >= max || addr < 0) \
|
||||
{ \
|
||||
Py_INCREF(Py_None); \
|
||||
return Py_None; \
|
||||
} \
|
||||
\
|
||||
return PyLong_FromLong((long)data[addr]); \
|
||||
}
|
||||
|
||||
PY_READ_FUNC(WRAM)
|
||||
PY_READ_FUNC(VRAM)
|
||||
PY_READ_FUNC(APURAM)
|
||||
PY_READ_FUNC(CGRAM)
|
||||
PY_READ_FUNC(OAM)
|
||||
|
||||
static PyObject* py_read_input(PyObject *self, PyObject *args)
|
||||
{
|
||||
(void)self;
|
||||
|
||||
if (!driver.input_data)
|
||||
return PyBool_FromLong(0);
|
||||
|
||||
unsigned player;
|
||||
unsigned key;
|
||||
if (!PyArg_ParseTuple(args, "II", &player, &key))
|
||||
return NULL;
|
||||
|
||||
if (player > MAX_PLAYERS || player < 1)
|
||||
return NULL;
|
||||
|
||||
const struct snes_keybind *binds[MAX_PLAYERS];
|
||||
for (int i = 0; i < MAX_PLAYERS; i++)
|
||||
binds[i] = g_settings.input.binds[i];
|
||||
|
||||
int16_t res = driver.input->input_state(driver.input_data,
|
||||
binds, player > 1,
|
||||
player > 2 ? SNES_DEVICE_MULTITAP : SNES_DEVICE_JOYPAD,
|
||||
player > 2 ? player - 2 : 0,
|
||||
key);
|
||||
|
||||
return PyBool_FromLong((long)res);
|
||||
}
|
||||
|
||||
static PyMethodDef SNESMethods[] = {
|
||||
{ "read_wram", PY_READ_FUNC_DECL(WRAM), METH_VARARGS, "Read WRAM from SNES." },
|
||||
{ "read_vram", PY_READ_FUNC_DECL(VRAM), METH_VARARGS, "Read VRAM from SNES." },
|
||||
{ "read_apuram", PY_READ_FUNC_DECL(APURAM), METH_VARARGS, "Read APURAM from SNES." },
|
||||
{ "read_cgram", PY_READ_FUNC_DECL(CGRAM), METH_VARARGS, "Read CGRAM from SNES." },
|
||||
{ "read_oam", PY_READ_FUNC_DECL(OAM), METH_VARARGS, "Read OAM from SNES." },
|
||||
{ "input", py_read_input, METH_VARARGS, "Read input state from SNES." },
|
||||
{ NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static void py_set_attrs(PyObject *mod)
|
||||
{
|
||||
PyObject_SetAttrString(mod, "B", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_B));
|
||||
PyObject_SetAttrString(mod, "Y", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_Y));
|
||||
PyObject_SetAttrString(mod, "SELECT", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_SELECT));
|
||||
PyObject_SetAttrString(mod, "START", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_START));
|
||||
PyObject_SetAttrString(mod, "UP", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_UP));
|
||||
PyObject_SetAttrString(mod, "DOWN", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_DOWN));
|
||||
PyObject_SetAttrString(mod, "LEFT", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_LEFT));
|
||||
PyObject_SetAttrString(mod, "RIGHT", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_RIGHT));
|
||||
PyObject_SetAttrString(mod, "A", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_A));
|
||||
PyObject_SetAttrString(mod, "X", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_X));
|
||||
PyObject_SetAttrString(mod, "L", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_L));
|
||||
PyObject_SetAttrString(mod, "R", PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_R));
|
||||
}
|
||||
|
||||
static PyModuleDef SNESModule = {
|
||||
PyModuleDef_HEAD_INIT, "snes", NULL, -1, SNESMethods,
|
||||
NULL, NULL, NULL, NULL
|
||||
};
|
||||
|
||||
static PyObject* PyInit_SNES(void)
|
||||
{
|
||||
PyObject *mod = PyModule_Create(&SNESModule);
|
||||
if (!mod)
|
||||
return NULL;
|
||||
|
||||
py_set_attrs(mod);
|
||||
return mod;
|
||||
}
|
||||
|
||||
struct py_state
|
||||
{
|
||||
PyObject *main;
|
||||
PyObject *dict;
|
||||
PyObject *inst;
|
||||
|
||||
bool warned_ret;
|
||||
bool warned_type;
|
||||
};
|
||||
|
||||
static char *dupe_newline(const char *str)
|
||||
{
|
||||
if (!str)
|
||||
return NULL;
|
||||
|
||||
unsigned size = strlen(str) + 2;
|
||||
char *ret = malloc(size);
|
||||
if (!ret)
|
||||
return NULL;
|
||||
|
||||
strlcpy(ret, str, size);
|
||||
ret[size - 2] = '\n';
|
||||
ret[size - 1] = '\0';
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Need to make sure that first-line indentation is 0. :(
|
||||
static char* align_program(const char *program)
|
||||
{
|
||||
char *prog = strdup(program);
|
||||
if (!prog)
|
||||
return NULL;
|
||||
|
||||
size_t prog_size = strlen(program) + 1;
|
||||
char *new_prog = calloc(1, prog_size);
|
||||
if (!new_prog)
|
||||
return NULL;
|
||||
|
||||
char *line = dupe_newline(strtok(prog, "\n"));
|
||||
if (!line)
|
||||
{
|
||||
free(prog);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned skip_chars = 0;
|
||||
while (isblank(line[skip_chars]) && line[skip_chars])
|
||||
skip_chars++;
|
||||
|
||||
while (line)
|
||||
{
|
||||
unsigned length = strlen(line);
|
||||
unsigned skip_len = skip_chars > length ? length : skip_chars;
|
||||
|
||||
strlcat(new_prog, line + skip_len, prog_size);
|
||||
|
||||
free(line);
|
||||
line = dupe_newline(strtok(NULL, "\n"));
|
||||
}
|
||||
|
||||
free(prog);
|
||||
|
||||
return new_prog;
|
||||
}
|
||||
|
||||
py_state_t *py_state_new(const char *script, bool is_file, const char *pyclass)
|
||||
{
|
||||
PyImport_AppendInittab("snes", &PyInit_SNES);
|
||||
Py_Initialize();
|
||||
|
||||
py_state_t *handle = calloc(1, sizeof(*handle));
|
||||
|
||||
handle->main = PyImport_AddModule("__main__");
|
||||
if (!handle->main)
|
||||
goto error;
|
||||
|
||||
if (is_file)
|
||||
{
|
||||
FILE *file = fopen(script, "r");
|
||||
if (!file)
|
||||
goto error;
|
||||
PyRun_SimpleFile(file, script);
|
||||
fclose(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
char *script_ = align_program(script);
|
||||
if (script_)
|
||||
{
|
||||
PyRun_SimpleString(script_);
|
||||
free(script_);
|
||||
}
|
||||
}
|
||||
|
||||
handle->dict = PyModule_GetDict(handle->main);
|
||||
if (!handle->dict)
|
||||
goto error;
|
||||
|
||||
PyObject *hook = PyDict_GetItemString(handle->dict, pyclass);
|
||||
if (!hook)
|
||||
goto error;
|
||||
|
||||
handle->inst = PyObject_CallFunction(hook, NULL);
|
||||
if (!handle->inst)
|
||||
goto error;
|
||||
|
||||
return handle;
|
||||
|
||||
error:
|
||||
PyErr_Print();
|
||||
py_state_free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void py_state_free(py_state_t *handle)
|
||||
{
|
||||
if (handle)
|
||||
{
|
||||
if (handle->main)
|
||||
Py_DECREF(handle->main);
|
||||
if (handle->dict)
|
||||
Py_DECREF(handle->dict);
|
||||
if (handle->inst)
|
||||
Py_DECREF(handle->inst);
|
||||
|
||||
free(handle);
|
||||
}
|
||||
|
||||
Py_Finalize();
|
||||
}
|
||||
|
||||
int py_state_get(py_state_t *handle, const char *id,
|
||||
unsigned frame_count)
|
||||
{
|
||||
PyObject *ret = PyObject_CallMethod(handle->inst, (char*)id, (char*)"I", frame_count);
|
||||
if (!ret)
|
||||
{
|
||||
if (!handle->warned_ret)
|
||||
SSNES_WARN("Didn't get return value from script! Bug?\n");
|
||||
handle->warned_ret = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int retval = 0;
|
||||
if (PyLong_Check(ret))
|
||||
retval = (int)PyLong_AsLong(ret);
|
||||
else
|
||||
{
|
||||
if (!handle->warned_type)
|
||||
SSNES_WARN("Didn't get long compatible value from script! Bug?\n");
|
||||
handle->warned_type = true;
|
||||
}
|
||||
|
||||
Py_DECREF(ret);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __SSNES_PY_STATE_H
|
||||
#define __SSNES_PY_STATE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct py_state py_state_t;
|
||||
|
||||
py_state_t *py_state_new(const char *program, bool is_file, const char *pyclass);
|
||||
void py_state_free(py_state_t *handle);
|
||||
|
||||
int py_state_get(py_state_t *handle,
|
||||
const char *id, unsigned frame_count);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,520 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "SDL.h"
|
||||
#include "driver.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "general.h"
|
||||
#include "input/ssnes_sdl_input.h"
|
||||
#include "gfx_common.h"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
#include "fonts.h"
|
||||
#endif
|
||||
|
||||
static void convert_15bit_15bit_direct(uint16_t *out, unsigned outpitch, const uint16_t *input, unsigned width, unsigned height, unsigned pitch, const SDL_PixelFormat *fmt);
|
||||
static void convert_32bit_32bit_direct(uint32_t *out, unsigned outpitch, const uint32_t *input, unsigned width, unsigned height, unsigned pitch, const SDL_PixelFormat *fmt);
|
||||
static void convert_15bit_15bit_shift(uint16_t *out, unsigned outpitch, const uint16_t *input, unsigned width, unsigned height, unsigned pitch, const SDL_PixelFormat *fmt);
|
||||
static void convert_32bit_32bit_shift(uint32_t *out, unsigned outpitch, const uint32_t *input, unsigned width, unsigned height, unsigned pitch, const SDL_PixelFormat *fmt);
|
||||
|
||||
typedef struct sdl_video sdl_video_t;
|
||||
struct sdl_video
|
||||
{
|
||||
SDL_Surface *screen, *buffer;
|
||||
bool quitting;
|
||||
bool rgb32;
|
||||
bool upsample;
|
||||
|
||||
void (*convert_15_func)(uint16_t*, unsigned, const uint16_t*, unsigned, unsigned, unsigned, const SDL_PixelFormat*);
|
||||
void (*convert_32_func)(uint32_t*, unsigned, const uint32_t*, unsigned, unsigned, unsigned, const SDL_PixelFormat*);
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
font_renderer_t *font;
|
||||
#endif
|
||||
};
|
||||
|
||||
static void sdl_gfx_free(void *data)
|
||||
{
|
||||
sdl_video_t *vid = data;
|
||||
if (!vid)
|
||||
return;
|
||||
|
||||
if (vid->buffer)
|
||||
SDL_FreeSurface(vid->buffer);
|
||||
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
if (vid->font)
|
||||
font_renderer_free(vid->font);
|
||||
#endif
|
||||
|
||||
free(vid);
|
||||
}
|
||||
|
||||
static void sdl_init_font(sdl_video_t *vid, const char *font_path, unsigned font_size)
|
||||
{
|
||||
#ifdef HAVE_FREETYPE
|
||||
if (*font_path)
|
||||
{
|
||||
vid->font = font_renderer_new(font_path, font_size);
|
||||
if (!vid->font)
|
||||
SSNES_WARN("Failed to init font.\n");
|
||||
}
|
||||
#else
|
||||
(void)vid;
|
||||
(void)font_path;
|
||||
(void)font_size;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Not very optimized, but hey :D
|
||||
static void sdl_render_msg_15(sdl_video_t *vid, SDL_Surface *buffer, const char *msg, unsigned width, unsigned height, const SDL_PixelFormat *fmt)
|
||||
{
|
||||
#ifdef HAVE_FREETYPE
|
||||
if (!vid->font)
|
||||
return;
|
||||
|
||||
struct font_output_list out;
|
||||
font_renderer_msg(vid->font, msg, &out);
|
||||
struct font_output *head = out.head;
|
||||
|
||||
int base_x = g_settings.video.msg_pos_x * width;
|
||||
int base_y = (1.0 - g_settings.video.msg_pos_y) * height;
|
||||
|
||||
unsigned rshift = fmt->Rshift;
|
||||
unsigned gshift = fmt->Gshift;
|
||||
unsigned bshift = fmt->Bshift;
|
||||
|
||||
while (head)
|
||||
{
|
||||
int rbase_x = base_x + head->off_x;
|
||||
int rbase_y = base_y - head->off_y;
|
||||
if (rbase_y >= 0)
|
||||
{
|
||||
for (int y = 0; y < head->height && (y + rbase_y) < height; y++)
|
||||
{
|
||||
if (rbase_x < 0)
|
||||
continue;
|
||||
|
||||
const uint8_t *a = head->output + head->pitch * y;
|
||||
uint16_t *out = (uint16_t*)buffer->pixels + (rbase_y - head->height + y) * (buffer->pitch >> 1) + rbase_x;
|
||||
|
||||
for (int x = 0; x < head->width && (x + rbase_x) < width; x++)
|
||||
{
|
||||
unsigned blend = a[x];
|
||||
unsigned out_pix = out[x];
|
||||
unsigned r = (out_pix >> rshift) & 0x1f;
|
||||
unsigned g = (out_pix >> gshift) & 0x1f;
|
||||
unsigned b = (out_pix >> bshift) & 0x1f;
|
||||
|
||||
unsigned out_r = (r * (256 - blend) + 0x1f * blend) >> 8;
|
||||
unsigned out_g = (g * (256 - blend) + 0x1f * blend) >> 8;
|
||||
unsigned out_b = (b * (256 - blend) + 0x1f * blend) >> 8;
|
||||
out[x] = (out_r << rshift) | (out_g << gshift) | (out_b << bshift);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
head = head->next;
|
||||
}
|
||||
|
||||
font_renderer_free_output(&out);
|
||||
|
||||
#else
|
||||
(void)vid;
|
||||
(void)buffer;
|
||||
(void)msg;
|
||||
(void)width;
|
||||
(void)height;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void sdl_render_msg_32(sdl_video_t *vid, SDL_Surface *buffer, const char *msg, unsigned width, unsigned height, const SDL_PixelFormat *fmt)
|
||||
{
|
||||
#ifdef HAVE_FREETYPE
|
||||
if (!vid->font)
|
||||
return;
|
||||
|
||||
struct font_output_list out;
|
||||
font_renderer_msg(vid->font, msg, &out);
|
||||
struct font_output *head = out.head;
|
||||
|
||||
int base_x = g_settings.video.msg_pos_x * width;
|
||||
int base_y = (1.0 - g_settings.video.msg_pos_y) * height;
|
||||
|
||||
unsigned rshift = fmt->Rshift;
|
||||
unsigned gshift = fmt->Gshift;
|
||||
unsigned bshift = fmt->Bshift;
|
||||
|
||||
while (head)
|
||||
{
|
||||
int rbase_x = base_x + head->off_x;
|
||||
int rbase_y = base_y - head->off_y;
|
||||
if (rbase_y >= 0)
|
||||
{
|
||||
for (int y = 0; y < head->height && (y + rbase_y) < height; y++)
|
||||
{
|
||||
if (rbase_x < 0)
|
||||
continue;
|
||||
|
||||
const uint8_t *a = head->output + head->pitch * y;
|
||||
uint32_t *out = (uint32_t*)buffer->pixels + (rbase_y - head->height + y) * (buffer->pitch >> 2) + rbase_x;
|
||||
|
||||
for (int x = 0; x < head->width && (x + rbase_x) < width; x++)
|
||||
{
|
||||
unsigned blend = a[x];
|
||||
unsigned out_pix = out[x];
|
||||
unsigned r = (out_pix >> rshift) & 0xff;
|
||||
unsigned g = (out_pix >> gshift) & 0xff;
|
||||
unsigned b = (out_pix >> bshift) & 0xff;
|
||||
|
||||
unsigned out_r = (r * (256 - blend) + 0xff * blend) >> 8;
|
||||
unsigned out_g = (g * (256 - blend) + 0xff * blend) >> 8;
|
||||
unsigned out_b = (b * (256 - blend) + 0xff * blend) >> 8;
|
||||
out[x] = (out_r << rshift) | (out_g << gshift) | (out_b << bshift);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
head = head->next;
|
||||
}
|
||||
|
||||
font_renderer_free_output(&out);
|
||||
|
||||
#else
|
||||
(void)vid;
|
||||
(void)buffer;
|
||||
(void)msg;
|
||||
(void)width;
|
||||
(void)height;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void* sdl_gfx_init(const video_info_t *video, const input_driver_t **input, void **input_data)
|
||||
{
|
||||
SDL_InitSubSystem(SDL_INIT_VIDEO);
|
||||
|
||||
sdl_video_t *vid = calloc(1, sizeof(*vid));
|
||||
if (!vid)
|
||||
return NULL;
|
||||
|
||||
const SDL_VideoInfo *video_info = SDL_GetVideoInfo();
|
||||
assert(video_info);
|
||||
unsigned full_x = video_info->current_w;
|
||||
unsigned full_y = video_info->current_h;
|
||||
SSNES_LOG("Detecting desktop resolution %ux%u.\n", full_x, full_y);
|
||||
|
||||
if (!video->fullscreen)
|
||||
SSNES_LOG("Creating window @ %ux%u\n", video->width, video->height);
|
||||
|
||||
vid->screen = SDL_SetVideoMode(video->width, video->height, (g_settings.video.force_16bit || !video->rgb32) ? 15 : 32, SDL_HWSURFACE | SDL_HWACCEL | SDL_DOUBLEBUF | (video->fullscreen ? SDL_FULLSCREEN : 0));
|
||||
|
||||
if (!vid->screen && !g_settings.video.force_16bit && !video->rgb32)
|
||||
{
|
||||
vid->upsample = true;
|
||||
vid->screen = SDL_SetVideoMode(video->width, video->height, 32, SDL_HWSURFACE | SDL_HWACCEL | SDL_DOUBLEBUF | (video->fullscreen ? SDL_FULLSCREEN : 0));
|
||||
SSNES_WARN("SDL: 15-bit colors failed, attempting 32-bit colors.\n");
|
||||
}
|
||||
|
||||
if (!vid->screen)
|
||||
{
|
||||
SSNES_ERR("Failed to init SDL surface: %s\n", SDL_GetError());
|
||||
goto error;
|
||||
}
|
||||
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
|
||||
const SDL_PixelFormat *fmt = vid->screen->format;
|
||||
if (!g_settings.video.force_16bit && (video->rgb32 || vid->upsample))
|
||||
{
|
||||
SSNES_LOG("SDL: Creating 32-bit buffer.\n");
|
||||
vid->buffer = SDL_CreateRGBSurface(SDL_SWSURFACE, 256 * video->input_scale, 256 * video->input_scale, 32,
|
||||
fmt->Rmask, fmt->Bmask, fmt->Gmask, fmt->Amask);
|
||||
}
|
||||
else
|
||||
{
|
||||
SSNES_LOG("SDL: Creating 15-bit buffer.\n");
|
||||
vid->buffer = SDL_CreateRGBSurface(SDL_SWSURFACE, 256 * video->input_scale, 256 * video->input_scale, 15,
|
||||
fmt->Rmask, fmt->Gmask, fmt->Bmask, fmt->Amask);
|
||||
}
|
||||
SSNES_LOG("[Debug]: SDL Pixel format: Rshift = %u, Gshift = %u, Bshift = %u\n",
|
||||
(unsigned)fmt->Rshift, (unsigned)fmt->Gshift, (unsigned)fmt->Bshift);
|
||||
|
||||
if (!vid->buffer)
|
||||
{
|
||||
SSNES_ERR("SDL_CreateRGBSurface failed: %s\n", SDL_GetError());
|
||||
goto error;
|
||||
}
|
||||
|
||||
sdl_input_t *sdl_input = input_sdl.init();
|
||||
if (sdl_input)
|
||||
{
|
||||
*input = &input_sdl;
|
||||
*input_data = sdl_input;
|
||||
}
|
||||
else
|
||||
*input = NULL;
|
||||
|
||||
vid->rgb32 = video->rgb32;
|
||||
|
||||
sdl_init_font(vid, g_settings.video.font_path, g_settings.video.font_size);
|
||||
|
||||
|
||||
if (fmt->Rshift == 10 && fmt->Gshift == 5 && fmt->Bshift == 0)
|
||||
{
|
||||
SSNES_LOG("SDL: 15-bit format matches. Fast blit.\n");
|
||||
vid->convert_15_func = convert_15bit_15bit_direct;
|
||||
}
|
||||
else
|
||||
{
|
||||
SSNES_LOG("SDL: 15-bit format does not match. Needs conversion.\n");
|
||||
vid->convert_15_func = convert_15bit_15bit_shift;
|
||||
}
|
||||
|
||||
if (fmt->Rshift == 16 && fmt->Gshift == 8 && fmt->Bshift == 0)
|
||||
{
|
||||
SSNES_LOG("SDL: 32-bit format matches. Fast blit.\n");
|
||||
vid->convert_32_func = convert_32bit_32bit_direct;
|
||||
}
|
||||
else
|
||||
{
|
||||
SSNES_LOG("SDL: 32-bit format does not match. Needs conversion.\n");
|
||||
vid->convert_32_func = convert_32bit_32bit_shift;
|
||||
}
|
||||
|
||||
return vid;
|
||||
|
||||
error:
|
||||
sdl_gfx_free(vid);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline uint16_t conv_pixel_32_15(uint32_t pix, const SDL_PixelFormat *fmt)
|
||||
{
|
||||
uint16_t r = ((pix & 0x00f80000) >> 19) << fmt->Rshift;
|
||||
uint16_t g = ((pix & 0x0000f800) >> 11) << fmt->Gshift;
|
||||
uint16_t b = ((pix & 0x000000f8) >> 3) << fmt->Bshift;
|
||||
return r | g | b;
|
||||
}
|
||||
|
||||
static inline uint32_t conv_pixel_15_32(uint16_t pix, const SDL_PixelFormat *fmt)
|
||||
{
|
||||
uint32_t r = ((pix >> 10) & 0x1f) << (fmt->Rshift + 3);
|
||||
uint32_t g = ((pix >> 5) & 0x1f) << (fmt->Gshift + 3);
|
||||
uint32_t b = ((pix >> 0) & 0x1f) << (fmt->Bshift + 3);
|
||||
return r | g | b;
|
||||
}
|
||||
|
||||
static void convert_32bit_15bit(uint16_t *out, unsigned outpitch, const uint32_t *input, unsigned width, unsigned height, unsigned pitch, const SDL_PixelFormat *fmt)
|
||||
{
|
||||
for (unsigned y = 0; y < height; y++)
|
||||
{
|
||||
for (unsigned x = 0; x < width; x++)
|
||||
out[x] = conv_pixel_32_15(input[x], fmt);
|
||||
|
||||
out += outpitch >> 1;
|
||||
input += pitch >> 2;
|
||||
}
|
||||
}
|
||||
|
||||
static void convert_15bit_32bit(uint32_t *out, unsigned outpitch, const uint16_t *input, unsigned width, unsigned height, unsigned pitch, const SDL_PixelFormat *fmt)
|
||||
{
|
||||
for (unsigned y = 0; y < height; y++)
|
||||
{
|
||||
for (unsigned x = 0; x < width; x++)
|
||||
out[x] = conv_pixel_15_32(input[x], fmt);
|
||||
|
||||
out += outpitch >> 2;
|
||||
input += pitch >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void convert_15bit_15bit_direct(uint16_t *out, unsigned outpitch, const uint16_t *input, unsigned width, unsigned height, unsigned pitch, const SDL_PixelFormat *fmt)
|
||||
{
|
||||
for (unsigned y = 0; y < height; y++)
|
||||
{
|
||||
uint16_t *dest = out + ((y * outpitch) >> 1);
|
||||
const uint16_t *src = input + ((y * pitch) >> 1);
|
||||
memcpy(dest, src, width * sizeof(uint16_t));
|
||||
}
|
||||
(void)fmt;
|
||||
}
|
||||
|
||||
static void convert_32bit_32bit_direct(uint32_t *out, unsigned outpitch, const uint32_t *input, unsigned width, unsigned height, unsigned pitch, const SDL_PixelFormat *fmt)
|
||||
{
|
||||
for (unsigned y = 0; y < height; y++)
|
||||
{
|
||||
uint32_t *dest = out + ((y * outpitch) >> 2);
|
||||
const uint32_t *src = input + ((y * pitch) >> 2);
|
||||
memcpy(dest, src, width * sizeof(uint32_t));
|
||||
}
|
||||
(void)fmt;
|
||||
}
|
||||
|
||||
static void convert_15bit_15bit_shift(uint16_t *out, unsigned outpitch, const uint16_t *input, unsigned width, unsigned height, unsigned pitch, const SDL_PixelFormat *fmt)
|
||||
{
|
||||
for (unsigned y = 0; y < height; y++)
|
||||
{
|
||||
uint16_t *dest = out + ((y * outpitch) >> 1);
|
||||
const uint16_t *src = input + ((y * pitch) >> 1);
|
||||
for (unsigned x = 0; x < width; x++)
|
||||
{
|
||||
uint16_t color = src[x];
|
||||
uint16_t r = ((color >> 10) & 0x1f) << fmt->Rshift;
|
||||
uint16_t g = ((color >> 5) & 0x1f) << fmt->Gshift;
|
||||
uint16_t b = ((color >> 0) & 0x1f) << fmt->Bshift;
|
||||
dest[x] = r | g | b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void convert_32bit_32bit_shift(uint32_t *out, unsigned outpitch, const uint32_t *input, unsigned width, unsigned height, unsigned pitch, const SDL_PixelFormat *fmt)
|
||||
{
|
||||
for (unsigned y = 0; y < height; y++)
|
||||
{
|
||||
uint32_t *dest = out + ((y * outpitch) >> 2);
|
||||
const uint32_t *src = input + ((y * pitch) >> 2);
|
||||
for (unsigned x = 0; x < width; x++)
|
||||
{
|
||||
uint32_t color = src[x];
|
||||
uint32_t r = ((color >> 16) & 0xff) << fmt->Rshift;
|
||||
uint32_t g = ((color >> 8) & 0xff) << fmt->Gshift;
|
||||
uint32_t b = ((color >> 0) & 0xff) << fmt->Bshift;
|
||||
dest[x] = r | g | b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void check_window(sdl_video_t *vid)
|
||||
{
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
vid->quitting = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool sdl_gfx_frame(void *data, const void* frame, unsigned width, unsigned height, unsigned pitch, const char *msg)
|
||||
{
|
||||
sdl_video_t *vid = data;
|
||||
check_window(vid);
|
||||
|
||||
if (SDL_MUSTLOCK(vid->buffer))
|
||||
SDL_LockSurface(vid->buffer);
|
||||
|
||||
// :(
|
||||
// 15-bit -> 32-bit (Sometimes 15-bit won't work on "modern" OSes :\)
|
||||
if (vid->upsample)
|
||||
{
|
||||
convert_15bit_32bit(vid->buffer->pixels, vid->buffer->pitch, frame, width, height, pitch, vid->screen->format);
|
||||
}
|
||||
// 15-bit -> 15-bit
|
||||
else if (!vid->rgb32)
|
||||
{
|
||||
vid->convert_15_func(vid->buffer->pixels, vid->buffer->pitch, frame, width, height, pitch, vid->screen->format);
|
||||
}
|
||||
// 32-bit -> 15-bit
|
||||
else if (vid->rgb32 && g_settings.video.force_16bit)
|
||||
{
|
||||
convert_32bit_15bit(vid->buffer->pixels, vid->buffer->pitch, frame, width, height, pitch, vid->screen->format);
|
||||
}
|
||||
// 32-bit -> 32-bit
|
||||
else
|
||||
{
|
||||
vid->convert_32_func(vid->buffer->pixels, vid->buffer->pitch, frame, width, height, pitch, vid->screen->format);
|
||||
}
|
||||
|
||||
if (msg)
|
||||
{
|
||||
if ((!vid->rgb32 || g_settings.video.force_16bit) && !vid->upsample)
|
||||
sdl_render_msg_15(vid, vid->buffer, msg, width, height, vid->screen->format);
|
||||
else
|
||||
sdl_render_msg_32(vid, vid->buffer, msg, width, height, vid->screen->format);
|
||||
}
|
||||
|
||||
if (SDL_MUSTLOCK(vid->buffer))
|
||||
SDL_UnlockSurface(vid->buffer);
|
||||
|
||||
SDL_Rect src = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.w = width,
|
||||
.h = height
|
||||
};
|
||||
|
||||
SDL_Rect dest = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.w = vid->screen->w,
|
||||
.h = vid->screen->h
|
||||
};
|
||||
|
||||
SDL_SoftStretch(vid->buffer, &src, vid->screen, &dest);
|
||||
|
||||
char buf[128];
|
||||
if (gfx_window_title(buf, sizeof(buf)))
|
||||
SDL_WM_SetCaption(buf, NULL);
|
||||
|
||||
SDL_Flip(vid->screen);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void sdl_gfx_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
(void)data; // Can SDL even do this?
|
||||
(void)state;
|
||||
}
|
||||
|
||||
static bool sdl_gfx_alive(void *data)
|
||||
{
|
||||
sdl_video_t *vid = data;
|
||||
return !vid->quitting;
|
||||
}
|
||||
|
||||
static bool sdl_gfx_focus(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return (SDL_GetAppState() & (SDL_APPINPUTFOCUS | SDL_APPACTIVE)) == (SDL_APPINPUTFOCUS | SDL_APPACTIVE);
|
||||
}
|
||||
|
||||
|
||||
const video_driver_t video_sdl = {
|
||||
.init = sdl_gfx_init,
|
||||
.frame = sdl_gfx_frame,
|
||||
.alive = sdl_gfx_alive,
|
||||
.set_nonblock_state = sdl_gfx_set_nonblock_state,
|
||||
.focus = sdl_gfx_focus,
|
||||
.free = sdl_gfx_free,
|
||||
.ident = "sdl"
|
||||
};
|
||||
|
||||
+813
-62
@@ -20,6 +20,16 @@
|
||||
#include <Cg/cgGL.h>
|
||||
#include "general.h"
|
||||
#include <string.h>
|
||||
#include "strl.h"
|
||||
#include "conf/config_file.h"
|
||||
#include "image.h"
|
||||
#include "dynamic.h"
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
#include "snes_state.h"
|
||||
#endif
|
||||
|
||||
//#define SSNES_CG_DEBUG
|
||||
|
||||
// Used when we call deactivate() since just unbinding the program didn't seem to work... :(
|
||||
static const char* stock_cg_program =
|
||||
@@ -33,7 +43,7 @@ static const char* stock_cg_program =
|
||||
""
|
||||
" out float4 oPosition : POSITION,"
|
||||
" out float4 oColor : COLOR,"
|
||||
" out float2 otexCoord : TEXCOORD"
|
||||
" out float2 otexCoord : TEXCOORD0"
|
||||
")"
|
||||
"{"
|
||||
" oPosition = mul(modelViewProj, position);"
|
||||
@@ -41,21 +51,51 @@ static const char* stock_cg_program =
|
||||
" otexCoord = texCoord;"
|
||||
"}"
|
||||
""
|
||||
""
|
||||
"struct output "
|
||||
"float4 main_fragment(float2 tex : TEXCOORD0, uniform sampler2D s0 : TEXUNIT0) : COLOR"
|
||||
"{"
|
||||
" float4 color : COLOR;"
|
||||
"};"
|
||||
""
|
||||
"output main_fragment(float2 texCoord : TEXCOORD0, uniform sampler2D decal : TEXUNIT0) "
|
||||
"{"
|
||||
" output OUT;"
|
||||
" OUT.color = tex2D(decal, texCoord);"
|
||||
" return OUT;"
|
||||
" return tex2D(s0, tex);"
|
||||
"}";
|
||||
|
||||
#ifdef SSNES_CG_DEBUG
|
||||
static void cg_error_handler(CGcontext ctx, CGerror error, void *data)
|
||||
{
|
||||
(void)ctx;
|
||||
(void)data;
|
||||
|
||||
switch (error)
|
||||
{
|
||||
case CG_INVALID_PARAM_HANDLE_ERROR:
|
||||
SSNES_ERR("Invalid param handle.\n");
|
||||
break;
|
||||
|
||||
case CG_INVALID_PARAMETER_ERROR:
|
||||
SSNES_ERR("Invalid parameter.\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
SSNES_ERR("CG error!: \"%s\".\n", cgGetErrorString(error));
|
||||
}
|
||||
#endif
|
||||
|
||||
static CGcontext cgCtx;
|
||||
|
||||
struct cg_fbo_params
|
||||
{
|
||||
CGparameter vid_size_f;
|
||||
CGparameter tex_size_f;
|
||||
CGparameter vid_size_v;
|
||||
CGparameter tex_size_v;
|
||||
CGparameter tex;
|
||||
CGparameter coord;
|
||||
};
|
||||
|
||||
#define MAX_SHADERS 16
|
||||
#define MAX_TEXTURES 8
|
||||
#define MAX_VARIABLES 64
|
||||
|
||||
struct cg_program
|
||||
{
|
||||
CGprogram vprg;
|
||||
@@ -63,70 +103,176 @@ struct cg_program
|
||||
CGparameter vid_size_f;
|
||||
CGparameter tex_size_f;
|
||||
CGparameter out_size_f;
|
||||
CGparameter frame_cnt_f;
|
||||
CGparameter vid_size_v;
|
||||
CGparameter tex_size_v;
|
||||
CGparameter out_size_v;
|
||||
CGparameter frame_cnt_v;
|
||||
CGparameter mvp;
|
||||
|
||||
struct cg_fbo_params fbo[MAX_SHADERS];
|
||||
struct cg_fbo_params orig;
|
||||
};
|
||||
|
||||
static struct cg_program prg[3];
|
||||
#define FILTER_UNSPEC 0
|
||||
#define FILTER_LINEAR 1
|
||||
#define FILTER_NEAREST 2
|
||||
|
||||
static struct cg_program prg[MAX_SHADERS];
|
||||
static bool cg_active = false;
|
||||
static CGprofile cgVProf, cgFProf;
|
||||
static unsigned active_index = 0;
|
||||
static unsigned cg_shader_num = 0;
|
||||
static struct gl_fbo_scale cg_scale[MAX_SHADERS];
|
||||
static unsigned fbo_smooth[MAX_SHADERS];
|
||||
|
||||
static unsigned lut_textures[MAX_TEXTURES];
|
||||
static unsigned lut_textures_num = 0;
|
||||
static char lut_textures_uniform[MAX_TEXTURES][64];
|
||||
|
||||
static snes_tracker_t *snes_tracker = NULL;
|
||||
|
||||
void gl_cg_set_proj_matrix(void)
|
||||
{
|
||||
if (cg_active)
|
||||
if (cg_active && prg[active_index].mvp)
|
||||
cgGLSetStateMatrixParameter(prg[active_index].mvp, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
|
||||
}
|
||||
|
||||
#define set_param_2f(param, x, y) \
|
||||
if (param) cgGLSetParameter2f(param, x, y)
|
||||
#define set_param_1f(param, x) \
|
||||
if (param) cgGLSetParameter1f(param, x)
|
||||
|
||||
void gl_cg_set_params(unsigned width, unsigned height,
|
||||
unsigned tex_width, unsigned tex_height,
|
||||
unsigned out_width, unsigned out_height)
|
||||
unsigned out_width, unsigned out_height,
|
||||
unsigned frame_count,
|
||||
const struct gl_tex_info *info,
|
||||
const struct gl_tex_info *fbo_info,
|
||||
unsigned fbo_info_cnt)
|
||||
{
|
||||
if (cg_active)
|
||||
if (cg_active && active_index > 0)
|
||||
{
|
||||
cgGLSetParameter2f(prg[active_index].vid_size_f, width, height);
|
||||
cgGLSetParameter2f(prg[active_index].tex_size_f, tex_width, tex_height);
|
||||
cgGLSetParameter2f(prg[active_index].out_size_f, out_width, out_height);
|
||||
// Set frame.
|
||||
set_param_2f(prg[active_index].vid_size_f, width, height);
|
||||
set_param_2f(prg[active_index].tex_size_f, tex_width, tex_height);
|
||||
set_param_2f(prg[active_index].out_size_f, out_width, out_height);
|
||||
set_param_1f(prg[active_index].frame_cnt_f, (float)frame_count);
|
||||
|
||||
cgGLSetParameter2f(prg[active_index].vid_size_v, width, height);
|
||||
cgGLSetParameter2f(prg[active_index].tex_size_v, tex_width, tex_height);
|
||||
cgGLSetParameter2f(prg[active_index].out_size_v, out_width, out_height);
|
||||
set_param_2f(prg[active_index].vid_size_v, width, height);
|
||||
set_param_2f(prg[active_index].tex_size_v, tex_width, tex_height);
|
||||
set_param_2f(prg[active_index].out_size_v, out_width, out_height);
|
||||
set_param_1f(prg[active_index].frame_cnt_v, (float)frame_count);
|
||||
|
||||
// Set orig texture.
|
||||
CGparameter param = prg[active_index].orig.tex;
|
||||
if (param)
|
||||
{
|
||||
cgGLSetTextureParameter(param, info->tex);
|
||||
//fprintf(stderr, "ORIGtex = (%d) %d\n", cgGLGetTextureParameter(param), cgGLGetTextureEnum(param) - GL_TEXTURE0);
|
||||
cgGLEnableTextureParameter(param);
|
||||
}
|
||||
|
||||
set_param_2f(prg[active_index].orig.vid_size_v, info->input_size[0], info->input_size[1]);
|
||||
set_param_2f(prg[active_index].orig.vid_size_f, info->input_size[0], info->input_size[1]);
|
||||
set_param_2f(prg[active_index].orig.tex_size_v, info->tex_size[0], info->tex_size[1]);
|
||||
set_param_2f(prg[active_index].orig.tex_size_f, info->tex_size[0], info->tex_size[1]);
|
||||
if (prg[active_index].orig.coord)
|
||||
{
|
||||
cgGLSetParameterPointer(prg[active_index].orig.coord, 2, GL_FLOAT, 0, info->coord);
|
||||
cgGLEnableClientState(prg[active_index].orig.coord);
|
||||
}
|
||||
|
||||
// Set lookup textures.
|
||||
for (unsigned i = 0; i < lut_textures_num; i++)
|
||||
{
|
||||
CGparameter param = cgGetNamedParameter(prg[active_index].fprg, lut_textures_uniform[i]);
|
||||
if (param)
|
||||
{
|
||||
cgGLSetTextureParameter(param, lut_textures[i]);
|
||||
cgGLEnableTextureParameter(param);
|
||||
//fprintf(stderr, "LUTtex = (%d) %d\n", cgGLGetTextureParameter(param), cgGLGetTextureEnum(param) - GL_TEXTURE0);
|
||||
}
|
||||
}
|
||||
|
||||
// Set FBO textures.
|
||||
if (active_index > 2)
|
||||
{
|
||||
for (unsigned i = 0; i < fbo_info_cnt; i++)
|
||||
{
|
||||
if (prg[active_index].fbo[i].tex)
|
||||
{
|
||||
cgGLSetTextureParameter(prg[active_index].fbo[i].tex, fbo_info[i].tex);
|
||||
cgGLEnableTextureParameter(prg[active_index].fbo[i].tex);
|
||||
}
|
||||
|
||||
set_param_2f(prg[active_index].fbo[i].vid_size_v,
|
||||
fbo_info[i].input_size[0], fbo_info[i].input_size[1]);
|
||||
set_param_2f(prg[active_index].fbo[i].vid_size_f,
|
||||
fbo_info[i].input_size[0], fbo_info[i].input_size[1]);
|
||||
|
||||
set_param_2f(prg[active_index].fbo[i].tex_size_v,
|
||||
fbo_info[i].tex_size[0], fbo_info[i].tex_size[1]);
|
||||
set_param_2f(prg[active_index].fbo[i].tex_size_f,
|
||||
fbo_info[i].tex_size[0], fbo_info[i].tex_size[1]);
|
||||
|
||||
if (prg[active_index].fbo[i].coord)
|
||||
{
|
||||
cgGLSetParameterPointer(prg[active_index].fbo[i].coord, 2, GL_FLOAT, 0, fbo_info[i].coord);
|
||||
cgGLEnableClientState(prg[active_index].fbo[i].coord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set state parameters
|
||||
if (snes_tracker)
|
||||
{
|
||||
static struct snes_tracker_uniform info[MAX_VARIABLES];
|
||||
static unsigned cnt = 0;
|
||||
|
||||
if (active_index == 1)
|
||||
cnt = snes_get_uniform(snes_tracker, info, MAX_VARIABLES, frame_count);
|
||||
|
||||
for (unsigned i = 0; i < cnt; i++)
|
||||
{
|
||||
CGparameter param_v = cgGetNamedParameter(prg[active_index].vprg, info[i].id);
|
||||
CGparameter param_f = cgGetNamedParameter(prg[active_index].fprg, info[i].id);
|
||||
set_param_1f(param_v, info[i].value);
|
||||
set_param_1f(param_f, info[i].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gl_cg_deinit(void)
|
||||
{
|
||||
if (cg_active)
|
||||
cgDestroyContext(cgCtx);
|
||||
if (!cg_active)
|
||||
return;
|
||||
|
||||
cg_active = false;
|
||||
cg_shader_num = 0;
|
||||
memset(prg, 0, sizeof(prg));
|
||||
memset(cg_scale, 0, sizeof(cg_scale));
|
||||
memset(fbo_smooth, 0, sizeof(fbo_smooth));
|
||||
|
||||
glDeleteTextures(lut_textures_num, lut_textures);
|
||||
lut_textures_num = 0;
|
||||
|
||||
if (snes_tracker)
|
||||
{
|
||||
snes_tracker_free(snes_tracker);
|
||||
snes_tracker = NULL;
|
||||
}
|
||||
|
||||
cgDestroyContext(cgCtx);
|
||||
}
|
||||
|
||||
bool gl_cg_init(const char *path)
|
||||
static bool load_plain(const char *path)
|
||||
{
|
||||
SSNES_LOG("Loading Cg file: %s\n", path);
|
||||
if (strlen(g_settings.video.second_pass_shader) > 0)
|
||||
SSNES_LOG("Loading 2nd pass: %s\n", g_settings.video.second_pass_shader);
|
||||
|
||||
cgCtx = cgCreateContext();
|
||||
if (cgCtx == NULL)
|
||||
{
|
||||
SSNES_ERR("Failed to create Cg context\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
cgFProf = cgGLGetLatestProfile(CG_GL_FRAGMENT);
|
||||
cgVProf = cgGLGetLatestProfile(CG_GL_VERTEX);
|
||||
if (cgFProf == CG_PROFILE_UNKNOWN || cgVProf == CG_PROFILE_UNKNOWN)
|
||||
{
|
||||
SSNES_ERR("Invalid profile type\n");
|
||||
return false;
|
||||
}
|
||||
cgGLSetOptimalOptions(cgFProf);
|
||||
cgGLSetOptimalOptions(cgVProf);
|
||||
|
||||
|
||||
prg[0].fprg = cgCreateProgram(cgCtx, CG_SOURCE, stock_cg_program, cgFProf, "main_fragment", 0);
|
||||
prg[0].vprg = cgCreateProgram(cgCtx, CG_SOURCE, stock_cg_program, cgVProf, "main_vertex", 0);
|
||||
|
||||
@@ -137,13 +283,17 @@ bool gl_cg_init(const char *path)
|
||||
{
|
||||
prg[2].fprg = cgCreateProgramFromFile(cgCtx, CG_SOURCE, g_settings.video.second_pass_shader, cgFProf, "main_fragment", 0);
|
||||
prg[2].vprg = cgCreateProgramFromFile(cgCtx, CG_SOURCE, g_settings.video.second_pass_shader, cgVProf, "main_vertex", 0);
|
||||
cg_shader_num = 2;
|
||||
}
|
||||
else
|
||||
prg[2] = prg[0];
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (prg[i].fprg == NULL || prg[i].vprg == NULL)
|
||||
prg[2] = prg[0];
|
||||
cg_shader_num = 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < cg_shader_num + 1; i++)
|
||||
{
|
||||
if (!prg[i].fprg || !prg[i].vprg)
|
||||
{
|
||||
CGerror err = cgGetError();
|
||||
SSNES_ERR("CG error: %s\n", cgGetErrorString(err));
|
||||
@@ -154,10 +304,581 @@ bool gl_cg_init(const char *path)
|
||||
cgGLLoadProgram(prg[i].vprg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define print_buf(buf, ...) snprintf(buf, sizeof(buf), __VA_ARGS__)
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
static bool load_textures(const char *dir_path, config_file_t *conf)
|
||||
{
|
||||
char *textures;
|
||||
if (!config_get_string(conf, "textures", &textures)) // No textures here ...
|
||||
return true;
|
||||
|
||||
const char *id = strtok(textures, ";");;
|
||||
while (id && lut_textures_num < MAX_TEXTURES)
|
||||
{
|
||||
char *path;
|
||||
if (!config_get_string(conf, id, &path))
|
||||
{
|
||||
SSNES_ERR("Cannot find path to texture \"%s\" ...\n", id);
|
||||
goto error;
|
||||
}
|
||||
|
||||
char id_filter[64];
|
||||
print_buf(id_filter, "%s_linear", id);
|
||||
bool smooth;
|
||||
if (!config_get_bool(conf, id_filter, &smooth))
|
||||
smooth = true;
|
||||
|
||||
char image_path[512];
|
||||
print_buf(image_path, "%s%s", dir_path, path);
|
||||
|
||||
SSNES_LOG("Loading image from: \"%s\".\n", image_path);
|
||||
struct texture_image img;
|
||||
if (!texture_image_load(image_path, &img))
|
||||
{
|
||||
SSNES_ERR("Failed to load picture ...\n");
|
||||
free(path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
strlcpy(lut_textures_uniform[lut_textures_num], id, sizeof(lut_textures_uniform[lut_textures_num]));
|
||||
|
||||
glGenTextures(1, &lut_textures[lut_textures_num]);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, lut_textures[lut_textures_num]);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, smooth ? GL_LINEAR : GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, smooth ? GL_LINEAR : GL_NEAREST);
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, img.width);
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0, GL_RGBA, img.width, img.height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, img.pixels);
|
||||
|
||||
lut_textures_num++;
|
||||
|
||||
free(img.pixels);
|
||||
free(path);
|
||||
|
||||
id = strtok(NULL, ";");;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
free(textures);
|
||||
return true;
|
||||
|
||||
error:
|
||||
if (textures)
|
||||
free(textures);
|
||||
|
||||
pglActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool load_imports(const char *dir_path, config_file_t *conf)
|
||||
{
|
||||
char *imports = NULL;
|
||||
|
||||
if (!config_get_string(conf, "imports", &imports))
|
||||
return true;
|
||||
|
||||
struct snes_tracker_uniform_info info[MAX_VARIABLES];
|
||||
unsigned info_cnt = 0;
|
||||
|
||||
const char *id = strtok(imports, ";");
|
||||
while (id && info_cnt < MAX_VARIABLES)
|
||||
{
|
||||
char semantic_buf[64];
|
||||
char wram_buf[64];
|
||||
char input_slot_buf[64];
|
||||
char apuram_buf[64];
|
||||
char oam_buf[64];
|
||||
char cgram_buf[64];
|
||||
char vram_buf[64];
|
||||
char mask_buf[64];
|
||||
|
||||
print_buf(semantic_buf, "%s_semantic", id);
|
||||
print_buf(wram_buf, "%s_wram", id);
|
||||
print_buf(input_slot_buf, "%s_input_slot", id);
|
||||
print_buf(apuram_buf, "%s_apuram", id);
|
||||
print_buf(oam_buf, "%s_oam", id);
|
||||
print_buf(cgram_buf, "%s_cgram", id);
|
||||
print_buf(vram_buf, "%s_vram", id);
|
||||
print_buf(mask_buf, "%s_mask", id);
|
||||
|
||||
char *semantic = NULL;
|
||||
|
||||
config_get_string(conf, semantic_buf, &semantic);
|
||||
|
||||
if (!semantic)
|
||||
{
|
||||
SSNES_ERR("No semantic for import variable.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
enum snes_tracker_type tracker_type;
|
||||
enum snes_ram_type ram_type = SNES_MEMORY_WRAM;
|
||||
|
||||
if (strcmp(semantic, "capture") == 0)
|
||||
tracker_type = SSNES_STATE_CAPTURE;
|
||||
else if (strcmp(semantic, "transition") == 0)
|
||||
tracker_type = SSNES_STATE_TRANSITION;
|
||||
else if (strcmp(semantic, "transition_count") == 0)
|
||||
tracker_type = SSNES_STATE_TRANSITION_COUNT;
|
||||
else if (strcmp(semantic, "capture_previous") == 0)
|
||||
tracker_type = SSNES_STATE_CAPTURE_PREV;
|
||||
else if (strcmp(semantic, "transition_previous") == 0)
|
||||
tracker_type = SSNES_STATE_TRANSITION_PREV;
|
||||
#ifdef HAVE_PYTHON
|
||||
else if (strcmp(semantic, "python") == 0)
|
||||
tracker_type = SSNES_STATE_PYTHON;
|
||||
#endif
|
||||
else
|
||||
{
|
||||
SSNES_ERR("Invalid semantic.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
unsigned addr = 0;
|
||||
#ifdef HAVE_PYTHON
|
||||
if (tracker_type != SSNES_STATE_PYTHON)
|
||||
{
|
||||
#endif
|
||||
unsigned input_slot = 0;
|
||||
if (config_get_hex(conf, input_slot_buf, &input_slot))
|
||||
{
|
||||
switch (input_slot)
|
||||
{
|
||||
case 1:
|
||||
ram_type = SSNES_STATE_INPUT_SLOT1;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
ram_type = SSNES_STATE_INPUT_SLOT2;
|
||||
break;
|
||||
|
||||
default:
|
||||
SSNES_ERR("Invalid input slot for import.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
else if (config_get_hex(conf, wram_buf, &addr))
|
||||
ram_type = SSNES_STATE_WRAM;
|
||||
else if (config_get_hex(conf, apuram_buf, &addr))
|
||||
ram_type = SSNES_STATE_APURAM;
|
||||
else if (config_get_hex(conf, oam_buf, &addr))
|
||||
ram_type = SSNES_STATE_OAM;
|
||||
else if (config_get_hex(conf, cgram_buf, &addr))
|
||||
ram_type = SSNES_STATE_CGRAM;
|
||||
else if (config_get_hex(conf, vram_buf, &addr))
|
||||
ram_type = SSNES_STATE_VRAM;
|
||||
else
|
||||
{
|
||||
SSNES_ERR("No address assigned to semantic.\n");
|
||||
goto error;
|
||||
}
|
||||
#ifdef HAVE_PYTHON
|
||||
}
|
||||
#endif
|
||||
|
||||
int memtype = 0;
|
||||
switch (ram_type)
|
||||
{
|
||||
case SSNES_STATE_WRAM:
|
||||
memtype = SNES_MEMORY_WRAM;
|
||||
break;
|
||||
case SSNES_STATE_APURAM:
|
||||
memtype = SNES_MEMORY_APURAM;
|
||||
break;
|
||||
case SSNES_STATE_VRAM:
|
||||
memtype = SNES_MEMORY_VRAM;
|
||||
break;
|
||||
case SSNES_STATE_OAM:
|
||||
memtype = SNES_MEMORY_OAM;
|
||||
break;
|
||||
case SSNES_STATE_CGRAM:
|
||||
memtype = SNES_MEMORY_CGRAM;
|
||||
break;
|
||||
|
||||
default:
|
||||
memtype = SNES_MEMORY_WRAM;
|
||||
}
|
||||
|
||||
if (addr >= psnes_get_memory_size(memtype))
|
||||
{
|
||||
SSNES_ERR("Address out of bounds.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
unsigned bitmask = 0;
|
||||
if (!config_get_hex(conf, mask_buf, &bitmask))
|
||||
bitmask = 0;
|
||||
|
||||
strlcpy(info[info_cnt].id, id, sizeof(info[info_cnt].id));
|
||||
info[info_cnt].addr = addr;
|
||||
info[info_cnt].type = tracker_type;
|
||||
info[info_cnt].ram_type = ram_type;
|
||||
info[info_cnt].mask = bitmask;
|
||||
|
||||
info_cnt++;
|
||||
free(semantic);
|
||||
|
||||
id = strtok(NULL, ";");
|
||||
}
|
||||
|
||||
struct snes_tracker_info tracker_info = {
|
||||
.wram = psnes_get_memory_data(SNES_MEMORY_WRAM),
|
||||
.vram = psnes_get_memory_data(SNES_MEMORY_VRAM),
|
||||
.cgram = psnes_get_memory_data(SNES_MEMORY_CGRAM),
|
||||
.oam = psnes_get_memory_data(SNES_MEMORY_OAM),
|
||||
.apuram = psnes_get_memory_data(SNES_MEMORY_APURAM),
|
||||
.info = info,
|
||||
.info_elem = info_cnt
|
||||
};
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
char script_path[128];
|
||||
char *script = NULL;
|
||||
char *script_class = NULL;
|
||||
if (config_get_string(conf, "import_script", &script))
|
||||
{
|
||||
strlcpy(script_path, dir_path, sizeof(script_path));
|
||||
strlcat(script_path, script, sizeof(script_path));
|
||||
|
||||
tracker_info.script = script_path;
|
||||
}
|
||||
if (config_get_string(conf, "import_script_class", &script_class))
|
||||
tracker_info.script_class = script_class;
|
||||
|
||||
tracker_info.script_is_file = true;
|
||||
#endif
|
||||
|
||||
snes_tracker = snes_tracker_init(&tracker_info);
|
||||
if (!snes_tracker)
|
||||
SSNES_WARN("Failed to init SNES tracker.\n");
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
if (script)
|
||||
free(script);
|
||||
if (script_class)
|
||||
free(script_class);
|
||||
#endif
|
||||
|
||||
free(imports);
|
||||
|
||||
return true;
|
||||
|
||||
error:
|
||||
free(imports);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static bool load_preset(const char *path)
|
||||
{
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
// Create passthrough shader.
|
||||
prg[0].fprg = cgCreateProgram(cgCtx, CG_SOURCE, stock_cg_program, cgFProf, "main_fragment", 0);
|
||||
prg[0].vprg = cgCreateProgram(cgCtx, CG_SOURCE, stock_cg_program, cgVProf, "main_vertex", 0);
|
||||
if (!prg[0].fprg || !prg[0].vprg)
|
||||
{
|
||||
SSNES_ERR("Failed to compile passthrough shader, is something wrong with your environment?\n");
|
||||
return false;
|
||||
}
|
||||
cgGLLoadProgram(prg[0].fprg);
|
||||
cgGLLoadProgram(prg[0].vprg);
|
||||
|
||||
SSNES_LOG("Loading Cg meta-shader: %s\n", path);
|
||||
config_file_t *conf = config_file_new(path);
|
||||
if (!conf)
|
||||
{
|
||||
SSNES_ERR("Failed to load preset.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
int shaders;
|
||||
if (!config_get_int(conf, "shaders", &shaders))
|
||||
{
|
||||
SSNES_ERR("Cannot find \"shaders\" param.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (shaders < 1)
|
||||
{
|
||||
SSNES_ERR("Need to define at least 1 shader!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
cg_shader_num = shaders;
|
||||
if (shaders > MAX_SHADERS - 2)
|
||||
{
|
||||
SSNES_WARN("Too many shaders ... Capping shader amount to %d.\n", MAX_SHADERS - 2);
|
||||
cg_shader_num = shaders = MAX_SHADERS - 2;
|
||||
}
|
||||
// If we aren't using last pass non-FBO shader,
|
||||
// this shader will be assumed to be "fixed-function".
|
||||
// Just use prg[0] for that pass, which will be
|
||||
// pass-through.
|
||||
prg[shaders + 1] = prg[0];
|
||||
|
||||
// Check filter params.
|
||||
for (unsigned i = 0; i < shaders; i++)
|
||||
{
|
||||
bool smooth;
|
||||
char filter_name_buf[64];
|
||||
print_buf(filter_name_buf, "filter_linear%u", i);
|
||||
if (config_get_bool(conf, filter_name_buf, &smooth))
|
||||
fbo_smooth[i + 1] = smooth ? FILTER_LINEAR : FILTER_NEAREST;
|
||||
}
|
||||
|
||||
// Bigass for-loop ftw. Check scaling params.
|
||||
for (unsigned i = 0; i < shaders; i++)
|
||||
{
|
||||
char *scale_type = NULL;
|
||||
char *scale_type_x = NULL;
|
||||
char *scale_type_y = NULL;
|
||||
bool has_scale_type;
|
||||
bool has_scale_type_x;
|
||||
bool has_scale_type_y;
|
||||
|
||||
char scale_name_buf[64];
|
||||
print_buf(scale_name_buf, "scale_type%u", i);
|
||||
has_scale_type = config_get_string(conf, scale_name_buf, &scale_type);
|
||||
print_buf(scale_name_buf, "scale_type_x%u", i);
|
||||
has_scale_type_x = config_get_string(conf, scale_name_buf, &scale_type_x);
|
||||
print_buf(scale_name_buf, "scale_type_y%u", i);
|
||||
has_scale_type_y = config_get_string(conf, scale_name_buf, &scale_type_y);
|
||||
|
||||
if (!has_scale_type && !has_scale_type_x && !has_scale_type_y)
|
||||
continue;
|
||||
|
||||
if (has_scale_type)
|
||||
{
|
||||
if (scale_type_x)
|
||||
free(scale_type_x);
|
||||
if (scale_type_y)
|
||||
free(scale_type_y);
|
||||
|
||||
scale_type_x = strdup(scale_type);
|
||||
scale_type_y = strdup(scale_type);
|
||||
free(scale_type);
|
||||
}
|
||||
|
||||
char attr_name_buf[64];
|
||||
double fattr;
|
||||
int iattr;
|
||||
struct gl_fbo_scale *scale = &cg_scale[i + 1]; // Shader 0 is passthrough shader. Start at 1.
|
||||
|
||||
scale->valid = true;
|
||||
scale->type_x = SSNES_SCALE_INPUT;
|
||||
scale->type_y = SSNES_SCALE_INPUT;
|
||||
scale->scale_x = 1.0;
|
||||
scale->scale_y = 1.0;
|
||||
scale->abs_x = 256;
|
||||
scale->abs_y = 224;
|
||||
|
||||
if (strcmp(scale_type_x, "source") == 0)
|
||||
scale->type_x = SSNES_SCALE_INPUT;
|
||||
else if (strcmp(scale_type_x, "viewport") == 0)
|
||||
scale->type_x = SSNES_SCALE_VIEWPORT;
|
||||
else if (strcmp(scale_type_x, "absolute") == 0)
|
||||
scale->type_x = SSNES_SCALE_ABSOLUTE;
|
||||
else
|
||||
{
|
||||
SSNES_ERR("Invalid attribute.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (strcmp(scale_type_y, "source") == 0)
|
||||
scale->type_y = SSNES_SCALE_INPUT;
|
||||
else if (strcmp(scale_type_y, "viewport") == 0)
|
||||
scale->type_y = SSNES_SCALE_VIEWPORT;
|
||||
else if (strcmp(scale_type_y, "absolute") == 0)
|
||||
scale->type_y = SSNES_SCALE_ABSOLUTE;
|
||||
else
|
||||
{
|
||||
SSNES_ERR("Invalid attribute.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (scale->type_x == SSNES_SCALE_ABSOLUTE)
|
||||
{
|
||||
print_buf(attr_name_buf, "scale%u", i);
|
||||
if (config_get_int(conf, attr_name_buf, &iattr))
|
||||
scale->abs_x = iattr;
|
||||
else
|
||||
{
|
||||
print_buf(attr_name_buf, "scale_x%u", i);
|
||||
if (config_get_int(conf, attr_name_buf, &iattr))
|
||||
scale->abs_x = iattr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
print_buf(attr_name_buf, "scale%u", i);
|
||||
if (config_get_double(conf, attr_name_buf, &fattr))
|
||||
scale->scale_x = fattr;
|
||||
else
|
||||
{
|
||||
print_buf(attr_name_buf, "scale_x%u", i);
|
||||
if (config_get_double(conf, attr_name_buf, &fattr))
|
||||
scale->scale_x = fattr;
|
||||
}
|
||||
}
|
||||
|
||||
if (scale->type_y == SSNES_SCALE_ABSOLUTE)
|
||||
{
|
||||
print_buf(attr_name_buf, "scale%u", i);
|
||||
if (config_get_int(conf, attr_name_buf, &iattr))
|
||||
scale->abs_y = iattr;
|
||||
else
|
||||
{
|
||||
print_buf(attr_name_buf, "scale_y%u", i);
|
||||
if (config_get_int(conf, attr_name_buf, &iattr))
|
||||
scale->abs_y = iattr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
print_buf(attr_name_buf, "scale%u", i);
|
||||
if (config_get_double(conf, attr_name_buf, &fattr))
|
||||
scale->scale_y = fattr;
|
||||
else
|
||||
{
|
||||
print_buf(attr_name_buf, "scale_y%u", i);
|
||||
if (config_get_double(conf, attr_name_buf, &fattr))
|
||||
scale->scale_y = fattr;
|
||||
}
|
||||
}
|
||||
|
||||
if (scale_type_x)
|
||||
free(scale_type_x);
|
||||
if (scale_type_y)
|
||||
free(scale_type_y);
|
||||
}
|
||||
|
||||
// Basedir.
|
||||
char dir_path[256];
|
||||
strlcpy(dir_path, path, sizeof(dir_path));
|
||||
char *ptr = strrchr(dir_path, '/');
|
||||
if (!ptr) ptr = strrchr(dir_path, '\\');
|
||||
if (ptr)
|
||||
ptr[1] = '\0';
|
||||
else // No directory.
|
||||
dir_path[0] = '\0';
|
||||
|
||||
// Finally load shaders :)
|
||||
for (unsigned i = 0; i < shaders; i++)
|
||||
{
|
||||
char *shader_path;
|
||||
char attr_buf[64];
|
||||
char path_buf[512];
|
||||
|
||||
print_buf(attr_buf, "shader%u", i);
|
||||
if (config_get_string(conf, attr_buf, &shader_path))
|
||||
{
|
||||
strlcpy(path_buf, dir_path, sizeof(path_buf));
|
||||
strlcat(path_buf, shader_path, sizeof(path_buf));
|
||||
free(shader_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
SSNES_ERR("Didn't find shader path in config ...\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
SSNES_LOG("Loading Cg shader: \"%s\".\n", path_buf);
|
||||
|
||||
struct cg_program *prog = &prg[i + 1];
|
||||
prog->fprg = cgCreateProgramFromFile(cgCtx, CG_SOURCE, path_buf, cgFProf, "main_fragment", 0);
|
||||
prog->vprg = cgCreateProgramFromFile(cgCtx, CG_SOURCE, path_buf, cgVProf, "main_vertex", 0);
|
||||
|
||||
if (!prog->fprg || !prog->vprg)
|
||||
{
|
||||
CGerror err = cgGetError();
|
||||
SSNES_ERR("CG error: %s\n", cgGetErrorString(err));
|
||||
goto error;
|
||||
}
|
||||
|
||||
cgGLLoadProgram(prog->fprg);
|
||||
cgGLLoadProgram(prog->vprg);
|
||||
}
|
||||
|
||||
if (!load_textures(dir_path, conf))
|
||||
{
|
||||
SSNES_ERR("Failed to load lookup textures ...\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!load_imports(dir_path, conf))
|
||||
{
|
||||
SSNES_ERR("Failed to load imports ...\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
config_file_free(conf);
|
||||
return true;
|
||||
|
||||
error:
|
||||
if (conf)
|
||||
config_file_free(conf);
|
||||
return false;
|
||||
#else
|
||||
(void)path;
|
||||
SSNES_ERR("No config file support compiled in.\n");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool gl_cg_init(const char *path)
|
||||
{
|
||||
cgCtx = cgCreateContext();
|
||||
if (cgCtx == NULL)
|
||||
{
|
||||
SSNES_ERR("Failed to create Cg context\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef SSNES_CG_DEBUG
|
||||
cgGLSetDebugMode(CG_TRUE);
|
||||
cgSetErrorHandler(cg_error_handler, NULL);
|
||||
#endif
|
||||
|
||||
cgFProf = cgGLGetLatestProfile(CG_GL_FRAGMENT);
|
||||
cgVProf = cgGLGetLatestProfile(CG_GL_VERTEX);
|
||||
if (cgFProf == CG_PROFILE_UNKNOWN || cgVProf == CG_PROFILE_UNKNOWN)
|
||||
{
|
||||
SSNES_ERR("Invalid profile type\n");
|
||||
return false;
|
||||
}
|
||||
cgGLSetOptimalOptions(cgFProf);
|
||||
cgGLSetOptimalOptions(cgVProf);
|
||||
cgGLEnableProfile(cgFProf);
|
||||
cgGLEnableProfile(cgVProf);
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
if (strstr(path, ".cgp"))
|
||||
{
|
||||
if (!load_preset(path))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!load_plain(path))
|
||||
return false;
|
||||
}
|
||||
|
||||
prg[0].mvp = cgGetNamedParameter(prg[0].vprg, "modelViewProj");
|
||||
if (prg[0].mvp)
|
||||
cgGLSetStateMatrixParameter(prg[0].mvp, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
|
||||
|
||||
for (unsigned i = 1; i < cg_shader_num + 1; i++)
|
||||
{
|
||||
cgGLBindProgram(prg[i].fprg);
|
||||
cgGLBindProgram(prg[i].vprg);
|
||||
@@ -165,11 +886,40 @@ bool gl_cg_init(const char *path)
|
||||
prg[i].vid_size_f = cgGetNamedParameter(prg[i].fprg, "IN.video_size");
|
||||
prg[i].tex_size_f = cgGetNamedParameter(prg[i].fprg, "IN.texture_size");
|
||||
prg[i].out_size_f = cgGetNamedParameter(prg[i].fprg, "IN.output_size");
|
||||
prg[i].frame_cnt_f = cgGetNamedParameter(prg[i].fprg, "IN.frame_count");
|
||||
prg[i].vid_size_v = cgGetNamedParameter(prg[i].vprg, "IN.video_size");
|
||||
prg[i].tex_size_v = cgGetNamedParameter(prg[i].vprg, "IN.texture_size");
|
||||
prg[i].out_size_v = cgGetNamedParameter(prg[i].vprg, "IN.output_size");
|
||||
prg[i].frame_cnt_v = cgGetNamedParameter(prg[i].vprg, "IN.frame_count");
|
||||
prg[i].mvp = cgGetNamedParameter(prg[i].vprg, "modelViewProj");
|
||||
cgGLSetStateMatrixParameter(prg[i].mvp, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
|
||||
if (prg[i].mvp)
|
||||
cgGLSetStateMatrixParameter(prg[i].mvp, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
|
||||
|
||||
prg[i].orig.tex = cgGetNamedParameter(prg[i].fprg, "ORIG.texture");
|
||||
prg[i].orig.vid_size_v = cgGetNamedParameter(prg[i].vprg, "ORIG.video_size");
|
||||
prg[i].orig.vid_size_f = cgGetNamedParameter(prg[i].fprg, "ORIG.video_size");
|
||||
prg[i].orig.tex_size_v = cgGetNamedParameter(prg[i].vprg, "ORIG.texture_size");
|
||||
prg[i].orig.tex_size_f = cgGetNamedParameter(prg[i].fprg, "ORIG.texture_size");
|
||||
prg[i].orig.coord = cgGetNamedParameter(prg[i].vprg, "ORIG.tex_coord");
|
||||
|
||||
for (unsigned j = 0; j < i - 1; j++)
|
||||
{
|
||||
char attr_buf[64];
|
||||
|
||||
snprintf(attr_buf, sizeof(attr_buf), "PASS%u.texture", j + 1);
|
||||
prg[i].fbo[j].tex = cgGetNamedParameter(prg[i].fprg, attr_buf);
|
||||
|
||||
snprintf(attr_buf, sizeof(attr_buf), "PASS%u.video_size", j + 1);
|
||||
prg[i].fbo[j].vid_size_v = cgGetNamedParameter(prg[i].vprg, attr_buf);
|
||||
prg[i].fbo[j].vid_size_f = cgGetNamedParameter(prg[i].fprg, attr_buf);
|
||||
|
||||
snprintf(attr_buf, sizeof(attr_buf), "PASS%u.texture_size", j + 1);
|
||||
prg[i].fbo[j].tex_size_v = cgGetNamedParameter(prg[i].vprg, attr_buf);
|
||||
prg[i].fbo[j].tex_size_f = cgGetNamedParameter(prg[i].fprg, attr_buf);
|
||||
|
||||
snprintf(attr_buf, sizeof(attr_buf), "PASS%u.tex_coord", j + 1);
|
||||
prg[i].fbo[j].coord = cgGetNamedParameter(prg[i].vprg, attr_buf);
|
||||
}
|
||||
}
|
||||
|
||||
cgGLBindProgram(prg[1].fprg);
|
||||
@@ -181,7 +931,7 @@ bool gl_cg_init(const char *path)
|
||||
|
||||
void gl_cg_use(unsigned index)
|
||||
{
|
||||
if (cg_active)
|
||||
if (cg_active && prg[index].vprg && prg[index].fprg)
|
||||
{
|
||||
active_index = index;
|
||||
cgGLBindProgram(prg[index].vprg);
|
||||
@@ -192,27 +942,28 @@ void gl_cg_use(unsigned index)
|
||||
unsigned gl_cg_num(void)
|
||||
{
|
||||
if (cg_active)
|
||||
{
|
||||
if (prg[0].fprg == prg[2].fprg)
|
||||
return 1;
|
||||
else
|
||||
return 2;
|
||||
}
|
||||
return cg_shader_num;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool gl_cg_filter_type(unsigned index, bool *smooth)
|
||||
{
|
||||
(void)index;
|
||||
(void)smooth;
|
||||
// We don't really care since .cg doesn't have those kinds of semantics by itself ...
|
||||
return false;
|
||||
if (cg_active)
|
||||
{
|
||||
if (fbo_smooth[index] == FILTER_UNSPEC)
|
||||
return false;
|
||||
*smooth = (fbo_smooth[index] == FILTER_LINEAR);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void gl_cg_shader_scale(unsigned index, struct gl_fbo_scale *scale)
|
||||
{
|
||||
(void)index;
|
||||
// We don't really care since .cg doesn't have those kinds of semantics by itself ...
|
||||
scale->valid = false;
|
||||
if (cg_active)
|
||||
*scale = cg_scale[index];
|
||||
else
|
||||
scale->valid = false;
|
||||
}
|
||||
+4
-1
@@ -30,7 +30,10 @@ void gl_cg_set_proj_matrix(void);
|
||||
|
||||
void gl_cg_set_params(unsigned width, unsigned height,
|
||||
unsigned tex_width, unsigned tex_height,
|
||||
unsigned out_width, unsigned out_height);
|
||||
unsigned out_width, unsigned out_height,
|
||||
unsigned frame_count,
|
||||
const struct gl_tex_info *info,
|
||||
const struct gl_tex_info *fbo_info, unsigned fbo_info_cnt);
|
||||
|
||||
void gl_cg_use(unsigned index);
|
||||
|
||||
|
||||
+543
-33
@@ -19,6 +19,9 @@
|
||||
#include <string.h>
|
||||
#include "general.h"
|
||||
#include "shader_glsl.h"
|
||||
#include "strl.h"
|
||||
#include "snes_state.h"
|
||||
#include "dynamic.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/gl.h>
|
||||
@@ -37,6 +40,8 @@
|
||||
#include <libxml/tree.h>
|
||||
|
||||
#include "gl_common.h"
|
||||
#include "image.h"
|
||||
|
||||
|
||||
#ifdef __APPLE__
|
||||
#define pglCreateProgram glCreateProgram
|
||||
@@ -58,6 +63,9 @@
|
||||
#define pglGetProgramInfoLog glGetProgramInfoLog
|
||||
#define pglDeleteProgram glDeleteProgram
|
||||
#define pglGetAttachedShaders glGetAttachedShaders
|
||||
#define pglGetAttribLocation glGetAttribLocation
|
||||
#define pglEnableVertexAttribArray glEnableVertexAttribArray
|
||||
#define pglVertexAttribPointer glVertexAttribPointer
|
||||
#else
|
||||
static PFNGLCREATEPROGRAMPROC pglCreateProgram = NULL;
|
||||
static PFNGLUSEPROGRAMPROC pglUseProgram = NULL;
|
||||
@@ -78,9 +86,14 @@ static PFNGLGETPROGRAMIVPROC pglGetProgramiv = NULL;
|
||||
static PFNGLGETPROGRAMINFOLOGPROC pglGetProgramInfoLog = NULL;
|
||||
static PFNGLDELETEPROGRAMPROC pglDeleteProgram = NULL;
|
||||
static PFNGLGETATTACHEDSHADERSPROC pglGetAttachedShaders = NULL;
|
||||
static PFNGLGETATTRIBLOCATIONPROC pglGetAttribLocation = NULL;
|
||||
static PFNGLENABLEVERTEXATTRIBARRAYPROC pglEnableVertexAttribArray = NULL;
|
||||
static PFNGLVERTEXATTRIBPOINTERPROC pglVertexAttribPointer = NULL;
|
||||
#endif
|
||||
|
||||
#define MAX_PROGRAMS 16
|
||||
#define MAX_TEXTURES 8
|
||||
#define MAX_VARIABLES 256
|
||||
|
||||
enum filter_type
|
||||
{
|
||||
@@ -96,6 +109,17 @@ static struct gl_fbo_scale gl_scale[MAX_PROGRAMS];
|
||||
static unsigned gl_num_programs = 0;
|
||||
static unsigned active_index = 0;
|
||||
|
||||
static GLuint gl_teximage[MAX_TEXTURES];
|
||||
static unsigned gl_teximage_cnt = 0;
|
||||
static char gl_teximage_uniforms[MAX_TEXTURES][64];
|
||||
|
||||
static snes_tracker_t *gl_snes_tracker = NULL;
|
||||
static struct snes_tracker_uniform_info gl_tracker_info[MAX_VARIABLES];
|
||||
static unsigned gl_tracker_info_cnt = 0;
|
||||
static char gl_tracker_script[256];
|
||||
static char gl_tracker_script_class[64];
|
||||
static xmlChar *gl_script_program = NULL;
|
||||
|
||||
struct shader_program
|
||||
{
|
||||
char *vertex;
|
||||
@@ -273,6 +297,292 @@ static bool get_xml_attrs(struct shader_program *prog, xmlNodePtr ptr)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool get_texture_image(const char *shader_path, xmlNodePtr ptr)
|
||||
{
|
||||
if (gl_teximage_cnt >= MAX_TEXTURES)
|
||||
{
|
||||
SSNES_WARN("Too many texture images! Ignoring ...\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool linear = true;
|
||||
xmlChar *filename = xmlGetProp(ptr, (const xmlChar*)"file");
|
||||
xmlChar *filter = xmlGetProp(ptr, (const xmlChar*)"filter");
|
||||
xmlChar *id = xmlGetProp(ptr, (const xmlChar*)"id");
|
||||
|
||||
if (!id)
|
||||
{
|
||||
SSNES_ERR("Could not find ID in texture.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!filename)
|
||||
{
|
||||
SSNES_ERR("Could not find filename in texture.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (filter && strcmp((const char*)filter, "nearest") == 0)
|
||||
linear = false;
|
||||
|
||||
char tex_path[256];
|
||||
strlcpy(tex_path, shader_path, sizeof(tex_path));
|
||||
|
||||
char *last = strrchr(tex_path, '/');
|
||||
if (!last) last = strrchr(tex_path, '\\');
|
||||
if (last) last[1] = '\0';
|
||||
|
||||
strlcat(tex_path, (const char*)filename, sizeof(tex_path));
|
||||
|
||||
struct texture_image img;
|
||||
SSNES_LOG("Loading texture image from: \"%s\" ...\n", tex_path);
|
||||
if (!texture_image_load(tex_path, &img))
|
||||
{
|
||||
SSNES_ERR("Failed to load texture image from: \"%s\"\n", tex_path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
strlcpy(gl_teximage_uniforms[gl_teximage_cnt], (const char*)id, sizeof(gl_teximage_uniforms[0]));
|
||||
|
||||
glGenTextures(1, &gl_teximage[gl_teximage_cnt]);
|
||||
|
||||
pglActiveTexture(GL_TEXTURE0 + gl_teximage_cnt + 1);
|
||||
glBindTexture(GL_TEXTURE_2D, gl_teximage[gl_teximage_cnt]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, linear ? GL_LINEAR : GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, linear ? GL_LINEAR : GL_NEAREST);
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, img.width);
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0, GL_RGBA, img.width, img.height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, img.pixels);
|
||||
|
||||
pglActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
free(img.pixels);
|
||||
|
||||
xmlFree(filename);
|
||||
xmlFree(id);
|
||||
if (filter)
|
||||
xmlFree(filter);
|
||||
|
||||
gl_teximage_cnt++;
|
||||
|
||||
return true;
|
||||
|
||||
error:
|
||||
if (filename)
|
||||
xmlFree(filename);
|
||||
if (filter)
|
||||
xmlFree(filter);
|
||||
if (filter)
|
||||
xmlFree(id);
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
static bool get_script(const char *path, xmlNodePtr ptr)
|
||||
{
|
||||
if (*gl_tracker_script || gl_script_program)
|
||||
{
|
||||
SSNES_ERR("Script already imported.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
xmlChar *script_class = xmlGetProp(ptr, (const xmlChar*)"class");
|
||||
if (script_class)
|
||||
{
|
||||
strlcpy(gl_tracker_script_class, (const char*)script_class, sizeof(gl_tracker_script_class));
|
||||
xmlFree(script_class);
|
||||
}
|
||||
|
||||
xmlChar *language = xmlGetProp(ptr, (const xmlChar*)"language");
|
||||
if (!language || strcmp((const char*)language, "python") != 0)
|
||||
{
|
||||
SSNES_ERR("Script language is not Python!\n");
|
||||
if (language)
|
||||
xmlFree(language);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (language)
|
||||
xmlFree(language);
|
||||
|
||||
xmlChar *src = xmlGetProp(ptr, (const xmlChar*)"src");
|
||||
if (src)
|
||||
{
|
||||
strlcpy(gl_tracker_script, path, sizeof(gl_tracker_script));
|
||||
char *dir_ptr = strrchr(gl_tracker_script, '/');
|
||||
if (!dir_ptr) dir_ptr = strrchr(gl_tracker_script, '\\');
|
||||
if (dir_ptr) dir_ptr[1] = '\0';
|
||||
strlcat(gl_tracker_script, (const char*)src, sizeof(gl_tracker_script));
|
||||
|
||||
xmlFree(src);
|
||||
}
|
||||
else
|
||||
{
|
||||
xmlChar *script = xmlNodeGetContent(ptr);
|
||||
if (!script)
|
||||
{
|
||||
SSNES_ERR("No content in script!\n");
|
||||
return false;
|
||||
}
|
||||
gl_script_program = script;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool get_import_value(xmlNodePtr ptr)
|
||||
{
|
||||
if (gl_tracker_info_cnt >= MAX_VARIABLES)
|
||||
{
|
||||
SSNES_ERR("Too many import variables ...\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
xmlChar *id = xmlGetProp(ptr, (const xmlChar*)"id");
|
||||
xmlChar *semantic = xmlGetProp(ptr, (const xmlChar*)"semantic");
|
||||
xmlChar *wram = xmlGetProp(ptr, (const xmlChar*)"wram");
|
||||
xmlChar *input = xmlGetProp(ptr, (const xmlChar*)"input_slot");
|
||||
xmlChar *apuram = xmlGetProp(ptr, (const xmlChar*)"apuram");
|
||||
xmlChar *vram = xmlGetProp(ptr, (const xmlChar*)"vram");
|
||||
xmlChar *oam = xmlGetProp(ptr, (const xmlChar*)"oam");
|
||||
xmlChar *cgram = xmlGetProp(ptr, (const xmlChar*)"cgram");
|
||||
xmlChar *bitmask = xmlGetProp(ptr, (const xmlChar*)"mask");
|
||||
|
||||
if (!semantic || !id)
|
||||
{
|
||||
SSNES_ERR("No semantic or ID for import value!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
enum snes_tracker_type tracker_type;
|
||||
|
||||
if (strcmp((const char*)semantic, "capture") == 0)
|
||||
tracker_type = SSNES_STATE_CAPTURE;
|
||||
else if (strcmp((const char*)semantic, "capture_previous") == 0)
|
||||
tracker_type = SSNES_STATE_CAPTURE_PREV;
|
||||
else if (strcmp((const char*)semantic, "transition") == 0)
|
||||
tracker_type = SSNES_STATE_TRANSITION;
|
||||
else if (strcmp((const char*)semantic, "transition_count") == 0)
|
||||
tracker_type = SSNES_STATE_TRANSITION_COUNT;
|
||||
else if (strcmp((const char*)semantic, "transition_previous") == 0)
|
||||
tracker_type = SSNES_STATE_TRANSITION_PREV;
|
||||
#ifdef HAVE_PYTHON
|
||||
else if (strcmp((const char*)semantic, "python") == 0)
|
||||
tracker_type = SSNES_STATE_PYTHON;
|
||||
#endif
|
||||
else
|
||||
{
|
||||
SSNES_ERR("Invalid semantic for import value.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
enum snes_ram_type ram_type = SSNES_STATE_WRAM;
|
||||
uint32_t addr = 0;
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
if (tracker_type != SSNES_STATE_PYTHON)
|
||||
{
|
||||
#endif
|
||||
if (input)
|
||||
{
|
||||
unsigned slot = strtoul((const char*)input, NULL, 0);
|
||||
switch (slot)
|
||||
{
|
||||
case 1:
|
||||
ram_type = SSNES_STATE_INPUT_SLOT1;
|
||||
break;
|
||||
case 2:
|
||||
ram_type = SSNES_STATE_INPUT_SLOT2;
|
||||
break;
|
||||
|
||||
default:
|
||||
SSNES_ERR("Invalid input slot for import!\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
else if (wram) { addr = strtoul((const char*)wram, NULL, 16); ram_type = SSNES_STATE_WRAM; }
|
||||
else if (apuram) { addr = strtoul((const char*)apuram, NULL, 16); ram_type = SSNES_STATE_APURAM; }
|
||||
else if (vram) { addr = strtoul((const char*)vram, NULL, 16); ram_type = SSNES_STATE_VRAM; }
|
||||
else if (oam) { addr = strtoul((const char*)oam, NULL, 16); ram_type = SSNES_STATE_OAM; }
|
||||
else if (cgram) { addr = strtoul((const char*)cgram, NULL, 16); ram_type = SSNES_STATE_CGRAM; }
|
||||
else
|
||||
{
|
||||
SSNES_ERR("No RAM address specificed for import value.\n");
|
||||
goto error;
|
||||
}
|
||||
#ifdef HAVE_PYTHON
|
||||
}
|
||||
#endif
|
||||
|
||||
int memtype = 0;
|
||||
switch (ram_type)
|
||||
{
|
||||
case SSNES_STATE_WRAM:
|
||||
memtype = SNES_MEMORY_WRAM;
|
||||
break;
|
||||
case SSNES_STATE_APURAM:
|
||||
memtype = SNES_MEMORY_APURAM;
|
||||
break;
|
||||
case SSNES_STATE_VRAM:
|
||||
memtype = SNES_MEMORY_VRAM;
|
||||
break;
|
||||
case SSNES_STATE_OAM:
|
||||
memtype = SNES_MEMORY_OAM;
|
||||
break;
|
||||
case SSNES_STATE_CGRAM:
|
||||
memtype = SNES_MEMORY_CGRAM;
|
||||
break;
|
||||
|
||||
default:
|
||||
memtype = SNES_MEMORY_WRAM;
|
||||
}
|
||||
|
||||
if (addr >= psnes_get_memory_size(memtype))
|
||||
{
|
||||
SSNES_ERR("Address out of bounds.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
unsigned mask_value = 0;
|
||||
if (bitmask)
|
||||
mask_value = strtoul((const char*)bitmask, NULL, 16);
|
||||
|
||||
strlcpy(gl_tracker_info[gl_tracker_info_cnt].id, (const char*)id, sizeof(gl_tracker_info[0].id));
|
||||
gl_tracker_info[gl_tracker_info_cnt].addr = addr;
|
||||
gl_tracker_info[gl_tracker_info_cnt].type = tracker_type;
|
||||
gl_tracker_info[gl_tracker_info_cnt].ram_type = ram_type;
|
||||
gl_tracker_info[gl_tracker_info_cnt].mask = mask_value;
|
||||
gl_tracker_info_cnt++;
|
||||
|
||||
if (id) xmlFree(id);
|
||||
if (semantic) xmlFree(semantic);
|
||||
if (wram) xmlFree(wram);
|
||||
if (input) xmlFree(input);
|
||||
if (apuram) xmlFree(apuram);
|
||||
if (vram) xmlFree(vram);
|
||||
if (oam) xmlFree(oam);
|
||||
if (cgram) xmlFree(cgram);
|
||||
if (bitmask) xmlFree(bitmask);
|
||||
return true;
|
||||
|
||||
error:
|
||||
if (id) xmlFree(id);
|
||||
if (semantic) xmlFree(semantic);
|
||||
if (wram) xmlFree(wram);
|
||||
if (input) xmlFree(input);
|
||||
if (apuram) xmlFree(apuram);
|
||||
if (vram) xmlFree(vram);
|
||||
if (oam) xmlFree(oam);
|
||||
if (cgram) xmlFree(cgram);
|
||||
if (bitmask) xmlFree(bitmask);
|
||||
return false;
|
||||
}
|
||||
|
||||
static unsigned get_xml_shaders(const char *path, struct shader_program *prog, size_t size)
|
||||
{
|
||||
LIBXML_TEST_VERSION;
|
||||
@@ -354,6 +664,32 @@ static unsigned get_xml_shaders(const char *path, struct shader_program *prog, s
|
||||
}
|
||||
num++;
|
||||
}
|
||||
else if (strcmp((const char*)cur->name, "texture") == 0)
|
||||
{
|
||||
if (!get_texture_image(path, cur))
|
||||
{
|
||||
SSNES_ERR("Texture image failed to load.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
else if (strcmp((const char*)cur->name, "import") == 0)
|
||||
{
|
||||
if (!get_import_value(cur))
|
||||
{
|
||||
SSNES_ERR("Import value is invalid.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
#ifdef HAVE_PYTHON
|
||||
else if (strcmp((const char*)cur->name, "script") == 0)
|
||||
{
|
||||
if (!get_script(path, cur))
|
||||
{
|
||||
SSNES_ERR("Script is invalid.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (num == 0)
|
||||
@@ -402,13 +738,42 @@ static void print_linker_log(GLuint obj)
|
||||
SSNES_LOG("Linker log: %s\n", info_log);
|
||||
}
|
||||
|
||||
static bool compile_shader(GLuint shader, const char *program)
|
||||
{
|
||||
pglShaderSource(shader, 1, &program, 0);
|
||||
pglCompileShader(shader);
|
||||
|
||||
GLint status;
|
||||
pglGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||
print_shader_log(shader);
|
||||
|
||||
return status == GL_TRUE;
|
||||
}
|
||||
|
||||
static bool link_program(GLuint prog)
|
||||
{
|
||||
pglLinkProgram(prog);
|
||||
|
||||
GLint status;
|
||||
pglGetProgramiv(prog, GL_LINK_STATUS, &status);
|
||||
print_linker_log(prog);
|
||||
|
||||
if (status == GL_TRUE)
|
||||
{
|
||||
pglUseProgram(prog);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool compile_programs(GLuint *gl_prog, struct shader_program *progs, size_t num)
|
||||
{
|
||||
for (unsigned i = 0; i < num; i++)
|
||||
{
|
||||
gl_prog[i] = pglCreateProgram();
|
||||
|
||||
if (!gl_check_error() || gl_prog[i] == 0)
|
||||
if (gl_prog[i] == 0)
|
||||
{
|
||||
SSNES_ERR("Failed to create GL program #%u.\n", i);
|
||||
return false;
|
||||
@@ -418,61 +783,49 @@ static bool compile_programs(GLuint *gl_prog, struct shader_program *progs, size
|
||||
{
|
||||
SSNES_LOG("Found GLSL vertex shader.\n");
|
||||
GLuint shader = pglCreateShader(GL_VERTEX_SHADER);
|
||||
pglShaderSource(shader, 1, (const char**)&progs[i].vertex, 0);
|
||||
pglCompileShader(shader);
|
||||
print_shader_log(shader);
|
||||
if (!compile_shader(shader, progs[i].vertex))
|
||||
{
|
||||
SSNES_ERR("Failed to compile vertex shader #%u\n", i);
|
||||
return false;
|
||||
}
|
||||
|
||||
pglAttachShader(gl_prog[i], shader);
|
||||
free(progs[i].vertex);
|
||||
}
|
||||
|
||||
if (!gl_check_error())
|
||||
{
|
||||
SSNES_ERR("Failed to compile vertex shader #%u\n", i);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (progs[i].fragment)
|
||||
{
|
||||
SSNES_LOG("Found GLSL fragment shader.\n");
|
||||
GLuint shader = pglCreateShader(GL_FRAGMENT_SHADER);
|
||||
pglShaderSource(shader, 1, (const char**)&progs[i].fragment, 0);
|
||||
pglCompileShader(shader);
|
||||
print_shader_log(shader);
|
||||
if (!compile_shader(shader, progs[i].fragment))
|
||||
{
|
||||
SSNES_ERR("Failed to compile fragment shader #%u\n", i);
|
||||
return false;
|
||||
}
|
||||
|
||||
pglAttachShader(gl_prog[i], shader);
|
||||
free(progs[i].fragment);
|
||||
}
|
||||
|
||||
if (!gl_check_error())
|
||||
{
|
||||
SSNES_ERR("Failed to compile fragment shader #%u\n", i);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (progs[i].vertex || progs[i].fragment)
|
||||
{
|
||||
SSNES_LOG("Linking GLSL program.\n");
|
||||
pglLinkProgram(gl_prog[i]);
|
||||
pglUseProgram(gl_prog[i]);
|
||||
print_linker_log(gl_prog[i]);
|
||||
if (!link_program(gl_prog[i]))
|
||||
{
|
||||
SSNES_ERR("Failed to link program #%u\n", i);
|
||||
return false;
|
||||
}
|
||||
|
||||
GLint location = pglGetUniformLocation(gl_prog[i], "rubyTexture");
|
||||
pglUniform1i(location, 0);
|
||||
pglUseProgram(0);
|
||||
}
|
||||
|
||||
if (!gl_check_error())
|
||||
{
|
||||
SSNES_ERR("Failed to link program #%u\n", i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define LOAD_GL_SYM(SYM) if (!(pgl##SYM)) pgl##SYM = SDL_GL_GetProcAddress("gl" #SYM)
|
||||
#define LOAD_GL_SYM(SYM) if (!pgl##SYM) { SDL_SYM_WRAP(pgl##SYM, "gl" #SYM) }
|
||||
|
||||
bool gl_glsl_init(const char *path)
|
||||
{
|
||||
@@ -497,6 +850,9 @@ bool gl_glsl_init(const char *path)
|
||||
LOAD_GL_SYM(GetProgramInfoLog);
|
||||
LOAD_GL_SYM(DeleteProgram);
|
||||
LOAD_GL_SYM(GetAttachedShaders);
|
||||
LOAD_GL_SYM(GetAttribLocation);
|
||||
LOAD_GL_SYM(EnableVertexAttribArray);
|
||||
LOAD_GL_SYM(VertexAttribPointer);
|
||||
#endif
|
||||
|
||||
SSNES_LOG("Checking GLSL shader support ...\n");
|
||||
@@ -508,7 +864,9 @@ bool gl_glsl_init(const char *path)
|
||||
&& pglDetachShader && pglLinkProgram && pglGetUniformLocation
|
||||
&& pglUniform1i && pglUniform2fv && pglUniform4fv
|
||||
&& pglGetShaderiv && pglGetShaderInfoLog && pglGetProgramiv && pglGetProgramInfoLog
|
||||
&& pglDeleteProgram && pglGetAttachedShaders;
|
||||
&& pglDeleteProgram && pglGetAttachedShaders
|
||||
&& pglGetAttribLocation && pglEnableVertexAttribArray
|
||||
&& pglVertexAttribPointer;
|
||||
#endif
|
||||
|
||||
if (!shader_support)
|
||||
@@ -557,8 +915,37 @@ bool gl_glsl_init(const char *path)
|
||||
}
|
||||
}
|
||||
|
||||
if (!gl_check_error())
|
||||
return false;
|
||||
//if (!gl_check_error())
|
||||
// SSNES_WARN("Detected GL error!\n");
|
||||
|
||||
if (gl_tracker_info_cnt > 0)
|
||||
{
|
||||
struct snes_tracker_info info = {
|
||||
.wram = psnes_get_memory_data(SNES_MEMORY_WRAM),
|
||||
.vram = psnes_get_memory_data(SNES_MEMORY_VRAM),
|
||||
.cgram = psnes_get_memory_data(SNES_MEMORY_CGRAM),
|
||||
.apuram = psnes_get_memory_data(SNES_MEMORY_APURAM),
|
||||
.oam = psnes_get_memory_data(SNES_MEMORY_OAM),
|
||||
.info = gl_tracker_info,
|
||||
.info_elem = gl_tracker_info_cnt,
|
||||
};
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
if (*gl_tracker_script)
|
||||
info.script = gl_tracker_script;
|
||||
else if (gl_script_program)
|
||||
info.script = (const char*)gl_script_program;
|
||||
else
|
||||
info.script = NULL;
|
||||
|
||||
info.script_class = *gl_tracker_script_class ? gl_tracker_script_class : NULL;
|
||||
info.script_is_file = *gl_tracker_script;
|
||||
#endif
|
||||
|
||||
gl_snes_tracker = snes_tracker_init(&info);
|
||||
if (!gl_snes_tracker)
|
||||
SSNES_WARN("Failed to init SNES tracker!\n");
|
||||
}
|
||||
|
||||
glsl_enable = true;
|
||||
gl_num_programs = num_progs;
|
||||
@@ -587,16 +974,38 @@ void gl_glsl_deinit(void)
|
||||
|
||||
pglDeleteProgram(gl_program[i]);
|
||||
}
|
||||
|
||||
glDeleteTextures(gl_teximage_cnt, gl_teximage);
|
||||
gl_teximage_cnt = 0;
|
||||
memset(gl_teximage_uniforms, 0, sizeof(gl_teximage_uniforms));
|
||||
}
|
||||
|
||||
memset(gl_program, 0, sizeof(gl_program));
|
||||
glsl_enable = false;
|
||||
active_index = 0;
|
||||
|
||||
gl_tracker_info_cnt = 0;
|
||||
memset(gl_tracker_info, 0, sizeof(gl_tracker_info));
|
||||
memset(gl_tracker_script, 0, sizeof(gl_tracker_script));
|
||||
memset(gl_tracker_script_class, 0, sizeof(gl_tracker_script_class));
|
||||
if (gl_script_program)
|
||||
{
|
||||
xmlFree(gl_script_program);
|
||||
gl_script_program = NULL;
|
||||
}
|
||||
if (gl_snes_tracker)
|
||||
{
|
||||
snes_tracker_free(gl_snes_tracker);
|
||||
gl_snes_tracker = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void gl_glsl_set_params(unsigned width, unsigned height,
|
||||
unsigned tex_width, unsigned tex_height,
|
||||
unsigned out_width, unsigned out_height)
|
||||
unsigned out_width, unsigned out_height,
|
||||
unsigned frame_count,
|
||||
const struct gl_tex_info *info,
|
||||
const struct gl_tex_info *fbo_info, unsigned fbo_info_cnt)
|
||||
{
|
||||
if (glsl_enable && gl_program[active_index] > 0)
|
||||
{
|
||||
@@ -613,6 +1022,107 @@ void gl_glsl_set_params(unsigned width, unsigned height,
|
||||
float textureSize[2] = {tex_width, tex_height};
|
||||
location = pglGetUniformLocation(gl_program[active_index], "rubyTextureSize");
|
||||
pglUniform2fv(location, 1, textureSize);
|
||||
|
||||
location = pglGetUniformLocation(gl_program[active_index], "rubyFrameCount");
|
||||
pglUniform1i(location, frame_count);
|
||||
|
||||
for (unsigned i = 0; i < gl_teximage_cnt; i++)
|
||||
{
|
||||
location = pglGetUniformLocation(gl_program[active_index], gl_teximage_uniforms[i]);
|
||||
pglUniform1i(location, i + 1);
|
||||
}
|
||||
|
||||
// Set original texture unless we're in first pass (pointless).
|
||||
if (active_index > 1)
|
||||
{
|
||||
// Bind original texture.
|
||||
pglActiveTexture(GL_TEXTURE0 + gl_teximage_cnt + 1);
|
||||
glBindTexture(GL_TEXTURE_2D, info->tex);
|
||||
|
||||
location = pglGetUniformLocation(gl_program[active_index], "rubyOrigTexture");
|
||||
pglUniform1i(location, gl_teximage_cnt + 1);
|
||||
|
||||
location = pglGetUniformLocation(gl_program[active_index], "rubyOrigTextureSize");
|
||||
pglUniform2fv(location, 1, info->tex_size);
|
||||
location = pglGetUniformLocation(gl_program[active_index], "rubyOrigInputSize");
|
||||
pglUniform2fv(location, 1, info->input_size);
|
||||
|
||||
// Pass texture coordinates.
|
||||
location = pglGetAttribLocation(gl_program[active_index], "rubyOrigTexCoord");
|
||||
if (location >= 0)
|
||||
{
|
||||
pglEnableVertexAttribArray(location);
|
||||
pglVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, 0, info->coord);
|
||||
}
|
||||
|
||||
GLuint base_tex = GL_TEXTURE0 + gl_teximage_cnt + 2;
|
||||
|
||||
// Bind new texture in the chain.
|
||||
if (fbo_info_cnt > 0)
|
||||
{
|
||||
pglActiveTexture(base_tex + fbo_info_cnt - 1);
|
||||
glBindTexture(GL_TEXTURE_2D, fbo_info[fbo_info_cnt - 1].tex);
|
||||
}
|
||||
|
||||
// Bind FBO textures.
|
||||
for (unsigned i = 0; i < fbo_info_cnt; i++)
|
||||
{
|
||||
char attrib_buf[64];
|
||||
|
||||
snprintf(attrib_buf, sizeof(attrib_buf), "rubyPass%uTexture", i + 1);
|
||||
location = pglGetUniformLocation(gl_program[active_index], attrib_buf);
|
||||
pglUniform1i(location, base_tex + i - GL_TEXTURE0);
|
||||
|
||||
snprintf(attrib_buf, sizeof(attrib_buf), "rubyPass%uTextureSize", i + 1);
|
||||
location = pglGetUniformLocation(gl_program[active_index], attrib_buf);
|
||||
pglUniform2fv(location, 1, fbo_info[i].tex_size);
|
||||
|
||||
snprintf(attrib_buf, sizeof(attrib_buf), "rubyPass%uInputSize", i + 1);
|
||||
location = pglGetUniformLocation(gl_program[active_index], attrib_buf);
|
||||
pglUniform2fv(location, 1, fbo_info[i].input_size);
|
||||
|
||||
snprintf(attrib_buf, sizeof(attrib_buf), "rubyPass%uTexCoord", i + 1);
|
||||
location = pglGetAttribLocation(gl_program[active_index], attrib_buf);
|
||||
if (location >= 0)
|
||||
{
|
||||
pglEnableVertexAttribArray(location);
|
||||
pglVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, 0, fbo_info[i].coord);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// First pass, so unbind everything to avoid collitions.
|
||||
pglActiveTexture(GL_TEXTURE0 + gl_teximage_cnt + 1);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
GLuint base_tex = GL_TEXTURE0 + gl_teximage_cnt + 2;
|
||||
// Unbind any lurking FBO passes.
|
||||
// Rendering to a texture that is bound to a texture unit
|
||||
// sounds very shaky ... ;)
|
||||
for (int i = 0; i < gl_num_programs; i++)
|
||||
{
|
||||
pglActiveTexture(GL_TEXTURE0 + base_tex + i);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
}
|
||||
|
||||
pglActiveTexture(GL_TEXTURE0);
|
||||
|
||||
if (gl_snes_tracker)
|
||||
{
|
||||
static struct snes_tracker_uniform info[MAX_VARIABLES];
|
||||
static unsigned cnt = 0;
|
||||
|
||||
if (active_index == 1)
|
||||
cnt = snes_get_uniform(gl_snes_tracker, info, MAX_VARIABLES, frame_count);
|
||||
|
||||
for (unsigned i = 0; i < cnt; i++)
|
||||
{
|
||||
location = pglGetUniformLocation(gl_program[active_index], info[i].id);
|
||||
pglUniform1i(location, info[i].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -30,7 +30,10 @@ void gl_glsl_set_proj_matrix(void);
|
||||
|
||||
void gl_glsl_set_params(unsigned width, unsigned height,
|
||||
unsigned tex_width, unsigned tex_height,
|
||||
unsigned out_width, unsigned out_height);
|
||||
unsigned out_width, unsigned out_height,
|
||||
unsigned frame_counter,
|
||||
const struct gl_tex_info *info,
|
||||
const struct gl_tex_info *fbo_info, unsigned fbo_info_cnt);
|
||||
|
||||
void gl_glsl_use(unsigned index);
|
||||
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "snes_state.h"
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include "strl.h"
|
||||
#include "general.h"
|
||||
#include <libsnes.hpp>
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
#include "py_state/py_state.h"
|
||||
#endif
|
||||
|
||||
struct snes_tracker_internal
|
||||
{
|
||||
char id[64];
|
||||
|
||||
bool is_input;
|
||||
unsigned input_slot;
|
||||
const uint16_t *input_ptr;
|
||||
const uint8_t *ptr;
|
||||
#ifdef HAVE_PYTHON
|
||||
py_state_t *py;
|
||||
#endif
|
||||
|
||||
uint32_t addr;
|
||||
unsigned mask;
|
||||
|
||||
enum snes_tracker_type type;
|
||||
|
||||
uint32_t prev[2];
|
||||
int frame_count;
|
||||
int frame_count_prev;
|
||||
uint32_t old_value;
|
||||
int transition_count;
|
||||
};
|
||||
|
||||
struct snes_tracker
|
||||
{
|
||||
struct snes_tracker_internal *info;
|
||||
unsigned info_elem;
|
||||
|
||||
uint16_t input_state[2];
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
py_state_t *py;
|
||||
#endif
|
||||
};
|
||||
|
||||
snes_tracker_t* snes_tracker_init(const struct snes_tracker_info *info)
|
||||
{
|
||||
snes_tracker_t *tracker = calloc(1, sizeof(*tracker));
|
||||
if (!tracker)
|
||||
return NULL;
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
if (info->script)
|
||||
{
|
||||
tracker->py = py_state_new(info->script, info->script_is_file, info->script_class ? info->script_class : "GameAware");
|
||||
if (!tracker->py)
|
||||
{
|
||||
free(tracker);
|
||||
SSNES_ERR("Failed to init Python script.\n");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
tracker->info = calloc(info->info_elem, sizeof(struct snes_tracker_internal));
|
||||
tracker->info_elem = info->info_elem;
|
||||
|
||||
for (unsigned i = 0; i < info->info_elem; i++)
|
||||
{
|
||||
strlcpy(tracker->info[i].id, info->info[i].id, sizeof(tracker->info[i].id));
|
||||
tracker->info[i].addr = info->info[i].addr;
|
||||
tracker->info[i].type = info->info[i].type;
|
||||
tracker->info[i].mask = (info->info[i].mask == 0) ? 0xffffffffu : info->info[i].mask;
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
if (info->info[i].type == SSNES_STATE_PYTHON)
|
||||
tracker->info[i].py = tracker->py;
|
||||
#endif
|
||||
|
||||
assert(info->wram && info->vram && info->cgram &&
|
||||
info->oam && info->apuram);
|
||||
|
||||
switch (info->info[i].ram_type)
|
||||
{
|
||||
case SSNES_STATE_WRAM:
|
||||
tracker->info[i].ptr = info->wram;
|
||||
break;
|
||||
case SSNES_STATE_APURAM:
|
||||
tracker->info[i].ptr = info->apuram;
|
||||
break;
|
||||
case SSNES_STATE_OAM:
|
||||
tracker->info[i].ptr = info->oam;
|
||||
break;
|
||||
case SSNES_STATE_CGRAM:
|
||||
tracker->info[i].ptr = info->cgram;
|
||||
break;
|
||||
case SSNES_STATE_VRAM:
|
||||
tracker->info[i].ptr = info->vram;
|
||||
break;
|
||||
case SSNES_STATE_INPUT_SLOT1:
|
||||
tracker->info[i].input_ptr = &tracker->input_state[0];
|
||||
tracker->info[i].input_slot = 0;
|
||||
tracker->info[i].is_input = true;
|
||||
break;
|
||||
case SSNES_STATE_INPUT_SLOT2:
|
||||
tracker->info[i].input_ptr = &tracker->input_state[1];
|
||||
tracker->info[i].input_slot = 1;
|
||||
tracker->info[i].is_input = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
tracker->info[i].ptr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
void snes_tracker_free(snes_tracker_t *tracker)
|
||||
{
|
||||
free(tracker->info);
|
||||
#ifdef HAVE_PYTHON
|
||||
py_state_free(tracker->py);
|
||||
#endif
|
||||
free(tracker);
|
||||
}
|
||||
|
||||
#define fetch() ((info->is_input ? info->input_ptr[info->input_slot] : info->ptr[info->addr]) & info->mask)
|
||||
|
||||
static void update_element(
|
||||
struct snes_tracker_uniform *uniform,
|
||||
struct snes_tracker_internal *info,
|
||||
unsigned frame_count)
|
||||
{
|
||||
uniform->id = info->id;
|
||||
|
||||
switch (info->type)
|
||||
{
|
||||
case SSNES_STATE_CAPTURE:
|
||||
uniform->value = fetch();
|
||||
break;
|
||||
|
||||
case SSNES_STATE_CAPTURE_PREV:
|
||||
if (info->prev[0] != fetch())
|
||||
{
|
||||
info->prev[1] = info->prev[0];
|
||||
info->prev[0] = fetch();
|
||||
}
|
||||
uniform->value = info->prev[1];
|
||||
break;
|
||||
|
||||
case SSNES_STATE_TRANSITION:
|
||||
if (info->old_value != fetch())
|
||||
{
|
||||
info->old_value = fetch();
|
||||
info->frame_count = frame_count;
|
||||
}
|
||||
uniform->value = info->frame_count;
|
||||
break;
|
||||
|
||||
case SSNES_STATE_TRANSITION_COUNT:
|
||||
if (info->old_value != fetch())
|
||||
{
|
||||
info->old_value = fetch();
|
||||
info->transition_count++;
|
||||
}
|
||||
uniform->value = info->transition_count;
|
||||
break;
|
||||
|
||||
case SSNES_STATE_TRANSITION_PREV:
|
||||
if (info->old_value != fetch())
|
||||
{
|
||||
info->old_value = fetch();
|
||||
info->frame_count_prev = info->frame_count;
|
||||
info->frame_count = frame_count;
|
||||
}
|
||||
uniform->value = info->frame_count_prev;
|
||||
break;
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
case SSNES_STATE_PYTHON:
|
||||
uniform->value = py_state_get(info->py, info->id, frame_count);
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#undef fetch
|
||||
|
||||
// Updates 16-bit input in same format as SNES itself.
|
||||
static void update_input(snes_tracker_t *tracker)
|
||||
{
|
||||
if (!driver.input_data)
|
||||
return;
|
||||
|
||||
static const unsigned buttons[] = {
|
||||
SNES_DEVICE_ID_JOYPAD_R,
|
||||
SNES_DEVICE_ID_JOYPAD_L,
|
||||
SNES_DEVICE_ID_JOYPAD_X,
|
||||
SNES_DEVICE_ID_JOYPAD_A,
|
||||
SNES_DEVICE_ID_JOYPAD_RIGHT,
|
||||
SNES_DEVICE_ID_JOYPAD_LEFT,
|
||||
SNES_DEVICE_ID_JOYPAD_DOWN,
|
||||
SNES_DEVICE_ID_JOYPAD_UP,
|
||||
SNES_DEVICE_ID_JOYPAD_START,
|
||||
SNES_DEVICE_ID_JOYPAD_SELECT,
|
||||
SNES_DEVICE_ID_JOYPAD_Y,
|
||||
SNES_DEVICE_ID_JOYPAD_B
|
||||
};
|
||||
|
||||
static const struct snes_keybind *binds[MAX_PLAYERS] = {
|
||||
g_settings.input.binds[0],
|
||||
g_settings.input.binds[1],
|
||||
g_settings.input.binds[2],
|
||||
g_settings.input.binds[3],
|
||||
g_settings.input.binds[4]
|
||||
};
|
||||
|
||||
uint16_t state[2] = {0};
|
||||
for (unsigned i = 4; i < 16; i++)
|
||||
{
|
||||
state[0] |= (driver.input->input_state(driver.input_data, binds, SNES_PORT_1, SNES_DEVICE_JOYPAD, 0, buttons[i - 4]) ? 1 : 0) << i;
|
||||
state[1] |= (driver.input->input_state(driver.input_data, binds, SNES_PORT_2, SNES_DEVICE_JOYPAD, 0, buttons[i - 4]) ? 1 : 0) << i;
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
tracker->input_state[i] = state[i];
|
||||
}
|
||||
|
||||
unsigned snes_get_uniform(snes_tracker_t *tracker, struct snes_tracker_uniform *uniforms, unsigned elem, unsigned frame_count)
|
||||
{
|
||||
unsigned elems = tracker->info_elem < elem ? tracker->info_elem : elem;
|
||||
|
||||
update_input(tracker);
|
||||
|
||||
for (unsigned i = 0; i < elems; i++)
|
||||
update_element(&uniforms[i], &tracker->info[i], frame_count);
|
||||
|
||||
return elems;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __SSNES_SNES_STATE_H
|
||||
#define __SSNES_SNES_STATE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
enum snes_tracker_type
|
||||
{
|
||||
SSNES_STATE_CAPTURE,
|
||||
SSNES_STATE_CAPTURE_PREV,
|
||||
SSNES_STATE_TRANSITION,
|
||||
SSNES_STATE_TRANSITION_COUNT,
|
||||
SSNES_STATE_TRANSITION_PREV,
|
||||
#ifdef HAVE_PYTHON
|
||||
SSNES_STATE_PYTHON
|
||||
#endif
|
||||
};
|
||||
|
||||
enum snes_ram_type
|
||||
{
|
||||
SSNES_STATE_WRAM,
|
||||
SSNES_STATE_APURAM,
|
||||
SSNES_STATE_OAM,
|
||||
SSNES_STATE_CGRAM,
|
||||
SSNES_STATE_VRAM,
|
||||
SSNES_STATE_INPUT_SLOT1,
|
||||
SSNES_STATE_INPUT_SLOT2
|
||||
};
|
||||
|
||||
struct snes_tracker_uniform_info
|
||||
{
|
||||
char id[64];
|
||||
uint32_t addr;
|
||||
enum snes_tracker_type type;
|
||||
enum snes_ram_type ram_type;
|
||||
unsigned mask;
|
||||
};
|
||||
|
||||
struct snes_tracker_info
|
||||
{
|
||||
const uint8_t *wram;
|
||||
const uint8_t *vram;
|
||||
const uint8_t *cgram;
|
||||
const uint8_t *oam;
|
||||
const uint8_t *apuram;
|
||||
|
||||
const struct snes_tracker_uniform_info *info;
|
||||
unsigned info_elem;
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
const char *script;
|
||||
const char *script_class;
|
||||
bool script_is_file;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct snes_tracker_uniform
|
||||
{
|
||||
const char *id;
|
||||
int value;
|
||||
};
|
||||
|
||||
typedef struct snes_tracker snes_tracker_t;
|
||||
|
||||
snes_tracker_t* snes_tracker_init(const struct snes_tracker_info *info);
|
||||
void snes_tracker_free(snes_tracker_t *tracker);
|
||||
|
||||
unsigned snes_get_uniform(snes_tracker_t *tracker, struct snes_tracker_uniform *uniforms, unsigned elem, unsigned frame_count);
|
||||
|
||||
#endif
|
||||
+7
-7
@@ -77,7 +77,7 @@ static void xv_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
xv_t *xv = data;
|
||||
Atom atom = XInternAtom(xv->display, "XV_SYNC_TO_VBLANK", true);
|
||||
if (atom != None && xv->port >= 0)
|
||||
if (atom != None && xv->port)
|
||||
XvSetPortAttribute(xv->display, xv->port, atom, !state);
|
||||
else
|
||||
SSNES_WARN("Failed to set SYNC_TO_VBLANK attribute.\n");
|
||||
@@ -177,7 +177,7 @@ static void xv_init_font(xv_t *xv, const char *font_path, unsigned font_size)
|
||||
{
|
||||
xv->font = font_renderer_new(font_path, font_size);
|
||||
if (!xv->font)
|
||||
SSNES_WARN("Failed to init font\n");
|
||||
SSNES_WARN("Failed to init font.\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -249,7 +249,7 @@ static void render32_yuy2(xv_t *xv, const void *input_, unsigned width, unsigned
|
||||
for (unsigned x = 0; x < width; x++)
|
||||
{
|
||||
uint32_t p = *input++;
|
||||
p = ((p >> 17) & 0x7c00) | ((p >> 14) & 0x03e0) | ((p >> 11) & 0x1f); // RGBA -> RGB15
|
||||
p = ((p >> 9) & 0x7c00) | ((p >> 6) & 0x03e0) | ((p >> 3) & 0x1f); // ARGB -> RGB15
|
||||
|
||||
uint8_t y0 = xv->ytable[p];
|
||||
uint8_t u = xv->utable[p];
|
||||
@@ -278,7 +278,7 @@ static void render32_uyvy(xv_t *xv, const void *input_, unsigned width, unsigned
|
||||
for (unsigned x = 0; x < width; x++)
|
||||
{
|
||||
uint32_t p = *input++;
|
||||
p = ((p >> 17) & 0x7c00) | ((p >> 14) & 0x03e0) | ((p >> 11) & 0x1f); // RGBA -> RGB15
|
||||
p = ((p >> 9) & 0x7c00) | ((p >> 6) & 0x03e0) | ((p >> 3) & 0x1f); // ARGB -> RGB15
|
||||
|
||||
uint8_t y0 = xv->ytable[p];
|
||||
uint8_t u = xv->utable[p];
|
||||
@@ -298,7 +298,7 @@ static void render32_uyvy(xv_t *xv, const void *input_, unsigned width, unsigned
|
||||
}
|
||||
|
||||
|
||||
static void* xv_init(video_info_t *video, const input_driver_t **input, void **input_data)
|
||||
static void* xv_init(const video_info_t *video, const input_driver_t **input, void **input_data)
|
||||
{
|
||||
xv_t *xv = calloc(1, sizeof(*xv));
|
||||
if (!xv)
|
||||
@@ -315,7 +315,7 @@ static void* xv_init(video_info_t *video, const input_driver_t **input, void **i
|
||||
xv->keep_aspect = video->force_aspect;
|
||||
|
||||
// Find an appropriate Xv port.
|
||||
xv->port = -1;
|
||||
xv->port = 0;
|
||||
XvAdaptorInfo *adaptor_info;
|
||||
unsigned adaptor_count = 0;
|
||||
XvQueryAdaptors(xv->display, DefaultRootWindow(xv->display), &adaptor_count, &adaptor_info);
|
||||
@@ -333,7 +333,7 @@ static void* xv_init(video_info_t *video, const input_driver_t **input, void **i
|
||||
}
|
||||
XvFreeAdaptorInfo(adaptor_info);
|
||||
|
||||
if (xv->port < 0)
|
||||
if (xv->port == 0)
|
||||
{
|
||||
SSNES_ERR("XVideo: Failed to find valid XvPort.\n");
|
||||
goto error;
|
||||
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "SDL.h"
|
||||
#include "SDL_syswm.h"
|
||||
#include <stdbool.h>
|
||||
#include "ssnes_dinput.h"
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
void sdl_dinput_free(sdl_dinput_t *di)
|
||||
{
|
||||
if (di)
|
||||
{
|
||||
for (unsigned i = 0; i < MAX_PLAYERS; i++)
|
||||
{
|
||||
if (di->joypad[i])
|
||||
{
|
||||
IDirectInputDevice8_Unacquire(di->joypad[i]);
|
||||
IDirectInputDevice8_Release(di->joypad[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (di->ctx)
|
||||
IDirectInput8_Release(di->ctx);
|
||||
|
||||
free(di);
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL CALLBACK enum_axes_cb(const DIDEVICEOBJECTINSTANCE *inst, void *p)
|
||||
{
|
||||
IDirectInputDevice8 *joypad = p;
|
||||
|
||||
DIPROPRANGE range;
|
||||
memset(&range, 0, sizeof(range));
|
||||
range.diph.dwSize = sizeof(DIPROPRANGE);
|
||||
range.diph.dwHeaderSize = sizeof(DIPROPHEADER);
|
||||
range.diph.dwHow = DIPH_BYID;
|
||||
range.diph.dwObj = inst->dwType;
|
||||
range.lMin = -32768;
|
||||
range.lMax = 32767;
|
||||
IDirectInputDevice8_SetProperty(joypad, DIPROP_RANGE, &range.diph);
|
||||
|
||||
return DIENUM_CONTINUE;
|
||||
}
|
||||
|
||||
static BOOL CALLBACK enum_joypad_cb(const DIDEVICEINSTANCE *inst, void *p)
|
||||
{
|
||||
sdl_dinput_t *di = p;
|
||||
|
||||
unsigned active = 0;
|
||||
unsigned n;
|
||||
for (n = 0; n < MAX_PLAYERS; n++)
|
||||
{
|
||||
if (!di->joypad[n] && g_settings.input.joypad_map[n] == di->joypad_cnt)
|
||||
break;
|
||||
|
||||
if (di->joypad[n])
|
||||
active++;
|
||||
}
|
||||
|
||||
if (active == MAX_PLAYERS) return DIENUM_STOP;
|
||||
|
||||
di->joypad_cnt++;
|
||||
if (n == MAX_PLAYERS)
|
||||
return DIENUM_CONTINUE;
|
||||
|
||||
if (FAILED(IDirectInput8_CreateDevice(di->ctx, &inst->guidInstance, &di->joypad[n], NULL)))
|
||||
return DIENUM_CONTINUE;
|
||||
|
||||
IDirectInputDevice8_SetDataFormat(di->joypad[n], &c_dfDIJoystick2);
|
||||
IDirectInputDevice8_SetCooperativeLevel(di->joypad[n], di->hWnd,
|
||||
DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
|
||||
|
||||
IDirectInputDevice8_EnumObjects(di->joypad[n], enum_axes_cb,
|
||||
di->joypad[n], DIDFT_ABSAXIS);
|
||||
|
||||
return DIENUM_CONTINUE;
|
||||
}
|
||||
|
||||
sdl_dinput_t* sdl_dinput_init(void)
|
||||
{
|
||||
sdl_dinput_t *di = calloc(1, sizeof(*di));
|
||||
if (!di)
|
||||
return NULL;
|
||||
|
||||
CoInitialize(NULL);
|
||||
|
||||
SDL_SysWMinfo info;
|
||||
if (!SDL_GetWMInfo(&info))
|
||||
{
|
||||
SSNES_ERR("Failed to get SysWM info.\n");
|
||||
goto error;
|
||||
}
|
||||
di->hWnd = info.window;
|
||||
|
||||
if (FAILED(DirectInput8Create(
|
||||
GetModuleHandle(NULL), DIRECTINPUT_VERSION, &IID_IDirectInput8,
|
||||
(void**)&di->ctx, NULL)))
|
||||
{
|
||||
SSNES_ERR("Failed to init DirectInput.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
SSNES_LOG("Enumerating DInput devices ...\n");
|
||||
IDirectInput8_EnumDevices(di->ctx, DI8DEVCLASS_GAMECTRL, enum_joypad_cb, di, DIEDFL_ATTACHEDONLY);
|
||||
SSNES_LOG("Done enumerating DInput devices ...\n");
|
||||
|
||||
return di;
|
||||
|
||||
error:
|
||||
sdl_dinput_free(di);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool dinput_joykey_pressed(sdl_dinput_t *di, unsigned port_num, uint16_t joykey)
|
||||
{
|
||||
if (joykey == NO_BTN)
|
||||
return false;
|
||||
|
||||
// Check hat.
|
||||
if (GET_HAT_DIR(joykey))
|
||||
{
|
||||
unsigned hat = GET_HAT(joykey);
|
||||
|
||||
unsigned elems = sizeof(di->joy_state[0].rgdwPOV) / sizeof(di->joy_state[0].rgdwPOV[0]);
|
||||
if (hat >= elems)
|
||||
return false;
|
||||
|
||||
unsigned pov = di->joy_state[port_num].rgdwPOV[hat];
|
||||
|
||||
// Magic numbers I'm not sure where originate from.
|
||||
if (pov < 36000)
|
||||
{
|
||||
switch (GET_HAT_DIR(joykey))
|
||||
{
|
||||
case HAT_UP_MASK:
|
||||
return (pov >= 31500) || (pov <= 4500);
|
||||
case HAT_RIGHT_MASK:
|
||||
return (pov >= 4500) && (pov <= 13500);
|
||||
case HAT_DOWN_MASK:
|
||||
return (pov >= 13500) && (pov <= 22500);
|
||||
case HAT_LEFT_MASK:
|
||||
return (pov >= 22500) && (pov <= 31500);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned elems = sizeof(di->joy_state[0].rgbButtons) / sizeof(di->joy_state[0].rgbButtons[0]);
|
||||
|
||||
if (joykey < elems)
|
||||
return di->joy_state[port_num].rgbButtons[joykey];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool dinput_joyaxis_pressed(sdl_dinput_t *di, unsigned port_num, uint32_t joyaxis)
|
||||
{
|
||||
if (joyaxis == AXIS_NONE)
|
||||
return false;
|
||||
|
||||
int min = -32678 * g_settings.input.axis_threshold;
|
||||
int max = 32677 * g_settings.input.axis_threshold;
|
||||
|
||||
switch (AXIS_NEG_GET(joyaxis))
|
||||
{
|
||||
case 0:
|
||||
return di->joy_state[port_num].lX <= min;
|
||||
case 1:
|
||||
return di->joy_state[port_num].lY <= min;
|
||||
case 2:
|
||||
return di->joy_state[port_num].lZ <= min;
|
||||
case 3:
|
||||
return di->joy_state[port_num].lRx <= min;
|
||||
case 4:
|
||||
return di->joy_state[port_num].lRy <= min;
|
||||
case 5:
|
||||
return di->joy_state[port_num].lRz <= min;
|
||||
}
|
||||
|
||||
switch (AXIS_POS_GET(joyaxis))
|
||||
{
|
||||
case 0:
|
||||
return di->joy_state[port_num].lX >= max;
|
||||
case 1:
|
||||
return di->joy_state[port_num].lY >= max;
|
||||
case 2:
|
||||
return di->joy_state[port_num].lZ >= max;
|
||||
case 3:
|
||||
return di->joy_state[port_num].lRx >= max;
|
||||
case 4:
|
||||
return di->joy_state[port_num].lRy >= max;
|
||||
case 5:
|
||||
return di->joy_state[port_num].lRz >= max;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sdl_dinput_pressed(sdl_dinput_t *di, unsigned port_num, const struct snes_keybind *key)
|
||||
{
|
||||
if (di->joypad[port_num] == NULL)
|
||||
return false;
|
||||
if (dinput_joykey_pressed(di, port_num, key->joykey))
|
||||
return true;
|
||||
if (dinput_joyaxis_pressed(di, port_num, key->joyaxis))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void sdl_dinput_poll(sdl_dinput_t *di)
|
||||
{
|
||||
memset(di->joy_state, 0, sizeof(di->joy_state));
|
||||
for (unsigned i = 0; i < MAX_PLAYERS; i++)
|
||||
{
|
||||
if (di->joypad[i])
|
||||
{
|
||||
if (FAILED(IDirectInputDevice8_Poll(di->joypad[i])))
|
||||
{
|
||||
IDirectInputDevice8_Acquire(di->joypad[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
IDirectInputDevice8_GetDeviceState(di->joypad[i], sizeof(DIJOYSTATE2), &di->joy_state[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+59
-58
@@ -25,12 +25,22 @@
|
||||
#include <libsnes.hpp>
|
||||
#include "ssnes_sdl_input.h"
|
||||
|
||||
|
||||
static void* sdl_input_init(void)
|
||||
{
|
||||
sdl_input_t *sdl = calloc(1, sizeof(*sdl));
|
||||
if (!sdl)
|
||||
return NULL;
|
||||
|
||||
#ifdef HAVE_DINPUT
|
||||
sdl->di = sdl_dinput_init();
|
||||
if (!sdl->di)
|
||||
{
|
||||
free(sdl);
|
||||
SSNES_ERR("Failed to init SDL/DInput.\n");
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
if (SDL_Init(SDL_INIT_JOYSTICK) < 0)
|
||||
return NULL;
|
||||
|
||||
@@ -60,9 +70,9 @@ static void* sdl_input_init(void)
|
||||
sdl->num_hats[i] = SDL_JoystickNumHats(sdl->joysticks[i]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
sdl->use_keyboard = true;
|
||||
|
||||
return sdl;
|
||||
}
|
||||
|
||||
@@ -78,8 +88,12 @@ static bool sdl_key_pressed(int key)
|
||||
return keymap[key];
|
||||
}
|
||||
|
||||
#ifndef HAVE_DINPUT
|
||||
static bool sdl_joykey_pressed(sdl_input_t *sdl, int port_num, uint16_t joykey)
|
||||
{
|
||||
if (joykey == NO_BTN)
|
||||
return false;
|
||||
|
||||
// Check hat.
|
||||
if (GET_HAT_DIR(joykey))
|
||||
{
|
||||
@@ -120,37 +134,43 @@ static bool sdl_joykey_pressed(sdl_input_t *sdl, int port_num, uint16_t joykey)
|
||||
|
||||
static bool sdl_axis_pressed(sdl_input_t *sdl, int port_num, uint32_t joyaxis)
|
||||
{
|
||||
if (joyaxis != AXIS_NONE)
|
||||
if (joyaxis == AXIS_NONE)
|
||||
return false;
|
||||
|
||||
if (AXIS_NEG_GET(joyaxis) < sdl->num_axes[port_num])
|
||||
{
|
||||
if (AXIS_NEG_GET(joyaxis) < sdl->num_axes[port_num])
|
||||
{
|
||||
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_NEG_GET(joyaxis));
|
||||
float scaled = (float)val / 0x8000;
|
||||
if (scaled < -g_settings.input.axis_threshold)
|
||||
return true;
|
||||
}
|
||||
if (AXIS_POS_GET(joyaxis) < sdl->num_axes[port_num])
|
||||
{
|
||||
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_POS_GET(joyaxis));
|
||||
float scaled = (float)val / 0x8000;
|
||||
if (scaled > g_settings.input.axis_threshold)
|
||||
return true;
|
||||
}
|
||||
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_NEG_GET(joyaxis));
|
||||
float scaled = (float)val / 0x8000;
|
||||
if (scaled < -g_settings.input.axis_threshold)
|
||||
return true;
|
||||
}
|
||||
if (AXIS_POS_GET(joyaxis) < sdl->num_axes[port_num])
|
||||
{
|
||||
Sint16 val = SDL_JoystickGetAxis(sdl->joysticks[port_num], AXIS_POS_GET(joyaxis));
|
||||
float scaled = (float)val / 0x8000;
|
||||
if (scaled > g_settings.input.axis_threshold)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool sdl_is_pressed(sdl_input_t *sdl, int port_num, const struct snes_keybind *key)
|
||||
static bool sdl_is_pressed(sdl_input_t *sdl, unsigned port_num, const struct snes_keybind *key)
|
||||
{
|
||||
if (sdl->use_keyboard && sdl_key_pressed(key->key))
|
||||
return true;
|
||||
|
||||
#ifdef HAVE_DINPUT
|
||||
return sdl_dinput_pressed(sdl->di, port_num, key);
|
||||
#else
|
||||
if (sdl->joysticks[port_num] == NULL)
|
||||
return false;
|
||||
if (sdl_joykey_pressed(sdl, port_num, key->joykey))
|
||||
return true;
|
||||
if (sdl_axis_pressed(sdl, port_num, key->joyaxis))
|
||||
return true;
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -159,7 +179,7 @@ static bool sdl_bind_button_pressed(void *data, int key)
|
||||
{
|
||||
// Only let player 1 use special binds called from main loop.
|
||||
const struct snes_keybind *binds = g_settings.input.binds[0];
|
||||
for (int i = 0; binds[i].id != -1; i++)
|
||||
for (unsigned i = 0; binds[i].id != -1; i++)
|
||||
{
|
||||
if (binds[i].id == key)
|
||||
return sdl_is_pressed(data, 0, &binds[i]);
|
||||
@@ -168,23 +188,21 @@ static bool sdl_bind_button_pressed(void *data, int key)
|
||||
}
|
||||
|
||||
static int16_t sdl_joypad_device_state(sdl_input_t *sdl, const struct snes_keybind **binds,
|
||||
int port_num, unsigned device, unsigned index, unsigned id)
|
||||
unsigned port_num, int id)
|
||||
{
|
||||
const struct snes_keybind *snes_keybinds = binds[port_num];
|
||||
|
||||
for (int i = 0; snes_keybinds[i].id != -1; i++)
|
||||
for (unsigned i = 0; snes_keybinds[i].id != -1; i++)
|
||||
{
|
||||
if (snes_keybinds[i].id == (int)id)
|
||||
return sdl_is_pressed(sdl, port_num, &snes_keybinds[i]);
|
||||
if (snes_keybinds[i].id == id)
|
||||
return sdl_is_pressed(sdl, port_num, &snes_keybinds[i]) ? 1 : 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int16_t sdl_mouse_device_state(sdl_input_t *sdl, bool port, unsigned id)
|
||||
static int16_t sdl_mouse_device_state(sdl_input_t *sdl, unsigned id)
|
||||
{
|
||||
// Might implement support for joypad mapping later.
|
||||
(void)port;
|
||||
switch (id)
|
||||
{
|
||||
case SNES_DEVICE_ID_MOUSE_LEFT:
|
||||
@@ -248,11 +266,11 @@ static int16_t sdl_input_state(void *data, const struct snes_keybind **binds, bo
|
||||
switch (device)
|
||||
{
|
||||
case SNES_DEVICE_JOYPAD:
|
||||
return sdl_joypad_device_state(data, binds, (port == SNES_PORT_1) ? 0 : 1, device, index, id);
|
||||
return sdl_joypad_device_state(data, binds, (port == SNES_PORT_1) ? 0 : 1, id);
|
||||
case SNES_DEVICE_MULTITAP:
|
||||
return sdl_joypad_device_state(data, binds, (port == SNES_PORT_2) ? 1 + index : 0, device, index, id);
|
||||
return sdl_joypad_device_state(data, binds, (port == SNES_PORT_2) ? 1 + index : 0, id);
|
||||
case SNES_DEVICE_MOUSE:
|
||||
return sdl_mouse_device_state(data, port, id);
|
||||
return sdl_mouse_device_state(data, id);
|
||||
case SNES_DEVICE_SUPER_SCOPE:
|
||||
return sdl_scope_device_state(data, id);
|
||||
case SNES_DEVICE_JUSTIFIER:
|
||||
@@ -273,14 +291,19 @@ static void sdl_input_free(void *data)
|
||||
while (SDL_PollEvent(&event));
|
||||
|
||||
sdl_input_t *sdl = data;
|
||||
|
||||
#ifdef HAVE_DINPUT
|
||||
sdl_dinput_free(sdl->di);
|
||||
#else
|
||||
for (int i = 0; i < MAX_PLAYERS; i++)
|
||||
{
|
||||
if (sdl->joysticks[i])
|
||||
SDL_JoystickClose(sdl->joysticks[i]);
|
||||
}
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
#endif
|
||||
|
||||
free(data);
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,37 +321,15 @@ static void sdl_poll_mouse(sdl_input_t *sdl)
|
||||
static void sdl_input_poll(void *data)
|
||||
{
|
||||
SDL_PumpEvents();
|
||||
SDL_Event event;
|
||||
SDL_JoystickUpdate();
|
||||
sdl_poll_mouse(data);
|
||||
|
||||
sdl_input_t *sdl = data;
|
||||
// Search for events...
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
if (sdl->quitting)
|
||||
{
|
||||
*sdl->quitting = true;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_VIDEORESIZE:
|
||||
if (sdl->should_resize)
|
||||
{
|
||||
*sdl->new_width = event.resize.w;
|
||||
*sdl->new_height = event.resize.h;
|
||||
*sdl->should_resize = true;
|
||||
}
|
||||
break;
|
||||
#ifdef HAVE_DINPUT
|
||||
sdl_dinput_poll(sdl->di);
|
||||
#else
|
||||
SDL_JoystickUpdate();
|
||||
#endif
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
sdl_poll_mouse(sdl);
|
||||
}
|
||||
|
||||
const input_driver_t input_sdl = {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __SSNES_DINPUT_H
|
||||
#define __SSNES_DINPUT_H
|
||||
|
||||
#undef DIRECTINPUT_VERSION
|
||||
#define DIRECTINPUT_VERSION 0x0800
|
||||
|
||||
#include <dinput.h>
|
||||
#include <stdbool.h>
|
||||
#include "general.h"
|
||||
|
||||
// Piggyback joypad driver for SDL.
|
||||
|
||||
typedef struct sdl_dinput
|
||||
{
|
||||
HWND hWnd;
|
||||
IDirectInput8 *ctx;
|
||||
IDirectInputDevice8 *joypad[MAX_PLAYERS];
|
||||
unsigned joypad_cnt;
|
||||
DIJOYSTATE2 joy_state[MAX_PLAYERS];
|
||||
} sdl_dinput_t;
|
||||
|
||||
sdl_dinput_t *sdl_dinput_init(void);
|
||||
void sdl_dinput_free(sdl_dinput_t *di);
|
||||
|
||||
bool sdl_dinput_pressed(sdl_dinput_t *di, unsigned port_num,
|
||||
const struct snes_keybind *key);
|
||||
|
||||
void sdl_dinput_poll(sdl_dinput_t *di);
|
||||
|
||||
|
||||
#endif
|
||||
+10
-5
@@ -20,20 +20,25 @@
|
||||
|
||||
#include "SDL.h"
|
||||
#include "general.h"
|
||||
|
||||
#ifdef HAVE_DINPUT
|
||||
#include "ssnes_dinput.h"
|
||||
#endif
|
||||
|
||||
typedef struct sdl_input
|
||||
{
|
||||
#ifdef HAVE_DINPUT
|
||||
sdl_dinput_t *di;
|
||||
#else
|
||||
SDL_Joystick *joysticks[MAX_PLAYERS];
|
||||
unsigned num_axes[MAX_PLAYERS];
|
||||
unsigned num_buttons[MAX_PLAYERS];
|
||||
unsigned num_hats[MAX_PLAYERS];
|
||||
unsigned num_joysticks;
|
||||
#endif
|
||||
|
||||
bool use_keyboard;
|
||||
|
||||
// A video driver could pre-init with the SDL driver and have it handle resizing events...
|
||||
bool *quitting;
|
||||
bool *should_resize;
|
||||
unsigned *new_width;
|
||||
unsigned *new_height;
|
||||
int16_t mouse_x, mouse_y;
|
||||
int16_t mouse_l, mouse_r, mouse_m;
|
||||
} sdl_input_t;
|
||||
|
||||
@@ -59,6 +59,33 @@ static const struct key_bind lut_binds[] = {
|
||||
{ XK_Escape, SDLK_ESCAPE },
|
||||
{ XK_KP_Add, SDLK_KP_PLUS },
|
||||
{ XK_KP_Subtract, SDLK_KP_MINUS },
|
||||
{ XK_BackSpace, SDLK_BACKSPACE },
|
||||
{ XK_KP_Enter, SDLK_KP_ENTER },
|
||||
{ XK_KP_Add, SDLK_KP_PLUS },
|
||||
{ XK_KP_Subtract, SDLK_KP_MINUS },
|
||||
{ XK_KP_Multiply, SDLK_KP_MULTIPLY },
|
||||
{ XK_KP_Divide, SDLK_KP_DIVIDE },
|
||||
{ XK_grave, SDLK_BACKQUOTE },
|
||||
{ XK_KP_0, SDLK_KP0 },
|
||||
{ XK_KP_1, SDLK_KP1 },
|
||||
{ XK_KP_2, SDLK_KP2 },
|
||||
{ XK_KP_3, SDLK_KP3 },
|
||||
{ XK_KP_4, SDLK_KP4 },
|
||||
{ XK_KP_5, SDLK_KP5 },
|
||||
{ XK_KP_6, SDLK_KP6 },
|
||||
{ XK_KP_7, SDLK_KP7 },
|
||||
{ XK_KP_8, SDLK_KP8 },
|
||||
{ XK_KP_9, SDLK_KP9 },
|
||||
{ XK_0, SDLK_0 },
|
||||
{ XK_1, SDLK_1 },
|
||||
{ XK_2, SDLK_2 },
|
||||
{ XK_3, SDLK_3 },
|
||||
{ XK_4, SDLK_4 },
|
||||
{ XK_5, SDLK_5 },
|
||||
{ XK_6, SDLK_6 },
|
||||
{ XK_7, SDLK_7 },
|
||||
{ XK_8, SDLK_8 },
|
||||
{ XK_9, SDLK_9 },
|
||||
{ XK_F1, SDLK_F1 },
|
||||
{ XK_F2, SDLK_F2 },
|
||||
{ XK_F3, SDLK_F3 },
|
||||
|
||||
+5
-2
@@ -15,7 +15,7 @@ if [ $HAVE_DYNAMIC != yes ]; then
|
||||
add_define_make libsnes $LIBSNES
|
||||
fi
|
||||
|
||||
check_lib FILTER -ldl dlopen
|
||||
check_lib DYLIB -ldl dlopen
|
||||
check_lib NETPLAY -lc socket
|
||||
|
||||
check_lib ALSA -lasound snd_pcm_open
|
||||
@@ -43,6 +43,7 @@ check_critical SDL "Cannot find SDL library."
|
||||
|
||||
check_lib CG -lCg cgCreateContext
|
||||
check_pkgconf XML libxml-2.0
|
||||
check_pkgconf SDL_IMAGE SDL_image
|
||||
|
||||
if [ $HAVE_FFMPEG != no ]; then
|
||||
check_pkgconf AVCODEC libavcodec
|
||||
@@ -63,8 +64,10 @@ check_lib XVIDEO -lXv XvShmCreateImage
|
||||
|
||||
check_lib STRL -lc strlcpy
|
||||
|
||||
check_pkgconf PYTHON python3
|
||||
|
||||
# Creates config.mk and config.h.
|
||||
VARS="ALSA OSS AL RSOUND ROAR JACK PULSE SDL FILTER CG XML DYNAMIC FFMPEG AVCODEC AVFORMAT AVCORE AVUTIL SWSCALE SRC CONFIGFILE FREETYPE XVIDEO NETPLAY FBO STRL"
|
||||
VARS="ALSA OSS AL RSOUND ROAR JACK PULSE SDL DYLIB CG XML SDL_IMAGE DYNAMIC FFMPEG AVCODEC AVFORMAT AVCORE AVUTIL SWSCALE SRC CONFIGFILE FREETYPE XVIDEO NETPLAY FBO STRL PYTHON"
|
||||
create_config_make config.mk $VARS
|
||||
create_config_header config.h $VARS
|
||||
|
||||
+4
-2
@@ -1,7 +1,7 @@
|
||||
. qb/qb.params.sh
|
||||
|
||||
PACKAGE_NAME=ssnes
|
||||
PACKAGE_VERSION=0.4
|
||||
PACKAGE_VERSION=0.5.2
|
||||
|
||||
# Adds a command line opt to ./configure --help
|
||||
# $1: Variable (HAVE_ALSA, HAVE_OSS, etc)
|
||||
@@ -10,7 +10,7 @@ PACKAGE_VERSION=0.4
|
||||
add_command_line_enable DYNAMIC "Enable dynamic loading of libsnes library." no
|
||||
add_command_line_string LIBSNES "libsnes library used" "-lsnes"
|
||||
add_command_line_enable FFMPEG "Enable FFmpeg recording support" no
|
||||
add_command_line_enable FILTER "Enable CPU filter support" auto
|
||||
add_command_line_enable DYLIB "Enable dynamic loading support" auto
|
||||
add_command_line_enable NETPLAY "Enable netplay support" auto
|
||||
add_command_line_enable SRC "Enable libsamplerate support" no
|
||||
add_command_line_enable CONFIGFILE "Disable support for config file" yes
|
||||
@@ -26,3 +26,5 @@ add_command_line_enable JACK "Enable JACK support" auto
|
||||
add_command_line_enable PULSE "Enable PulseAudio support" auto
|
||||
add_command_line_enable FREETYPE "Enable FreeType support" auto
|
||||
add_command_line_enable XVIDEO "Enable XVideo support" auto
|
||||
add_command_line_enable SDL_IMAGE "Enable SDL_image support" auto
|
||||
add_command_line_enable PYTHON "Enable Python 3 support for shaders" no
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "screenshot.h"
|
||||
#include "strl.h"
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "general.h"
|
||||
|
||||
// Simple 24bpp .BMP writer.
|
||||
|
||||
static void write_header(FILE *file, unsigned width, unsigned height)
|
||||
{
|
||||
unsigned line_size = (width * 3 + 3) & ~3;
|
||||
unsigned size = line_size * height + 54;
|
||||
unsigned size_array = line_size * height;
|
||||
|
||||
// Generic BMP stuff.
|
||||
const uint8_t header[] = {
|
||||
'B', 'M',
|
||||
(size >> 0) & 0xff, (size >> 8) & 0xff, (size >> 16) & 0xff, (size >> 24) & 0xff,
|
||||
0, 0, 0, 0,
|
||||
54, 0, 0, 0,
|
||||
40, 0, 0, 0,
|
||||
(width >> 0) & 0xff, (width >> 8) & 0xff, (width >> 16) & 0xff, (width >> 24) & 0xff,
|
||||
(height >> 0) & 0xff, (height >> 8) & 0xff, (height >> 16) & 0xff, (height >> 24) & 0xff,
|
||||
1, 0,
|
||||
24, 0,
|
||||
0, 0, 0, 0,
|
||||
(size_array >> 0) & 0xff, (size_array >> 8) & 0xff, (size_array >> 16) & 0xff, (size_array >> 24) & 0xff,
|
||||
19, 11, 0, 0,
|
||||
19, 11, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0
|
||||
};
|
||||
|
||||
fwrite(header, 1, sizeof(header), file);
|
||||
}
|
||||
|
||||
static void dump_content(FILE *file, const uint16_t *frame, unsigned width, unsigned height, unsigned pitch)
|
||||
{
|
||||
pitch >>= 1;
|
||||
unsigned line_size = (width * 3 + 3) & ~3;
|
||||
uint8_t line[line_size];
|
||||
memset(line, 0, sizeof(line));
|
||||
|
||||
// BMP likes reverse ordering for some reason :v
|
||||
for (int j = height - 1; j >= 0; j--)
|
||||
{
|
||||
uint8_t *dst = line;
|
||||
const uint16_t *src = frame + j * pitch;
|
||||
for (unsigned i = 0; i < width; i++)
|
||||
{
|
||||
uint16_t pixel = *src++;
|
||||
uint8_t b = (pixel & 0x1f) << 3;
|
||||
uint8_t g = (pixel & 0x03e0) >> 2;
|
||||
uint8_t r = (pixel & 0x7c00) >> 7;
|
||||
*dst++ = b;
|
||||
*dst++ = g;
|
||||
*dst++ = r;
|
||||
}
|
||||
|
||||
fwrite(line, 1, sizeof(line), file);
|
||||
}
|
||||
}
|
||||
|
||||
bool screenshot_dump(const char *folder, const uint16_t *frame,
|
||||
unsigned width, unsigned height, unsigned pitch)
|
||||
{
|
||||
time_t cur_time;
|
||||
time(&cur_time);
|
||||
|
||||
char timefmt[128];
|
||||
strftime(timefmt, sizeof(timefmt), "SSNES-%m%d-%H%M%S.bmp", localtime(&cur_time));
|
||||
|
||||
char filename[256];
|
||||
strlcpy(filename, folder, sizeof(filename));
|
||||
strlcat(filename, "/", sizeof(filename));
|
||||
strlcat(filename, timefmt, sizeof(filename));
|
||||
|
||||
FILE *file = fopen(filename, "wb");
|
||||
if (!file)
|
||||
{
|
||||
SSNES_ERR("Failed to open file \"%s\" for screenshot.\n", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
write_header(file, width, height);
|
||||
dump_content(file, frame, width, height, pitch);
|
||||
|
||||
fclose(file);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __SSNES_SCREENSHOT_H
|
||||
#define __SSNES_SCREENSHOT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
bool screenshot_dump(const char *folder, const uint16_t *frame,
|
||||
unsigned width, unsigned height, unsigned pitch);
|
||||
|
||||
#endif
|
||||
+92
-12
@@ -47,6 +47,12 @@ static void set_defaults(void)
|
||||
case VIDEO_XVIDEO:
|
||||
def_video = "xvideo";
|
||||
break;
|
||||
case VIDEO_SDL:
|
||||
def_video = "sdl";
|
||||
break;
|
||||
case VIDEO_EXT:
|
||||
def_video = "ext";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -77,6 +83,9 @@ static void set_defaults(void)
|
||||
case AUDIO_PULSE:
|
||||
def_audio = "pulse";
|
||||
break;
|
||||
case AUDIO_EXT:
|
||||
def_audio = "ext";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -105,9 +114,11 @@ static void set_defaults(void)
|
||||
g_settings.video.fullscreen = fullscreen;
|
||||
g_settings.video.fullscreen_x = fullscreen_x;
|
||||
g_settings.video.fullscreen_y = fullscreen_y;
|
||||
g_settings.video.force_16bit = force_16bit;
|
||||
g_settings.video.vsync = vsync;
|
||||
g_settings.video.smooth = video_smooth;
|
||||
g_settings.video.force_aspect = force_aspect;
|
||||
g_settings.video.crop_overscan = crop_overscan;
|
||||
g_settings.video.aspect_ratio = SNES_ASPECT_RATIO;
|
||||
g_settings.video.shader_type = SSNES_SHADER_AUTO;
|
||||
|
||||
@@ -144,15 +155,10 @@ static void set_defaults(void)
|
||||
g_settings.autosave_interval = autosave_interval;
|
||||
|
||||
assert(sizeof(g_settings.input.binds[0]) >= sizeof(snes_keybinds_1));
|
||||
assert(sizeof(g_settings.input.binds[1]) >= sizeof(snes_keybinds_2));
|
||||
assert(sizeof(g_settings.input.binds[2]) >= sizeof(snes_keybinds_3));
|
||||
assert(sizeof(g_settings.input.binds[3]) >= sizeof(snes_keybinds_4));
|
||||
assert(sizeof(g_settings.input.binds[4]) >= sizeof(snes_keybinds_5));
|
||||
assert(sizeof(g_settings.input.binds[1]) >= sizeof(snes_keybinds_rest));
|
||||
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));
|
||||
memcpy(g_settings.input.binds[2], snes_keybinds_3, sizeof(snes_keybinds_3));
|
||||
memcpy(g_settings.input.binds[3], snes_keybinds_4, sizeof(snes_keybinds_4));
|
||||
memcpy(g_settings.input.binds[4], snes_keybinds_5, sizeof(snes_keybinds_5));
|
||||
for (unsigned i = 1; i < 5; i++)
|
||||
memcpy(g_settings.input.binds[i], snes_keybinds_rest, sizeof(snes_keybinds_rest));
|
||||
|
||||
g_settings.input.axis_threshold = AXIS_THRESHOLD;
|
||||
g_settings.input.netplay_client_swap_input = netplay_client_swap_input;
|
||||
@@ -175,6 +181,7 @@ void parse_config(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
static config_file_t *open_default_config_file(void)
|
||||
{
|
||||
config_file_t *conf = NULL;
|
||||
@@ -206,7 +213,7 @@ static config_file_t *open_default_config_file(void)
|
||||
#else
|
||||
const char *xdg = getenv("XDG_CONFIG_HOME");
|
||||
if (!xdg)
|
||||
SSNES_WARN("XDG_CONFIG_HOME is not defined. Will look for config in $HOME/.ssnesrc ...\n");
|
||||
SSNES_WARN("XDG_CONFIG_HOME is not defined. Will look for config in $HOME/.ssnes.cfg ...\n");
|
||||
|
||||
const char *home = getenv("HOME");
|
||||
if (xdg)
|
||||
@@ -230,6 +237,7 @@ static config_file_t *open_default_config_file(void)
|
||||
|
||||
return conf;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Macros to ease config getting.
|
||||
#define CONFIG_GET_BOOL(var, key) if (config_get_bool(conf, key, &tmp_bool)) \
|
||||
@@ -282,9 +290,11 @@ static void parse_config_file(void)
|
||||
CONFIG_GET_INT(video.fullscreen_x, "video_fullscreen_x");
|
||||
CONFIG_GET_INT(video.fullscreen_y, "video_fullscreen_y");
|
||||
CONFIG_GET_BOOL(video.fullscreen, "video_fullscreen");
|
||||
CONFIG_GET_BOOL(video.force_16bit, "video_force_16bit");
|
||||
CONFIG_GET_BOOL(video.vsync, "video_vsync");
|
||||
CONFIG_GET_BOOL(video.smooth, "video_smooth");
|
||||
CONFIG_GET_BOOL(video.force_aspect, "video_force_aspect");
|
||||
CONFIG_GET_BOOL(video.crop_overscan, "video_crop_overscan");
|
||||
CONFIG_GET_DOUBLE(video.aspect_ratio, "video_aspect_ratio");
|
||||
|
||||
CONFIG_GET_STRING(video.cg_shader_path, "video_cg_shader");
|
||||
@@ -302,8 +312,10 @@ static void parse_config_file(void)
|
||||
CONFIG_GET_DOUBLE(video.msg_pos_y, "video_message_pos_y");
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FILTER
|
||||
#ifdef HAVE_DYLIB
|
||||
CONFIG_GET_STRING(video.filter_path, "video_filter");
|
||||
CONFIG_GET_STRING(video.external_driver, "video_external_driver");
|
||||
CONFIG_GET_STRING(audio.external_driver, "audio_external_driver");
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_CG) || defined(HAVE_XML)
|
||||
@@ -356,8 +368,10 @@ static void parse_config_file(void)
|
||||
|
||||
CONFIG_GET_STRING(video.driver, "video_driver");
|
||||
CONFIG_GET_STRING(audio.driver, "audio_driver");
|
||||
CONFIG_GET_STRING(audio.dsp_plugin, "audio_dsp_plugin");
|
||||
CONFIG_GET_STRING(input.driver, "input_driver");
|
||||
CONFIG_GET_STRING(libsnes, "libsnes_path");
|
||||
CONFIG_GET_STRING(screenshot_directory, "screenshot_directory");
|
||||
|
||||
CONFIG_GET_BOOL(rewind_enable, "rewind_enable");
|
||||
|
||||
@@ -369,6 +383,8 @@ static void parse_config_file(void)
|
||||
CONFIG_GET_BOOL(pause_nonactive, "pause_nonactive");
|
||||
CONFIG_GET_INT(autosave_interval, "autosave_interval");
|
||||
|
||||
CONFIG_GET_STRING(cheat_database, "cheat_database_path");
|
||||
|
||||
read_keybinds(conf);
|
||||
|
||||
config_file_free(conf);
|
||||
@@ -400,6 +416,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(player1_up, SNES_DEVICE_ID_JOYPAD_UP)
|
||||
DECLARE_BIND(player1_down, SNES_DEVICE_ID_JOYPAD_DOWN)
|
||||
DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY)
|
||||
DECLARE_BIND(hold_fast_forward, SSNES_FAST_FORWARD_HOLD_KEY)
|
||||
DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY)
|
||||
DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY)
|
||||
DECLARE_BIND(state_slot_increase, SSNES_STATE_SLOT_PLUS)
|
||||
@@ -414,6 +431,11 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(reset, SSNES_RESET)
|
||||
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
|
||||
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
|
||||
DECLARE_BIND(cheat_index_plus, SSNES_CHEAT_INDEX_PLUS)
|
||||
DECLARE_BIND(cheat_index_minus, SSNES_CHEAT_INDEX_MINUS)
|
||||
DECLARE_BIND(cheat_toggle, SSNES_CHEAT_TOGGLE)
|
||||
DECLARE_BIND(screenshot, SSNES_SCREENSHOT)
|
||||
DECLARE_BIND(dsp_config, SSNES_DSP_CONFIG)
|
||||
},
|
||||
{
|
||||
DECLARE_BIND(player2_a, SNES_DEVICE_ID_JOYPAD_A)
|
||||
@@ -429,6 +451,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(player2_up, SNES_DEVICE_ID_JOYPAD_UP)
|
||||
DECLARE_BIND(player2_down, SNES_DEVICE_ID_JOYPAD_DOWN)
|
||||
DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY)
|
||||
DECLARE_BIND(hold_fast_forward, SSNES_FAST_FORWARD_HOLD_KEY)
|
||||
DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY)
|
||||
DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY)
|
||||
DECLARE_BIND(state_slot_increase, SSNES_STATE_SLOT_PLUS)
|
||||
@@ -443,6 +466,11 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(reset, SSNES_RESET)
|
||||
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
|
||||
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
|
||||
DECLARE_BIND(cheat_index_plus, SSNES_CHEAT_INDEX_PLUS)
|
||||
DECLARE_BIND(cheat_index_minus, SSNES_CHEAT_INDEX_MINUS)
|
||||
DECLARE_BIND(cheat_toggle, SSNES_CHEAT_TOGGLE)
|
||||
DECLARE_BIND(screenshot, SSNES_SCREENSHOT)
|
||||
DECLARE_BIND(dsp_config, SSNES_DSP_CONFIG)
|
||||
},
|
||||
{
|
||||
DECLARE_BIND(player3_a, SNES_DEVICE_ID_JOYPAD_A)
|
||||
@@ -458,6 +486,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(player3_up, SNES_DEVICE_ID_JOYPAD_UP)
|
||||
DECLARE_BIND(player3_down, SNES_DEVICE_ID_JOYPAD_DOWN)
|
||||
DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY)
|
||||
DECLARE_BIND(hold_fast_forward, SSNES_FAST_FORWARD_HOLD_KEY)
|
||||
DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY)
|
||||
DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY)
|
||||
DECLARE_BIND(state_slot_increase, SSNES_STATE_SLOT_PLUS)
|
||||
@@ -472,6 +501,11 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(reset, SSNES_RESET)
|
||||
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
|
||||
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
|
||||
DECLARE_BIND(cheat_index_plus, SSNES_CHEAT_INDEX_PLUS)
|
||||
DECLARE_BIND(cheat_index_minus, SSNES_CHEAT_INDEX_MINUS)
|
||||
DECLARE_BIND(cheat_toggle, SSNES_CHEAT_TOGGLE)
|
||||
DECLARE_BIND(screenshot, SSNES_SCREENSHOT)
|
||||
DECLARE_BIND(dsp_config, SSNES_DSP_CONFIG)
|
||||
},
|
||||
{
|
||||
DECLARE_BIND(player4_a, SNES_DEVICE_ID_JOYPAD_A)
|
||||
@@ -487,6 +521,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(player4_up, SNES_DEVICE_ID_JOYPAD_UP)
|
||||
DECLARE_BIND(player4_down, SNES_DEVICE_ID_JOYPAD_DOWN)
|
||||
DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY)
|
||||
DECLARE_BIND(hold_fast_forward, SSNES_FAST_FORWARD_HOLD_KEY)
|
||||
DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY)
|
||||
DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY)
|
||||
DECLARE_BIND(state_slot_increase, SSNES_STATE_SLOT_PLUS)
|
||||
@@ -501,6 +536,11 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(reset, SSNES_RESET)
|
||||
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
|
||||
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
|
||||
DECLARE_BIND(cheat_index_plus, SSNES_CHEAT_INDEX_PLUS)
|
||||
DECLARE_BIND(cheat_index_minus, SSNES_CHEAT_INDEX_MINUS)
|
||||
DECLARE_BIND(cheat_toggle, SSNES_CHEAT_TOGGLE)
|
||||
DECLARE_BIND(screenshot, SSNES_SCREENSHOT)
|
||||
DECLARE_BIND(dsp_config, SSNES_DSP_CONFIG)
|
||||
},
|
||||
{
|
||||
DECLARE_BIND(player5_a, SNES_DEVICE_ID_JOYPAD_A)
|
||||
@@ -516,6 +556,7 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(player5_up, SNES_DEVICE_ID_JOYPAD_UP)
|
||||
DECLARE_BIND(player5_down, SNES_DEVICE_ID_JOYPAD_DOWN)
|
||||
DECLARE_BIND(toggle_fast_forward, SSNES_FAST_FORWARD_KEY)
|
||||
DECLARE_BIND(hold_fast_forward, SSNES_FAST_FORWARD_HOLD_KEY)
|
||||
DECLARE_BIND(save_state, SSNES_SAVE_STATE_KEY)
|
||||
DECLARE_BIND(load_state, SSNES_LOAD_STATE_KEY)
|
||||
DECLARE_BIND(state_slot_increase, SSNES_STATE_SLOT_PLUS)
|
||||
@@ -530,6 +571,11 @@ static const struct bind_map bind_maps[MAX_PLAYERS][MAX_BINDS - 1] = {
|
||||
DECLARE_BIND(reset, SSNES_RESET)
|
||||
DECLARE_BIND(shader_next, SSNES_SHADER_NEXT)
|
||||
DECLARE_BIND(shader_prev, SSNES_SHADER_PREV)
|
||||
DECLARE_BIND(cheat_index_plus, SSNES_CHEAT_INDEX_PLUS)
|
||||
DECLARE_BIND(cheat_index_minus, SSNES_CHEAT_INDEX_MINUS)
|
||||
DECLARE_BIND(cheat_toggle, SSNES_CHEAT_TOGGLE)
|
||||
DECLARE_BIND(screenshot, SSNES_SCREENSHOT)
|
||||
DECLARE_BIND(dsp_config, SSNES_DSP_CONFIG)
|
||||
},
|
||||
};
|
||||
|
||||
@@ -547,17 +593,20 @@ static const struct key_map sdlk_map[] = {
|
||||
{ "up", SDLK_UP },
|
||||
{ "down", SDLK_DOWN },
|
||||
{ "enter", SDLK_RETURN },
|
||||
{ "kp_enter", SDLK_KP_ENTER },
|
||||
{ "tab", SDLK_TAB },
|
||||
{ "insert", SDLK_INSERT },
|
||||
{ "del", SDLK_DELETE },
|
||||
{ "end", SDLK_END },
|
||||
{ "home", SDLK_HOME },
|
||||
{ "rshift", SDLK_RSHIFT },
|
||||
{ "shift", SDLK_LSHIFT },
|
||||
{ "ctrl", SDLK_LCTRL },
|
||||
{ "alt", SDLK_LALT },
|
||||
{ "space", SDLK_SPACE },
|
||||
{ "escape", SDLK_ESCAPE },
|
||||
{ "kp_plus", SDLK_KP_PLUS },
|
||||
{ "kp_minus", SDLK_KP_MINUS },
|
||||
{ "add", SDLK_KP_PLUS },
|
||||
{ "subtract", SDLK_KP_MINUS },
|
||||
{ "f1", SDLK_F1 },
|
||||
{ "f2", SDLK_F2 },
|
||||
{ "f3", SDLK_F3 },
|
||||
@@ -570,6 +619,37 @@ static const struct key_map sdlk_map[] = {
|
||||
{ "f10", SDLK_F10 },
|
||||
{ "f11", SDLK_F11 },
|
||||
{ "f12", SDLK_F12 },
|
||||
{ "num0", SDLK_0 },
|
||||
{ "num1", SDLK_1 },
|
||||
{ "num2", SDLK_2 },
|
||||
{ "num3", SDLK_3 },
|
||||
{ "num4", SDLK_4 },
|
||||
{ "num5", SDLK_5 },
|
||||
{ "num6", SDLK_6 },
|
||||
{ "num7", SDLK_7 },
|
||||
{ "num8", SDLK_8 },
|
||||
{ "num9", SDLK_9 },
|
||||
{ "pageup", SDLK_PAGEUP },
|
||||
{ "pagedown", SDLK_PAGEDOWN },
|
||||
{ "keypad0", SDLK_KP0 },
|
||||
{ "keypad1", SDLK_KP1 },
|
||||
{ "keypad2", SDLK_KP2 },
|
||||
{ "keypad3", SDLK_KP3 },
|
||||
{ "keypad4", SDLK_KP4 },
|
||||
{ "keypad5", SDLK_KP5 },
|
||||
{ "keypad6", SDLK_KP6 },
|
||||
{ "keypad7", SDLK_KP7 },
|
||||
{ "keypad8", SDLK_KP8 },
|
||||
{ "keypad9", SDLK_KP9 },
|
||||
{ "period", SDLK_PERIOD },
|
||||
{ "capslock", SDLK_CAPSLOCK },
|
||||
{ "numlock", SDLK_NUMLOCK },
|
||||
{ "backspace", SDLK_BACKSPACE },
|
||||
{ "multiply", SDLK_KP_MULTIPLY },
|
||||
{ "divide", SDLK_KP_DIVIDE },
|
||||
{ "print_screen", SDLK_PRINT },
|
||||
{ "scroll_lock", SDLK_SCROLLOCK },
|
||||
{ "tilde", SDLK_BACKQUOTE },
|
||||
{ "nul", SDLK_UNKNOWN },
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// SHA256 implementation from bSNES. Written by valditx.
|
||||
//
|
||||
|
||||
#include "sha256.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define PTR(t, a) ((t*)(a))
|
||||
|
||||
#define SWAP32(x) ((uint32_t)( \
|
||||
(((uint32_t)(x) & 0x000000ff) << 24) | \
|
||||
(((uint32_t)(x) & 0x0000ff00) << 8) | \
|
||||
(((uint32_t)(x) & 0x00ff0000) >> 8) | \
|
||||
(((uint32_t)(x) & 0xff000000) >> 24) \
|
||||
))
|
||||
|
||||
#define ST32(a, d) *PTR(uint32_t, a) = (d)
|
||||
#define ST32BE(a, d) ST32(a, SWAP32(d))
|
||||
|
||||
#define LD32(a) *PTR(uint32_t, a)
|
||||
#define LD32BE(a) SWAP32(LD32(a))
|
||||
|
||||
#define LSL32(x, n) ((uint32_t)(x) << (n))
|
||||
#define LSR32(x, n) ((uint32_t)(x) >> (n))
|
||||
#define ROR32(x, n) (LSR32(x, n) | LSL32(x, 32 - (n)))
|
||||
|
||||
// First 32 bits of the fractional parts of the square roots of the first 8 primes 2..19
|
||||
static const uint32_t T_H[8] = {
|
||||
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
||||
};
|
||||
|
||||
// First 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311
|
||||
static const uint32_t T_K[64] = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
||||
};
|
||||
|
||||
struct sha256_ctx
|
||||
{
|
||||
uint8_t in[64];
|
||||
unsigned inlen;
|
||||
|
||||
uint32_t w[64];
|
||||
uint32_t h[8];
|
||||
uint64_t len;
|
||||
};
|
||||
|
||||
static void sha256_init(struct sha256_ctx *p)
|
||||
{
|
||||
memset(p, 0, sizeof(struct sha256_ctx));
|
||||
memcpy(p->h, T_H, sizeof(T_H));
|
||||
}
|
||||
|
||||
static void sha256_block(struct sha256_ctx *p)
|
||||
{
|
||||
unsigned i;
|
||||
uint32_t s0, s1;
|
||||
uint32_t a, b, c, d, e, f, g, h;
|
||||
uint32_t t1, t2, maj, ch;
|
||||
|
||||
for (i = 0; i < 16; i++)
|
||||
p->w[i] = LD32BE(p->in + i * 4);
|
||||
|
||||
for (i = 16; i < 64; i++)
|
||||
{
|
||||
s0 = ROR32(p->w[i - 15], 7) ^ ROR32(p->w[i - 15], 18) ^ LSR32(p->w[i - 15], 3);
|
||||
s1 = ROR32(p->w[i - 2], 17) ^ ROR32(p->w[i - 2], 19) ^ LSR32(p->w[i - 2], 10);
|
||||
p->w[i] = p->w[i - 16] + s0 + p->w[i - 7] + s1;
|
||||
}
|
||||
|
||||
a = p->h[0]; b = p->h[1]; c = p->h[2]; d = p->h[3];
|
||||
e = p->h[4]; f = p->h[5]; g = p->h[6]; h = p->h[7];
|
||||
|
||||
for (i = 0; i < 64; i++)
|
||||
{
|
||||
s0 = ROR32(a, 2) ^ ROR32(a, 13) ^ ROR32(a, 22);
|
||||
maj = (a & b) ^ (a & c) ^ (b & c);
|
||||
t2 = s0 + maj;
|
||||
s1 = ROR32(e, 6) ^ ROR32(e, 11) ^ ROR32(e, 25);
|
||||
ch = (e & f) ^ (~e & g);
|
||||
t1 = h + s1 + ch + T_K[i] + p->w[i];
|
||||
|
||||
h = g; g = f; f = e; e = d + t1;
|
||||
d = c; c = b; b = a; a = t1 + t2;
|
||||
}
|
||||
|
||||
p->h[0] += a; p->h[1] += b; p->h[2] += c; p->h[3] += d;
|
||||
p->h[4] += e; p->h[5] += f; p->h[6] += g; p->h[7] += h;
|
||||
|
||||
// Next block
|
||||
p->inlen = 0;
|
||||
}
|
||||
|
||||
static void sha256_chunk(struct sha256_ctx *p, const uint8_t *s, unsigned len)
|
||||
{
|
||||
unsigned l;
|
||||
p->len += len;
|
||||
|
||||
while (len)
|
||||
{
|
||||
l = 64 - p->inlen;
|
||||
l = (len < l) ? len : l;
|
||||
|
||||
memcpy(p->in + p->inlen, s, l);
|
||||
s += l;
|
||||
p->inlen += l;
|
||||
len -= l;
|
||||
|
||||
if (p->inlen == 64)
|
||||
sha256_block(p);
|
||||
}
|
||||
}
|
||||
|
||||
static void sha256_final(struct sha256_ctx *p)
|
||||
{
|
||||
uint64_t len;
|
||||
p->in[p->inlen++] = 0x80;
|
||||
|
||||
if (p->inlen > 56)
|
||||
{
|
||||
memset(p->in + p->inlen, 0, 64 - p->inlen);
|
||||
sha256_block(p);
|
||||
}
|
||||
|
||||
memset(p->in + p->inlen, 0, 56 - p->inlen);
|
||||
|
||||
len = p->len << 3;
|
||||
ST32BE(p->in + 56, len >> 32);
|
||||
ST32BE(p->in + 60, len);
|
||||
sha256_block(p);
|
||||
}
|
||||
|
||||
static void sha256_subhash(struct sha256_ctx *p, uint32_t *t)
|
||||
{
|
||||
for (unsigned i = 0; i < 8; i++)
|
||||
ST32BE(t++, p->h[i]);
|
||||
}
|
||||
|
||||
void sha256_hash(char *out, const uint8_t *in, size_t size)
|
||||
{
|
||||
struct sha256_ctx sha;
|
||||
|
||||
union
|
||||
{
|
||||
uint32_t u32[8];
|
||||
uint8_t u8[32];
|
||||
} shahash;
|
||||
|
||||
sha256_init(&sha);
|
||||
sha256_chunk(&sha, in, size);
|
||||
sha256_final(&sha);
|
||||
sha256_subhash(&sha, shahash.u32);
|
||||
|
||||
for (unsigned i = 0; i < 32; i++)
|
||||
snprintf(out + 2 * i, 3, "%02x", (unsigned)shahash.u8[i]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
*
|
||||
* Some code herein may be based on code found in BSNES.
|
||||
*
|
||||
* SSNES is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* SSNES is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with SSNES.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __SSNES_SHA256_H
|
||||
#define __SSNES_SHA256_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// Hashes sha256 and outputs a human readable string for comparing with the cheat XML values.
|
||||
void sha256_hash(char *out, const uint8_t *in, size_t size);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -31,7 +31,10 @@ extract_zip()
|
||||
die "Cannot find .smc nor .sfc file in archive"
|
||||
fi
|
||||
|
||||
unzip -p "$1" "$FILENAME" > "$TMPROM"
|
||||
# The people who made the [!] naming convention obviously have never used regex ... <_<
|
||||
FILENAME_FIXED="`echo $FILENAME | sed -e 's|\[|\\\\[|g' -e 's|!|\\\\!|g' -e 's|\]|\\\\]|g'`"
|
||||
|
||||
unzip -p "$1" "$FILENAME_FIXED" > "$TMPROM"
|
||||
}
|
||||
|
||||
extract_7z()
|
||||
@@ -79,13 +82,14 @@ esac
|
||||
SAVENAME="`basename \"$COMP_FILE\" $EXTENSION`"
|
||||
DIRNAME="`dirname \"$COMP_FILE\"`"
|
||||
SAVENAME="${SAVENAME}.srm"
|
||||
echo "Savename: ${DIRNAME}/${SAVENAME}"
|
||||
|
||||
SSNES_PATH="`which ssnes`"
|
||||
if [ -z "$SSNES_PATH" ]; then
|
||||
die "Cannot find SSNES in path."
|
||||
fi
|
||||
|
||||
echo "Temp ROM: $TMPROM"
|
||||
echo "Savename: ${DIRNAME}/${SAVENAME}"
|
||||
"$SSNES_PATH" "$TMPROM" -s "${DIRNAME}/${SAVENAME}" "$@"
|
||||
rm "$TMPROM"
|
||||
rm -r "$TMPDIR"
|
||||
@@ -32,6 +32,8 @@
|
||||
#include "movie.h"
|
||||
#include "netplay.h"
|
||||
#include "strl.h"
|
||||
#include "screenshot.h"
|
||||
#include "cheats.h"
|
||||
#include <assert.h>
|
||||
#ifdef HAVE_SRC
|
||||
#include <samplerate.h>
|
||||
@@ -57,13 +59,26 @@ struct global g_extern = {
|
||||
};
|
||||
|
||||
// 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.
|
||||
static void set_fast_forward_button(bool new_button_state)
|
||||
static void set_fast_forward_button(bool new_button_state, bool new_hold_button_state)
|
||||
{
|
||||
bool update_sync = false;
|
||||
static bool old_button_state = false;
|
||||
static bool old_hold_button_state = false;
|
||||
static bool syncing_state = false;
|
||||
|
||||
if (new_button_state && !old_button_state)
|
||||
{
|
||||
syncing_state = !syncing_state;
|
||||
update_sync = true;
|
||||
}
|
||||
else if (old_hold_button_state != new_hold_button_state)
|
||||
{
|
||||
syncing_state = new_hold_button_state;
|
||||
update_sync = true;
|
||||
}
|
||||
|
||||
if (update_sync)
|
||||
{
|
||||
if (g_extern.video_active)
|
||||
driver.video->set_nonblock_state(driver.video_data, syncing_state);
|
||||
if (g_extern.audio_active)
|
||||
@@ -74,16 +89,59 @@ static void set_fast_forward_button(bool new_button_state)
|
||||
else
|
||||
g_extern.audio_data.chunk_size = g_extern.audio_data.block_chunk_size;
|
||||
}
|
||||
|
||||
old_button_state = new_button_state;
|
||||
old_hold_button_state = new_hold_button_state;
|
||||
}
|
||||
|
||||
static void take_screenshot(const uint16_t *frame, unsigned width, unsigned height)
|
||||
{
|
||||
if (!*(g_settings.screenshot_directory))
|
||||
return;
|
||||
|
||||
bool ret = screenshot_dump(g_settings.screenshot_directory, frame,
|
||||
width, height, (height == 448 || height == 478) ? 1024 : 2048);
|
||||
|
||||
const char *msg = NULL;
|
||||
if (ret)
|
||||
{
|
||||
SSNES_LOG("Taking screenshot!\n");
|
||||
msg = "Taking screenshot!";
|
||||
}
|
||||
else
|
||||
{
|
||||
SSNES_WARN("Failed to take screenshot ...\n");
|
||||
msg = "Failed to take screenshot!";
|
||||
}
|
||||
|
||||
msg_queue_clear(g_extern.msg_queue);
|
||||
msg_queue_push(g_extern.msg_queue, msg, 1, 180);
|
||||
}
|
||||
|
||||
// libsnes: 0.065
|
||||
// Format received is 16-bit 0RRRRRGGGGGBBBBB
|
||||
static void video_frame(const uint16_t *data, unsigned width, unsigned height)
|
||||
{
|
||||
if ( !g_extern.video_active )
|
||||
if (!g_extern.video_active)
|
||||
return;
|
||||
|
||||
if (g_settings.video.crop_overscan)
|
||||
{
|
||||
if (height == 239)
|
||||
{
|
||||
data += 7 * 1024; // Skip 7 top scanlines.
|
||||
height = 224;
|
||||
}
|
||||
else if (height == 478)
|
||||
{
|
||||
data += 15 * 512; // Skip 15 top scanlines.
|
||||
height = 448;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_extern.do_screenshot)
|
||||
take_screenshot(data, width, height);
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
if (g_extern.recording)
|
||||
{
|
||||
@@ -99,7 +157,7 @@ static void video_frame(const uint16_t *data, unsigned width, unsigned height)
|
||||
|
||||
const char *msg = msg_queue_pull(g_extern.msg_queue);
|
||||
|
||||
#ifdef HAVE_FILTER
|
||||
#ifdef HAVE_DYLIB
|
||||
if (g_extern.filter.active)
|
||||
{
|
||||
unsigned owidth = width;
|
||||
@@ -137,58 +195,85 @@ static void audio_sample(uint16_t left, uint16_t right)
|
||||
}
|
||||
#endif
|
||||
|
||||
const float *output_data = NULL;
|
||||
unsigned output_frames = 0;
|
||||
|
||||
g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(int16_t)left/0x8000;
|
||||
g_extern.audio_data.data[g_extern.audio_data.data_ptr++] = (float)(int16_t)right/0x8000;
|
||||
|
||||
if (g_extern.audio_data.data_ptr >= g_extern.audio_data.chunk_size)
|
||||
{
|
||||
if (g_extern.audio_data.data_ptr < g_extern.audio_data.chunk_size)
|
||||
return;
|
||||
|
||||
if (g_extern.frame_is_reverse) // Disable fucked up audio when rewinding...
|
||||
memset(g_extern.audio_data.data, 0, g_extern.audio_data.chunk_size * sizeof(float));
|
||||
ssnes_dsp_input_t dsp_input = {
|
||||
.samples = g_extern.audio_data.data,
|
||||
.frames = g_extern.audio_data.data_ptr / 2
|
||||
};
|
||||
|
||||
ssnes_dsp_output_t dsp_output = {
|
||||
.should_resample = SSNES_TRUE
|
||||
};
|
||||
|
||||
if (g_extern.audio_data.dsp_plugin)
|
||||
g_extern.audio_data.dsp_plugin->process(g_extern.audio_data.dsp_handle, &dsp_output, &dsp_input);
|
||||
|
||||
if (g_extern.frame_is_reverse) // Disable fucked up audio when rewinding...
|
||||
memset(g_extern.audio_data.data, 0, g_extern.audio_data.chunk_size * sizeof(float));
|
||||
|
||||
#ifdef HAVE_SRC
|
||||
SRC_DATA src_data = {
|
||||
SRC_DATA src_data = {
|
||||
#else
|
||||
struct hermite_data src_data = {
|
||||
struct hermite_data src_data = {
|
||||
#endif
|
||||
.data_in = g_extern.audio_data.data,
|
||||
.data_out = g_extern.audio_data.outsamples,
|
||||
.input_frames = g_extern.audio_data.chunk_size / 2,
|
||||
.output_frames = g_extern.audio_data.chunk_size * 8,
|
||||
.end_of_input = 0,
|
||||
.src_ratio = (double)g_settings.audio.out_rate / (double)g_settings.audio.in_rate,
|
||||
};
|
||||
.data_in = dsp_output.samples ? dsp_output.samples : g_extern.audio_data.data,
|
||||
.data_out = g_extern.audio_data.outsamples,
|
||||
.input_frames = dsp_output.samples ? dsp_output.frames : (g_extern.audio_data.data_ptr / 2),
|
||||
.output_frames = g_extern.audio_data.chunk_size * 8,
|
||||
.end_of_input = 0,
|
||||
.src_ratio = (double)g_settings.audio.out_rate / (double)g_settings.audio.in_rate,
|
||||
};
|
||||
|
||||
if (dsp_output.should_resample)
|
||||
{
|
||||
#ifdef HAVE_SRC
|
||||
src_process(g_extern.audio_data.source, &src_data);
|
||||
#else
|
||||
hermite_process(g_extern.audio_data.source, &src_data);
|
||||
#endif
|
||||
if (g_extern.audio_data.use_float)
|
||||
{
|
||||
if (driver.audio->write(driver.audio_data, g_extern.audio_data.outsamples, src_data.output_frames_gen * sizeof(float) * 2) < 0)
|
||||
{
|
||||
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
|
||||
g_extern.audio_active = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (unsigned i = 0; i < src_data.output_frames_gen * 2; i++)
|
||||
{
|
||||
int32_t val = g_extern.audio_data.outsamples[i] * 0x8000;
|
||||
g_extern.audio_data.conv_outsamples[i] = (val > 0x7FFF) ? 0x7FFF : (val < -0x8000 ? -0x8000 : (int16_t)val);
|
||||
}
|
||||
|
||||
if (driver.audio->write(driver.audio_data, g_extern.audio_data.conv_outsamples, src_data.output_frames_gen * sizeof(int16_t) * 2) < 0)
|
||||
{
|
||||
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
|
||||
g_extern.audio_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
g_extern.audio_data.data_ptr = 0;
|
||||
output_data = g_extern.audio_data.outsamples;
|
||||
output_frames = src_data.output_frames_gen;
|
||||
}
|
||||
else
|
||||
{
|
||||
output_data = dsp_output.samples;
|
||||
output_frames = dsp_output.frames;
|
||||
}
|
||||
|
||||
|
||||
if (g_extern.audio_data.use_float)
|
||||
{
|
||||
if (driver.audio->write(driver.audio_data, output_data, output_frames * sizeof(float) * 2) < 0)
|
||||
{
|
||||
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
|
||||
g_extern.audio_active = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (unsigned i = 0; i < output_frames * 2; i++)
|
||||
{
|
||||
int32_t val = output_data[i] * 0x8000;
|
||||
g_extern.audio_data.conv_outsamples[i] = (val > 0x7FFF) ? 0x7FFF : (val < -0x8000 ? -0x8000 : (int16_t)val);
|
||||
}
|
||||
|
||||
if (driver.audio->write(driver.audio_data, g_extern.audio_data.conv_outsamples, output_frames * sizeof(int16_t) * 2) < 0)
|
||||
{
|
||||
fprintf(stderr, "SSNES [ERROR]: Audio backend failed to write. Will continue without sound.\n");
|
||||
g_extern.audio_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
g_extern.audio_data.data_ptr = 0;
|
||||
}
|
||||
|
||||
static void input_poll(void)
|
||||
@@ -210,9 +295,13 @@ static int16_t input_state(bool port, unsigned device, unsigned index, unsigned
|
||||
}
|
||||
}
|
||||
|
||||
const struct snes_keybind *binds[MAX_PLAYERS];
|
||||
for (int i = 0; i < MAX_PLAYERS; i++)
|
||||
binds[i] = g_settings.input.binds[i];
|
||||
static const struct snes_keybind *binds[MAX_PLAYERS] = {
|
||||
g_settings.input.binds[0],
|
||||
g_settings.input.binds[1],
|
||||
g_settings.input.binds[2],
|
||||
g_settings.input.binds[3],
|
||||
g_settings.input.binds[4]
|
||||
};
|
||||
|
||||
int16_t res = driver.input->input_state(driver.input_data, binds, port, device, index, id);
|
||||
if (g_extern.bsv_movie && !g_extern.bsv_movie_playback)
|
||||
@@ -248,7 +337,7 @@ static void fill_pathname_noext(char *out_path, const char *in_path, const char
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define PACKAGE_VERSION "0.4"
|
||||
#define PACKAGE_VERSION "0.5.2"
|
||||
#endif
|
||||
|
||||
#include "config.features.h"
|
||||
@@ -267,9 +356,10 @@ static void print_features(void)
|
||||
_PSUPP(pulse, "PulseAudio", "audio driver");
|
||||
_PSUPP(xaudio, "XAudio2", "audio driver");
|
||||
_PSUPP(al, "OpenAL", "audio driver");
|
||||
_PSUPP(filter, "Filter", "CPU based video filters");
|
||||
_PSUPP(dylib, "External", "External filter and driver support");
|
||||
_PSUPP(cg, "Cg", "Cg pixel shaders");
|
||||
_PSUPP(xml, "XML", "bSNES XML pixel shaders");
|
||||
_PSUPP(sdl_image, "SDL_image", "SDL_image image loading");
|
||||
_PSUPP(fbo, "FBO", "OpenGL render-to-texture (multi-pass shaders)");
|
||||
_PSUPP(dynamic, "Dynamic", "Dynamic run-time loading of libsnes library");
|
||||
_PSUPP(ffmpeg, "FFmpeg", "On-the-fly recording of gameplay with libavcodec");
|
||||
@@ -277,6 +367,7 @@ static void print_features(void)
|
||||
_PSUPP(configfile, "Config file", "Configuration file support");
|
||||
_PSUPP(freetype, "FreeType", "TTF font rendering with FreeType");
|
||||
_PSUPP(netplay, "Netplay", "Peer-to-peer netplay");
|
||||
_PSUPP(python, "Python", "Script support in shaders");
|
||||
}
|
||||
#undef _PSUPP
|
||||
|
||||
@@ -315,7 +406,8 @@ static void print_help(void)
|
||||
puts("\t-r/--record: Path to record video file. Settings for video/audio codecs are found in config file.");
|
||||
#endif
|
||||
puts("\t-v/--verbose: Verbose logging");
|
||||
puts("\t-U/--ups: Specifies path for UPS patch that will be applied to ROM.\n");
|
||||
puts("\t-U/--ups: Specifies path for UPS patch that will be applied to ROM.");
|
||||
puts("\t-D/--detach: Detach SSNES from the running console. Not relevant for all platforms.\n");
|
||||
|
||||
print_features();
|
||||
}
|
||||
@@ -367,6 +459,7 @@ static void parse_input(int argc, char *argv[])
|
||||
{ "frames", 1, NULL, 'F' },
|
||||
{ "port", 1, &val, 'p' },
|
||||
{ "ups", 1, NULL, 'U' },
|
||||
{ "detach", 0, NULL, 'D' },
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
@@ -384,7 +477,7 @@ static void parse_input(int argc, char *argv[])
|
||||
#define CONFIG_FILE_ARG
|
||||
#endif
|
||||
|
||||
char optstring[] = "hs:vS:m:p4jJg:b:B:Y:Z:P:HC:F:U:" FFMPEG_RECORD_ARG CONFIG_FILE_ARG;
|
||||
char optstring[] = "hs:vS:m:p4jJg:b:B:Y:Z:P:HC:F:U:D" FFMPEG_RECORD_ARG CONFIG_FILE_ARG;
|
||||
for(;;)
|
||||
{
|
||||
val = 0;
|
||||
@@ -503,6 +596,12 @@ static void parse_input(int argc, char *argv[])
|
||||
strlcpy(g_extern.ups_name, optarg, sizeof(g_extern.ups_name));
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
#ifdef _WIN32
|
||||
FreeConsole();
|
||||
#endif
|
||||
break;
|
||||
|
||||
case 0:
|
||||
switch (val)
|
||||
{
|
||||
@@ -708,6 +807,20 @@ static void deinit_msg_queue(void)
|
||||
msg_queue_free(g_extern.msg_queue);
|
||||
}
|
||||
|
||||
#ifdef HAVE_XML
|
||||
static void init_cheats(void)
|
||||
{
|
||||
if (*g_settings.cheat_database)
|
||||
g_extern.cheat = cheat_manager_new(g_settings.cheat_database);
|
||||
}
|
||||
|
||||
static void deinit_cheats(void)
|
||||
{
|
||||
if (g_extern.cheat)
|
||||
cheat_manager_free(g_extern.cheat);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void init_rewind(void)
|
||||
{
|
||||
if (g_settings.rewind_enable)
|
||||
@@ -953,13 +1066,17 @@ static void check_savestates(void)
|
||||
|
||||
static void check_fullscreen(void)
|
||||
{
|
||||
static bool was_pressed = false;
|
||||
bool pressed;
|
||||
// If we go fullscreen we drop all drivers and reinit to be safe.
|
||||
if (driver.input->key_pressed(driver.input_data, SSNES_FULLSCREEN_TOGGLE_KEY))
|
||||
if ((pressed = driver.input->key_pressed(driver.input_data, SSNES_FULLSCREEN_TOGGLE_KEY)) && !was_pressed)
|
||||
{
|
||||
g_settings.video.fullscreen = !g_settings.video.fullscreen;
|
||||
uninit_drivers();
|
||||
init_drivers();
|
||||
}
|
||||
|
||||
was_pressed = pressed;
|
||||
}
|
||||
|
||||
static void check_stateslots(void)
|
||||
@@ -1173,7 +1290,7 @@ static void check_reset(void)
|
||||
static void check_shader_dir(void)
|
||||
{
|
||||
static bool old_pressed_next = false;
|
||||
static bool old_pressed_prev = true;
|
||||
static bool old_pressed_prev = false;
|
||||
|
||||
if (!g_extern.shader_dir.elems || !driver.video->xml_shader)
|
||||
return;
|
||||
@@ -1216,6 +1333,54 @@ static void check_shader_dir(void)
|
||||
old_pressed_prev = pressed_prev;
|
||||
|
||||
}
|
||||
|
||||
static void check_cheats(void)
|
||||
{
|
||||
if (!g_extern.cheat)
|
||||
return;
|
||||
|
||||
static bool old_pressed_prev = false;
|
||||
static bool old_pressed_next = false;
|
||||
static bool old_pressed_toggle = false;
|
||||
|
||||
bool pressed_next = driver.input->key_pressed(driver.input_data, SSNES_CHEAT_INDEX_PLUS);
|
||||
bool pressed_prev = driver.input->key_pressed(driver.input_data, SSNES_CHEAT_INDEX_MINUS);
|
||||
bool pressed_toggle = driver.input->key_pressed(driver.input_data, SSNES_CHEAT_TOGGLE);
|
||||
|
||||
if (pressed_next && !old_pressed_next)
|
||||
cheat_manager_index_next(g_extern.cheat);
|
||||
else if (pressed_prev && !old_pressed_prev)
|
||||
cheat_manager_index_prev(g_extern.cheat);
|
||||
else if (pressed_toggle && !old_pressed_toggle)
|
||||
cheat_manager_toggle(g_extern.cheat);
|
||||
|
||||
old_pressed_prev = pressed_prev;
|
||||
old_pressed_next = pressed_next;
|
||||
old_pressed_toggle = pressed_toggle;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void check_screenshot(void)
|
||||
{
|
||||
static bool old_pressed = false;
|
||||
bool pressed = driver.input->key_pressed(driver.input_data, SSNES_SCREENSHOT);
|
||||
g_extern.do_screenshot = pressed && !old_pressed;
|
||||
old_pressed = pressed;
|
||||
}
|
||||
|
||||
#ifdef HAVE_DYLIB
|
||||
static void check_dsp_config(void)
|
||||
{
|
||||
if (!g_extern.audio_data.dsp_plugin || !g_extern.audio_data.dsp_plugin->config)
|
||||
return;
|
||||
|
||||
static bool old_pressed = false;
|
||||
bool pressed = driver.input->key_pressed(driver.input_data, SSNES_DSP_CONFIG);
|
||||
if (pressed && !old_pressed)
|
||||
g_extern.audio_data.dsp_plugin->config(g_extern.audio_data.dsp_handle);
|
||||
|
||||
old_pressed = pressed;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void do_state_checks(void)
|
||||
@@ -1223,11 +1388,16 @@ static void do_state_checks(void)
|
||||
if (!g_extern.netplay)
|
||||
{
|
||||
check_reset();
|
||||
#ifdef HAVE_XML
|
||||
check_cheats();
|
||||
#endif
|
||||
check_pause();
|
||||
if (g_extern.is_paused)
|
||||
return;
|
||||
|
||||
set_fast_forward_button(driver.input->key_pressed(driver.input_data, SSNES_FAST_FORWARD_KEY));
|
||||
set_fast_forward_button(
|
||||
driver.input->key_pressed(driver.input_data, SSNES_FAST_FORWARD_KEY),
|
||||
driver.input->key_pressed(driver.input_data, SSNES_FAST_FORWARD_HOLD_KEY));
|
||||
|
||||
if (!g_extern.bsv_movie)
|
||||
{
|
||||
@@ -1242,10 +1412,21 @@ static void do_state_checks(void)
|
||||
#ifdef HAVE_XML
|
||||
check_shader_dir();
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_DYLIB
|
||||
check_dsp_config();
|
||||
#endif
|
||||
}
|
||||
|
||||
check_fullscreen();
|
||||
check_input_rate();
|
||||
|
||||
#ifdef HAVE_DYLIB
|
||||
// DSP plugin doesn't use variable input rate.
|
||||
if (!g_extern.audio_data.dsp_plugin)
|
||||
#endif
|
||||
check_input_rate();
|
||||
|
||||
check_screenshot();
|
||||
}
|
||||
|
||||
static void fill_title_buf(void)
|
||||
@@ -1275,6 +1456,10 @@ int main(int argc, char *argv[])
|
||||
if (!init_rom_file(g_extern.game_type))
|
||||
goto error;
|
||||
|
||||
#ifdef HAVE_XML
|
||||
init_cheats();
|
||||
#endif
|
||||
|
||||
init_msg_queue();
|
||||
init_movie();
|
||||
|
||||
@@ -1376,6 +1561,10 @@ int main(int argc, char *argv[])
|
||||
if (!g_extern.netplay)
|
||||
deinit_rewind();
|
||||
|
||||
#ifdef HAVE_XML
|
||||
deinit_cheats();
|
||||
#endif
|
||||
|
||||
deinit_movie();
|
||||
deinit_msg_queue();
|
||||
|
||||
|
||||
@@ -5,7 +5,13 @@
|
||||
|
||||
#### Video
|
||||
|
||||
# Windowed xscale and yscale (Real x res: 296 * xscale, real y scale: 224 * xscale)
|
||||
# Video driver to use. "gl", "xvideo", "sdl" or "ext" (external API driver)
|
||||
# video_driver = "gl"
|
||||
|
||||
# Path to external video driver using the SSNES driver API.
|
||||
# video_external_driver =
|
||||
|
||||
# Windowed xscale and yscale (Real x res: 224 * xscale * aspect_ratio, real y scale: 224 * xscale)
|
||||
# video_xscale = 3.0
|
||||
# video_yscale = 3.0
|
||||
|
||||
@@ -16,6 +22,9 @@
|
||||
# Start in fullscreen. Can be changed at runtime.
|
||||
# video_fullscreen = false
|
||||
|
||||
# Force 16-bit colors. Apparently some video cards in use today have troubles with 32-bit ...
|
||||
# video_force_16bit = false
|
||||
|
||||
# Video vsync.
|
||||
# video_vsync = true
|
||||
|
||||
@@ -28,6 +37,9 @@
|
||||
# A floating point value for video aspect ratio (width / height)
|
||||
# video_aspect_ratio = 1.333
|
||||
|
||||
# Forces cropping of overscanned frames. Crops away top 7 scanlines and 8 bottom scanlines. (15/15 for interlaced frames).
|
||||
# video_crop_overscan = false
|
||||
|
||||
# Path to Cg shader.
|
||||
# video_cg_shader = "/path/to/cg/shader.cg"
|
||||
|
||||
@@ -87,12 +99,18 @@
|
||||
# When altering audio_in_rate on-the-fly, define by how much each time.
|
||||
# audio_rate_step = 0.25
|
||||
|
||||
# Audio driver backend. Depending on configuration possible candidates are: alsa, pulse, oss, jack, rsound, roar, openal, sdl and xaudio
|
||||
# Audio driver backend. Depending on configuration possible candidates are: alsa, pulse, oss, jack, rsound, roar, openal, sdl, xaudio and ext (external driver).
|
||||
# audio_driver =
|
||||
|
||||
# Path to external audio driver using the SSNES audio driver API.
|
||||
# audio_external_driver =
|
||||
|
||||
# Override the default audio device the audio_driver uses. This is driver dependant. E.g. ALSA wants a PCM device, OSS wants a path (e.g. /dev/dsp), Jack wants portnames (e.g. system:playback1,system:playback_2), and so on ...
|
||||
# audio_device =
|
||||
|
||||
# External DSP plugin that processes audio before it's sent to the driver.
|
||||
# audio_dsp_plugin =
|
||||
|
||||
# Will sync (block) on audio. Recommended.
|
||||
# audio_sync = true
|
||||
|
||||
@@ -207,6 +225,9 @@
|
||||
# Toggles between fast-forwarding and normal speed.
|
||||
# input_toggle_fast_forward = space
|
||||
|
||||
# Hold for fast-forward. Releasing button disables fast-forward.
|
||||
# input_hold_fast_forward = l
|
||||
|
||||
# Key to exit emulator cleanly.
|
||||
# Killing it in any hard way (SIGTERM, SIGKILL, etc, will terminate emulator without saving RAM, etc.)
|
||||
# input_exit_emulator = escape
|
||||
@@ -242,6 +263,9 @@
|
||||
# Reset the emulated SNES.
|
||||
# input_reset = h
|
||||
|
||||
# Configures DSP plugin
|
||||
# input_dsp_config = c
|
||||
|
||||
# Pause gameplay when window focus is lost.
|
||||
# pause_nonactive = true
|
||||
|
||||
@@ -252,3 +276,17 @@
|
||||
# When being client over netplay, use keybinds for player 1.
|
||||
# netplay_client_swap_input = false
|
||||
|
||||
|
||||
# Path to XML cheat database (as used by bSNES).
|
||||
# cheat_database_path =
|
||||
|
||||
# Cheats.
|
||||
# input_cheat_index_plus = y
|
||||
# input_cheat_index_minus = t
|
||||
# input_cheat_toggle = u
|
||||
|
||||
# Take screenshot
|
||||
# input_screenshot = print_screen
|
||||
|
||||
# Directory to dump screenshots to.
|
||||
# screenshot_directory =
|
||||
@@ -41,7 +41,12 @@ size_t strlcat(char *dest, const char *source, size_t size)
|
||||
{
|
||||
size_t len = strlen(dest);
|
||||
dest += len;
|
||||
size -= len;
|
||||
|
||||
if (len > size)
|
||||
size = 0;
|
||||
else
|
||||
size -= len;
|
||||
|
||||
return len + strlcpy(dest, source, size);
|
||||
}
|
||||
#endif
|
||||
@@ -20,7 +20,10 @@
|
||||
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef HAVE_CONFIG
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_STRL
|
||||
// Avoid possible naming collitions during link since we prefer to use the actual name.
|
||||
|
||||
@@ -86,6 +86,7 @@ static struct bind binds[] = {
|
||||
MISC_BIND("Save state slot increase", state_slot_increase)
|
||||
MISC_BIND("Save state slot decrease", state_slot_decrease)
|
||||
MISC_BIND("Toggle fast forward", toggle_fast_forward)
|
||||
MISC_BIND("Hold fast forward", hold_fast_forward)
|
||||
MISC_BIND("Audio input rate step up", rate_step_up)
|
||||
MISC_BIND("Audio input rate step down", rate_step_down)
|
||||
MISC_BIND("Rewind", rewind)
|
||||
@@ -94,6 +95,11 @@ static struct bind binds[] = {
|
||||
MISC_BIND("Reset", reset)
|
||||
MISC_BIND("Next shader", shader_next)
|
||||
MISC_BIND("Previous shader", shader_prev)
|
||||
MISC_BIND("Toggle cheat on/off", cheat_toggle)
|
||||
MISC_BIND("Cheat index plus", cheat_index_plus)
|
||||
MISC_BIND("Cheat index minus", cheat_index_minus)
|
||||
MISC_BIND("Screenshot", screenshot)
|
||||
MISC_BIND("DSP config", dsp_config)
|
||||
};
|
||||
|
||||
static void get_binds(config_file_t *conf, int player, int joypad)
|
||||
|
||||
Reference in new issue
Block a user