mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 08:10:30 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2b06a92c5 | ||
|
|
3901934d97 | ||
|
|
a76b25aff9 | ||
|
|
343852af26 | ||
|
|
4395f124ca | ||
|
|
b16ff3024c | ||
|
|
bc23b421e1 | ||
|
|
d9cb85a8d5 | ||
|
|
7d1cc23d16 | ||
|
|
4496ed3c13 | ||
|
|
3f5b6a450e | ||
|
|
9e24474047 | ||
|
|
7c17ede1ef | ||
|
|
5d9805c3e5 | ||
|
|
b17617bed5 | ||
|
|
a405708f1c | ||
|
|
594e66704d | ||
|
|
50579aaeea | ||
|
|
38eb855b48 | ||
|
|
12cc85c8a5 | ||
|
|
5b12595c85 | ||
|
|
3753f0bfca | ||
|
|
14ae5e397a | ||
|
|
657b4ce8c7 | ||
|
|
777e6a5334 | ||
|
|
a2ec78320b | ||
|
|
1fb53627ac | ||
|
|
fc2a39cd58 | ||
|
|
7425e1f879 | ||
|
|
6a260a57dd | ||
|
|
dadd794ece | ||
|
|
706a3738b5 | ||
|
|
9aef33aabd | ||
|
|
ac4219bcde | ||
|
|
b337856f80 | ||
|
|
079a0100c0 | ||
|
|
014c3c90e0 | ||
|
|
4759e3411f | ||
|
|
4a05e50636 | ||
|
|
72c070f338 | ||
|
|
776291a137 | ||
|
|
f3cdb95b04 | ||
|
|
033860ceb9 | ||
|
|
ab601ee3dc | ||
|
|
783ab7acd5 | ||
|
|
19a46b1f8b | ||
|
|
508cfdbda7 | ||
|
|
d81b9ee0a7 | ||
|
|
36c6c21194 | ||
|
|
1631e940f6 | ||
|
|
2eeb1fa6d1 | ||
|
|
26ac00ad76 | ||
|
|
bafc4f596f | ||
|
|
eee89dddb1 | ||
|
|
089b4fbba0 | ||
|
|
80727cef5a | ||
|
|
346a9f2b4e | ||
|
|
363d8ca5b4 | ||
|
|
06a574225d | ||
|
|
fa777b8356 | ||
|
|
7cf01318cb | ||
|
|
7c538274b4 | ||
|
|
72fb694e97 | ||
|
|
59c92dddc3 | ||
|
|
3f60fdd106 | ||
|
|
d221c480e6 | ||
|
|
59d9ff0d29 | ||
|
|
d279ea79e8 | ||
|
|
77cf9423cc | ||
|
|
60b94bbe43 | ||
|
|
b6272f9653 | ||
|
|
25fce1a6c6 | ||
|
|
59229f99c5 | ||
|
|
db3e1ae053 | ||
|
|
ec1401e657 | ||
|
|
f85fd50fa1 | ||
|
|
c39b0ab09b | ||
|
|
4b5ed0fa41 | ||
|
|
75d1155a23 | ||
|
|
753d2057bb | ||
|
|
b06d814d9c | ||
|
|
eb085a8068 | ||
|
|
45904ccd73 | ||
|
|
3b0606bbd6 | ||
|
|
0f1a76f932 | ||
|
|
d3e0140f01 | ||
|
|
ea7dded915 | ||
|
|
f187f3db29 | ||
|
|
3099a79bac | ||
|
|
7cdea39c95 | ||
|
|
95f6a5116a | ||
|
|
da94877155 | ||
|
|
b1feed9d2b | ||
|
|
a3816bffa1 | ||
|
|
612270ec9b | ||
|
|
54bc0a8d96 | ||
|
|
f761811189 | ||
|
|
0e964c5cc2 | ||
|
|
c34a944fcf | ||
|
|
3feade6b6e | ||
|
|
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 | ||
|
|
a4c901df57 | ||
|
|
f2cc19d1a7 | ||
|
|
ad55829890 | ||
|
|
6b8eea6363 | ||
|
|
b2d214ee79 | ||
|
|
f5fb113563 | ||
|
|
844a523706 | ||
|
|
d05379b340 | ||
|
|
006948b973 | ||
|
|
e5196026a1 | ||
|
|
66d18e4ad7 | ||
|
|
b539df1ccd | ||
|
|
c0117bfbb6 | ||
|
|
074234f182 | ||
|
|
bab03ba8c5 | ||
|
|
4878a283ed | ||
|
|
d3eed4f8c4 | ||
|
|
647db4d5de | ||
|
|
3455f83fa7 | ||
|
|
f730ba4043 | ||
|
|
caf87b1198 | ||
|
|
1706763a4c | ||
|
|
c1b8cdb35b | ||
|
|
936b7f5395 | ||
|
|
a01e3347f7 | ||
|
|
142411c08c | ||
|
|
29808c4421 | ||
|
|
48dcd174a7 | ||
|
|
6c85e6e702 | ||
|
|
e484e22e2f | ||
|
|
8ef54de812 | ||
|
|
c1d5a6e140 | ||
|
|
ea153f9df2 | ||
|
|
1ba9854070 | ||
|
|
012e955a05 | ||
|
|
37c0b49a99 | ||
|
|
de30ce9ef5 | ||
|
|
027a21278e | ||
|
|
f77fe62ab0 | ||
|
|
d42c35e1aa | ||
|
|
d5761b6385 | ||
|
|
bb3ab3b49c | ||
|
|
0441725045 | ||
|
|
ac4e6e89a4 | ||
|
|
12d21cf7a6 | ||
|
|
3fda3effda | ||
|
|
7e07b821aa | ||
|
|
e502a8b566 | ||
|
|
ec51ceb010 | ||
|
|
3fa037daee | ||
|
|
46c6955cfe | ||
|
|
252425c35c | ||
|
|
d309d472e1 | ||
|
|
898c4e8c6c | ||
|
|
56ac7b4649 | ||
|
|
9dd05b7101 | ||
|
|
e2daab9ce9 | ||
|
|
ef19ea7a85 | ||
|
|
01cf24f15f | ||
|
|
f870080497 | ||
|
|
c5942ec5a2 | ||
|
|
50d78fb804 | ||
|
|
8053aca3c3 | ||
|
|
0f3171edea | ||
|
|
6b478b9dda | ||
|
|
caaa9cea40 | ||
|
|
ea7f8f5552 | ||
|
|
69caca6017 | ||
|
|
703cfadf63 | ||
|
|
adfd461c3e | ||
|
|
bb79dbaa98 | ||
|
|
394de09907 | ||
|
|
560cafccaf | ||
|
|
0b09032164 | ||
|
|
1ee7162784 | ||
|
|
4949a131f8 | ||
|
|
8b241c3deb | ||
|
|
78c457b996 | ||
|
|
0be1b0b88b | ||
|
|
3ef0276b2a | ||
|
|
42d19f1a35 | ||
|
|
1cf9381cb0 | ||
|
|
f94dc125e5 | ||
|
|
96b978d55a | ||
|
|
ec367d333d | ||
|
|
9535097894 | ||
|
|
b541c27226 | ||
|
|
41b2af66d5 | ||
|
|
84b2b9c7a5 | ||
|
|
52c8d96dca | ||
|
|
8c6519a53b | ||
|
|
336adb9f85 | ||
|
|
7053006e77 | ||
|
|
bbf4804d12 | ||
|
|
7c16424ba3 | ||
|
|
de9dc686ac | ||
|
|
069e9b574f | ||
|
|
d5319a879a | ||
|
|
f64880aee1 | ||
|
|
fed1857918 | ||
|
|
9aeb40155e | ||
|
|
e49aeb1071 | ||
|
|
13f8ec6f3b | ||
|
|
f75c6df270 | ||
|
|
60b0afce95 | ||
|
|
f24653768f | ||
|
|
f135972a6b | ||
|
|
42c68e21a2 | ||
|
|
e8865060ca | ||
|
|
b07827a856 | ||
|
|
0735398e64 | ||
|
|
ec2d10e069 | ||
|
|
4cdb6151e5 | ||
|
|
e482b1bd18 | ||
|
|
9be3c13a07 | ||
|
|
8a4bcacf88 | ||
|
|
6b9b37b528 | ||
|
|
8e87c1ff19 | ||
|
|
aa648c1a49 | ||
|
|
056a590671 | ||
|
|
97f2503794 | ||
|
|
143079efbe | ||
|
|
7bcc173c5f | ||
|
|
0485f783b3 | ||
|
|
f3c07325bd | ||
|
|
e8eabb0102 | ||
|
|
b73f5e77ff | ||
|
|
d07712b308 | ||
|
|
89ba64d27f | ||
|
|
1a6f112d8a | ||
|
|
70e8681487 | ||
|
|
17ae0f72df | ||
|
|
466485e62e | ||
|
|
f6ee0845f2 | ||
|
|
df1eee1a74 | ||
|
|
46b46ba0b1 | ||
|
|
a763848644 | ||
|
|
9ba2d6c9fa | ||
|
|
fd1367e257 | ||
|
|
85dbc05ded | ||
|
|
1eea7786bc | ||
|
|
d039bedad4 | ||
|
|
ad6719426c | ||
|
|
719e4942ce | ||
|
|
ad9a08aea6 | ||
|
|
604fe709d5 | ||
|
|
81e8368099 | ||
|
|
2137ca8e3f | ||
|
|
672824539c | ||
|
|
1915be5aea | ||
|
|
bdea56370d | ||
|
|
e5e0c33ed6 | ||
|
|
d0c10e7523 | ||
|
|
91d2cea259 | ||
|
|
d9e2072733 | ||
|
|
820b124165 | ||
|
|
caf3eb7b05 | ||
|
|
1b5e63f024 | ||
|
|
93202f191c | ||
|
|
3033fb6791 | ||
|
|
87589609bf | ||
|
|
757514242d | ||
|
|
359d228632 | ||
|
|
24dda40863 | ||
|
|
6c0eebd4bf | ||
|
|
31db2a30bb | ||
|
|
5e241d79c8 | ||
|
|
96389ef83b | ||
|
|
af010712f3 | ||
|
|
c2896db558 | ||
|
|
57eebebcdd | ||
|
|
9ad9cbd7de | ||
|
|
1b5adde885 | ||
|
|
ca35dd79d2 | ||
|
|
491f2e3e76 | ||
|
|
b6591e21cd | ||
|
|
67ed89729d | ||
|
|
6033e75dc4 | ||
|
|
5f8993cb83 | ||
|
|
2446300dd9 | ||
|
|
b97c45df78 | ||
|
|
9034e82298 | ||
|
|
ae395f05e1 | ||
|
|
0c1d359091 | ||
|
|
dd8a9c67c9 | ||
|
|
de331bcd9e | ||
|
|
a81d6bc247 | ||
|
|
47d0b12d40 | ||
|
|
849e9aa734 | ||
|
|
351e098da1 | ||
|
|
c5f825eaa5 | ||
|
|
d0a4102efa | ||
|
|
3aa4181380 |
No files matched your search
@@ -1,3 +1,7 @@
|
||||
*.o
|
||||
*.so
|
||||
*.dll
|
||||
ssnes
|
||||
/config.h
|
||||
/config.mk
|
||||
/tools/ssnes-joyconfig
|
||||
@@ -3,3 +3,6 @@ Hans-Kristian Arntzen - <[email protected]>
|
||||
|
||||
Devin J. Pohly - <[email protected]>
|
||||
- Joypad axis support
|
||||
|
||||
Chris Moeller - <[email protected]>
|
||||
- CoreAudio audio driver
|
||||
@@ -2,45 +2,80 @@ include config.mk
|
||||
|
||||
TARGET = ssnes tools/ssnes-joyconfig
|
||||
|
||||
OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.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)
|
||||
|
||||
LIBS =
|
||||
LIBS = -lm
|
||||
DEFINES = -DHAVE_CONFIG_H
|
||||
|
||||
ifneq ($(findstring Darwin,$(OS)),)
|
||||
OSX := 1
|
||||
LIBS += -framework AppKit
|
||||
else
|
||||
OSX := 0
|
||||
endif
|
||||
|
||||
BSD_LOCAL_INC =
|
||||
DYLIB_LIB = -ldl
|
||||
ifneq ($(findstring BSD,$(OS)),)
|
||||
BSD_LOCAL_INC = -I/usr/local/include
|
||||
DYLIB_LIB = -lc
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SRC), 1)
|
||||
LIBS += $(SRC_LIBS)
|
||||
DEFINES += $(SRC_CFLAGS)
|
||||
else
|
||||
OBJ += audio/hermite.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_CONFIGFILE), 1)
|
||||
OBJ += conf/config_file.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_NETPLAY), 1)
|
||||
OBJ += netplay.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_RSOUND), 1)
|
||||
OBJ += audio/rsound.o
|
||||
LIBS += -lrsound
|
||||
LIBS += $(RSOUND_LIBS)
|
||||
DEFINES += $(RSOUND_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_OSS), 1)
|
||||
OBJ += audio/oss.o
|
||||
endif
|
||||
ifeq ($(HAVE_OSS_BSD), 1)
|
||||
OBJ += audio/oss.o
|
||||
endif
|
||||
ifeq ($(HAVE_OSS_LIB), 1)
|
||||
LIBS += -lossaudio
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_ALSA), 1)
|
||||
OBJ += audio/alsa.o
|
||||
LIBS += -lasound
|
||||
LIBS += $(ALSA_LIBS)
|
||||
DEFINES += $(ALSA_CFLAGS)
|
||||
endif
|
||||
ifeq ($(HAVE_ROAR), 1)
|
||||
OBJ += audio/roar.o
|
||||
LIBS += -lroar
|
||||
LIBS += $(ROAR_LIBS)
|
||||
DEFINES += $(ROAR_CFLAGS)
|
||||
endif
|
||||
ifeq ($(HAVE_AL), 1)
|
||||
OBJ += audio/openal.o
|
||||
ifeq ($(OSX),1)
|
||||
LIBS += -framework OpenAL
|
||||
else
|
||||
LIBS += -lopenal
|
||||
endif
|
||||
endif
|
||||
ifeq ($(HAVE_JACK),1)
|
||||
OBJ += audio/jack.o
|
||||
LIBS += -ljack
|
||||
LIBS += $(JACK_LIBS)
|
||||
DEFINES += $(JACK_CFLAGS)
|
||||
endif
|
||||
ifeq ($(HAVE_PULSE), 1)
|
||||
OBJ += audio/pulse.o
|
||||
@@ -48,10 +83,26 @@ ifeq ($(HAVE_PULSE), 1)
|
||||
DEFINES += $(PULSE_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_COREAUDIO), 1)
|
||||
OBJ += audio/coreaudio.o
|
||||
LIBS += -framework CoreServices -framework CoreAudio -framework AudioUnit
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL), 1)
|
||||
OBJ += gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o
|
||||
LIBS += $(SDL_LIBS) -lGL
|
||||
DEFINES += $(SDL_CFLAGS)
|
||||
OBJ += gfx/sdl.o gfx/gl.o input/sdl.o audio/sdl.o fifo_buffer.o
|
||||
DEFINES += $(SDL_CFLAGS) $(BSD_LOCAL_INC)
|
||||
LIBS += $(SDL_LIBS)
|
||||
ifeq ($(OSX),1)
|
||||
LIBS += -framework OpenGL
|
||||
else
|
||||
LIBS += -lGL
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XVIDEO), 1)
|
||||
OBJ += gfx/xvideo.o input/x11_input.o
|
||||
LIBS += $(XVIDEO_LIBS)
|
||||
DEFINES += $(XVIDEO_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_CG), 1)
|
||||
@@ -60,17 +111,14 @@ 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)
|
||||
OBJ += hqflt/hq.o
|
||||
OBJ += hqflt/grayscale.o
|
||||
OBJ += hqflt/bleed.o
|
||||
OBJ += hqflt/ntsc.o
|
||||
OBJ += hqflt/snes_ntsc/snes_ntsc.o
|
||||
ifeq ($(HAVE_DYLIB), 1)
|
||||
OBJ += gfx/ext.o audio/ext.o
|
||||
LIBS += $(DYLIB_LIB)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_FREETYPE), 1)
|
||||
@@ -79,23 +127,43 @@ 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)
|
||||
DEFINES += $(AVCODEC_CFLAGS) $(AVCORE_CFLAGS) $(AVFORMAT_CFLAGS) $(AVUTIL_CFLAGS) $(SWSCALE_CFLAGS)
|
||||
LIBS += $(AVCODEC_LIBS) $(AVFORMAT_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS)
|
||||
DEFINES += $(AVCODEC_CFLAGS) $(AVFORMAT_CFLAGS) $(AVUTIL_CFLAGS) $(SWSCALE_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DYNAMIC), 1)
|
||||
LIBS += -ldl
|
||||
LIBS += $(DYLIB_LIB)
|
||||
else
|
||||
LIBS += $(libsnes)
|
||||
endif
|
||||
|
||||
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 -std=gnu99 -I.
|
||||
CFLAGS += -Wall -O3 -g -I. -pedantic
|
||||
ifneq ($(findstring icc,$(CC)),)
|
||||
CFLAGS += -std=c99
|
||||
else
|
||||
CFLAGS += -std=gnu99
|
||||
endif
|
||||
|
||||
all: $(TARGET) config.mk
|
||||
|
||||
@@ -116,26 +184,30 @@ 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,ssnes-joyconfig}.1 $(DESTDIR)$(PREFIX)/share/man/man1
|
||||
install -m644 docs/ssnes.1 $(DESTDIR)$(PREFIX)/share/man/man1
|
||||
install -m644 docs/ssnes-joyconfig.1 $(DESTDIR)$(PREFIX)/share/man/man1
|
||||
install -m755 ssnes-zip $(DESTDIR)$(PREFIX)/bin
|
||||
|
||||
uninstall:
|
||||
rm -f $(DESTDIR)$(PREFIX)/bin/{ssnes,ssnes-joyconfig}
|
||||
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)
|
||||
|
||||
+71
-6
@@ -1,20 +1,29 @@
|
||||
TARGET = ssnes.exe
|
||||
JTARGET = ssnes-joyconfig.exe
|
||||
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o message.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_SRC = 1
|
||||
HAVE_DINPUT = 1
|
||||
HAVE_SRC = 0
|
||||
HAVE_SDL = 1
|
||||
HAVE_SDL_IMAGE = 1
|
||||
HAVE_XML = 1
|
||||
HAVE_FREETYPE = 1
|
||||
HAVE_XAUDIO = 1
|
||||
HAVE_DSOUND = 1
|
||||
HAVE_RSOUND = 1
|
||||
HAVE_DYLIB = 1
|
||||
HAVE_NETPLAY = 1
|
||||
HAVE_FBO = 1
|
||||
HAVE_CG = 1
|
||||
HAVE_PYTHON = 1
|
||||
HAVE_FFMPEG = 0
|
||||
libsnes ?= -lsnes
|
||||
|
||||
LIBS =
|
||||
LIBS = -lm
|
||||
DEFINES = -I. -DHAVE_CONFIGFILE
|
||||
LDFLAGS = -L. -static-libgcc -s
|
||||
|
||||
@@ -25,19 +34,38 @@ 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/gl.o input/sdl.o audio/sdl.o audio/buffer.o
|
||||
OBJ += gfx/sdl.o gfx/gl.o input/sdl.o audio/sdl.o fifo_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
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DSOUND), 1)
|
||||
OBJ += audio/dsound.o
|
||||
DEFINES += -DHAVE_DSOUND
|
||||
LIBS += -ldxguid -ldsound
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_RSOUND), 1)
|
||||
OBJ += audio/rsound.o
|
||||
DEFINES += -DHAVE_RSOUND
|
||||
@@ -45,11 +73,21 @@ 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_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
|
||||
@@ -62,6 +100,32 @@ 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 -Iffmpeg
|
||||
OBJ += input/dinput.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_FFMPEG), 1)
|
||||
LIBS += -lavformat -lavcodec -lavutil -lswscale -lws2_32 -lzlib1
|
||||
DEFINES += -DHAVE_FFMPEG
|
||||
OBJ += record/ffemu.o
|
||||
endif
|
||||
|
||||
ifneq ($(V),1)
|
||||
Q := @
|
||||
endif
|
||||
@@ -88,6 +152,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
|
||||
@@ -97,7 +162,7 @@ clean:
|
||||
rm -f tools/*.o
|
||||
|
||||
dist: all
|
||||
zip -r ssnes-win32-0.2.1.zip $(TARGET) ssnes.cfg snes.dll libxml2.dll iconv.dll zlib1.dll SDL.dll libsamplerate-0.dll freetype6.dll xaudio-c.dll rsound.dll pthreadGC2.dll README.win32.txt $(JTARGET)
|
||||
zip -r ssnes-win32-0.7.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
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
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_DSOUND = 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 fifo_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_DSOUND), 1)
|
||||
OBJ += audio/dsound.o
|
||||
DEFINES += -DHAVE_DSOUND
|
||||
LIBS += -ldxguid -ldsound
|
||||
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.7.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
|
||||
@@ -18,13 +18,13 @@ SSNES requires these libraries to build:
|
||||
|
||||
- [libsnes](http://byuu.org/bsnes/)
|
||||
- SDL
|
||||
- libsamplerate
|
||||
|
||||
SSNES can utilize these libraries if enabled:
|
||||
|
||||
- nvidia-cg-toolkit
|
||||
- libxml2 (bSNES XML shaders)
|
||||
- libfreetype2 (TTF font rendering on screen)
|
||||
- libsamplerate
|
||||
|
||||
SSNES needs at least one of these audio driver libraries:
|
||||
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@
|
||||
|
||||
#include "driver.h"
|
||||
#include <stdlib.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <asoundlib.h>
|
||||
#include "general.h"
|
||||
|
||||
#define TRY_ALSA(x) if ( x < 0 ) { \
|
||||
@@ -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 )
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
/* SSNES - A Super Nintendo Entertainment System (SNES) Emulator frontend for libsnes.
|
||||
* Copyright (C) 2010-2011 - Hans-Kristian Arntzen
|
||||
* Copyright (C) 2011 - Chris Moeller
|
||||
*
|
||||
* 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 "driver.h"
|
||||
#include "general.h"
|
||||
#include "fifo_buffer.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
#include <CoreServices/CoreServices.h>
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include <AudioUnit/AUComponent.h>
|
||||
|
||||
typedef struct coreaudio
|
||||
{
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t cond;
|
||||
|
||||
ComponentInstance dev;
|
||||
bool dev_alive;
|
||||
|
||||
fifo_buffer_t *buffer;
|
||||
bool nonblock;
|
||||
} coreaudio_t;
|
||||
|
||||
static void coreaudio_free(void *data)
|
||||
{
|
||||
coreaudio_t *dev = data;
|
||||
if (!dev)
|
||||
return;
|
||||
|
||||
if (dev->dev_alive)
|
||||
{
|
||||
AudioOutputUnitStop(dev->dev);
|
||||
CloseComponent(dev->dev);
|
||||
}
|
||||
|
||||
if (dev->buffer)
|
||||
fifo_free(dev->buffer);
|
||||
|
||||
pthread_mutex_destroy(&dev->lock);
|
||||
pthread_cond_destroy(&dev->cond);
|
||||
|
||||
free(dev);
|
||||
}
|
||||
|
||||
static OSStatus audio_cb(void *userdata, AudioUnitRenderActionFlags *action_flags,
|
||||
const AudioTimeStamp *time_stamp, UInt32 bus_number,
|
||||
UInt32 number_frames, AudioBufferList *io_data)
|
||||
{
|
||||
coreaudio_t *dev = userdata;
|
||||
(void)time_stamp;
|
||||
(void)bus_number;
|
||||
(void)number_frames;
|
||||
|
||||
if (!io_data)
|
||||
return noErr;
|
||||
if (io_data->mNumberBuffers != 1)
|
||||
return noErr;
|
||||
|
||||
unsigned write_avail = io_data->mBuffers[0].mDataByteSize;
|
||||
void *outbuf = io_data->mBuffers[0].mData;
|
||||
|
||||
pthread_mutex_lock(&dev->lock);
|
||||
if (fifo_read_avail(dev->buffer) < write_avail)
|
||||
{
|
||||
*action_flags = kAudioUnitRenderAction_OutputIsSilence;
|
||||
memset(outbuf, 0, write_avail); // Seems to be needed.
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
pthread_cond_signal(&dev->cond); // Technically possible to deadlock without.
|
||||
return noErr;
|
||||
}
|
||||
|
||||
fifo_read(dev->buffer, outbuf, write_avail);
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
pthread_cond_signal(&dev->cond);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static void* coreaudio_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
(void)device;
|
||||
|
||||
coreaudio_t *dev = calloc(1, sizeof(*dev));
|
||||
if (!dev)
|
||||
return NULL;
|
||||
|
||||
CFRunLoopRef run_loop = NULL;
|
||||
AudioObjectPropertyAddress addr = {
|
||||
kAudioHardwarePropertyRunLoop,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
pthread_mutex_init(&dev->lock, NULL);
|
||||
pthread_cond_init(&dev->cond, NULL);
|
||||
AudioObjectSetPropertyData(kAudioObjectSystemObject, &addr, 0, NULL,
|
||||
sizeof(CFRunLoopRef), &run_loop);
|
||||
|
||||
ComponentDescription desc = {
|
||||
.componentType = kAudioUnitType_Output,
|
||||
.componentSubType = kAudioUnitSubType_HALOutput,
|
||||
.componentManufacturer = kAudioUnitManufacturer_Apple,
|
||||
};
|
||||
|
||||
Component comp = FindNextComponent(NULL, &desc);
|
||||
if (comp == NULL)
|
||||
goto error;
|
||||
|
||||
OSStatus res = OpenAComponent(comp, &dev->dev);
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
dev->dev_alive = true;
|
||||
|
||||
AudioStreamBasicDescription stream_desc = {
|
||||
.mSampleRate = rate,
|
||||
.mBitsPerChannel = sizeof(float) * CHAR_BIT,
|
||||
.mChannelsPerFrame = 2,
|
||||
.mBytesPerPacket = 2 * sizeof(float),
|
||||
.mBytesPerFrame = 2 * sizeof(float),
|
||||
.mFramesPerPacket = 1,
|
||||
.mFormatID = kAudioFormatLinearPCM,
|
||||
.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | (is_little_endian() ? 0 : kAudioFormatFlagIsBigEndian),
|
||||
};
|
||||
|
||||
res = AudioUnitSetProperty(dev->dev, kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0, &stream_desc, sizeof(stream_desc));
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
AudioStreamBasicDescription real_desc;
|
||||
UInt32 i_size = sizeof(real_desc);
|
||||
res = AudioUnitGetProperty(dev->dev, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &real_desc, &i_size);
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
SSNES_LOG("[CoreAudio]: Using output sample rate of %.1f Hz\n", (float)real_desc.mSampleRate);
|
||||
g_settings.audio.out_rate = real_desc.mSampleRate;
|
||||
|
||||
if (real_desc.mChannelsPerFrame != stream_desc.mChannelsPerFrame)
|
||||
goto error;
|
||||
if (real_desc.mBitsPerChannel != stream_desc.mBitsPerChannel)
|
||||
goto error;
|
||||
if (real_desc.mFormatFlags != stream_desc.mFormatFlags)
|
||||
goto error;
|
||||
if (real_desc.mFormatID != stream_desc.mFormatID)
|
||||
goto error;
|
||||
|
||||
AudioChannelLayout layout = {
|
||||
.mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelBitmap,
|
||||
.mChannelBitmap = (1 << 2) - 1,
|
||||
};
|
||||
|
||||
res = AudioUnitSetProperty(dev->dev, kAudioUnitProperty_AudioChannelLayout,
|
||||
kAudioUnitScope_Input, 0, &layout, sizeof(layout));
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
AURenderCallbackStruct cb = {
|
||||
.inputProc = audio_cb,
|
||||
.inputProcRefCon = dev,
|
||||
};
|
||||
|
||||
res = AudioUnitSetProperty(dev->dev, kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0, &cb, sizeof(cb));
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
res = AudioUnitInitialize(dev->dev);
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
size_t fifo_size = (latency * g_settings.audio.out_rate) / 1000;
|
||||
fifo_size *= 2 * sizeof(float);
|
||||
|
||||
dev->buffer = fifo_new(fifo_size);
|
||||
if (!dev->buffer)
|
||||
goto error;
|
||||
|
||||
SSNES_LOG("[CoreAudio]: Using buffer size of %u bytes: (latency = %u ms)\n", (unsigned)fifo_size, latency);
|
||||
|
||||
res = AudioOutputUnitStart(dev->dev);
|
||||
if (res != noErr)
|
||||
goto error;
|
||||
|
||||
return dev;
|
||||
|
||||
error:
|
||||
SSNES_ERR("[CoreAudio]: Failed to initialize driver ...\n");
|
||||
coreaudio_free(dev);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ssize_t coreaudio_write(void* data, const void* buf_, size_t size)
|
||||
{
|
||||
coreaudio_t *dev = data;
|
||||
|
||||
const uint8_t *buf = buf_;
|
||||
size_t written = 0;
|
||||
|
||||
while (size > 0)
|
||||
{
|
||||
pthread_mutex_lock(&dev->lock);
|
||||
|
||||
size_t write_avail = fifo_write_avail(dev->buffer);
|
||||
if (write_avail > size)
|
||||
write_avail = size;
|
||||
|
||||
fifo_write(dev->buffer, buf, write_avail);
|
||||
buf += write_avail;
|
||||
written += write_avail;
|
||||
size -= write_avail;
|
||||
|
||||
if (dev->nonblock)
|
||||
{
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
break;
|
||||
}
|
||||
|
||||
if (write_avail == 0)
|
||||
pthread_cond_wait(&dev->cond, &dev->lock);
|
||||
pthread_mutex_unlock(&dev->lock);
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
static bool coreaudio_stop(void *data)
|
||||
{
|
||||
coreaudio_t *dev = data;
|
||||
return AudioOutputUnitStop(dev->dev) == noErr;
|
||||
}
|
||||
|
||||
static void coreaudio_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
coreaudio_t *dev = data;
|
||||
dev->nonblock = state;
|
||||
}
|
||||
|
||||
static bool coreaudio_start(void *data)
|
||||
{
|
||||
coreaudio_t *dev = data;
|
||||
return AudioOutputUnitStart(dev->dev) == noErr;
|
||||
}
|
||||
|
||||
static bool coreaudio_use_float(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
const audio_driver_t audio_coreaudio = {
|
||||
.init = coreaudio_init,
|
||||
.write = coreaudio_write,
|
||||
.stop = coreaudio_stop,
|
||||
.start = coreaudio_start,
|
||||
.set_nonblock_state = coreaudio_set_nonblock_state,
|
||||
.free = coreaudio_free,
|
||||
.use_float = coreaudio_use_float,
|
||||
.ident = "coreaudio"
|
||||
};
|
||||
|
||||
+387
@@ -0,0 +1,387 @@
|
||||
/* 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 "driver.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <dsound.h>
|
||||
#include "fifo_buffer.h"
|
||||
#include "general.h"
|
||||
|
||||
typedef struct dsound
|
||||
{
|
||||
LPDIRECTSOUND ds;
|
||||
LPDIRECTSOUNDBUFFER dsb;
|
||||
HANDLE event;
|
||||
bool nonblock;
|
||||
|
||||
fifo_buffer_t *buffer;
|
||||
CRITICAL_SECTION crit;
|
||||
|
||||
volatile bool thread_alive;
|
||||
HANDLE thread;
|
||||
unsigned buffer_size;
|
||||
} dsound_t;
|
||||
|
||||
static inline unsigned write_avail(unsigned read_ptr, unsigned write_ptr, unsigned buffer_size)
|
||||
{
|
||||
return (read_ptr + buffer_size - write_ptr) % buffer_size;
|
||||
}
|
||||
|
||||
static inline void get_positions(dsound_t *ds, DWORD *read_ptr, DWORD *write_ptr)
|
||||
{
|
||||
IDirectSoundBuffer_GetCurrentPosition(ds->dsb, read_ptr, write_ptr);
|
||||
}
|
||||
|
||||
#define CHUNK_SIZE 256
|
||||
|
||||
struct audio_lock
|
||||
{
|
||||
void *chunk1;
|
||||
DWORD size1;
|
||||
void *chunk2;
|
||||
DWORD size2;
|
||||
};
|
||||
|
||||
static inline bool grab_region(dsound_t *ds, DWORD write_ptr, struct audio_lock *region)
|
||||
{
|
||||
HRESULT res = IDirectSoundBuffer_Lock(ds->dsb, write_ptr, CHUNK_SIZE, ®ion->chunk1, ®ion->size1, ®ion->chunk2, ®ion->size2, 0);
|
||||
if (res == DSERR_BUFFERLOST)
|
||||
{
|
||||
res = IDirectSoundBuffer_Restore(ds->dsb);
|
||||
if (res != DS_OK)
|
||||
return false;
|
||||
|
||||
res = IDirectSoundBuffer_Lock(ds->dsb, write_ptr, CHUNK_SIZE, ®ion->chunk1, ®ion->size1, ®ion->chunk2, ®ion->size2, 0);
|
||||
if (res != DS_OK)
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *err;
|
||||
switch (res)
|
||||
{
|
||||
case DSERR_BUFFERLOST:
|
||||
err = "DSERR_BUFFERLOST";
|
||||
break;
|
||||
case DSERR_INVALIDCALL:
|
||||
err = "DSERR_INVALIDCALL";
|
||||
break;
|
||||
case DSERR_INVALIDPARAM:
|
||||
err = "DSERR_INVALIDPARAM";
|
||||
break;
|
||||
case DSERR_PRIOLEVELNEEDED:
|
||||
err = "DSERR_PRIOLEVELNEEDED";
|
||||
break;
|
||||
|
||||
default:
|
||||
err = NULL;
|
||||
}
|
||||
|
||||
if (err)
|
||||
{
|
||||
SSNES_WARN("[DirectSound error]: %s\n", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void release_region(dsound_t *ds, const struct audio_lock *region)
|
||||
{
|
||||
IDirectSoundBuffer_Unlock(ds->dsb, region->chunk1, region->size1, region->chunk2, region->size2);
|
||||
}
|
||||
|
||||
static DWORD CALLBACK dsound_thread(PVOID data)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
|
||||
|
||||
DWORD write_ptr;
|
||||
get_positions(ds, NULL, &write_ptr);
|
||||
write_ptr = (write_ptr + ds->buffer_size / 2) % ds->buffer_size;
|
||||
|
||||
while (ds->thread_alive)
|
||||
{
|
||||
DWORD read_ptr;
|
||||
get_positions(ds, &read_ptr, NULL);
|
||||
|
||||
DWORD avail = write_avail(read_ptr, write_ptr, ds->buffer_size);
|
||||
|
||||
EnterCriticalSection(&ds->crit);
|
||||
DWORD fifo_avail = fifo_read_avail(ds->buffer);
|
||||
LeaveCriticalSection(&ds->crit);
|
||||
|
||||
// No space to write, or we don't have data in our fifo, but we can wait some time before it underruns ...
|
||||
if (avail < CHUNK_SIZE || ((fifo_avail < CHUNK_SIZE) && (avail < ds->buffer_size / 2)))
|
||||
{
|
||||
Sleep(1);
|
||||
// We could opt for using the notification interface,
|
||||
// but it is not guaranteed to work, so use high priority sleeping patterns. :(
|
||||
}
|
||||
else if (fifo_avail < CHUNK_SIZE) // Got space to write, but nothing in FIFO (underrun), fill block with silence.
|
||||
{
|
||||
struct audio_lock region;
|
||||
if (!grab_region(ds, write_ptr, ®ion))
|
||||
{
|
||||
ds->thread_alive = false;
|
||||
SetEvent(ds->event);
|
||||
break;
|
||||
}
|
||||
|
||||
memset(region.chunk1, 0, region.size1);
|
||||
memset(region.chunk2, 0, region.size2);
|
||||
|
||||
release_region(ds, ®ion);
|
||||
write_ptr = (write_ptr + region.size1 + region.size2) % ds->buffer_size;
|
||||
}
|
||||
else // All is good. Pull from it and notify FIFO :D
|
||||
{
|
||||
struct audio_lock region;
|
||||
if (!grab_region(ds, write_ptr, ®ion))
|
||||
{
|
||||
ds->thread_alive = false;
|
||||
SetEvent(ds->event);
|
||||
break;
|
||||
}
|
||||
|
||||
EnterCriticalSection(&ds->crit);
|
||||
if (region.chunk1)
|
||||
fifo_read(ds->buffer, region.chunk1, region.size1);
|
||||
if (region.chunk2)
|
||||
fifo_read(ds->buffer, region.chunk2, region.size2);
|
||||
LeaveCriticalSection(&ds->crit);
|
||||
|
||||
release_region(ds, ®ion);
|
||||
write_ptr = (write_ptr + region.size1 + region.size2) % ds->buffer_size;
|
||||
|
||||
SetEvent(ds->event);
|
||||
}
|
||||
}
|
||||
|
||||
ExitThread(0);
|
||||
}
|
||||
|
||||
static void dsound_stop_thread(dsound_t *ds)
|
||||
{
|
||||
if (ds->thread)
|
||||
{
|
||||
ds->thread_alive = false;
|
||||
WaitForSingleObject(ds->thread, INFINITE);
|
||||
CloseHandle(ds->thread);
|
||||
ds->thread = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool dsound_start_thread(dsound_t *ds)
|
||||
{
|
||||
if (!ds->thread)
|
||||
{
|
||||
ds->thread_alive = true;
|
||||
ds->thread = CreateThread(NULL, 0, dsound_thread, ds, 0, NULL);
|
||||
if (ds->thread == NULL)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void dsound_clear_buffer(dsound_t *ds)
|
||||
{
|
||||
IDirectSoundBuffer_SetCurrentPosition(ds->dsb, 0);
|
||||
void *ptr;
|
||||
DWORD size;
|
||||
|
||||
if (IDirectSoundBuffer_Lock(ds->dsb, 0, 0, &ptr, &size, NULL, NULL, DSBLOCK_ENTIREBUFFER) == DS_OK)
|
||||
{
|
||||
memset(ptr, 0, size);
|
||||
IDirectSoundBuffer_Unlock(ds->dsb, ptr, size, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void dsound_free(void *data)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
if (ds)
|
||||
{
|
||||
if (ds->thread)
|
||||
{
|
||||
ds->thread_alive = false;
|
||||
WaitForSingleObject(ds->thread, INFINITE);
|
||||
CloseHandle(ds->thread);
|
||||
}
|
||||
|
||||
DeleteCriticalSection(&ds->crit);
|
||||
|
||||
if (ds->dsb)
|
||||
{
|
||||
IDirectSoundBuffer_Stop(ds->dsb);
|
||||
IDirectSoundBuffer_Release(ds->dsb);
|
||||
}
|
||||
|
||||
if (ds)
|
||||
IDirectSound_Release(ds->ds);
|
||||
|
||||
if (ds->event)
|
||||
CloseHandle(ds->event);
|
||||
|
||||
if (ds->buffer)
|
||||
fifo_free(ds->buffer);
|
||||
|
||||
free(ds);
|
||||
}
|
||||
}
|
||||
|
||||
static void* dsound_init(const char *device, unsigned rate, unsigned latency)
|
||||
{
|
||||
dsound_t *ds = calloc(1, sizeof(*ds));
|
||||
if (!ds)
|
||||
goto error;
|
||||
|
||||
InitializeCriticalSection(&ds->crit);
|
||||
|
||||
if (DirectSoundCreate(NULL, &ds->ds, NULL) != DS_OK)
|
||||
goto error;
|
||||
|
||||
if (IDirectSound_SetCooperativeLevel(ds->ds, GetDesktopWindow(), DSSCL_PRIORITY) != DS_OK)
|
||||
goto error;
|
||||
|
||||
WAVEFORMATEX wfx = {
|
||||
.wFormatTag = WAVE_FORMAT_PCM,
|
||||
.nChannels = 2,
|
||||
.nSamplesPerSec = rate,
|
||||
.wBitsPerSample = 16,
|
||||
.nBlockAlign = 2 * sizeof(int16_t),
|
||||
.nAvgBytesPerSec = rate * 2 * sizeof(int16_t),
|
||||
};
|
||||
|
||||
ds->buffer_size = (latency * wfx.nAvgBytesPerSec) / 1000;
|
||||
ds->buffer_size /= CHUNK_SIZE;
|
||||
ds->buffer_size *= CHUNK_SIZE;
|
||||
if (ds->buffer_size < 4 * CHUNK_SIZE)
|
||||
ds->buffer_size = 4 * CHUNK_SIZE;
|
||||
|
||||
SSNES_LOG("[DirectSound]: Setting buffer size of %u bytes\n", ds->buffer_size);
|
||||
SSNES_LOG("[DirectSound]: Latency = %u ms\n", (unsigned)((1000 * ds->buffer_size) / wfx.nAvgBytesPerSec));
|
||||
|
||||
DSBUFFERDESC bufdesc = {
|
||||
.dwSize = sizeof(DSBUFFERDESC),
|
||||
.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS,
|
||||
.dwBufferBytes = ds->buffer_size,
|
||||
.lpwfxFormat = &wfx,
|
||||
};
|
||||
|
||||
ds->event = CreateEvent(NULL, false, false, NULL);
|
||||
if (!ds->event)
|
||||
goto error;
|
||||
|
||||
ds->buffer = fifo_new(4 * 1024);
|
||||
if (!ds->buffer)
|
||||
goto error;
|
||||
|
||||
if (IDirectSound_CreateSoundBuffer(ds->ds, &bufdesc, &ds->dsb, 0) != DS_OK)
|
||||
goto error;
|
||||
|
||||
IDirectSoundBuffer_SetCurrentPosition(ds->dsb, 0);
|
||||
|
||||
dsound_clear_buffer(ds);
|
||||
|
||||
if (IDirectSoundBuffer_Play(ds->dsb, 0, 0, DSBPLAY_LOOPING) != DS_OK)
|
||||
goto error;
|
||||
|
||||
if (!dsound_start_thread(ds))
|
||||
goto error;
|
||||
|
||||
return ds;
|
||||
|
||||
error:
|
||||
SSNES_ERR("[DirectSound] Error occured in init!\n");
|
||||
dsound_free(ds);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool dsound_stop(void *data)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
dsound_stop_thread(ds);
|
||||
return IDirectSoundBuffer_Stop(ds->dsb) == DS_OK;
|
||||
}
|
||||
|
||||
static bool dsound_start(void *data)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
dsound_clear_buffer(ds);
|
||||
|
||||
if (!dsound_start_thread(ds))
|
||||
return false;
|
||||
|
||||
return IDirectSoundBuffer_Play(ds->dsb, 0, 0, DSBPLAY_LOOPING) == DS_OK;
|
||||
}
|
||||
|
||||
static void dsound_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
ds->nonblock = state;
|
||||
}
|
||||
|
||||
static ssize_t dsound_write(void *data, const void *buf_, size_t size)
|
||||
{
|
||||
dsound_t *ds = data;
|
||||
const uint8_t *buf = buf_;
|
||||
|
||||
if (!ds->thread_alive)
|
||||
return -1;
|
||||
|
||||
size_t written = 0;
|
||||
while (size > 0)
|
||||
{
|
||||
EnterCriticalSection(&ds->crit);
|
||||
size_t avail = fifo_write_avail(ds->buffer);
|
||||
if (avail > size)
|
||||
avail = size;
|
||||
|
||||
fifo_write(ds->buffer, buf, avail);
|
||||
LeaveCriticalSection(&ds->crit);
|
||||
|
||||
buf += avail;
|
||||
size -= avail;
|
||||
written += avail;
|
||||
|
||||
if (ds->nonblock || !ds->thread_alive)
|
||||
break;
|
||||
|
||||
if (avail == 0)
|
||||
WaitForSingleObject(ds->event, INFINITE);
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
|
||||
const audio_driver_t audio_dsound = {
|
||||
.init = dsound_init,
|
||||
.write = dsound_write,
|
||||
.stop = dsound_stop,
|
||||
.start = dsound_start,
|
||||
.set_nonblock_state = dsound_set_nonblock_state,
|
||||
.free = dsound_free,
|
||||
.ident = "dsound"
|
||||
};
|
||||
|
||||
+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,129 @@
|
||||
/////
|
||||
// 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 3
|
||||
|
||||
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;
|
||||
|
||||
// Called every frame, allows creating a GUI main loop in the main thread.
|
||||
// GUI events can be processed here in a non-blocking fashion.
|
||||
// Can be set to NULL to ignore it.
|
||||
void (*events)(void *data);
|
||||
} 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
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
// Hermite resampler based on bsnes' audio library.
|
||||
|
||||
#include "hermite.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#define MAX_CHANS 8
|
||||
|
||||
struct hermite_resampler
|
||||
{
|
||||
float chan_data[MAX_CHANS][4];
|
||||
double r_frac;
|
||||
unsigned chans;
|
||||
};
|
||||
|
||||
static inline float hermite_kernel(float mu1, float a, float b, float c, float d)
|
||||
{
|
||||
float mu2, mu3, m0, m1, a0, a1, a2, a3;
|
||||
|
||||
mu2 = mu1 * mu1;
|
||||
mu3 = mu2 * mu1;
|
||||
|
||||
m0 = (c - a) * 0.5;
|
||||
m1 = (d - b) * 0.5;
|
||||
|
||||
a0 = +2 * mu3 - 3 * mu2 + 1;
|
||||
a1 = mu3 - 2 * mu2 + mu1;
|
||||
a2 = mu3 - mu2;
|
||||
a3 = -2 * mu3 + 3 * mu2;
|
||||
|
||||
return (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c);
|
||||
}
|
||||
|
||||
hermite_resampler_t *hermite_new(unsigned channels)
|
||||
{
|
||||
if (channels > MAX_CHANS)
|
||||
return NULL;
|
||||
|
||||
hermite_resampler_t *re = calloc(1, sizeof(*re));
|
||||
if (!re)
|
||||
return NULL;
|
||||
re->chans = channels;
|
||||
return re;
|
||||
}
|
||||
|
||||
void hermite_process(hermite_resampler_t *re, struct hermite_data *data)
|
||||
{
|
||||
double r_step = 1.0 / data->src_ratio;
|
||||
size_t processed_out = 0;
|
||||
size_t processed_in = 0;
|
||||
|
||||
size_t in_frames = data->input_frames;
|
||||
size_t out_frames = data->output_frames;
|
||||
const float *in_data = data->data_in;
|
||||
float *out_data = data->data_out;
|
||||
|
||||
while (processed_in < in_frames && processed_out < out_frames)
|
||||
{
|
||||
while (re->r_frac <= 1.0 && processed_out < out_frames)
|
||||
{
|
||||
re->r_frac += r_step;
|
||||
for (unsigned i = 0; i < re->chans; i++)
|
||||
{
|
||||
float res = hermite_kernel(re->r_frac,
|
||||
re->chan_data[i][0], re->chan_data[i][1], re->chan_data[i][2], re->chan_data[i][3]);
|
||||
*out_data++ = res;
|
||||
}
|
||||
processed_out++;
|
||||
}
|
||||
|
||||
if (re->r_frac >= 1.0)
|
||||
{
|
||||
re->r_frac -= 1.0;
|
||||
|
||||
for (unsigned i = 0; i < re->chans; i++)
|
||||
{
|
||||
re->chan_data[i][0] = re->chan_data[i][1];
|
||||
re->chan_data[i][1] = re->chan_data[i][2];
|
||||
re->chan_data[i][2] = re->chan_data[i][3];
|
||||
re->chan_data[i][3] = *in_data++;
|
||||
}
|
||||
processed_in++;
|
||||
}
|
||||
}
|
||||
|
||||
data->input_frames_used = processed_in;
|
||||
data->output_frames_gen = processed_out;
|
||||
}
|
||||
|
||||
void hermite_free(hermite_resampler_t *re)
|
||||
{
|
||||
free(re);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
// Hermite resampler based on bsnes' audio library.
|
||||
// Attempts to be similiar to the libsamplerate process interface.
|
||||
|
||||
#ifndef __SSNES_HERMITE_H
|
||||
#define __SSNES_HERMITE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct hermite_resampler hermite_resampler_t;
|
||||
|
||||
hermite_resampler_t *hermite_new(unsigned channels);
|
||||
|
||||
struct hermite_data
|
||||
{
|
||||
const float *data_in;
|
||||
float *data_out;
|
||||
|
||||
size_t input_frames;
|
||||
size_t output_frames;
|
||||
|
||||
size_t input_frames_used;
|
||||
size_t output_frames_gen;
|
||||
|
||||
bool end_of_input; // Just used to clone the SRC API.
|
||||
double src_ratio;
|
||||
};
|
||||
|
||||
void hermite_process(hermite_resampler_t *re, struct hermite_data *data);
|
||||
void hermite_free(hermite_resampler_t *re);
|
||||
|
||||
#endif
|
||||
|
||||
+27
-20
@@ -103,7 +103,15 @@ static size_t find_buffersize(jack_t *jd, int latency)
|
||||
{
|
||||
int frames = latency * g_settings.audio.out_rate / 1000;
|
||||
|
||||
int jack_latency = jack_port_get_total_latency(jd->client, jd->ports[0]);
|
||||
jack_latency_range_t range;
|
||||
int jack_latency = 0;
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
jack_port_get_latency_range(jd->ports[i], JackPlaybackLatency, &range);
|
||||
if (range.max > jack_latency)
|
||||
jack_latency = range.max;
|
||||
}
|
||||
|
||||
SSNES_LOG("JACK: Jack latency is %d frames.\n", jack_latency);
|
||||
|
||||
int buffer_frames = frames - jack_latency;
|
||||
@@ -116,12 +124,15 @@ 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 )
|
||||
if (!jd)
|
||||
return NULL;
|
||||
|
||||
pthread_cond_init(&jd->cond, NULL);
|
||||
pthread_mutex_init(&jd->cond_lock, NULL);
|
||||
|
||||
const char **jports = NULL;
|
||||
|
||||
jd->client = jack_client_open("SSNES", JackNullOption, NULL);
|
||||
@@ -140,7 +151,6 @@ static void* __jack_init(const char* device, int rate, int latency)
|
||||
SSNES_ERR("Failed to register ports.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
||||
char *dest_ports[2];
|
||||
jports = jack_get_ports(jd->client, NULL, NULL, JackPortIsPhysical | JackPortIsInput);
|
||||
@@ -150,6 +160,18 @@ static void* __jack_init(const char* device, int rate, int latency)
|
||||
goto error;
|
||||
}
|
||||
|
||||
size_t bufsize = find_buffersize(jd, latency);
|
||||
SSNES_LOG("JACK: Internal buffer size: %d frames.\n", (int)(bufsize / sizeof(jack_default_audio_sample_t)));
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
jd->buffer[i] = jack_ringbuffer_create(bufsize);
|
||||
if (jd->buffer[i] == NULL)
|
||||
{
|
||||
SSNES_ERR("Failed to create buffers.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
int parsed = parse_ports(dest_ports, jports);
|
||||
|
||||
if (jack_activate(jd->client) < 0)
|
||||
@@ -170,22 +192,7 @@ static void* __jack_init(const char* device, int rate, int latency)
|
||||
for (int i = 0; i < parsed; i++)
|
||||
free(dest_ports[i]);
|
||||
|
||||
size_t bufsize = find_buffersize(jd, latency);
|
||||
|
||||
SSNES_LOG("JACK: Internal buffer size: %d frames.\n", (int)(bufsize / sizeof(jack_default_audio_sample_t)));
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
jd->buffer[i] = jack_ringbuffer_create(bufsize);
|
||||
if (jd->buffer[i] == NULL)
|
||||
{
|
||||
SSNES_ERR("Failed to create buffers.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_cond_init(&jd->cond, NULL);
|
||||
pthread_mutex_init(&jd->cond_lock, NULL);
|
||||
|
||||
|
||||
jack_free(jports);
|
||||
return jd;
|
||||
|
||||
|
||||
+8
-1
@@ -17,8 +17,15 @@
|
||||
|
||||
|
||||
#include "driver.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenAL/al.h>
|
||||
#include <OpenAL/alc.h>
|
||||
#else
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#endif
|
||||
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -49,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));
|
||||
|
||||
+30
-18
@@ -15,28 +15,44 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "driver.h"
|
||||
#include "general.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef HAVE_OSS_BSD
|
||||
#include <soundcard.h>
|
||||
#else
|
||||
#include <sys/soundcard.h>
|
||||
#endif
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static void* __oss_init(const char* device, int rate, int latency)
|
||||
#ifdef HAVE_OSS_BSD
|
||||
#define DEFAULT_OSS_DEV "/dev/audio"
|
||||
#else
|
||||
#define DEFAULT_OSS_DEV "/dev/dsp"
|
||||
#endif
|
||||
|
||||
static void* __oss_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
int *fd = calloc(1, sizeof(int));
|
||||
if ( fd == NULL )
|
||||
if (fd == NULL)
|
||||
return NULL;
|
||||
|
||||
const char *oss_device = "/dev/dsp";
|
||||
const char *oss_device = DEFAULT_OSS_DEV;
|
||||
|
||||
if ( device != NULL )
|
||||
if (device != NULL)
|
||||
oss_device = device;
|
||||
|
||||
if ( (*fd = open(oss_device, O_WRONLY)) < 0 )
|
||||
if ((*fd = open(oss_device, O_WRONLY)) < 0)
|
||||
{
|
||||
free(fd);
|
||||
return NULL;
|
||||
@@ -45,7 +61,7 @@ static void* __oss_init(const char* device, int rate, int latency)
|
||||
int frags = (latency * rate * 4)/(1000 * (1 << 9));
|
||||
int frag = (frags << 16) | 9;
|
||||
|
||||
if ( ioctl(*fd, SNDCTL_DSP_SETFRAGMENT, &frag) < 0 )
|
||||
if (ioctl(*fd, SNDCTL_DSP_SETFRAGMENT, &frag) < 0)
|
||||
{
|
||||
close(*fd);
|
||||
free(fd);
|
||||
@@ -53,23 +69,24 @@ static void* __oss_init(const char* device, int rate, int latency)
|
||||
}
|
||||
|
||||
int channels = 2;
|
||||
int format = AFMT_S16_LE;
|
||||
int format = is_little_endian() ?
|
||||
AFMT_S16_LE : AFMT_S16_BE;
|
||||
|
||||
if ( ioctl(*fd, SNDCTL_DSP_CHANNELS, &channels) < 0 )
|
||||
if (ioctl(*fd, SNDCTL_DSP_CHANNELS, &channels) < 0)
|
||||
{
|
||||
close(*fd);
|
||||
free(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ( ioctl(*fd, SNDCTL_DSP_SETFMT, &format) < 0 )
|
||||
if (ioctl(*fd, SNDCTL_DSP_SETFMT, &format) < 0)
|
||||
{
|
||||
close(*fd);
|
||||
free(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ( ioctl(*fd, SNDCTL_DSP_SPEED, &rate) < 0 )
|
||||
if (ioctl(*fd, SNDCTL_DSP_SPEED, &rate) < 0)
|
||||
{
|
||||
close(*fd);
|
||||
free(fd);
|
||||
@@ -83,13 +100,13 @@ static ssize_t __oss_write(void* data, const void* buf, size_t size)
|
||||
{
|
||||
int *fd = data;
|
||||
|
||||
if ( size == 0 )
|
||||
if (size == 0)
|
||||
return 0;
|
||||
|
||||
ssize_t ret;
|
||||
if ( (ret = write(*fd, buf, size)) <= 0 )
|
||||
if ((ret = write(*fd, buf, size)) <= 0)
|
||||
{
|
||||
if ( (fcntl(*fd, F_GETFL) & O_NONBLOCK) && errno == EAGAIN )
|
||||
if ((fcntl(*fd, F_GETFL) & O_NONBLOCK) && errno == EAGAIN)
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
@@ -139,9 +156,4 @@ const audio_driver_t audio_oss = {
|
||||
.free = __oss_free,
|
||||
.ident = "oss"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+1
-13
@@ -59,18 +59,6 @@ static void __pulse_free(void *data)
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint8_t is_little_endian(void)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint16_t x;
|
||||
uint8_t y[2];
|
||||
} u;
|
||||
|
||||
u.x = 1;
|
||||
return u.y[0];
|
||||
}
|
||||
|
||||
static void context_state_cb(pa_context *c, void *data)
|
||||
{
|
||||
pa_t *pa = data;
|
||||
@@ -115,7 +103,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)
|
||||
|
||||
+11
-8
@@ -22,6 +22,7 @@
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include "general.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
@@ -29,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));
|
||||
@@ -37,13 +38,14 @@ static void* __roar_init(const char* device, int rate, int latency)
|
||||
return NULL;
|
||||
|
||||
roar_vs_t *vss;
|
||||
if ( (vss = roar_vs_new_simple(NULL, NULL, rate, 2, ROAR_CODEC_PCM_S_LE, 16, ROAR_DIR_PLAY, &err)) == NULL )
|
||||
if ((vss = roar_vs_new_simple(device, "SSNES", rate, 2, ROAR_CODEC_PCM_S, 16, ROAR_DIR_PLAY, &err)) == NULL)
|
||||
{
|
||||
fprintf(stderr, "roar_vs: \"%s\"\n", roar_vs_strerr(err));
|
||||
SSNES_ERR("RoarAudio: \"%s\"\n", roar_vs_strerr(err));
|
||||
free(roar);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
roar_vs_role(vss, ROAR_ROLE_GAME, NULL);
|
||||
roar->vss = vss;
|
||||
|
||||
return roar;
|
||||
@@ -54,19 +56,20 @@ static ssize_t __roar_write(void* data, const void* buf, size_t size)
|
||||
roar_t *roar = data;
|
||||
ssize_t rc;
|
||||
|
||||
if ( size == 0 )
|
||||
if (size == 0)
|
||||
return 0;
|
||||
|
||||
int err;
|
||||
size_t written = 0;
|
||||
while (written < size)
|
||||
{
|
||||
if ((rc = roar_vs_write(roar->vss, (const char*)buf + written, size - written, &err)) < size)
|
||||
size_t write_amt = size - written;
|
||||
if ((rc = roar_vs_write(roar->vss, (const char*)buf + written, write_amt, &err)) < write_amt)
|
||||
{
|
||||
if (rc < 0)
|
||||
if (roar->nonblocking)
|
||||
return rc;
|
||||
else if (rc < 0)
|
||||
return -1;
|
||||
else if (roar->nonblocking)
|
||||
return 0;
|
||||
}
|
||||
written += rc;
|
||||
}
|
||||
|
||||
+78
-31
@@ -19,16 +19,42 @@
|
||||
#include "driver.h"
|
||||
#include <stdlib.h>
|
||||
#include <rsound.h>
|
||||
#include "fifo_buffer.h"
|
||||
#include <stdbool.h>
|
||||
#include "SDL.h"
|
||||
|
||||
typedef struct rsd
|
||||
{
|
||||
rsound_t *rd;
|
||||
int latency;
|
||||
int rate;
|
||||
int nonblock;
|
||||
bool nonblock;
|
||||
volatile bool has_error;
|
||||
|
||||
fifo_buffer_t *buffer;
|
||||
|
||||
SDL_mutex *cond_lock;
|
||||
SDL_cond *cond;
|
||||
} rsd_t;
|
||||
|
||||
static void* __rsd_init(const char* device, int rate, int latency)
|
||||
static ssize_t audio_cb(void *data, size_t bytes, void *userdata)
|
||||
{
|
||||
rsd_t *rsd = userdata;
|
||||
|
||||
size_t avail = fifo_read_avail(rsd->buffer);
|
||||
size_t write_size = bytes > avail ? avail : bytes;
|
||||
fifo_read(rsd->buffer, data, write_size);
|
||||
SDL_CondSignal(rsd->cond);
|
||||
|
||||
return write_size;
|
||||
}
|
||||
|
||||
static void err_cb(void *userdata)
|
||||
{
|
||||
rsd_t *rsd = userdata;
|
||||
rsd->has_error = true;
|
||||
SDL_CondSignal(rsd->cond);
|
||||
}
|
||||
|
||||
static void* __rsd_init(const char* device, unsigned rate, unsigned latency)
|
||||
{
|
||||
rsd_t *rsd = calloc(1, sizeof(rsd_t));
|
||||
if ( rsd == NULL )
|
||||
@@ -42,17 +68,25 @@ static void* __rsd_init(const char* device, int rate, int latency)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rsd->cond_lock = SDL_CreateMutex();
|
||||
rsd->cond = SDL_CreateCond();
|
||||
|
||||
rsd->buffer = fifo_new(1024 * 4);
|
||||
|
||||
int channels = 2;
|
||||
int format = RSD_S16_NE;
|
||||
|
||||
rsd_set_param(rd, RSD_CHANNELS, &channels);
|
||||
rsd_set_param(rd, RSD_SAMPLERATE, &rate);
|
||||
rsd_set_param(rd, RSD_LATENCY, &latency);
|
||||
|
||||
if ( device != NULL )
|
||||
rsd_set_param(rd, RSD_HOST, (void*)device);
|
||||
|
||||
rsd_set_param(rd, RSD_FORMAT, &format);
|
||||
|
||||
rsd_set_callback(rd, audio_cb, err_cb, 256, rsd);
|
||||
|
||||
if ( rsd_start(rd) < 0 )
|
||||
{
|
||||
free(rsd);
|
||||
@@ -60,14 +94,7 @@ static void* __rsd_init(const char* device, int rate, int latency)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int min_latency = (rsd_delay_ms(rd) > latency) ? (rsd_delay_ms(rd) * 3 / 2) : latency;
|
||||
|
||||
rsd_set_param(rd, RSD_LATENCY, &min_latency);
|
||||
|
||||
rsd->rd = rd;
|
||||
rsd->latency = min_latency;
|
||||
rsd->rate = rate;
|
||||
|
||||
return rsd;
|
||||
}
|
||||
|
||||
@@ -75,26 +102,46 @@ static ssize_t __rsd_write(void* data, const void* buf, size_t size)
|
||||
{
|
||||
rsd_t *rsd = data;
|
||||
|
||||
if ( (rsd_delay_ms(rsd->rd) > rsd->latency || rsd_get_avail(rsd->rd) < size) && rsd->nonblock )
|
||||
return 0;
|
||||
|
||||
if ( size == 0 )
|
||||
return 0;
|
||||
|
||||
rsd_delay_wait(rsd->rd);
|
||||
if ( rsd_write(rsd->rd, buf, size) == 0 )
|
||||
if (rsd->has_error)
|
||||
return -1;
|
||||
|
||||
if ( rsd_delay_ms(rsd->rd) < rsd->latency/2 )
|
||||
if (rsd->nonblock)
|
||||
{
|
||||
int ms = rsd->latency/2;
|
||||
size_t size = (ms * rsd->rate * 4) / 1000;
|
||||
void *temp = calloc(1, size);
|
||||
rsd_write(rsd->rd, temp, size);
|
||||
free(temp);
|
||||
rsd_callback_lock(rsd->rd);
|
||||
size_t avail = fifo_write_avail(rsd->buffer);
|
||||
size_t write_amt = avail > size ? size : avail;
|
||||
fifo_write(rsd->buffer, buf, write_amt);
|
||||
rsd_callback_unlock(rsd->rd);
|
||||
return write_amt;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t written = 0;
|
||||
while (written < size && !rsd->has_error)
|
||||
{
|
||||
rsd_callback_lock(rsd->rd);
|
||||
size_t avail = fifo_write_avail(rsd->buffer);
|
||||
|
||||
return size;
|
||||
if (avail == 0)
|
||||
{
|
||||
rsd_callback_unlock(rsd->rd);
|
||||
if (!rsd->has_error)
|
||||
{
|
||||
SDL_mutexP(rsd->cond_lock);
|
||||
SDL_CondWait(rsd->cond, rsd->cond_lock);
|
||||
SDL_mutexV(rsd->cond_lock);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t write_amt = size - written > avail ? avail : size - written;
|
||||
fifo_write(rsd->buffer, (const char*)buf + written, write_amt);
|
||||
rsd_callback_unlock(rsd->rd);
|
||||
written += write_amt;
|
||||
}
|
||||
}
|
||||
return written;
|
||||
}
|
||||
}
|
||||
|
||||
static bool __rsd_stop(void *data)
|
||||
@@ -126,6 +173,11 @@ static void __rsd_free(void *data)
|
||||
|
||||
rsd_stop(rsd->rd);
|
||||
rsd_free(rsd->rd);
|
||||
|
||||
fifo_free(rsd->buffer);
|
||||
SDL_DestroyMutex(rsd->cond_lock);
|
||||
SDL_DestroyCond(rsd->cond);
|
||||
|
||||
free(rsd);
|
||||
}
|
||||
|
||||
@@ -138,9 +190,4 @@ const audio_driver_t audio_rsound = {
|
||||
.free = __rsd_free,
|
||||
.ident = "rsound"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+12
-30
@@ -27,7 +27,7 @@
|
||||
#include "SDL_audio.h"
|
||||
#include "SDL_thread.h"
|
||||
#include "general.h"
|
||||
#include "buffer.h"
|
||||
#include "fifo_buffer.h"
|
||||
|
||||
typedef struct sdl_audio
|
||||
{
|
||||
@@ -35,35 +35,22 @@ typedef struct sdl_audio
|
||||
|
||||
SDL_mutex *lock;
|
||||
SDL_cond *cond;
|
||||
rsound_fifo_buffer_t *buffer;
|
||||
fifo_buffer_t *buffer;
|
||||
} sdl_audio_t;
|
||||
|
||||
static void sdl_audio_cb(void *data, Uint8 *stream, int len)
|
||||
{
|
||||
sdl_audio_t *sdl = data;
|
||||
|
||||
size_t avail = rsnd_fifo_read_avail(sdl->buffer);
|
||||
size_t avail = fifo_read_avail(sdl->buffer);
|
||||
size_t write_size = len > avail ? avail : len;
|
||||
rsnd_fifo_read(sdl->buffer, stream, write_size);
|
||||
fifo_read(sdl->buffer, stream, write_size);
|
||||
SDL_CondSignal(sdl->cond);
|
||||
|
||||
// If underrun, fill rest with silence.
|
||||
memset(stream + write_size, 0, len - write_size);
|
||||
}
|
||||
|
||||
// Interesting hack from http://www-graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||
static inline uint32_t next_pow2(uint32_t v)
|
||||
{
|
||||
v--;
|
||||
v |= v >> 1;
|
||||
v |= v >> 2;
|
||||
v |= v >> 4;
|
||||
v |= v >> 8;
|
||||
v |= v >> 16;
|
||||
v++;
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline int find_num_frames(int rate, int latency)
|
||||
{
|
||||
int frames = (rate * latency) / 1000;
|
||||
@@ -71,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)
|
||||
@@ -111,10 +98,10 @@ static void* sdl_audio_init(const char* device, int rate, int latency)
|
||||
// Create a buffer twice as big as needed and prefill the buffer.
|
||||
size_t bufsize = out.samples * 4 * sizeof(int16_t);
|
||||
void *tmp = calloc(1, bufsize);
|
||||
sdl->buffer = rsnd_fifo_new(bufsize);
|
||||
sdl->buffer = fifo_new(bufsize);
|
||||
if (tmp)
|
||||
{
|
||||
rsnd_fifo_write(sdl->buffer, tmp, bufsize);
|
||||
fifo_write(sdl->buffer, tmp, bufsize);
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
@@ -130,9 +117,9 @@ static ssize_t sdl_audio_write(void* data, const void* buf, size_t size)
|
||||
if (sdl->nonblock)
|
||||
{
|
||||
SDL_LockAudio();
|
||||
size_t avail = rsnd_fifo_write_avail(sdl->buffer);
|
||||
size_t avail = fifo_write_avail(sdl->buffer);
|
||||
size_t write_amt = avail > size ? size : avail;
|
||||
rsnd_fifo_write(sdl->buffer, buf, write_amt);
|
||||
fifo_write(sdl->buffer, buf, write_amt);
|
||||
SDL_UnlockAudio();
|
||||
ret = write_amt;
|
||||
}
|
||||
@@ -142,7 +129,7 @@ static ssize_t sdl_audio_write(void* data, const void* buf, size_t size)
|
||||
while (written < size)
|
||||
{
|
||||
SDL_LockAudio();
|
||||
size_t avail = rsnd_fifo_write_avail(sdl->buffer);
|
||||
size_t avail = fifo_write_avail(sdl->buffer);
|
||||
|
||||
if (avail == 0)
|
||||
{
|
||||
@@ -154,7 +141,7 @@ static ssize_t sdl_audio_write(void* data, const void* buf, size_t size)
|
||||
else
|
||||
{
|
||||
size_t write_amt = size - written > avail ? avail : size - written;
|
||||
rsnd_fifo_write(sdl->buffer, (const char*)buf + written, write_amt);
|
||||
fifo_write(sdl->buffer, (const char*)buf + written, write_amt);
|
||||
SDL_UnlockAudio();
|
||||
written += write_amt;
|
||||
}
|
||||
@@ -193,7 +180,7 @@ static void sdl_audio_free(void *data)
|
||||
sdl_audio_t *sdl = data;
|
||||
if (sdl)
|
||||
{
|
||||
rsnd_fifo_free(sdl->buffer);
|
||||
fifo_free(sdl->buffer);
|
||||
SDL_DestroyMutex(sdl->lock);
|
||||
SDL_DestroyCond(sdl->cond);
|
||||
}
|
||||
@@ -209,9 +196,4 @@ const audio_driver_t audio_sdl = {
|
||||
.free = sdl_audio_free,
|
||||
.ident = "sdl"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+4
-14
@@ -67,24 +67,14 @@ static bool init_lib(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Interesting hack from http://www-graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||
static inline uint32_t next_pow2(uint32_t v)
|
||||
{
|
||||
v--;
|
||||
v |= v >> 1;
|
||||
v |= v >> 2;
|
||||
v |= v >> 4;
|
||||
v |= v >> 8;
|
||||
v |= v >> 16;
|
||||
v++;
|
||||
return v;
|
||||
}
|
||||
|
||||
static void* __xa_init(const char* device, int rate, int latency)
|
||||
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;
|
||||
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
/* 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 "autosave.h"
|
||||
#include "SDL.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "general.h"
|
||||
|
||||
struct autosave
|
||||
{
|
||||
volatile bool quit;
|
||||
SDL_mutex *lock;
|
||||
|
||||
SDL_mutex *cond_lock;
|
||||
SDL_cond *cond;
|
||||
SDL_Thread *thread;
|
||||
|
||||
void *buffer;
|
||||
const void *snes_buffer;
|
||||
const char *path;
|
||||
size_t bufsize;
|
||||
unsigned interval;
|
||||
};
|
||||
|
||||
static int autosave_thread(void *data)
|
||||
{
|
||||
autosave_t *save = data;
|
||||
while (!save->quit)
|
||||
{
|
||||
autosave_lock(save);
|
||||
memcpy(save->buffer, save->snes_buffer, save->bufsize);
|
||||
autosave_unlock(save);
|
||||
|
||||
// Should probably deal with this more elegantly.
|
||||
FILE *file = fopen(save->path, "wb");
|
||||
if (file)
|
||||
{
|
||||
SSNES_LOG("Autosaving SRAM to \"%s\"\n", save->path);
|
||||
bool failed = false;
|
||||
failed |= fwrite(save->buffer, 1, save->bufsize, file) != save->bufsize;
|
||||
failed |= fflush(file) != 0;
|
||||
failed |= fclose(file) != 0;
|
||||
if (failed)
|
||||
SSNES_WARN("Failed to autosave SRAM! Disk might be full.\n");
|
||||
}
|
||||
|
||||
SDL_mutexP(save->cond_lock);
|
||||
if (!save->quit)
|
||||
SDL_CondWaitTimeout(save->cond, save->cond_lock, save->interval * 1000);
|
||||
SDL_mutexV(save->cond_lock);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
autosave_t *autosave_new(const char *path, const void *data, size_t size, unsigned interval)
|
||||
{
|
||||
autosave_t *handle = calloc(1, sizeof(*handle));
|
||||
if (!handle)
|
||||
return NULL;
|
||||
|
||||
handle->bufsize = size;
|
||||
handle->interval = interval;
|
||||
handle->path = path;
|
||||
handle->buffer = malloc(size);
|
||||
handle->snes_buffer = data;
|
||||
|
||||
if (!handle->buffer)
|
||||
{
|
||||
free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
handle->lock = SDL_CreateMutex();
|
||||
handle->cond_lock = SDL_CreateMutex();
|
||||
handle->cond = SDL_CreateCond();
|
||||
|
||||
handle->thread = SDL_CreateThread(autosave_thread, handle);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void autosave_lock(autosave_t *handle)
|
||||
{
|
||||
SDL_mutexP(handle->lock);
|
||||
}
|
||||
|
||||
void autosave_unlock(autosave_t *handle)
|
||||
{
|
||||
SDL_mutexV(handle->lock);
|
||||
}
|
||||
|
||||
void autosave_free(autosave_t *handle)
|
||||
{
|
||||
SDL_mutexP(handle->cond_lock);
|
||||
handle->quit = true;
|
||||
SDL_mutexV(handle->cond_lock);
|
||||
SDL_CondSignal(handle->cond);
|
||||
SDL_WaitThread(handle->thread, NULL);
|
||||
|
||||
SDL_DestroyMutex(handle->lock);
|
||||
SDL_DestroyMutex(handle->cond_lock);
|
||||
SDL_DestroyCond(handle->cond);
|
||||
|
||||
free(handle->buffer);
|
||||
free(handle);
|
||||
}
|
||||
|
||||
void lock_autosave(void)
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(g_extern.autosave)/sizeof(g_extern.autosave[0]); i++)
|
||||
{
|
||||
if (g_extern.autosave[i])
|
||||
autosave_lock(g_extern.autosave[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void unlock_autosave(void)
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(g_extern.autosave)/sizeof(g_extern.autosave[0]); i++)
|
||||
{
|
||||
if (g_extern.autosave[i])
|
||||
autosave_unlock(g_extern.autosave[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/* 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_AUTOSAVE_H
|
||||
#define __SSNES_AUTOSAVE_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct autosave autosave_t;
|
||||
|
||||
autosave_t *autosave_new(const char *path, const void *data, size_t size, unsigned interval);
|
||||
void autosave_lock(autosave_t *handle);
|
||||
void autosave_unlock(autosave_t *handle);
|
||||
void autosave_free(autosave_t *handle);
|
||||
|
||||
void lock_autosave(void);
|
||||
void unlock_autosave(void);
|
||||
|
||||
#endif
|
||||
@@ -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.
|
||||
|
||||
+113
-89
@@ -31,20 +31,21 @@
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SDL
|
||||
#include <SDL/SDL.h>
|
||||
#include "SDL.h"
|
||||
#else
|
||||
#error HAVE_SDL is not defined!
|
||||
#error "HAVE_SDL is not defined!"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SRC
|
||||
#include <samplerate.h>
|
||||
#else
|
||||
#error HAVE_SRC is not defined!
|
||||
#endif
|
||||
|
||||
|
||||
///////////////// 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
|
||||
@@ -55,11 +56,25 @@
|
||||
#define AUDIO_SDL 8
|
||||
#define AUDIO_XAUDIO 9
|
||||
#define AUDIO_PULSE 10
|
||||
#define AUDIO_EXT 15
|
||||
#define AUDIO_DSOUND 16
|
||||
#define AUDIO_COREAUDIO 17
|
||||
////////////////////////
|
||||
#define INPUT_SDL 7
|
||||
#define INPUT_X 12
|
||||
////////////////////////
|
||||
|
||||
#if defined(HAVE_SDL)
|
||||
#define VIDEO_DEFAULT_DRIVER VIDEO_GL
|
||||
#elif defined(HAVE_XVIDEO)
|
||||
#define VIDEO_DEFAULT_DRIVER VIDEO_XVIDEO
|
||||
#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)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_ALSA
|
||||
@@ -69,8 +84,12 @@
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_OSS
|
||||
#elif defined(HAVE_JACK)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_JACK
|
||||
#elif defined(HAVE_COREAUDIO)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_COREAUDIO
|
||||
#elif defined(HAVE_AL)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_AL
|
||||
#elif defined(HAVE_DSOUND)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_DSOUND
|
||||
#elif defined(HAVE_SDL)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_SDL
|
||||
#elif defined(HAVE_XAUDIO)
|
||||
@@ -79,11 +98,19 @@
|
||||
#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
|
||||
|
||||
|
||||
////////////////
|
||||
@@ -96,8 +123,14 @@ static const float yscale = 3.0; // Real y res = 224 * yscale
|
||||
|
||||
// Fullscreen
|
||||
static const bool fullscreen = false; // To start in Fullscreen or not
|
||||
static const unsigned fullscreen_x = 1280;
|
||||
static const unsigned fullscreen_y = 720;
|
||||
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;
|
||||
|
||||
// Forcibly disable composition. Only valid on Windows Vista/7 for now.
|
||||
static const bool disable_composition = false;
|
||||
|
||||
// Video VSYNC (recommended)
|
||||
static const bool vsync = true;
|
||||
@@ -108,6 +141,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;
|
||||
|
||||
@@ -115,6 +151,18 @@ static const unsigned font_size = 48;
|
||||
static const float message_pos_offset_x = 0.05;
|
||||
static const float message_pos_offset_y = 0.05;
|
||||
|
||||
// Render-to-texture before rendering to screen (multi-pass shaders)
|
||||
static const bool render_to_texture = false;
|
||||
static const float fbo_scale_x = 2.0;
|
||||
static const float fbo_scale_y = 2.0;
|
||||
static const bool second_pass_smooth = true;
|
||||
|
||||
// Record video assuming game runs hi-res.
|
||||
static const bool hires_record = false;
|
||||
|
||||
// Record post-filtered (CPU filter) video rather than raw SNES output.
|
||||
static const bool post_filter_record = false;
|
||||
|
||||
#define SNES_ASPECT_RATIO (4.0/3)
|
||||
|
||||
////////////////
|
||||
@@ -144,9 +192,28 @@ static const int out_latency = 64;
|
||||
static const bool audio_sync = true;
|
||||
|
||||
// Defines the quality (and cpu reqirements) of samplerate conversion.
|
||||
#ifdef HAVE_SRC
|
||||
#define SAMPLERATE_QUALITY SRC_LINEAR
|
||||
#endif
|
||||
|
||||
// Enables use of rewind. This will incur some memory footprint depending on the save state buffer.
|
||||
// This rewind only works when using bSNES core atm.
|
||||
static const bool rewind_enable = false;
|
||||
|
||||
// The buffer size for the rewind buffer. This needs to be about 15-20MB per minute. Very game dependant.
|
||||
static const unsigned rewind_buffer_size = 20 << 20; // 20MiB
|
||||
|
||||
// How many frames to rewind at a time.
|
||||
static const unsigned rewind_granularity = 1;
|
||||
|
||||
// Pause gameplay when gameplay loses focus.
|
||||
static const bool pause_nonactive = true;
|
||||
|
||||
// Saves non-volatile SRAM at a regular interval. It is measured in seconds. A value of 0 disables autosave.
|
||||
static const unsigned autosave_interval = 0;
|
||||
|
||||
// When being client over netplay, use keybinds for player 1 rather than player 2.
|
||||
static const bool netplay_client_swap_input = true;
|
||||
|
||||
|
||||
////////////////////
|
||||
@@ -163,19 +230,20 @@ static const bool audio_sync = true;
|
||||
// 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 },
|
||||
@@ -184,78 +252,34 @@ static const struct snes_keybind snes_keybinds_1[] = {
|
||||
{ SSNES_STATE_SLOT_PLUS, SDLK_F7, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_AUDIO_INPUT_RATE_PLUS, SDLK_KP_PLUS, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_AUDIO_INPUT_RATE_MINUS, SDLK_KP_MINUS, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_REWIND, SDLK_r, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_MOVIE_RECORD_TOGGLE, SDLK_o, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_PAUSE_TOGGLE, SDLK_p, NO_BTN, AXIS_NONE },
|
||||
{ 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 }
|
||||
};
|
||||
|
||||
|
||||
+34
-4
@@ -14,13 +14,19 @@ static const bool _sdl_supp = true;
|
||||
static const bool _sdl_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_XVIDEO
|
||||
static const bool _xvideo_supp = true;
|
||||
#else
|
||||
static const bool _xvideo_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_ALSA
|
||||
static const bool _alsa_supp = true;
|
||||
#else
|
||||
static const bool _alsa_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_OSS
|
||||
#if defined(HAVE_OSS) || defined(HAVE_OSS_BSD)
|
||||
static const bool _oss_supp = true;
|
||||
#else
|
||||
static const bool _oss_supp = false;
|
||||
@@ -62,10 +68,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 +86,18 @@ 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
|
||||
static const bool _fbo_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_DYNAMIC
|
||||
static const bool _dynamic_supp = true;
|
||||
#else
|
||||
@@ -110,4 +128,16 @@ static const bool _freetype_supp = true;
|
||||
static const bool _freetype_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_NETPLAY
|
||||
static const bool _netplay_supp = true;
|
||||
#else
|
||||
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
|
||||
@@ -34,6 +34,10 @@ Use config file found in PATH as a base. Old configuration values can be overwri
|
||||
\fB--output PATH, -o PATH\fR
|
||||
Writes the final config to a file, rather than \fBstdout\fR. If -i and -o point to the same file, the file found in PATH will simply be updated with the new configs.
|
||||
|
||||
.TP
|
||||
\fB--misc, -m\fR
|
||||
Also configure some binds that aren't directly related to SNES emulation, such as save states, fullscreen toggling, etc.
|
||||
|
||||
.TP
|
||||
\fB--help, -h\fR
|
||||
Prints help message.
|
||||
|
||||
+44
-1
@@ -40,6 +40,10 @@ Without this flag, the save state path will be inferred from the rom path name,
|
||||
When rom is loaded from \fBstdin\fR, this flag is mandatory to define as no path can be inferred.
|
||||
Do note that save states are bound to the libsnes implementation being used. Using a different libsnes could invalidate the save state file.
|
||||
|
||||
.TP
|
||||
\fB--fullscreen, -f\fR
|
||||
Always starts SSNES in fullscreen. Disregards settings in configuration file.
|
||||
|
||||
.TP
|
||||
\fB--config PATH, -c PATH\fR
|
||||
Sets the configuration file path. \fBssnes\fR will use this path to load the configuration file.
|
||||
@@ -47,7 +51,7 @@ Should this not be defined, \fBssnes\fR will look in platform specific paths to
|
||||
/etc/ssnes.cfg (when installed), or ssnes.cfg in the source tarball serves as a skeleton configuration file.
|
||||
|
||||
.IP
|
||||
Unix-like systems will look in $XDG_CONFIG_HOME/ssnes/ssnes.cfg first. Then it will try $HOME/.ssnesrc. Last, it will try /etc/ssnes.cfg. If no configuration is found, default settings will be assumed. A configuration file does not need to define every possible option, only those that should be overridden.
|
||||
Unix-like systems will look in $XDG_CONFIG_HOME/ssnes/ssnes.cfg first. Then it will try $HOME/.ssnes.cfg. Last, it will try /etc/ssnes.cfg. If no configuration is found, default settings will be assumed. A configuration file does not need to define every possible option, only those that should be overridden.
|
||||
|
||||
.IP
|
||||
Windows will look in ssnes.cfg in same folder where ssnes.exe resides.
|
||||
@@ -80,6 +84,9 @@ When using Sufami, save ram paths will be inferred from the Sufami BIOS path, no
|
||||
\fB--mouse PORT, -m PORT\fR
|
||||
Connects a Super Nintendo Mouse into port number PORT of the emulated SNES. Possible values for PORT are 1 and 2.
|
||||
|
||||
\fB--nodevice PORT, -N PORT\fR
|
||||
Disconnects an input device from port number PORT of the emulated SNES. Possible values for PORT are 1 and 2. This may be needed for some odd games to run properly.
|
||||
|
||||
.TP
|
||||
\fB--scope, -p\fR
|
||||
Connects a Super Scope into port 2 of the SNES. It is controlled with your mouse.
|
||||
@@ -100,9 +107,45 @@ Connects a four-way multitap into port 2 of the SNES. This allows for up to 5 pl
|
||||
\fB--record PATH, -r PATH\fR
|
||||
Activates video recording of gameplay into PATH. Video recording options can be configured in config file.
|
||||
|
||||
.TP
|
||||
\fB--bsvplay PATH, -P PATH\fR
|
||||
Play back a movie recorded in the .bsv format (bSNES). Cart ROM and movie file need to correspond.
|
||||
It also requires to play back with the same libsnes backend that was used for recording.
|
||||
|
||||
.TP
|
||||
\fB--verbose, -v\fR
|
||||
Activates verbose logging.
|
||||
|
||||
.TP
|
||||
\fB--host, -H\fR
|
||||
Be the host of netplay. Waits until a player connects. The host will always assume player 1.
|
||||
|
||||
.TP
|
||||
\fB--connect SERVER, -C SERVER\fR
|
||||
Connect to a host of netplay. Will always assume player 2.
|
||||
|
||||
.TP
|
||||
\fB--frames FRAMES, -F FRAMES\fR
|
||||
Sync frames to use when using netplay. More frames allow for more latency, but requires more CPU power.
|
||||
Set FRAMES to 0 to have perfect sync. 0 frames is only suitable for LAN. Defaults to 0.
|
||||
|
||||
.TP
|
||||
\fB--port PORT\fR
|
||||
Network port used for netplay. This defaults to 55435. This option affects both TCP and UDP.
|
||||
|
||||
.TP
|
||||
\fB--ups PATCH, -U PATCH\fR
|
||||
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--xml MAP, -X MAP\fR
|
||||
Specifies path to XML memory map for the given ROM.
|
||||
If this flag is not specified, SSNES will look for a .xml 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)
|
||||
@@ -18,10 +18,11 @@
|
||||
|
||||
#include "driver.h"
|
||||
#include "general.h"
|
||||
#include "file.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "hqflt/filters.h"
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
@@ -31,12 +32,15 @@ static const audio_driver_t *audio_drivers[] = {
|
||||
#ifdef HAVE_ALSA
|
||||
&audio_alsa,
|
||||
#endif
|
||||
#ifdef HAVE_OSS
|
||||
#if defined(HAVE_OSS) || defined(HAVE_OSS_BSD)
|
||||
&audio_oss,
|
||||
#endif
|
||||
#ifdef HAVE_RSOUND
|
||||
&audio_rsound,
|
||||
#endif
|
||||
#ifdef HAVE_COREAUDIO
|
||||
&audio_coreaudio,
|
||||
#endif
|
||||
#ifdef HAVE_AL
|
||||
&audio_openal,
|
||||
#endif
|
||||
@@ -52,14 +56,27 @@ static const audio_driver_t *audio_drivers[] = {
|
||||
#ifdef HAVE_XAUDIO
|
||||
&audio_xa,
|
||||
#endif
|
||||
#ifdef HAVE_DSOUND
|
||||
&audio_dsound,
|
||||
#endif
|
||||
#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
|
||||
};
|
||||
|
||||
@@ -67,6 +84,9 @@ static const input_driver_t *input_drivers[] = {
|
||||
#ifdef HAVE_SDL
|
||||
&input_sdl,
|
||||
#endif
|
||||
#ifdef HAVE_XVIDEO
|
||||
&input_x,
|
||||
#endif
|
||||
};
|
||||
|
||||
static void find_audio_driver(void)
|
||||
@@ -118,7 +138,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);
|
||||
}
|
||||
@@ -135,6 +155,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 *(SSNES_API_CALLTYPE*)(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");
|
||||
|
||||
const 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
|
||||
@@ -166,10 +251,16 @@ void init_audio(void)
|
||||
else
|
||||
g_extern.audio_data.chunk_size = g_extern.audio_data.block_chunk_size;
|
||||
|
||||
#ifdef HAVE_SRC
|
||||
int err;
|
||||
g_extern.source = src_new(g_settings.audio.src_quality, 2, &err);
|
||||
if (!g_extern.source)
|
||||
g_extern.audio_data.source = src_new(g_settings.audio.src_quality, 2, &err);
|
||||
if (!g_extern.audio_data.source)
|
||||
g_extern.audio_active = false;
|
||||
#else
|
||||
g_extern.audio_data.source = hermite_new(2);
|
||||
if (!g_extern.audio_data.source)
|
||||
g_extern.audio_active = false;
|
||||
#endif
|
||||
|
||||
size_t max_bufsamples = g_extern.audio_data.block_chunk_size > g_extern.audio_data.nonblock_chunk_size ?
|
||||
g_extern.audio_data.block_chunk_size : g_extern.audio_data.nonblock_chunk_size;
|
||||
@@ -179,6 +270,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)
|
||||
@@ -192,45 +285,172 @@ void uninit_audio(void)
|
||||
if ( driver.audio_data && driver.audio )
|
||||
driver.audio->free(driver.audio_data);
|
||||
|
||||
if ( g_extern.source )
|
||||
src_delete(g_extern.source);
|
||||
if ( g_extern.audio_data.source )
|
||||
{
|
||||
#ifdef HAVE_SRC
|
||||
src_delete(g_extern.audio_data.source);
|
||||
#else
|
||||
hermite_free(g_extern.audio_data.source);
|
||||
#endif
|
||||
}
|
||||
|
||||
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_DYLIB
|
||||
static void init_filter(void)
|
||||
{
|
||||
if (g_extern.filter.active)
|
||||
return;
|
||||
if (*g_settings.video.filter_path == '\0')
|
||||
return;
|
||||
|
||||
SSNES_LOG("Loading bSNES filter from \"%s\"\n", g_settings.video.filter_path);
|
||||
g_extern.filter.lib = dylib_load(g_settings.video.filter_path);
|
||||
if (!g_extern.filter.lib)
|
||||
{
|
||||
SSNES_ERR("Failed to load filter \"%s\"\n", g_settings.video.filter_path);
|
||||
return;
|
||||
}
|
||||
|
||||
g_extern.filter.psize =
|
||||
(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");
|
||||
dylib_close(g_extern.filter.lib);
|
||||
g_extern.filter.lib = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
g_extern.filter.active = true;
|
||||
|
||||
unsigned width = 512;
|
||||
unsigned height = 512;
|
||||
g_extern.filter.psize(&width, &height);
|
||||
|
||||
unsigned pow2_x = next_pow2(ceil(width));
|
||||
unsigned pow2_y = next_pow2(ceil(height));
|
||||
unsigned maxsize = pow2_x > pow2_y ? pow2_x : pow2_y;
|
||||
g_extern.filter.scale = maxsize / 256;
|
||||
|
||||
g_extern.filter.buffer = malloc(256 * 256 * g_extern.filter.scale * g_extern.filter.scale * sizeof(uint32_t));
|
||||
g_extern.filter.pitch = 256 * g_extern.filter.scale * sizeof(uint32_t);
|
||||
assert(g_extern.filter.buffer);
|
||||
|
||||
g_extern.filter.colormap = malloc(32768 * sizeof(uint32_t));
|
||||
assert(g_extern.filter.colormap);
|
||||
|
||||
// Set up conversion map from 16-bit XRGB1555 to 32-bit ARGB.
|
||||
for (int i = 0; i < 32768; i++)
|
||||
{
|
||||
unsigned r = (i >> 10) & 31;
|
||||
unsigned g = (i >> 5) & 31;
|
||||
unsigned b = (i >> 0) & 31;
|
||||
|
||||
r = (r << 3) | (r >> 2);
|
||||
g = (g << 3) | (g >> 2);
|
||||
b = (b << 3) | (b >> 2);
|
||||
g_extern.filter.colormap[i] = (r << 16) | (g << 8) | (b << 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void deinit_filter(void)
|
||||
{
|
||||
if (g_extern.filter.active)
|
||||
{
|
||||
g_extern.filter.active = false;
|
||||
dylib_close(g_extern.filter.lib);
|
||||
g_extern.filter.lib = NULL;
|
||||
free(g_extern.filter.buffer);
|
||||
free(g_extern.filter.colormap);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_XML
|
||||
static void init_shader_dir(void)
|
||||
{
|
||||
if (!*g_settings.video.shader_dir)
|
||||
return;
|
||||
|
||||
g_extern.shader_dir.elems = dir_list_new(g_settings.video.shader_dir, ".shader");
|
||||
g_extern.shader_dir.size = 0;
|
||||
g_extern.shader_dir.ptr = 0;
|
||||
if (g_extern.shader_dir.elems)
|
||||
{
|
||||
while (g_extern.shader_dir.elems[g_extern.shader_dir.size])
|
||||
{
|
||||
SSNES_LOG("Found shader \"%s\"\n", g_extern.shader_dir.elems[g_extern.shader_dir.size]);
|
||||
g_extern.shader_dir.size++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void deinit_shader_dir(void)
|
||||
{
|
||||
// It handles NULL, no worries :D
|
||||
dir_list_free(g_extern.shader_dir.elems);
|
||||
}
|
||||
#endif
|
||||
|
||||
void init_video_input(void)
|
||||
{
|
||||
int scale = 2;
|
||||
#ifdef HAVE_DYLIB
|
||||
init_filter();
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_XML
|
||||
init_shader_dir();
|
||||
#endif
|
||||
|
||||
// We use at least 512x512 textures to accomodate for hi-res games.
|
||||
unsigned scale = 2;
|
||||
|
||||
find_video_driver();
|
||||
find_input_driver();
|
||||
|
||||
// We multiply scales with 2 to allow for hi-res games.
|
||||
#if HAVE_FILTER
|
||||
switch (g_settings.video.filter)
|
||||
if (g_extern.filter.active)
|
||||
scale = g_extern.filter.scale;
|
||||
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
if (g_settings.video.fullscreen)
|
||||
{
|
||||
case FILTER_HQ2X:
|
||||
scale = 4;
|
||||
break;
|
||||
case FILTER_HQ4X:
|
||||
case FILTER_NTSC:
|
||||
scale = 8;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
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,
|
||||
.smooth = g_settings.video.smooth,
|
||||
.input_scale = scale,
|
||||
.rgb32 = g_extern.filter.active
|
||||
};
|
||||
|
||||
const input_driver_t *tmp = driver.input;
|
||||
@@ -238,7 +458,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);
|
||||
}
|
||||
|
||||
@@ -265,11 +485,17 @@ void init_video_input(void)
|
||||
|
||||
void uninit_video_input(void)
|
||||
{
|
||||
if ( driver.input_data != driver.video_data && driver.input )
|
||||
driver.input->free(driver.input_data);
|
||||
|
||||
if ( driver.video_data && driver.video )
|
||||
driver.video->free(driver.video_data);
|
||||
|
||||
if ( driver.input_data != driver.video_data && driver.input )
|
||||
driver.input->free(driver.input_data);
|
||||
deinit_filter();
|
||||
|
||||
#ifdef HAVE_XML
|
||||
deinit_shader_dir();
|
||||
#endif
|
||||
}
|
||||
|
||||
driver_t driver;
|
||||
|
||||
@@ -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,
|
||||
@@ -36,8 +37,20 @@ enum
|
||||
SSNES_STATE_SLOT_MINUS,
|
||||
SSNES_AUDIO_INPUT_RATE_PLUS,
|
||||
SSNES_AUDIO_INPUT_RATE_MINUS,
|
||||
SSNES_REWIND,
|
||||
SSNES_MOVIE_RECORD_TOGGLE,
|
||||
SSNES_PAUSE_TOGGLE,
|
||||
SSNES_RESET,
|
||||
SSNES_SHADER_NEXT,
|
||||
SSNES_SHADER_PREV,
|
||||
SSNES_CHEAT_INDEX_PLUS,
|
||||
SSNES_CHEAT_INDEX_MINUS,
|
||||
SSNES_CHEAT_TOGGLE,
|
||||
SSNES_SCREENSHOT,
|
||||
SSNES_DSP_CONFIG
|
||||
};
|
||||
|
||||
|
||||
struct snes_keybind
|
||||
{
|
||||
int id;
|
||||
@@ -48,18 +61,19 @@ struct snes_keybind
|
||||
|
||||
typedef struct video_info
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
bool fullscreen;
|
||||
bool vsync;
|
||||
bool force_aspect;
|
||||
bool smooth;
|
||||
int input_scale; // HQ2X => 2, HQ4X => 4, None => 1
|
||||
bool rgb32; // Use 32-bit RGBA rather than native XBGR1555.
|
||||
} video_info_t;
|
||||
|
||||
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);
|
||||
@@ -69,15 +83,14 @@ typedef struct audio_driver
|
||||
const char *ident;
|
||||
} audio_driver_t;
|
||||
|
||||
#define AXIS_NEG(x) ((uint32_t)(x << 16) | 0xFFFF)
|
||||
#define AXIS_NEG(x) (((uint32_t)(x) << 16) | 0xFFFFU)
|
||||
#define AXIS_POS(x) ((uint32_t)(x) | 0xFFFF0000U)
|
||||
#define AXIS_NONE ((uint32_t)0xFFFFFFFFU)
|
||||
#define AXIS_NONE (0xFFFFFFFFU)
|
||||
|
||||
#define AXIS_NEG_GET(x) ((x >> 16) & 0xFFFF)
|
||||
#define AXIS_POS_GET(x) (x & 0xFFFF)
|
||||
#define AXIS_NONE ((uint32_t)0xFFFFFFFFU)
|
||||
#define AXIS_NEG_GET(x) (((uint32_t)(x) >> 16) & 0xFFFFU)
|
||||
#define AXIS_POS_GET(x) ((uint32_t)(x) & 0xFFFFU)
|
||||
|
||||
#define NO_BTN 0xFFFF // I hope no joypad will ever have this many buttons ... ;)
|
||||
#define NO_BTN ((uint16_t)0xFFFFU) // I hope no joypad will ever have this many buttons ... ;)
|
||||
|
||||
#define HAT_UP_MASK (1 << 15)
|
||||
#define HAT_DOWN_MASK (1 << 14)
|
||||
@@ -101,12 +114,14 @@ 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 uint16_t* frame, unsigned width, unsigned height, unsigned pitch, const char *msg); // msg is for showing a message on the screen along with the video frame.
|
||||
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.
|
||||
// Is the window still active?
|
||||
bool (*alive)(void *data);
|
||||
bool (*focus)(void *data); // Does the window have focus?
|
||||
bool (*xml_shader)(void *data, const char *path); // Sets XML-shader. Might not be implemented.
|
||||
void (*free)(void* data);
|
||||
const char *ident;
|
||||
} video_driver_t;
|
||||
@@ -142,8 +157,15 @@ 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 audio_driver_t audio_dsound;
|
||||
extern const audio_driver_t audio_coreaudio;
|
||||
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;
|
||||
////////////////////////////////////////////////
|
||||
|
||||
#endif
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "dynamic.h"
|
||||
#include "general.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
@@ -25,31 +26,28 @@
|
||||
|
||||
#include <libsnes.hpp>
|
||||
|
||||
#ifdef HAVE_DYNAMIC
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#define SYM(x) do { \
|
||||
p##x = ((void*)GetProcAddress(lib_handle, #x)); \
|
||||
if (p##x == NULL) { SSNES_ERR("Failed to load symbol: \"%s\"\n", #x); exit(1); } \
|
||||
} while(0)
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#define SYM(x) do { \
|
||||
p##x = dlsym(lib_handle, #x); \
|
||||
if (p##x == NULL) { SSNES_ERR("Failed to load symbol: \"%s\"\n", #x); exit(1); } \
|
||||
} while(0)
|
||||
#endif // _WIN32
|
||||
#endif // HAVE_DYNAMIC
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_DYNAMIC
|
||||
#ifdef _WIN32
|
||||
static HMODULE lib_handle;
|
||||
#else
|
||||
static void *lib_handle = NULL;
|
||||
#endif
|
||||
#define DLSYM(lib, x) dylib_proc(lib, #x)
|
||||
|
||||
#define SYM(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(type, x) do { \
|
||||
p##x = (type)DLSYM(lib_handle, x); \
|
||||
} while(0)
|
||||
|
||||
static dylib_t lib_handle = NULL;
|
||||
#endif
|
||||
|
||||
|
||||
void (*psnes_init)(void);
|
||||
|
||||
void (*psnes_set_video_refresh)(snes_video_refresh_t);
|
||||
@@ -57,8 +55,13 @@ void (*psnes_set_audio_sample)(snes_audio_sample_t);
|
||||
void (*psnes_set_input_poll)(snes_input_poll_t);
|
||||
void (*psnes_set_input_state)(snes_input_state_t);
|
||||
|
||||
void (*psnes_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);
|
||||
|
||||
@@ -97,40 +100,45 @@ void (*psnes_term)(void);
|
||||
static void load_dynamic(void)
|
||||
{
|
||||
SSNES_LOG("Loading dynamic libsnes from: \"%s\"\n", g_settings.libsnes);
|
||||
#ifdef _WIN32
|
||||
lib_handle = LoadLibrary(g_settings.libsnes);
|
||||
#else
|
||||
lib_handle = dlopen(g_settings.libsnes, RTLD_LAZY);
|
||||
#endif
|
||||
lib_handle = dylib_load(g_settings.libsnes);
|
||||
if (!lib_handle)
|
||||
{
|
||||
SSNES_ERR("Failed to open dynamic library: \"%s\"\n", g_settings.libsnes);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
SYM(snes_init);
|
||||
SYM(snes_set_video_refresh);
|
||||
SYM(snes_set_audio_sample);
|
||||
SYM(snes_set_input_poll);
|
||||
SYM(snes_set_input_state);
|
||||
SYM(snes_library_revision_minor);
|
||||
SYM(snes_library_revision_major);
|
||||
SYM(snes_run);
|
||||
SYM(snes_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
|
||||
|
||||
@@ -148,6 +156,9 @@ 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);
|
||||
SSYM(snes_load_cartridge_normal);
|
||||
@@ -169,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();
|
||||
@@ -186,12 +200,42 @@ void uninit_dlsym(void)
|
||||
{
|
||||
#ifdef HAVE_DYNAMIC
|
||||
if (lib_handle)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
FreeLibrary(lib_handle);
|
||||
#else
|
||||
dlclose(lib_handle);
|
||||
#endif
|
||||
}
|
||||
dylib_close(lib_handle);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Platform independent dylib loading.
|
||||
dylib_t dylib_load(const char *path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return LoadLibrary(path);
|
||||
#else
|
||||
return dlopen(path, RTLD_LAZY);
|
||||
#endif
|
||||
}
|
||||
|
||||
function_t dylib_proc(dylib_t lib, const char *proc)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
function_t sym = (function_t)GetProcAddress(lib, proc);
|
||||
#else
|
||||
// Dirty hack to workaround the non-legality of void* -> fn-pointer casts.
|
||||
void *ptr_sym = dlsym(lib, proc);
|
||||
function_t sym;
|
||||
memcpy(&sym, &ptr_sym, sizeof(void*));
|
||||
#endif
|
||||
|
||||
if (!sym)
|
||||
SSNES_WARN("Failed to load symbol \"%s\"\n", proc);
|
||||
return sym;
|
||||
}
|
||||
|
||||
void dylib_close(dylib_t lib)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
FreeLibrary(lib);
|
||||
#else
|
||||
dlclose(lib);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -24,6 +24,13 @@
|
||||
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);
|
||||
function_t dylib_proc(dylib_t lib, const char *proc);
|
||||
|
||||
extern void (*psnes_init)(void);
|
||||
|
||||
extern void (*psnes_set_video_refresh)(snes_video_refresh_t);
|
||||
@@ -31,6 +38,10 @@ 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);
|
||||
|
||||
@@ -49,7 +60,6 @@ extern bool (*psnes_load_cartridge_sufami_turbo)(
|
||||
const char*, const uint8_t*, unsigned,
|
||||
const char*, const uint8_t*, unsigned);
|
||||
|
||||
|
||||
extern void (*psnes_set_controller_port_device)(bool, unsigned);
|
||||
|
||||
extern bool (*psnes_get_region)(void);
|
||||
@@ -58,6 +68,7 @@ extern unsigned (*psnes_serialize_size)(void);
|
||||
extern bool (*psnes_serialize)(uint8_t*, unsigned);
|
||||
extern bool (*psnes_unserialize)(const uint8_t*, unsigned);
|
||||
|
||||
extern void (*psnes_reset)(void);
|
||||
extern void (*psnes_run)(void);
|
||||
|
||||
extern void (*psnes_set_cartridge_basename)(const char*);
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "buffer.h"
|
||||
#include "fifo_buffer.h"
|
||||
|
||||
struct rsound_fifo_buffer
|
||||
struct fifo_buffer
|
||||
{
|
||||
char *buffer;
|
||||
size_t bufsize;
|
||||
@@ -23,9 +23,9 @@ struct rsound_fifo_buffer
|
||||
size_t end;
|
||||
};
|
||||
|
||||
rsound_fifo_buffer_t* rsnd_fifo_new(size_t size)
|
||||
fifo_buffer_t* fifo_new(size_t size)
|
||||
{
|
||||
rsound_fifo_buffer_t *buf = calloc(1, sizeof(*buf));
|
||||
fifo_buffer_t *buf = calloc(1, sizeof(*buf));
|
||||
if (buf == NULL)
|
||||
return NULL;
|
||||
|
||||
@@ -40,7 +40,7 @@ rsound_fifo_buffer_t* rsnd_fifo_new(size_t size)
|
||||
return buf;
|
||||
}
|
||||
|
||||
void rsnd_fifo_free(rsound_fifo_buffer_t* buffer)
|
||||
void fifo_free(fifo_buffer_t* buffer)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
@@ -49,7 +49,7 @@ void rsnd_fifo_free(rsound_fifo_buffer_t* buffer)
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
size_t rsnd_fifo_read_avail(rsound_fifo_buffer_t* buffer)
|
||||
size_t fifo_read_avail(fifo_buffer_t* buffer)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
@@ -61,7 +61,7 @@ size_t rsnd_fifo_read_avail(rsound_fifo_buffer_t* buffer)
|
||||
return end - first;
|
||||
}
|
||||
|
||||
size_t rsnd_fifo_write_avail(rsound_fifo_buffer_t* buffer)
|
||||
size_t fifo_write_avail(fifo_buffer_t* buffer)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
@@ -74,12 +74,12 @@ size_t rsnd_fifo_write_avail(rsound_fifo_buffer_t* buffer)
|
||||
return (buffer->bufsize - 1) - (end - first);
|
||||
}
|
||||
|
||||
void rsnd_fifo_write(rsound_fifo_buffer_t* buffer, const void* in_buf, size_t size)
|
||||
void fifo_write(fifo_buffer_t* buffer, const void* in_buf, size_t size)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
assert(in_buf);
|
||||
assert(rsnd_fifo_write_avail(buffer) >= size);
|
||||
assert(fifo_write_avail(buffer) >= size);
|
||||
|
||||
size_t first_write = size;
|
||||
size_t rest_write = 0;
|
||||
@@ -97,12 +97,12 @@ void rsnd_fifo_write(rsound_fifo_buffer_t* buffer, const void* in_buf, size_t si
|
||||
}
|
||||
|
||||
|
||||
void rsnd_fifo_read(rsound_fifo_buffer_t* buffer, void* in_buf, size_t size)
|
||||
void fifo_read(fifo_buffer_t* buffer, void* in_buf, size_t size)
|
||||
{
|
||||
assert(buffer);
|
||||
assert(buffer->buffer);
|
||||
assert(in_buf);
|
||||
assert(rsnd_fifo_read_avail(buffer) >= size);
|
||||
assert(fifo_read_avail(buffer) >= size);
|
||||
|
||||
size_t first_read = size;
|
||||
size_t rest_read = 0;
|
||||
@@ -21,16 +21,16 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef RSD_FIFO_BUF_TYPEDEF
|
||||
#define RSD_FIFO_BUF_TYPEDEF
|
||||
typedef struct rsound_fifo_buffer rsound_fifo_buffer_t;
|
||||
#ifndef FIFO_BUF_TYPEDEF
|
||||
#define FIFO_BUF_TYPEDEF
|
||||
typedef struct fifo_buffer fifo_buffer_t;
|
||||
#endif
|
||||
|
||||
rsound_fifo_buffer_t* rsnd_fifo_new(size_t size);
|
||||
void rsnd_fifo_write(rsound_fifo_buffer_t* buffer, const void* in_buf, size_t size);
|
||||
void rsnd_fifo_read(rsound_fifo_buffer_t* buffer, void* in_buf, size_t size);
|
||||
void rsnd_fifo_free(rsound_fifo_buffer_t* buffer);
|
||||
size_t rsnd_fifo_read_avail(rsound_fifo_buffer_t* buffer);
|
||||
size_t rsnd_fifo_write_avail(rsound_fifo_buffer_t* buffer);
|
||||
fifo_buffer_t* fifo_new(size_t size);
|
||||
void fifo_write(fifo_buffer_t* buffer, const void* in_buf, size_t size);
|
||||
void fifo_read(fifo_buffer_t* buffer, void* in_buf, size_t size);
|
||||
void fifo_free(fifo_buffer_t* buffer);
|
||||
size_t fifo_read_avail(fifo_buffer_t* buffer);
|
||||
size_t fifo_write_avail(fifo_buffer_t* buffer);
|
||||
|
||||
#endif
|
||||
@@ -22,16 +22,106 @@
|
||||
#include <libsnes.hpp>
|
||||
#include <string.h>
|
||||
#include "dynamic.h"
|
||||
#include "movie.h"
|
||||
#include "ups.h"
|
||||
#include "strl.h"
|
||||
|
||||
#ifdef HAVE_XML
|
||||
#include "sha256.h"
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
|
||||
// Generic file loader.
|
||||
ssize_t read_file(const char *path, void **buf)
|
||||
{
|
||||
void *rom_buf = NULL;
|
||||
FILE *file = fopen(path, "rb");
|
||||
if (!file)
|
||||
goto error;
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
long len = ftell(file);
|
||||
ssize_t rc = 0;
|
||||
rewind(file);
|
||||
rom_buf = malloc(len + 1);
|
||||
if (!rom_buf)
|
||||
{
|
||||
SSNES_ERR("Couldn't allocate memory!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((rc = fread(rom_buf, 1, len, file)) < len)
|
||||
SSNES_WARN("Didn't read whole file.\n");
|
||||
|
||||
*buf = rom_buf;
|
||||
// Allow for easy reading of strings to be safe.
|
||||
// Will only work with sane character formatting (Unix).
|
||||
((char*)rom_buf)[len] = '\0';
|
||||
fclose(file);
|
||||
return rc;
|
||||
|
||||
error:
|
||||
if (file)
|
||||
fclose(file);
|
||||
free(rom_buf);
|
||||
*buf = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Reads file content as one string.
|
||||
bool read_file_string(const char *path, char **buf)
|
||||
{
|
||||
*buf = NULL;
|
||||
FILE *file = fopen(path, "r");
|
||||
if (!file)
|
||||
goto error;
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
long len = ftell(file) + 2; // Takes account of being able to read in EOF and '\0' at end.
|
||||
rewind(file);
|
||||
|
||||
*buf = calloc(len, sizeof(char));
|
||||
if (!*buf)
|
||||
goto error;
|
||||
|
||||
char *ptr = *buf;
|
||||
|
||||
while (ptr && !feof(file))
|
||||
{
|
||||
size_t bufsize = (size_t)(((ptrdiff_t)*buf + (ptrdiff_t)len) - (ptrdiff_t)ptr);
|
||||
fgets(ptr, bufsize, file);
|
||||
|
||||
ptr = strchr(ptr, '\0');
|
||||
}
|
||||
|
||||
ptr = strchr(ptr, EOF);
|
||||
if (ptr) *ptr = '\0';
|
||||
|
||||
fclose(file);
|
||||
return true;
|
||||
|
||||
error:
|
||||
if (file)
|
||||
fclose(file);
|
||||
if (*buf)
|
||||
free(*buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load SNES rom only. Applies a hack for headered ROMs.
|
||||
static ssize_t read_rom_file(FILE* file, void** buf)
|
||||
{
|
||||
ssize_t ret;
|
||||
ssize_t ret = 0;
|
||||
uint8_t *ret_buf = NULL;
|
||||
|
||||
if (file == NULL) // stdin
|
||||
{
|
||||
#ifdef _WIN32
|
||||
@@ -41,7 +131,7 @@ static ssize_t read_rom_file(FILE* file, void** buf)
|
||||
SSNES_LOG("Reading ROM from stdin ...\n");
|
||||
size_t buf_size = 0xFFFFF; // Some initial guesstimate.
|
||||
size_t buf_ptr = 0;
|
||||
char *rom_buf = malloc(buf_size);
|
||||
uint8_t *rom_buf = malloc(buf_size);
|
||||
if (rom_buf == NULL)
|
||||
{
|
||||
SSNES_ERR("Couldn't allocate memory!\n");
|
||||
@@ -67,78 +157,79 @@ static ssize_t read_rom_file(FILE* file, void** buf)
|
||||
buf_size *= 2;
|
||||
}
|
||||
|
||||
if ((buf_ptr & 0x7fff) == 512)
|
||||
{
|
||||
memmove(rom_buf, rom_buf + 512, buf_ptr - 512);
|
||||
buf_ptr -= 512;
|
||||
}
|
||||
|
||||
*buf = rom_buf;
|
||||
ret_buf = rom_buf;
|
||||
ret = buf_ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
fseek(file, 0, SEEK_END);
|
||||
long length = ftell(file);
|
||||
ret = ftell(file);
|
||||
rewind(file);
|
||||
if ((length & 0x7fff) == 512)
|
||||
{
|
||||
length -= 512;
|
||||
fseek(file, 512, SEEK_SET);
|
||||
}
|
||||
|
||||
void *rom_buf = malloc(length);
|
||||
if ( rom_buf == NULL )
|
||||
void *rom_buf = malloc(ret);
|
||||
if (rom_buf == NULL)
|
||||
{
|
||||
SSNES_ERR("Couldn't allocate memory!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( fread(rom_buf, 1, length, file) < length )
|
||||
if (fread(rom_buf, 1, ret, file) < ret)
|
||||
{
|
||||
SSNES_ERR("Didn't read whole file.\n");
|
||||
free(rom_buf);
|
||||
return -1;
|
||||
}
|
||||
*buf = rom_buf;
|
||||
ret = length;
|
||||
|
||||
ret_buf = rom_buf;
|
||||
}
|
||||
|
||||
// Patch with UPS.
|
||||
ssize_t ups_patch_size;
|
||||
void *ups_patch = NULL;
|
||||
if (*g_extern.ups_name && (ups_patch_size = read_file(g_extern.ups_name, &ups_patch)) >= 0)
|
||||
{
|
||||
SSNES_LOG("Found UPS file in \"%s\", attempting to patch ...\n", g_extern.ups_name);
|
||||
|
||||
size_t target_size = ret * 4; // Just to be sure ...
|
||||
uint8_t *patched_rom = malloc(target_size);
|
||||
if (patched_rom)
|
||||
{
|
||||
ups_error_t err = ups_apply_patch(ups_patch, ups_patch_size, ret_buf, ret, patched_rom, &target_size);
|
||||
if (err == UPS_SUCCESS)
|
||||
{
|
||||
free(ret_buf);
|
||||
ret_buf = patched_rom;
|
||||
ret = target_size;
|
||||
SSNES_LOG("ROM patched successfully (UPS)!\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
free(patched_rom);
|
||||
SSNES_LOG("ROM failed to patch (UPS).\n");
|
||||
}
|
||||
|
||||
free(ups_patch);
|
||||
}
|
||||
}
|
||||
else if (*g_extern.ups_name)
|
||||
SSNES_LOG("Could not find UPS patch in: \"%s\".\n", g_extern.ups_name);
|
||||
|
||||
// Remove copier header if present (512 first bytes).
|
||||
if ((ret & 0x7fff) == 512)
|
||||
{
|
||||
memmove(ret_buf, ret_buf + 512, ret - 512);
|
||||
ret -= 512;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Generic file loader.
|
||||
static ssize_t read_file(const char *path, void **buf)
|
||||
{
|
||||
void *rom_buf = NULL;
|
||||
FILE *file = fopen(path, "rb");
|
||||
if (!file)
|
||||
goto error;
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
long len = ftell(file);
|
||||
ssize_t rc = 0;
|
||||
rewind(file);
|
||||
rom_buf = malloc(len);
|
||||
if (!rom_buf)
|
||||
{
|
||||
SSNES_ERR("Couldn't allocate memory!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((rc = fread(rom_buf, 1, len, file)) < len)
|
||||
SSNES_WARN("Didn't read whole file.\n");
|
||||
|
||||
*buf = rom_buf;
|
||||
fclose(file);
|
||||
return rc;
|
||||
|
||||
error:
|
||||
if (file)
|
||||
fclose(file);
|
||||
free(rom_buf);
|
||||
*buf = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Dump stuff to file.
|
||||
static bool dump_to_file(const char *path, const void *data, size_t size)
|
||||
@@ -218,10 +309,24 @@ void save_ram_file(const char* path, int type)
|
||||
size_t size = psnes_get_memory_size(type);
|
||||
uint8_t *data = psnes_get_memory_data(type);
|
||||
|
||||
if ( data && size > 0 )
|
||||
if (data && size > 0)
|
||||
dump_to_file(path, data, size);
|
||||
}
|
||||
|
||||
static char* load_xml_map(const char *path)
|
||||
{
|
||||
char *xml_buf = NULL;
|
||||
if (*path)
|
||||
{
|
||||
if (!read_file_string(path, &xml_buf))
|
||||
SSNES_LOG("Did not find XML memory map in \"%s\"\n", path);
|
||||
else
|
||||
SSNES_LOG("Found XML memory map in \"%s\"\n", path);
|
||||
}
|
||||
|
||||
return xml_buf;
|
||||
}
|
||||
|
||||
static bool load_sgb_rom(void)
|
||||
{
|
||||
void *rom_buf = NULL;
|
||||
@@ -237,27 +342,25 @@ static bool load_sgb_rom(void)
|
||||
goto error;
|
||||
}
|
||||
|
||||
extra_rom = fopen(g_extern.gb_rom_path, "rb");
|
||||
if (!extra_rom)
|
||||
{
|
||||
SSNES_ERR("Couldn't open GameBoy ROM!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((extra_rom_len = read_rom_file(extra_rom, &extra_rom_buf)) == -1)
|
||||
if ((extra_rom_len = read_file(g_extern.gb_rom_path, &extra_rom_buf)) == -1)
|
||||
{
|
||||
SSNES_ERR("Cannot read GameBoy rom.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
char *xml_buf = load_xml_map(g_extern.xml_name);
|
||||
|
||||
if (!psnes_load_cartridge_super_game_boy(
|
||||
NULL, rom_buf, rom_len,
|
||||
xml_buf, rom_buf, rom_len,
|
||||
NULL, extra_rom_buf, extra_rom_len))
|
||||
{
|
||||
SSNES_ERR("Cannot load SGB/GameBoy rom.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (xml_buf)
|
||||
free(xml_buf);
|
||||
|
||||
if (g_extern.rom_file)
|
||||
fclose(g_extern.rom_file);
|
||||
if (extra_rom)
|
||||
@@ -291,23 +394,18 @@ static bool load_bsx_rom(bool slotted)
|
||||
goto error;
|
||||
}
|
||||
|
||||
extra_rom = fopen(g_extern.bsx_rom_path, "rb");
|
||||
if (!extra_rom)
|
||||
{
|
||||
SSNES_ERR("Couldn't open BSX game rom!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((extra_rom_len = read_rom_file(extra_rom, &extra_rom_buf)) == -1)
|
||||
if ((extra_rom_len = read_file(g_extern.bsx_rom_path, &extra_rom_buf)) == -1)
|
||||
{
|
||||
SSNES_ERR("Cannot read BSX game rom.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
char *xml_buf = load_xml_map(g_extern.xml_name);
|
||||
|
||||
if (slotted)
|
||||
{
|
||||
if (!psnes_load_cartridge_bsx_slotted(
|
||||
NULL, rom_buf, rom_len,
|
||||
xml_buf, rom_buf, rom_len,
|
||||
NULL, extra_rom_buf, extra_rom_len))
|
||||
{
|
||||
SSNES_ERR("Cannot load BSX slotted rom.\n");
|
||||
@@ -326,6 +424,9 @@ static bool load_bsx_rom(bool slotted)
|
||||
}
|
||||
}
|
||||
|
||||
if (xml_buf)
|
||||
free(xml_buf);
|
||||
|
||||
if (g_extern.rom_file)
|
||||
fclose(g_extern.rom_file);
|
||||
if (extra_rom)
|
||||
@@ -365,23 +466,18 @@ static bool load_sufami_rom(void)
|
||||
{
|
||||
if (strlen(roms[i]) > 0)
|
||||
{
|
||||
extra_rom[i] = fopen(roms[i], "rb");
|
||||
if (!extra_rom[i])
|
||||
if ((extra_rom_len[i] = read_file(roms[i], &extra_rom_buf[i])) == -1)
|
||||
{
|
||||
SSNES_ERR("Couldn't open BSX game rom!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((extra_rom_len[i] = read_rom_file(extra_rom[i], &extra_rom_buf[i])) == -1)
|
||||
{
|
||||
SSNES_ERR("Cannot read BSX game rom.\n");
|
||||
SSNES_ERR("Cannot read Sufami game rom.\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char *xml_buf = load_xml_map(g_extern.xml_name);
|
||||
|
||||
if (!psnes_load_cartridge_sufami_turbo(
|
||||
NULL, rom_buf, rom_len,
|
||||
xml_buf, rom_buf, rom_len,
|
||||
NULL, extra_rom_buf[0], extra_rom_len[0],
|
||||
NULL, extra_rom_buf[1], extra_rom_len[1]))
|
||||
{
|
||||
@@ -389,6 +485,8 @@ static bool load_sufami_rom(void)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (xml_buf)
|
||||
free(xml_buf);
|
||||
|
||||
if (g_extern.rom_file)
|
||||
fclose(g_extern.rom_file);
|
||||
@@ -430,13 +528,18 @@ static bool load_normal_rom(void)
|
||||
|
||||
SSNES_LOG("ROM size: %d bytes\n", (int)rom_len);
|
||||
|
||||
if (!psnes_load_cartridge_normal(NULL, rom_buf, rom_len))
|
||||
char *xml_buf = load_xml_map(g_extern.xml_name);
|
||||
|
||||
if (!psnes_load_cartridge_normal(xml_buf, rom_buf, rom_len))
|
||||
{
|
||||
SSNES_ERR("ROM file is not valid!\n");
|
||||
free(rom_buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xml_buf)
|
||||
free(xml_buf);
|
||||
|
||||
free(rom_buf);
|
||||
return true;
|
||||
}
|
||||
@@ -479,3 +582,124 @@ bool init_rom_file(enum ssnes_game_type type)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Yep, this is C alright ;)
|
||||
char** dir_list_new(const char *dir, const char *ext)
|
||||
{
|
||||
size_t path_len = strlen(dir);
|
||||
|
||||
size_t cur_ptr = 0;
|
||||
size_t cur_size = 32;
|
||||
char **dir_list = NULL;
|
||||
|
||||
#ifdef _WIN32
|
||||
WIN32_FIND_DATAW ffd;
|
||||
HANDLE hFind = INVALID_HANDLE_VALUE;
|
||||
|
||||
|
||||
size_t final_off = MAX_PATH + path_len + 2;
|
||||
|
||||
wchar_t wchar_buf[MAX_PATH + 1];
|
||||
char utf8_buf[MAX_PATH + 3];
|
||||
|
||||
strlcpy(utf8_buf, dir, sizeof(utf8_buf));
|
||||
strlcat(utf8_buf, "/*", sizeof(utf8_buf));
|
||||
utf8_buf[MAX_PATH + 2] = '\0';
|
||||
|
||||
int ret = MultiByteToWideChar(CP_UTF8, 0, utf8_buf, strlen(utf8_buf), wchar_buf, MAX_PATH);
|
||||
wchar_buf[ret] = 0;
|
||||
|
||||
hFind = FindFirstFileW(wchar_buf, &ffd);
|
||||
if (hFind == INVALID_HANDLE_VALUE)
|
||||
goto error;
|
||||
#else
|
||||
DIR *directory = NULL;
|
||||
const struct dirent *entry = NULL;
|
||||
size_t final_off = sizeof(entry->d_name) + path_len + 2;
|
||||
|
||||
directory = opendir(dir);
|
||||
if (!directory)
|
||||
goto error;
|
||||
#endif
|
||||
|
||||
dir_list = calloc(cur_size, sizeof(char*));
|
||||
if (!dir_list)
|
||||
goto error;
|
||||
|
||||
#ifdef _WIN32 // Hard to read? Blame non-POSIX heathens!
|
||||
do
|
||||
#else
|
||||
while ((entry = readdir(directory)))
|
||||
#endif
|
||||
{
|
||||
// Not a perfect search of course, but hopefully good enough in practice.
|
||||
#ifdef _WIN32
|
||||
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
continue;
|
||||
int ret = WideCharToMultiByte(CP_UTF8, 0, ffd.cFileName, wcslen(ffd.cFileName), utf8_buf, MAX_PATH, NULL, NULL);
|
||||
utf8_buf[ret] = '\0';
|
||||
if (ext && !strstr(utf8_buf, ext))
|
||||
continue;
|
||||
#else
|
||||
if (ext && !strstr(entry->d_name, ext))
|
||||
continue;
|
||||
#endif
|
||||
|
||||
dir_list[cur_ptr] = malloc(final_off);
|
||||
if (!dir_list[cur_ptr])
|
||||
goto error;
|
||||
|
||||
strlcpy(dir_list[cur_ptr], dir, final_off);
|
||||
strlcat(dir_list[cur_ptr], "/", final_off);
|
||||
#ifdef _WIN32
|
||||
strlcat(dir_list[cur_ptr], utf8_buf, final_off);
|
||||
#else
|
||||
strlcat(dir_list[cur_ptr], entry->d_name, final_off);
|
||||
#endif
|
||||
|
||||
cur_ptr++;
|
||||
if (cur_ptr + 1 == cur_size) // Need to reserve for NULL.
|
||||
{
|
||||
cur_size *= 2;
|
||||
dir_list = realloc(dir_list, cur_size * sizeof(char*));
|
||||
if (!dir_list)
|
||||
goto error;
|
||||
|
||||
// Make sure it's all NULL'd out since we cannot rely on realloc to do this.
|
||||
memset(dir_list + cur_ptr, 0, (cur_size - cur_ptr) * sizeof(char*));
|
||||
}
|
||||
}
|
||||
#ifdef _WIN32
|
||||
while (FindNextFileW(hFind, &ffd) != 0);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
FindClose(hFind);
|
||||
#else
|
||||
closedir(directory);
|
||||
#endif
|
||||
return dir_list;
|
||||
|
||||
error:
|
||||
SSNES_ERR("Failed to open directory: \"%s\"\n", dir);
|
||||
#ifdef _WIN32
|
||||
if (hFind != INVALID_HANDLE_VALUE)
|
||||
FindClose(hFind);
|
||||
#else
|
||||
if (directory)
|
||||
closedir(directory);
|
||||
#endif
|
||||
dir_list_free(dir_list);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void dir_list_free(char **dir_list)
|
||||
{
|
||||
if (!dir_list)
|
||||
return;
|
||||
|
||||
char **orig = dir_list;
|
||||
while (*dir_list)
|
||||
free(*dir_list++);
|
||||
free(orig);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -34,4 +36,8 @@ void save_ram_file(const char* path, int type);
|
||||
|
||||
bool init_rom_file(enum ssnes_game_type type);
|
||||
|
||||
// Returns a NULL-terminated list of files in a directory with full paths.
|
||||
char** dir_list_new(const char *dir, const char *ext);
|
||||
void dir_list_free(char **dir_list);
|
||||
|
||||
#endif
|
||||
@@ -24,6 +24,13 @@
|
||||
#include <stdio.h>
|
||||
#include "record/ffemu.h"
|
||||
#include "message.h"
|
||||
#include "rewind.h"
|
||||
#include "movie.h"
|
||||
#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"
|
||||
@@ -31,12 +38,24 @@
|
||||
|
||||
#ifdef HAVE_SRC
|
||||
#include <samplerate.h>
|
||||
#else
|
||||
#include "audio/hermite.h"
|
||||
#endif
|
||||
|
||||
|
||||
#define MAX_PLAYERS 5
|
||||
#define MAX_BINDS 22 // 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
|
||||
{
|
||||
SSNES_SHADER_CG,
|
||||
SSNES_SHADER_BSNES,
|
||||
SSNES_SHADER_AUTO,
|
||||
SSNES_SHADER_NONE
|
||||
};
|
||||
|
||||
// All config related settings go here.
|
||||
struct settings
|
||||
{
|
||||
struct
|
||||
@@ -50,16 +69,32 @@ 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];
|
||||
unsigned filter;
|
||||
char filter_path[256];
|
||||
enum ssnes_shader_type shader_type;
|
||||
|
||||
bool render_to_texture;
|
||||
double fbo_scale_x;
|
||||
double fbo_scale_y;
|
||||
char second_pass_shader[256];
|
||||
bool second_pass_smooth;
|
||||
char shader_dir[256];
|
||||
|
||||
char font_path[256];
|
||||
unsigned font_size;
|
||||
float msg_pos_x;
|
||||
float msg_pos_y;
|
||||
|
||||
bool force_16bit;
|
||||
bool disable_composition;
|
||||
|
||||
bool hires_record;
|
||||
bool post_filter_record;
|
||||
|
||||
char external_driver[256];
|
||||
} video;
|
||||
|
||||
struct
|
||||
@@ -73,6 +108,9 @@ struct settings
|
||||
unsigned latency;
|
||||
bool sync;
|
||||
int src_quality;
|
||||
|
||||
char dsp_plugin[256];
|
||||
char external_driver[256];
|
||||
} audio;
|
||||
|
||||
struct
|
||||
@@ -81,9 +119,20 @@ struct settings
|
||||
struct snes_keybind binds[MAX_PLAYERS][MAX_BINDS];
|
||||
float axis_threshold;
|
||||
unsigned joypad_map[MAX_PLAYERS];
|
||||
bool netplay_client_swap_input;
|
||||
} input;
|
||||
|
||||
char libsnes[256];
|
||||
char cheat_database[256];
|
||||
|
||||
char screenshot_directory[256];
|
||||
|
||||
bool rewind_enable;
|
||||
unsigned rewind_buffer_size;
|
||||
unsigned rewind_granularity;
|
||||
|
||||
bool pause_nonactive;
|
||||
unsigned autosave_interval;
|
||||
};
|
||||
|
||||
enum ssnes_game_type
|
||||
@@ -95,25 +144,31 @@ enum ssnes_game_type
|
||||
SSNES_CART_SUFAMI,
|
||||
};
|
||||
|
||||
|
||||
// All run-time- / command line flag-related globals go here.
|
||||
struct global
|
||||
{
|
||||
bool verbose;
|
||||
SRC_STATE *source;
|
||||
bool audio_active;
|
||||
bool video_active;
|
||||
bool force_fullscreen;
|
||||
|
||||
bool has_mouse[2];
|
||||
bool has_scope[2];
|
||||
bool has_justifier;
|
||||
bool has_justifiers;
|
||||
bool has_multitap;
|
||||
bool disconnect_device[2];
|
||||
|
||||
FILE *rom_file;
|
||||
enum ssnes_game_type game_type;
|
||||
uint32_t cart_crc;
|
||||
|
||||
char gb_rom_path[256];
|
||||
char bsx_rom_path[256];
|
||||
char sufami_rom_path[2][256];
|
||||
bool has_set_save_path;
|
||||
bool has_set_state_path;
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
char config_path[256];
|
||||
@@ -126,11 +181,19 @@ struct global
|
||||
char savefile_name_asrm[512];
|
||||
char savefile_name_bsrm[512];
|
||||
char savestate_name[256];
|
||||
char ups_name[512];
|
||||
char xml_name[512];
|
||||
|
||||
unsigned state_slot;
|
||||
|
||||
struct
|
||||
{
|
||||
#ifdef HAVE_SRC
|
||||
SRC_STATE *source;
|
||||
#else
|
||||
hermite_resampler_t *source;
|
||||
#endif
|
||||
|
||||
float *data;
|
||||
size_t data_ptr;
|
||||
size_t chunk_size;
|
||||
@@ -141,15 +204,76 @@ 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
|
||||
{
|
||||
bool active;
|
||||
uint32_t *buffer;
|
||||
uint32_t *colormap;
|
||||
unsigned pitch;
|
||||
dylib_t lib;
|
||||
unsigned scale;
|
||||
|
||||
void (*psize)(unsigned *width, unsigned *height);
|
||||
void (*prender)(uint32_t *colormap, uint32_t *output, unsigned outpitch,
|
||||
const uint16_t *input, unsigned pitch, unsigned width, unsigned height);
|
||||
} filter;
|
||||
|
||||
msg_queue_t *msg_queue;
|
||||
|
||||
// Rewind support.
|
||||
state_manager_t *state_manager;
|
||||
void *state_buf;
|
||||
bool frame_is_reverse;
|
||||
|
||||
// Movie record support
|
||||
bsv_movie_t *bsv_movie;
|
||||
char bsv_movie_path[256];
|
||||
bool bsv_movie_end;
|
||||
bool bsv_movie_playback;
|
||||
|
||||
// Pausing support
|
||||
bool is_paused;
|
||||
|
||||
// Autosave support.
|
||||
autosave_t *autosave[2];
|
||||
|
||||
// Netplay.
|
||||
netplay_t *netplay;
|
||||
char netplay_server[256];
|
||||
bool netplay_enable;
|
||||
bool netplay_is_client;
|
||||
unsigned netplay_sync_frames;
|
||||
uint16_t netplay_port;
|
||||
|
||||
// FFmpeg record.
|
||||
#ifdef HAVE_FFMPEG
|
||||
ffemu_t *rec;
|
||||
char record_path[256];
|
||||
bool recording;
|
||||
#endif
|
||||
|
||||
char title_buf[64];
|
||||
|
||||
struct
|
||||
{
|
||||
char** elems;
|
||||
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);
|
||||
@@ -157,20 +281,46 @@ 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)
|
||||
|
||||
static inline uint32_t next_pow2(uint32_t v)
|
||||
{
|
||||
v--;
|
||||
v |= v >> 1;
|
||||
v |= v >> 2;
|
||||
v |= v >> 4;
|
||||
v |= v >> 8;
|
||||
v |= v >> 16;
|
||||
v++;
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline uint8_t is_little_endian(void)
|
||||
{
|
||||
union
|
||||
{
|
||||
uint16_t x;
|
||||
uint8_t y[2];
|
||||
} u;
|
||||
|
||||
u.x = 1;
|
||||
return u.y[0];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
/* 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;
|
||||
}
|
||||
|
||||
const char *cg_shader = NULL;
|
||||
const char *xml_shader = NULL;
|
||||
enum ssnes_shader_type type = g_settings.video.shader_type;
|
||||
if ((type == SSNES_SHADER_CG || type == SSNES_SHADER_AUTO) && *g_settings.video.cg_shader_path)
|
||||
cg_shader = g_settings.video.cg_shader_path;
|
||||
else if ((type == SSNES_SHADER_BSNES || type == SSNES_SHADER_AUTO) && *g_settings.video.bsnes_shader_path)
|
||||
xml_shader = g_settings.video.bsnes_shader_path;
|
||||
|
||||
|
||||
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 = xml_shader,
|
||||
.cg_shader = cg_shader,
|
||||
.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
|
||||
@@ -0,0 +1,143 @@
|
||||
/* 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 "gfx_common.h"
|
||||
#include "general.h"
|
||||
#include <sys/time.h>
|
||||
|
||||
static float tv_to_fps(const struct timeval *tv, const struct timeval *new_tv, int frames)
|
||||
{
|
||||
float time = new_tv->tv_sec - tv->tv_sec + (new_tv->tv_usec - tv->tv_usec)/1000000.0;
|
||||
return frames/time;
|
||||
}
|
||||
|
||||
static unsigned gl_frames = 0;
|
||||
|
||||
void gfx_window_title_reset(void)
|
||||
{
|
||||
gl_frames = 0;
|
||||
}
|
||||
|
||||
bool gfx_window_title(char *buf, size_t size)
|
||||
{
|
||||
static struct timeval tv;
|
||||
struct timeval new_tv;
|
||||
bool ret = false;
|
||||
|
||||
if (gl_frames == 0)
|
||||
{
|
||||
gettimeofday(&tv, NULL);
|
||||
snprintf(buf, size, "%s", g_extern.title_buf);
|
||||
ret = true;
|
||||
}
|
||||
else if ((gl_frames % 180) == 0)
|
||||
{
|
||||
gettimeofday(&new_tv, NULL);
|
||||
struct timeval tmp_tv = tv;
|
||||
tv = new_tv;
|
||||
|
||||
float fps = tv_to_fps(&tmp_tv, &new_tv, 180);
|
||||
|
||||
snprintf(buf, size, "%s || FPS: %6.1f || Frames: %d", g_extern.title_buf, fps, gl_frames);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
gl_frames++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include "dynamic.h"
|
||||
// We only load this library once, so we let it be unloaded at application shutdown,
|
||||
// since unloading it early seems to cause issues on some systems.
|
||||
|
||||
static dylib_t dwmlib = NULL;
|
||||
|
||||
static void gfx_dwm_shutdown(void)
|
||||
{
|
||||
if (dwmlib)
|
||||
dylib_close(dwmlib);
|
||||
}
|
||||
|
||||
void gfx_set_dwm(void)
|
||||
{
|
||||
static bool inited = false;
|
||||
if (inited)
|
||||
return;
|
||||
inited = true;
|
||||
|
||||
dwmlib = dylib_load("dwmapi.dll");
|
||||
if (!dwmlib)
|
||||
{
|
||||
SSNES_LOG("Did not find dwmapi.dll");
|
||||
return;
|
||||
}
|
||||
atexit(gfx_dwm_shutdown);
|
||||
|
||||
HRESULT (WINAPI *mmcss)(BOOL) = (HRESULT (WINAPI*)(BOOL))dylib_proc(dwmlib, "DwmEnableMMCSS");
|
||||
if (mmcss)
|
||||
{
|
||||
SSNES_LOG("Setting multimedia scheduling for DWM.\n");
|
||||
mmcss(TRUE);
|
||||
}
|
||||
|
||||
if (!g_settings.video.disable_composition)
|
||||
return;
|
||||
|
||||
HRESULT (WINAPI *composition_enable)(UINT) = (HRESULT (WINAPI*)(UINT))dylib_proc(dwmlib, "DwmEnableComposition");
|
||||
if (!composition_enable)
|
||||
{
|
||||
SSNES_ERR("Did not find DwmEnableComposition ...\n");
|
||||
return;
|
||||
}
|
||||
|
||||
HRESULT ret = composition_enable(0);
|
||||
if (FAILED(ret))
|
||||
SSNES_ERR("Failed to set composition state ...\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#include "SDL_syswm.h"
|
||||
#include "SDL.h"
|
||||
|
||||
void gfx_get_window_size(unsigned *width, unsigned *height)
|
||||
{
|
||||
#if defined(__APPLE__) || defined(_WIN32)
|
||||
SDL_Event evnt;
|
||||
while (SDL_PollEvent(&evnt));
|
||||
const SDL_VideoInfo *info = SDL_GetVideoInfo();
|
||||
*width = info->current_w;
|
||||
*height = info->current_h;
|
||||
#else
|
||||
// It seems that we need to go hardcore to get the actual
|
||||
// window sizes properly right after startup ... :D
|
||||
SDL_SysWMinfo info;
|
||||
SDL_VERSION(&info.version);
|
||||
SDL_GetWMInfo(&info);
|
||||
XWindowAttributes target;
|
||||
info.info.x11.lock_func();
|
||||
XGetWindowAttributes(info.info.x11.display, info.info.x11.window,
|
||||
&target);
|
||||
info.info.x11.unlock_func();
|
||||
|
||||
*width = target.width;
|
||||
*height = target.height;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/* 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 __GFX_COMMON_H
|
||||
#define __GFX_COMMON_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
bool gfx_window_title(char *buf, size_t size);
|
||||
void gfx_window_title_reset(void);
|
||||
|
||||
#ifdef _WIN32
|
||||
void gfx_set_dwm(void);
|
||||
#endif
|
||||
|
||||
void gfx_get_window_size(unsigned *width, unsigned *height);
|
||||
|
||||
#endif
|
||||
+67
-1
@@ -19,6 +19,20 @@
|
||||
#define __GL_COMMON_H
|
||||
|
||||
#include "general.h"
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/gl.h>
|
||||
#include <OpenGL/glext.h>
|
||||
#else
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
#endif
|
||||
|
||||
static inline bool gl_check_error(void)
|
||||
{
|
||||
@@ -29,7 +43,7 @@ static inline bool gl_check_error(void)
|
||||
SSNES_ERR("GL: Invalid enum.\n");
|
||||
break;
|
||||
case GL_INVALID_VALUE:
|
||||
SSNES_ERR("GL: Invalid value.\n");
|
||||
SSNES_ERR("GL: Invalid value. (You're not alone.)\n");
|
||||
break;
|
||||
case GL_INVALID_OPERATION:
|
||||
SSNES_ERR("GL: Invalid operation.\n");
|
||||
@@ -56,4 +70,56 @@ static inline bool gl_check_error(void)
|
||||
return false;
|
||||
}
|
||||
|
||||
struct gl_fbo_rect
|
||||
{
|
||||
unsigned img_width;
|
||||
unsigned img_height;
|
||||
unsigned max_img_width;
|
||||
unsigned max_img_height;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
};
|
||||
|
||||
enum gl_scale_type
|
||||
{
|
||||
SSNES_SCALE_ABSOLUTE,
|
||||
SSNES_SCALE_INPUT,
|
||||
SSNES_SCALE_VIEWPORT
|
||||
};
|
||||
|
||||
struct gl_fbo_scale
|
||||
{
|
||||
enum gl_scale_type type_x;
|
||||
enum gl_scale_type type_y;
|
||||
float scale_x;
|
||||
float scale_y;
|
||||
unsigned abs_x;
|
||||
unsigned abs_y;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
struct gl_tex_info
|
||||
{
|
||||
GLuint tex;
|
||||
GLfloat input_size[2];
|
||||
GLfloat tex_size[2];
|
||||
GLfloat 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,524 @@
|
||||
/* 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)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
gfx_set_dwm();
|
||||
#endif
|
||||
|
||||
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"
|
||||
};
|
||||
|
||||
+924
-70
File diff suppressed because it is too large.
Load diff
+12
-3
@@ -20,6 +20,7 @@
|
||||
#define __SSNES_CG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "gl_common.h"
|
||||
|
||||
bool gl_cg_init(const char *path);
|
||||
|
||||
@@ -29,9 +30,17 @@ 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 *prev_info,
|
||||
const struct gl_tex_info *fbo_info, unsigned fbo_info_cnt);
|
||||
|
||||
void gl_cg_activate(void);
|
||||
void gl_cg_deactivate(void);
|
||||
void gl_cg_use(unsigned index);
|
||||
|
||||
unsigned gl_cg_num(void);
|
||||
|
||||
bool gl_cg_filter_type(unsigned index, bool *smooth);
|
||||
void gl_cg_shader_scale(unsigned index, struct gl_fbo_scale *scale);
|
||||
|
||||
#endif
|
||||
+1001
-91
File diff suppressed because it is too large.
Load diff
+12
-3
@@ -20,6 +20,7 @@
|
||||
#define __SSNES_GLSL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "gl_common.h"
|
||||
|
||||
bool gl_glsl_init(const char *path);
|
||||
|
||||
@@ -29,9 +30,17 @@ 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 *prev_info,
|
||||
const struct gl_tex_info *fbo_info, unsigned fbo_info_cnt);
|
||||
|
||||
void gl_glsl_activate(void);
|
||||
void gl_glsl_deactivate(void);
|
||||
void gl_glsl_use(unsigned index);
|
||||
|
||||
unsigned gl_glsl_num(void);
|
||||
|
||||
bool gl_glsl_filter_type(unsigned index, bool *smooth);
|
||||
void gl_glsl_shader_scale(unsigned index, struct gl_fbo_scale *scale);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,260 @@
|
||||
/* 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;
|
||||
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].is_input = true;
|
||||
break;
|
||||
case SSNES_STATE_INPUT_SLOT2:
|
||||
tracker->info[i].input_ptr = &tracker->input_state[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->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
|
||||
+724
@@ -0,0 +1,724 @@
|
||||
/* 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 "driver.h"
|
||||
#include "general.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <math.h>
|
||||
#ifdef HAVE_FREETYPE
|
||||
#include "fonts.h"
|
||||
#endif
|
||||
#include "gfx_common.h"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
#include <X11/extensions/XShm.h>
|
||||
#include <X11/extensions/Xv.h>
|
||||
#include <X11/extensions/Xvlib.h>
|
||||
|
||||
// Adapted from bSNES and MPlayer source.
|
||||
|
||||
typedef struct xv xv_t;
|
||||
struct xv
|
||||
{
|
||||
Display *display;
|
||||
GC gc;
|
||||
Window window;
|
||||
Colormap colormap;
|
||||
XShmSegmentInfo shminfo;
|
||||
|
||||
Atom quit_atom;
|
||||
bool focus;
|
||||
|
||||
XvPortID port;
|
||||
int depth;
|
||||
int visualid;
|
||||
|
||||
XvImage *image;
|
||||
uint32_t fourcc;
|
||||
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
bool keep_aspect;
|
||||
|
||||
uint8_t *ytable;
|
||||
uint8_t *utable;
|
||||
uint8_t *vtable;
|
||||
unsigned luma_i;
|
||||
unsigned chroma_i;
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
font_renderer_t *font;
|
||||
#endif
|
||||
|
||||
void (*render_func)(xv_t*, const void *frame, unsigned width, unsigned height, unsigned pitch);
|
||||
};
|
||||
|
||||
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)
|
||||
XvSetPortAttribute(xv->display, xv->port, atom, !state);
|
||||
else
|
||||
SSNES_WARN("Failed to set SYNC_TO_VBLANK attribute.\n");
|
||||
}
|
||||
|
||||
static volatile sig_atomic_t g_quit = 0;
|
||||
static void sighandler(int sig)
|
||||
{
|
||||
g_quit = 1;
|
||||
}
|
||||
|
||||
static void init_yuv_tables(xv_t *xv)
|
||||
{
|
||||
xv->ytable = malloc(0x8000);
|
||||
xv->utable = malloc(0x8000);
|
||||
xv->vtable = malloc(0x8000);
|
||||
|
||||
for (unsigned i = 0; i < 0x8000; i++)
|
||||
{
|
||||
// Extract RGB555 color data from i
|
||||
uint8_t r = (i >> 10) & 0x1F, g = (i >> 5) & 0x1F, b = (i) & 0x1F;
|
||||
r = (r << 3) | (r >> 2); //R5->R8
|
||||
g = (g << 3) | (g >> 2); //G5->G8
|
||||
b = (b << 3) | (b >> 2); //B5->B8
|
||||
|
||||
int y = (int)(+((double)r * 0.257) + ((double)g * 0.504) + ((double)b * 0.098) + 16.0);
|
||||
int u = (int)(-((double)r * 0.148) - ((double)g * 0.291) + ((double)b * 0.439) + 128.0);
|
||||
int v = (int)(+((double)r * 0.439) - ((double)g * 0.368) - ((double)b * 0.071) + 128.0);
|
||||
|
||||
xv->ytable[i] = y < 0 ? 0 : y > 255 ? 255 : y;
|
||||
xv->utable[i] = u < 0 ? 0 : u > 255 ? 255 : u;
|
||||
xv->vtable[i] = v < 0 ? 0 : v > 255 ? 255 : v;
|
||||
}
|
||||
}
|
||||
|
||||
// Source: MPlayer
|
||||
static void hide_mouse(xv_t *xv)
|
||||
{
|
||||
Cursor no_ptr;
|
||||
Pixmap bm_no;
|
||||
XColor black, dummy;
|
||||
Colormap colormap;
|
||||
static char bm_no_data[] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
colormap = DefaultColormap(xv->display, DefaultScreen(xv->display));
|
||||
if (!XAllocNamedColor(xv->display, colormap, "black", &black, &dummy))
|
||||
return;
|
||||
|
||||
bm_no = XCreateBitmapFromData(xv->display, xv->window, bm_no_data, 8, 8);
|
||||
no_ptr = XCreatePixmapCursor(xv->display, bm_no, bm_no, &black, &black, 0, 0);
|
||||
XDefineCursor(xv->display, xv->window, no_ptr);
|
||||
XFreeCursor(xv->display, no_ptr);
|
||||
if (bm_no != None)
|
||||
XFreePixmap(xv->display, bm_no);
|
||||
XFreeColors(xv->display, colormap, &black.pixel, 1, 0);
|
||||
}
|
||||
|
||||
static Atom XA_NET_WM_STATE;
|
||||
static Atom XA_NET_WM_STATE_FULLSCREEN;
|
||||
#define XA_INIT(x) XA##x = XInternAtom(xv->display, #x, False)
|
||||
#define _NET_WM_STATE_ADD 1
|
||||
|
||||
// Source: MPlayer
|
||||
static void set_fullscreen(xv_t *xv)
|
||||
{
|
||||
XA_INIT(_NET_WM_STATE);
|
||||
XA_INIT(_NET_WM_STATE_FULLSCREEN);
|
||||
|
||||
if (!XA_NET_WM_STATE || !XA_NET_WM_STATE_FULLSCREEN)
|
||||
{
|
||||
SSNES_WARN("X11: Cannot set fullscreen.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
XEvent xev;
|
||||
|
||||
xev.xclient.type = ClientMessage;
|
||||
xev.xclient.serial = 0;
|
||||
xev.xclient.send_event = True;
|
||||
xev.xclient.message_type = XA_NET_WM_STATE;
|
||||
xev.xclient.window = xv->window;
|
||||
xev.xclient.format = 32;
|
||||
xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
|
||||
xev.xclient.data.l[1] = XA_NET_WM_STATE_FULLSCREEN;
|
||||
xev.xclient.data.l[2] = 0;
|
||||
xev.xclient.data.l[3] = 0;
|
||||
xev.xclient.data.l[4] = 0;
|
||||
|
||||
XSendEvent(xv->display, DefaultRootWindow(xv->display), False,
|
||||
SubstructureRedirectMask | SubstructureNotifyMask,
|
||||
&xev);
|
||||
}
|
||||
|
||||
static void xv_init_font(xv_t *xv, const char *font_path, unsigned font_size)
|
||||
{
|
||||
#ifdef HAVE_FREETYPE
|
||||
if (*font_path)
|
||||
{
|
||||
xv->font = font_renderer_new(font_path, font_size);
|
||||
if (!xv->font)
|
||||
SSNES_WARN("Failed to init font.\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// We render @ 2x scale to combat chroma downsampling. Also makes fonts more bearable :)
|
||||
static void render16_yuy2(xv_t *xv, const void *input_, unsigned width, unsigned height, unsigned pitch)
|
||||
{
|
||||
const uint16_t *input = input_;
|
||||
uint8_t *output = (uint8_t*)xv->image->data;
|
||||
|
||||
for (unsigned y = 0; y < height; y++)
|
||||
{
|
||||
for (unsigned x = 0; x < width; x++)
|
||||
{
|
||||
uint16_t p = *input++;
|
||||
|
||||
uint8_t y0 = xv->ytable[p];
|
||||
uint8_t u = xv->utable[p];
|
||||
uint8_t v = xv->vtable[p];
|
||||
|
||||
unsigned img_width = xv->width << 1;
|
||||
output[0] = output[img_width] = y0;
|
||||
output[1] = output[img_width + 1] = u;
|
||||
output[2] = output[img_width + 2] = y0;
|
||||
output[3] = output[img_width + 3] = v;
|
||||
output += 4;
|
||||
}
|
||||
|
||||
input += (pitch >> 1) - width;
|
||||
output += (xv->width - width) << 2;
|
||||
}
|
||||
}
|
||||
|
||||
static void render16_uyvy(xv_t *xv, const void *input_, unsigned width, unsigned height, unsigned pitch)
|
||||
{
|
||||
const uint16_t *input = input_;
|
||||
uint8_t *output = (uint8_t*)xv->image->data;
|
||||
|
||||
for (unsigned y = 0; y < height; y++)
|
||||
{
|
||||
for (unsigned x = 0; x < width; x++)
|
||||
{
|
||||
uint16_t p = *input++;
|
||||
|
||||
uint8_t y0 = xv->ytable[p];
|
||||
uint8_t u = xv->utable[p];
|
||||
uint8_t v = xv->vtable[p];
|
||||
|
||||
unsigned img_width = xv->width << 1;
|
||||
output[0] = output[img_width] = u;
|
||||
output[1] = output[img_width + 1] = y0;
|
||||
output[2] = output[img_width + 2] = v;
|
||||
output[3] = output[img_width + 3] = y0;
|
||||
output += 4;
|
||||
}
|
||||
|
||||
input += (pitch >> 1) - width;
|
||||
output += (xv->width - width) << 2;
|
||||
}
|
||||
}
|
||||
|
||||
static void render32_yuy2(xv_t *xv, const void *input_, unsigned width, unsigned height, unsigned pitch)
|
||||
{
|
||||
const uint32_t *input = input_;
|
||||
uint8_t *output = (uint8_t*)xv->image->data;
|
||||
|
||||
for (unsigned y = 0; y < height; y++)
|
||||
{
|
||||
for (unsigned x = 0; x < width; x++)
|
||||
{
|
||||
uint32_t p = *input++;
|
||||
p = ((p >> 9) & 0x7c00) | ((p >> 6) & 0x03e0) | ((p >> 3) & 0x1f); // ARGB -> RGB15
|
||||
|
||||
uint8_t y0 = xv->ytable[p];
|
||||
uint8_t u = xv->utable[p];
|
||||
uint8_t v = xv->vtable[p];
|
||||
|
||||
unsigned img_width = xv->width << 1;
|
||||
output[0] = output[img_width] = y0;
|
||||
output[1] = output[img_width + 1] = u;
|
||||
output[2] = output[img_width + 2] = y0;
|
||||
output[3] = output[img_width + 3] = v;
|
||||
output += 4;
|
||||
}
|
||||
|
||||
input += (pitch >> 2) - width;
|
||||
output += (xv->width - width) << 2;
|
||||
}
|
||||
}
|
||||
|
||||
static void render32_uyvy(xv_t *xv, const void *input_, unsigned width, unsigned height, unsigned pitch)
|
||||
{
|
||||
const uint32_t *input = input_;
|
||||
uint16_t *output = (uint16_t*)xv->image->data;
|
||||
|
||||
for (unsigned y = 0; y < height; y++)
|
||||
{
|
||||
for (unsigned x = 0; x < width; x++)
|
||||
{
|
||||
uint32_t p = *input++;
|
||||
p = ((p >> 9) & 0x7c00) | ((p >> 6) & 0x03e0) | ((p >> 3) & 0x1f); // ARGB -> RGB15
|
||||
|
||||
uint8_t y0 = xv->ytable[p];
|
||||
uint8_t u = xv->utable[p];
|
||||
uint8_t v = xv->vtable[p];
|
||||
|
||||
unsigned img_width = xv->width << 1;
|
||||
output[0] = output[img_width] = u;
|
||||
output[1] = output[img_width + 1] = y0;
|
||||
output[2] = output[img_width + 2] = v;
|
||||
output[3] = output[img_width + 3] = y0;
|
||||
output += 4;
|
||||
}
|
||||
|
||||
input += (pitch >> 2) - width;
|
||||
output += (xv->width - width) << 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
return NULL;
|
||||
|
||||
xv->display = XOpenDisplay(NULL);
|
||||
|
||||
if (!XShmQueryExtension(xv->display))
|
||||
{
|
||||
SSNES_ERR("XVideo: XShm extension not found.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
xv->keep_aspect = video->force_aspect;
|
||||
|
||||
// Find an appropriate Xv port.
|
||||
xv->port = 0;
|
||||
XvAdaptorInfo *adaptor_info;
|
||||
unsigned adaptor_count = 0;
|
||||
XvQueryAdaptors(xv->display, DefaultRootWindow(xv->display), &adaptor_count, &adaptor_info);
|
||||
for (unsigned i = 0; i < adaptor_count; i++)
|
||||
{
|
||||
// Find adaptor that supports both input (memory->drawable) and image (drawable->screen) masks.
|
||||
if (adaptor_info[i].num_formats < 1) continue;
|
||||
if (!(adaptor_info[i].type & XvInputMask)) continue;
|
||||
if (!(adaptor_info[i].type & XvImageMask)) continue;
|
||||
|
||||
xv->port = adaptor_info[i].base_id;
|
||||
xv->depth = adaptor_info[i].formats->depth;
|
||||
xv->visualid = adaptor_info[i].formats->visual_id;
|
||||
break;
|
||||
}
|
||||
XvFreeAdaptorInfo(adaptor_info);
|
||||
|
||||
if (xv->port == 0)
|
||||
{
|
||||
SSNES_ERR("XVideo: Failed to find valid XvPort.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
XVisualInfo visualtemplate;
|
||||
visualtemplate.visualid = xv->visualid;
|
||||
visualtemplate.screen = DefaultScreen(xv->display);
|
||||
visualtemplate.depth = xv->depth;
|
||||
visualtemplate.visual = 0;
|
||||
int visualmatches = 0;
|
||||
XVisualInfo *visualinfo = XGetVisualInfo(xv->display, VisualIDMask | VisualScreenMask | VisualDepthMask, &visualtemplate, &visualmatches);
|
||||
if (visualmatches < 1 || !visualinfo->visual)
|
||||
{
|
||||
if (visualinfo) XFree(visualinfo);
|
||||
SSNES_ERR("XVideo: Unable to find Xv-compatible visual.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
xv->colormap = XCreateColormap(xv->display, DefaultRootWindow(xv->display), visualinfo->visual, AllocNone);
|
||||
XSetWindowAttributes attributes;
|
||||
attributes.colormap = xv->colormap;
|
||||
attributes.border_pixel = 0;
|
||||
attributes.event_mask = StructureNotifyMask | DestroyNotify | ClientMessage;
|
||||
|
||||
unsigned width = video->fullscreen ? ((video->width == 0) ? 256 : video->width) : video->width;
|
||||
unsigned height = video->fullscreen ? ((video->height == 0) ? 224 : video->height) : video->height;
|
||||
xv->window = XCreateWindow(xv->display, DefaultRootWindow(xv->display),
|
||||
0, 0, width, height,
|
||||
0, xv->depth, InputOutput, visualinfo->visual,
|
||||
CWColormap | CWBorderPixel | CWEventMask, &attributes);
|
||||
|
||||
XFree(visualinfo);
|
||||
XSetWindowBackground(xv->display, xv->window, 0);
|
||||
|
||||
XMapWindow(xv->display, xv->window);
|
||||
|
||||
char buf[128];
|
||||
if (gfx_window_title(buf, sizeof(buf)))
|
||||
XStoreName(xv->display, xv->window, buf);
|
||||
|
||||
if (video->fullscreen)
|
||||
set_fullscreen(xv);
|
||||
hide_mouse(xv);
|
||||
|
||||
xv->gc = XCreateGC(xv->display, xv->window, 0, 0);
|
||||
|
||||
// Set colorkey to auto paint, so that Xv video output is always visible
|
||||
Atom atom = XInternAtom(xv->display, "XV_AUTOPAINT_COLORKEY", true);
|
||||
if(atom != None) XvSetPortAttribute(xv->display, xv->port, atom, 1);
|
||||
|
||||
int format_count;
|
||||
XvImageFormatValues *format = XvListImageFormats(xv->display, xv->port, &format_count);
|
||||
|
||||
bool has_format = false;
|
||||
for (int i = 0; i < format_count; i++)
|
||||
{
|
||||
if (format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked)
|
||||
{
|
||||
if (format[i].component_order[0] == 'Y' && format[i].component_order[1] == 'U'
|
||||
&& format[i].component_order[2] == 'Y' && format[i].component_order[3] == 'V')
|
||||
{
|
||||
has_format = true;
|
||||
xv->fourcc = format[i].id;
|
||||
xv->render_func = video->rgb32 ? render32_yuy2 : render16_yuy2;
|
||||
xv->luma_i = 0; xv->chroma_i = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_format) for (int i = 0; i < format_count; i++)
|
||||
{
|
||||
if (format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked)
|
||||
{
|
||||
if (format[i].component_order[0] == 'U' && format[i].component_order[1] == 'Y'
|
||||
&& format[i].component_order[2] == 'V' && format[i].component_order[3] == 'Y')
|
||||
{
|
||||
has_format = true;
|
||||
xv->fourcc = format[i].id;
|
||||
xv->render_func = video->rgb32 ? render32_uyvy : render16_uyvy;
|
||||
xv->chroma_i = 0; xv->luma_i = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
free(format);
|
||||
if (!has_format)
|
||||
{
|
||||
SSNES_ERR("XVideo: unable to find a supported image format.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
xv->width = 512;
|
||||
xv->height = 512;
|
||||
|
||||
xv->image = XvShmCreateImage(xv->display, xv->port, xv->fourcc, NULL, xv->width, xv->height, &xv->shminfo);
|
||||
if (!xv->image)
|
||||
{
|
||||
SSNES_ERR("XVideo: XShmCreateImage failed.\n");
|
||||
goto error;
|
||||
}
|
||||
xv->width = xv->image->width;
|
||||
xv->height = xv->image->height;
|
||||
|
||||
xv->shminfo.shmid = shmget(IPC_PRIVATE, xv->image->data_size, IPC_CREAT | 0777);
|
||||
xv->shminfo.shmaddr = xv->image->data = shmat(xv->shminfo.shmid, NULL, 0);
|
||||
xv->shminfo.readOnly = false;
|
||||
if (!XShmAttach(xv->display, &xv->shminfo))
|
||||
{
|
||||
SSNES_ERR("XVideo: XShmAttach failed.\n");
|
||||
goto error;
|
||||
}
|
||||
XSync(xv->display, False);
|
||||
memset(xv->image->data, 128, xv->image->data_size);
|
||||
|
||||
xv->quit_atom = XInternAtom(xv->display, "WM_DELETE_WINDOW", False);
|
||||
if (xv->quit_atom)
|
||||
XSetWMProtocols(xv->display, xv->window, &xv->quit_atom, 1);
|
||||
|
||||
struct sigaction sa;
|
||||
sa.sa_handler = sighandler;
|
||||
sa.sa_flags = SA_RESTART;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
sigaction(SIGTERM, &sa, NULL);
|
||||
|
||||
xv_set_nonblock_state(xv, !video->vsync);
|
||||
xv->focus = true;
|
||||
|
||||
xv_init_font(xv, g_settings.video.font_path, g_settings.video.font_size);
|
||||
|
||||
void *xinput = input_x.init();
|
||||
if (xinput)
|
||||
{
|
||||
*input = &input_x;
|
||||
*input_data = xinput;
|
||||
}
|
||||
else
|
||||
*input = NULL;
|
||||
|
||||
init_yuv_tables(xv);
|
||||
return xv;
|
||||
|
||||
error:
|
||||
free(xv);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool check_resize(xv_t *xv, unsigned width, unsigned height)
|
||||
{
|
||||
// We render @ 2x scale to combat chroma downsampling.
|
||||
if (xv->width != (width << 1) || xv->height != (height << 1))
|
||||
{
|
||||
xv->width = width << 1;
|
||||
xv->height = height << 1;
|
||||
|
||||
XShmDetach(xv->display, &xv->shminfo);
|
||||
shmdt(xv->shminfo.shmaddr);
|
||||
shmctl(xv->shminfo.shmid, IPC_RMID, NULL);
|
||||
XFree(xv->image);
|
||||
|
||||
memset(&xv->shminfo, 0, sizeof(xv->shminfo));
|
||||
xv->image = XvShmCreateImage(xv->display, xv->port, xv->fourcc, NULL, xv->width, xv->height, &xv->shminfo);
|
||||
if (xv->image == None)
|
||||
{
|
||||
SSNES_ERR("Failed to create image.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
xv->width = xv->image->width;
|
||||
xv->height = xv->image->height;
|
||||
|
||||
xv->shminfo.shmid = shmget(IPC_PRIVATE, xv->image->data_size, IPC_CREAT | 0777);
|
||||
if (xv->shminfo.shmid < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to init SHM.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
xv->shminfo.shmaddr = xv->image->data = shmat(xv->shminfo.shmid, NULL, 0);
|
||||
xv->shminfo.readOnly = false;
|
||||
|
||||
if (!XShmAttach(xv->display, &xv->shminfo))
|
||||
{
|
||||
SSNES_ERR("Failed to reattch XvShm image.\n");
|
||||
return false;
|
||||
}
|
||||
XSync(xv->display, False);
|
||||
memset(xv->image->data, 128, xv->image->data_size);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void calc_out_rect(bool keep_aspect, unsigned *x, unsigned *y, unsigned *width, unsigned *height, unsigned vp_width, unsigned vp_height)
|
||||
{
|
||||
if (!keep_aspect)
|
||||
{
|
||||
*x = 0; *y = 0; *width = vp_width; *height = vp_height;
|
||||
}
|
||||
else
|
||||
{
|
||||
float desired_aspect = g_settings.video.aspect_ratio;
|
||||
float device_aspect = (float)vp_width / vp_height;
|
||||
|
||||
// If the aspect ratios of screen and desired aspect ratio are sufficiently equal (floating point stuff),
|
||||
// assume they are actually equal.
|
||||
if (fabs(device_aspect - desired_aspect) < 0.0001)
|
||||
{
|
||||
*x = 0; *y = 0; *width = vp_width; *height = vp_height;
|
||||
}
|
||||
else if (device_aspect > desired_aspect)
|
||||
{
|
||||
float delta = (desired_aspect / device_aspect - 1.0) / 2.0 + 0.5;
|
||||
*x = vp_width * (0.5 - delta); *y = 0; *width = 2.0 * vp_width * delta; *height = vp_height;
|
||||
}
|
||||
else
|
||||
{
|
||||
float delta = (device_aspect / desired_aspect - 1.0) / 2.0 + 0.5;
|
||||
*x = 0; *y = vp_height * (0.5 - delta); *width = vp_width; *height = 2.0 * vp_height * delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Is there some way to render directly like GL? :(
|
||||
static void xv_render_msg(xv_t *xv, const char *msg, unsigned width, unsigned height)
|
||||
{
|
||||
#ifdef HAVE_FREETYPE
|
||||
if (!xv->font)
|
||||
return;
|
||||
|
||||
struct font_output_list out;
|
||||
font_renderer_msg(xv->font, msg, &out);
|
||||
struct font_output *head = out.head;
|
||||
|
||||
int _base_x = g_settings.video.msg_pos_x * width;
|
||||
int _base_y = height - g_settings.video.msg_pos_y * height;
|
||||
|
||||
unsigned luma_i = xv->luma_i;
|
||||
unsigned chroma_i = xv->chroma_i;
|
||||
|
||||
while (head)
|
||||
{
|
||||
int base_x = _base_x + head->off_x;
|
||||
int base_y = _base_y - head->off_y;
|
||||
if (base_y >= 0)
|
||||
{
|
||||
for (int y = 0; y < head->height && (base_y + y) < height; y++)
|
||||
{
|
||||
if (base_x < 0)
|
||||
continue;
|
||||
|
||||
const uint8_t *a = head->output + head->pitch * y;
|
||||
uint8_t *out = (uint8_t*)xv->image->data + (base_y - head->height + y) * (width << 1) + (base_x << 1);
|
||||
|
||||
for (int x = 0; x < (head->width << 1) && (base_x + x) < width; x += 2)
|
||||
{
|
||||
// Blend luma
|
||||
uint8_t blend = a[x >> 1];
|
||||
unsigned blended = blend + (((256 - blend) * (unsigned)out[x + luma_i]) >> 8);
|
||||
blended = blended > 255 ? 255 : blended;
|
||||
out[x + luma_i] = blended;
|
||||
|
||||
// Blend chroma
|
||||
blended = (128 * blend + ((256 - blend) * (unsigned)out[x + chroma_i])) >> 8;
|
||||
blended = blended > 255 ? 255 : blended;
|
||||
out[x + chroma_i] = blended;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
head = head->next;
|
||||
}
|
||||
|
||||
font_renderer_free_output(&out);
|
||||
#else
|
||||
(void)xv;
|
||||
(void)msg;
|
||||
(void)width;
|
||||
(void)height;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool xv_frame(void *data, const void* frame, unsigned width, unsigned height, unsigned pitch, const char *msg)
|
||||
{
|
||||
xv_t *xv = data;
|
||||
|
||||
if (!check_resize(xv, width, height))
|
||||
return false;
|
||||
|
||||
XWindowAttributes target;
|
||||
XGetWindowAttributes(xv->display, xv->window, &target);
|
||||
xv->render_func(xv, frame, width, height, pitch);
|
||||
|
||||
if (msg)
|
||||
xv_render_msg(xv, msg, xv->width, xv->height);
|
||||
|
||||
unsigned x, y, owidth, oheight;
|
||||
calc_out_rect(xv->keep_aspect, &x, &y, &owidth, &oheight, target.width, target.height);
|
||||
XvShmPutImage(xv->display, xv->port, xv->window, xv->gc, xv->image,
|
||||
0, 0, width << 1, height << 1,
|
||||
x, y, owidth, oheight,
|
||||
true);
|
||||
XSync(xv->display, False);
|
||||
|
||||
char buf[128];
|
||||
if (gfx_window_title(buf, sizeof(buf)))
|
||||
XStoreName(xv->display, xv->window, buf);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool xv_alive(void *data)
|
||||
{
|
||||
xv_t *xv = data;
|
||||
|
||||
XEvent event;
|
||||
while (XPending(xv->display))
|
||||
{
|
||||
XNextEvent(xv->display, &event);
|
||||
switch (event.type)
|
||||
{
|
||||
case ClientMessage:
|
||||
if (event.xclient.data.l[0] == xv->quit_atom)
|
||||
return false;
|
||||
break;
|
||||
case DestroyNotify:
|
||||
return false;
|
||||
case MapNotify: // Find something that works better.
|
||||
xv->focus = true;
|
||||
break;
|
||||
case UnmapNotify:
|
||||
xv->focus = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return !g_quit;
|
||||
}
|
||||
|
||||
static bool xv_focus(void *data)
|
||||
{
|
||||
xv_t *xv = data;
|
||||
return xv->focus;
|
||||
}
|
||||
|
||||
|
||||
static void xv_free(void *data)
|
||||
{
|
||||
xv_t *xv = data;
|
||||
XShmDetach(xv->display, &xv->shminfo);
|
||||
shmdt(xv->shminfo.shmaddr);
|
||||
shmctl(xv->shminfo.shmid, IPC_RMID, NULL);
|
||||
XFree(xv->image);
|
||||
|
||||
if(xv->window)
|
||||
XUnmapWindow(xv->display, xv->window);
|
||||
if(xv->colormap)
|
||||
XFreeColormap(xv->display, xv->colormap);
|
||||
|
||||
XCloseDisplay(xv->display);
|
||||
|
||||
free(xv->ytable);
|
||||
free(xv->utable);
|
||||
free(xv->vtable);
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
if (xv->font)
|
||||
font_renderer_free(xv->font);
|
||||
#endif
|
||||
|
||||
free(xv);
|
||||
}
|
||||
|
||||
const video_driver_t video_xvideo = {
|
||||
.init = xv_init,
|
||||
.frame = xv_frame,
|
||||
.alive = xv_alive,
|
||||
.set_nonblock_state = xv_set_nonblock_state,
|
||||
.focus = xv_focus,
|
||||
.free = xv_free,
|
||||
.ident = "xvideo"
|
||||
};
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
#include "bleed.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void bleed_filter(uint16_t *data, int width, int height)
|
||||
{
|
||||
uint16_t tmp[4];
|
||||
int ptr = 0;
|
||||
uint16_t r[4];
|
||||
uint16_t g[4];
|
||||
uint16_t b[4];
|
||||
uint16_t bleed_r;
|
||||
uint16_t bleed_g;
|
||||
uint16_t bleed_b;
|
||||
float rand_map[4];
|
||||
|
||||
for (int h = 0; h < height; h++ )
|
||||
{
|
||||
memcpy(tmp, data, sizeof(tmp));
|
||||
ptr = 3;
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
rand_map[i] = (rand() % 20)/1000.0; // 0.02 * 4 = 0.08
|
||||
}
|
||||
|
||||
for (int i = 3; i < width; i++, ptr++)
|
||||
{
|
||||
tmp[ptr & 0x3] = data[i];
|
||||
for (int y = 0; y < 4; y++)
|
||||
{
|
||||
r[y] = (tmp[(ptr + y) & 0x3] >> 10) & 0x1F;
|
||||
g[y] = (tmp[(ptr + y) & 0x3] >> 5) & 0x1F;
|
||||
b[y] = (tmp[(ptr + y) & 0x3] >> 0) & 0x1F;
|
||||
}
|
||||
|
||||
bleed_r = r[0] * (0.05 + rand_map[0] ) + r[1] * (0.10 + rand_map[1] ) + r[2] * (0.20 + rand_map[2]) + r[3] * (0.57 + rand_map[3] ); // 0.92
|
||||
bleed_g = g[0] * (0.03 + rand_map[2]/3) + g[1] * (0.10 + rand_map[0]/3) + g[2] * (0.20 + rand_map[3]/3) + g[3] * (0.63 + rand_map[1]/3); // 0.96
|
||||
bleed_b = b[0] * (0.05 + rand_map[3] ) + b[1] * (0.10 + rand_map[2] ) + b[2] * (0.20 + rand_map[1]) + b[3] * (0.57 + rand_map[0] ); // 0.92
|
||||
|
||||
data[i] = (bleed_r << 10) | (bleed_g << 5) | (bleed_b);
|
||||
}
|
||||
data += width;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
#include <stdint.h>
|
||||
|
||||
void bleed_filter(uint16_t *data, int width, int height);
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
|
||||
Copyright (C) 2007 guest(r) - [email protected]
|
||||
|
||||
This program 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 Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program 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 this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
The 2xSaL shader processes a gfx. surface and redraws it 2x finer.
|
||||
|
||||
A linear post-resize can fit the image to any resolution.
|
||||
|
||||
Note: set scaler to normal2x.
|
||||
|
||||
*/
|
||||
|
||||
/* Default Vertex shader */
|
||||
void main_vertex
|
||||
(
|
||||
float4 position : POSITION,
|
||||
float4 color : COLOR,
|
||||
float2 texCoord : TEXCOORD0,
|
||||
|
||||
uniform float4x4 modelViewProj,
|
||||
|
||||
out float4 oPosition : POSITION,
|
||||
out float4 oColor : COLOR,
|
||||
out float2 otexCoord : TEXCOORD
|
||||
)
|
||||
{
|
||||
oPosition = mul(modelViewProj, position);
|
||||
oColor = color;
|
||||
otexCoord = texCoord;
|
||||
}
|
||||
|
||||
struct output
|
||||
{
|
||||
float4 color : COLOR;
|
||||
};
|
||||
|
||||
struct input
|
||||
{
|
||||
float2 video_size;
|
||||
float2 texture_size;
|
||||
float2 output_size;
|
||||
};
|
||||
|
||||
struct deltas
|
||||
{
|
||||
float2 UL, UR, DL, DR;
|
||||
};
|
||||
|
||||
|
||||
output main_fragment (float2 tex : TEXCOORD0, uniform input IN, uniform sampler2D s_p : TEXUNIT0)
|
||||
{
|
||||
float2 texsize = IN.texture_size;
|
||||
float dx = pow(texsize.x, -1.0) * 0.25;
|
||||
float dy = pow(texsize.y, -1.0) * 0.25;
|
||||
float3 dt = float3(1.0, 1.0, 1.0);
|
||||
|
||||
deltas VAR = {
|
||||
tex + float2(-dx, -dy),
|
||||
tex + float2(dx, -dy),
|
||||
tex + float2(-dx, dy),
|
||||
tex + float2(dx, dy)
|
||||
};
|
||||
|
||||
float3 c00 = tex2D(s_p, VAR.UL).xyz;
|
||||
float3 c20 = tex2D(s_p, VAR.UR).xyz;
|
||||
float3 c02 = tex2D(s_p, VAR.DL).xyz;
|
||||
float3 c22 = tex2D(s_p, VAR.DR).xyz;
|
||||
|
||||
float m1=dot(abs(c00-c22),dt)+0.001;
|
||||
float m2=dot(abs(c02-c20),dt)+0.001;
|
||||
|
||||
output OUT;
|
||||
OUT.color = float4((m1*(c02+c20)+m2*(c22+c00))/(2.0*(m1+m2)),1.0);
|
||||
return OUT;
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
/* Default Vertex shader */
|
||||
void main_vertex
|
||||
(
|
||||
float4 position : POSITION,
|
||||
float4 color : COLOR,
|
||||
float2 texCoord : TEXCOORD0,
|
||||
|
||||
uniform float4x4 modelViewProj,
|
||||
|
||||
out float4 oPosition : POSITION,
|
||||
out float4 oColor : COLOR,
|
||||
out float2 otexCoord : TEXCOORD
|
||||
)
|
||||
{
|
||||
oPosition = mul(modelViewProj, position);
|
||||
oColor = color;
|
||||
otexCoord = texCoord;
|
||||
}
|
||||
|
||||
float Luminance = 0.09f;
|
||||
static const float fMiddleGray = 0.18f;
|
||||
static const float fWhiteCutoff = 0.8f;
|
||||
|
||||
#define NUM 13
|
||||
|
||||
float2 PixelOffsets[NUM] =
|
||||
{
|
||||
{ -0.006, -0.006 },
|
||||
{ -0.005, -0.005 },
|
||||
{ -0.004, -0.004 },
|
||||
{ -0.003, -0.003 },
|
||||
{ -0.002, -0.002 },
|
||||
{ -0.001, -0.001 },
|
||||
{ 0.000, 0.000 },
|
||||
{ 0.001, 0.001 },
|
||||
{ 0.002, 0.002 },
|
||||
{ 0.003, 0.003 },
|
||||
{ 0.004, 0.004 },
|
||||
{ 0.005, 0.005 },
|
||||
{ 0.006, 0.006 },
|
||||
};
|
||||
|
||||
static const float BlurWeights[NUM] =
|
||||
{
|
||||
0.002216,
|
||||
0.008764,
|
||||
0.026995,
|
||||
0.064759,
|
||||
0.120985,
|
||||
0.176033,
|
||||
0.199471,
|
||||
0.176033,
|
||||
0.120985,
|
||||
0.064759,
|
||||
0.026995,
|
||||
0.008764,
|
||||
0.002216,
|
||||
};
|
||||
|
||||
struct output
|
||||
{
|
||||
float4 color : COLOR;
|
||||
};
|
||||
|
||||
|
||||
output main_fragment( float2 Tex : TEXCOORD0, uniform sampler2D s0 : TEXUNIT0 )
|
||||
{
|
||||
float2 BloomSettings;
|
||||
BloomSettings.x = 1;
|
||||
BloomSettings.y = 0.3;
|
||||
|
||||
float3 pixel;
|
||||
float3 Color = 0;
|
||||
|
||||
for(int i = 0; i < NUM; i++)
|
||||
{
|
||||
pixel = tex2D(s0, Tex + PixelOffsets[i] * 5.0f)+BloomSettings.y;
|
||||
|
||||
pixel *= fMiddleGray / (Luminance + 0.001f);
|
||||
pixel *= (1.0f + (pixel / (fWhiteCutoff * fWhiteCutoff)));
|
||||
pixel -= 5.0f;
|
||||
|
||||
pixel = max(pixel,0.0f);
|
||||
pixel /= (10.0f + pixel);
|
||||
|
||||
Color += pixel * BlurWeights[i];
|
||||
}
|
||||
|
||||
Color *= BloomSettings.x;
|
||||
|
||||
output OUT;
|
||||
OUT.color = float4(Color,1.0) + tex2D(s0,Tex);
|
||||
return OUT;
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/* Default Vertex shader */
|
||||
void main_vertex
|
||||
(
|
||||
float4 position : POSITION,
|
||||
float4 color : COLOR,
|
||||
float2 texCoord : TEXCOORD0,
|
||||
|
||||
uniform float4x4 modelViewProj,
|
||||
|
||||
out float4 oPosition : POSITION,
|
||||
out float4 oColor : COLOR,
|
||||
out float2 otexCoord : TEXCOORD
|
||||
)
|
||||
{
|
||||
oPosition = mul(modelViewProj, position);
|
||||
oColor = color;
|
||||
otexCoord = texCoord;
|
||||
}
|
||||
|
||||
const float2 samples[12]
|
||||
= {
|
||||
-0.326212, -0.405805,
|
||||
-0.840144, -0.073580,
|
||||
-0.695914, 0.457137,
|
||||
-0.203345, 0.620716,
|
||||
0.962340, -0.194983,
|
||||
0.473434, -0.480026,
|
||||
0.519456, 0.767022,
|
||||
0.185461, -0.893124,
|
||||
0.507431, 0.064425,
|
||||
0.896420, 0.412458,
|
||||
-0.321940, -0.932615,
|
||||
-0.791559, -0.597705
|
||||
};
|
||||
|
||||
struct output
|
||||
{
|
||||
float4 col : COLOR;
|
||||
};
|
||||
|
||||
output main_fragment(in float2 Tex : TEXCOORD0, uniform sampler2D s0 : TEXUNIT0 )
|
||||
{
|
||||
float4 color = tex2D( s0, Tex.xy);
|
||||
float BlurFactor = 0.0025; //set between 0.001 and 0.05
|
||||
|
||||
for (int i = 0; i < 12; i++)
|
||||
{
|
||||
color += tex2D(s0, Tex + BlurFactor * samples[i]);
|
||||
}
|
||||
|
||||
output OUT;
|
||||
OUT.col = color / 13;
|
||||
return OUT;
|
||||
}
|
||||
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
|
||||
/* Default Vertex shader */
|
||||
void main_vertex
|
||||
(
|
||||
float4 position : POSITION,
|
||||
float4 color : COLOR,
|
||||
float2 texCoord : TEXCOORD0,
|
||||
|
||||
uniform float4x4 modelViewProj,
|
||||
|
||||
out float4 oPosition : POSITION,
|
||||
out float4 oColor : COLOR,
|
||||
out float2 otexCoord : TEXCOORD
|
||||
)
|
||||
{
|
||||
oPosition = mul(modelViewProj, position);
|
||||
oColor = color;
|
||||
otexCoord = texCoord;
|
||||
}
|
||||
|
||||
#define TEX2D(c) tex2D(decal,(c))
|
||||
#define PI 3.141592653589
|
||||
#define phase 0.0
|
||||
#define gamma 2.5
|
||||
#define distortion 0.04
|
||||
|
||||
struct output
|
||||
{
|
||||
float4 color : COLOR;
|
||||
};
|
||||
|
||||
struct input
|
||||
{
|
||||
float2 video_size;
|
||||
float2 texture_size;
|
||||
float2 output_size;
|
||||
};
|
||||
|
||||
float2 barrelDistortion(float2 coord)
|
||||
{
|
||||
float2 cc = coord - 0.5;
|
||||
float dist = dot(cc, cc);
|
||||
return coord + cc * (dist + distortion * dist * dist) * distortion;
|
||||
}
|
||||
|
||||
output main_fragment(float2 texCoord : TEXCOORD0, uniform sampler2D decal : TEXUNIT0, uniform input IN)
|
||||
{
|
||||
output OUT;
|
||||
|
||||
float2 rubyInputSize = IN.video_size;
|
||||
float2 rubyOutputSize = IN.output_size;
|
||||
float2 rubyTextureSize = IN.texture_size;
|
||||
|
||||
float2 xy = barrelDistortion(texCoord.xy);
|
||||
float2 one = 1.0/rubyTextureSize;
|
||||
xy = xy + float2(0.0 , -0.5 * (phase + (1-phase) * rubyInputSize.y/rubyOutputSize.y) * one.y);
|
||||
float4 texels[8];
|
||||
texels[0] = TEX2D(xy + float2(-one.x,0.0));
|
||||
texels[1] = TEX2D(xy);
|
||||
texels[2] = TEX2D(xy + float2(one.x, 0.0));
|
||||
texels[3] = TEX2D(xy + float2(2 * one.x, 0.0));
|
||||
texels[4] = TEX2D(xy + float2(-one.x,one.y));
|
||||
texels[5] = TEX2D(xy + float2(0.0, one.y));
|
||||
texels[6] = TEX2D(xy + one);
|
||||
texels[7] = TEX2D(xy + float2(2 * one.x, one.y));
|
||||
|
||||
|
||||
float2 uv_ratio = frac(xy*rubyTextureSize);
|
||||
|
||||
float4 col, col2;
|
||||
|
||||
float4 coeffs = float4(1.0 + uv_ratio.x, uv_ratio.x, 1.0 - uv_ratio.x, 2.0 - uv_ratio.x);
|
||||
coeffs = (sin(PI * coeffs) * sin(PI * coeffs / 2.0)) / (coeffs * coeffs);
|
||||
coeffs = coeffs / (coeffs.x+coeffs.y+coeffs.z+coeffs.w);
|
||||
|
||||
col = clamp(coeffs.x * texels[0] + coeffs.y * texels[1] + coeffs.z * texels[2] + coeffs.w * texels[3], 0.0, 1.0);
|
||||
col2 = clamp(coeffs.x * texels[4] + coeffs.y * texels[5] + coeffs.z * texels[6] + coeffs.w * texels[7], 0.0, 1.0);
|
||||
col = pow(col, gamma);
|
||||
col2 = pow(col2, gamma);
|
||||
|
||||
float4 wid = 2 + 2 * pow(col, 4.0);
|
||||
float4 weights = uv_ratio.y/0.3;
|
||||
weights = 0.51*exp(-pow(weights*sqrt(2/wid),wid))/0.3/(0.6+0.2*wid);
|
||||
wid = 2 + 4 * pow(col2,4.0);
|
||||
float4 weights2 = (1.0-uv_ratio.y)/0.3;
|
||||
weights2 = 0.51*exp(-pow(weights2*sqrt(2/wid),wid))/0.3/(0.6+0.2*wid);
|
||||
|
||||
float4 mcol = 1.0;
|
||||
if ( fmod(xy.x*rubyOutputSize.x,2.0) < 1.0)
|
||||
mcol.g = 0.7;
|
||||
else
|
||||
mcol.rb = 0.7;
|
||||
|
||||
//OUT.color = pow(mcol*(col * weights + col2 * weights2), 1.0/2.2);
|
||||
OUT.color = 1.0;
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
/* Default Vertex shader */
|
||||
void main_vertex
|
||||
(
|
||||
float4 position : POSITION,
|
||||
float4 color : COLOR,
|
||||
float2 texCoord : TEXCOORD0,
|
||||
|
||||
uniform float4x4 modelViewProj,
|
||||
|
||||
out float4 oPosition : POSITION,
|
||||
out float4 oColor : COLOR,
|
||||
out float2 otexCoord : TEXCOORD
|
||||
)
|
||||
{
|
||||
oPosition = mul(modelViewProj, position);
|
||||
oColor = color;
|
||||
otexCoord = texCoord;
|
||||
}
|
||||
|
||||
struct output
|
||||
{
|
||||
float4 color : COLOR;
|
||||
};
|
||||
|
||||
struct input
|
||||
{
|
||||
float2 video_size;
|
||||
float2 texture_size;
|
||||
float2 output_size;
|
||||
};
|
||||
|
||||
const float mx = 0.325; // start smoothing wt.
|
||||
const float k = -0.250; // wt. decrease factor
|
||||
const float max_w = 0.25; // max filter weigth
|
||||
const float min_w =-0.05; // min filter weigth
|
||||
const float lum_add = 0.25; // effects smoothing
|
||||
|
||||
output main_fragment(float2 texCoord : TEXCOORD0, uniform sampler2D decal : TEXUNIT0, uniform input IN)
|
||||
{
|
||||
float x = 0.5 * (1.0 / IN.texture_size.x);
|
||||
float y = 0.5 * (1.0 / IN.texture_size.y);
|
||||
float2 dg1 = float2( x, y);
|
||||
float2 dg2 = float2(-x, y);
|
||||
float2 dx = float2(x, 0.0);
|
||||
float2 dy = float2(0.0, y);
|
||||
|
||||
float4 TexCoord[5];
|
||||
TexCoord[0] = float4(texCoord, 0.0, 0.0);
|
||||
TexCoord[1].xy = TexCoord[0].xy - dg1;
|
||||
TexCoord[1].zw = TexCoord[0].xy - dy;
|
||||
TexCoord[2].xy = TexCoord[0].xy - dg2;
|
||||
TexCoord[2].zw = TexCoord[0].xy + dx;
|
||||
TexCoord[3].xy = TexCoord[0].xy + dg1;
|
||||
TexCoord[3].zw = TexCoord[0].xy + dy;
|
||||
TexCoord[4].xy = TexCoord[0].xy + dg2;
|
||||
TexCoord[4].zw = TexCoord[0].xy - dx;
|
||||
|
||||
float3 c00 = tex2D(decal, TexCoord[1].xy).xyz;
|
||||
float3 c10 = tex2D(decal, TexCoord[1].zw).xyz;
|
||||
float3 c20 = tex2D(decal, TexCoord[2].xy).xyz;
|
||||
float3 c01 = tex2D(decal, TexCoord[4].zw).xyz;
|
||||
float3 c11 = tex2D(decal, TexCoord[0].xy).xyz;
|
||||
float3 c21 = tex2D(decal, TexCoord[2].zw).xyz;
|
||||
float3 c02 = tex2D(decal, TexCoord[4].xy).xyz;
|
||||
float3 c12 = tex2D(decal, TexCoord[3].zw).xyz;
|
||||
float3 c22 = tex2D(decal, TexCoord[3].xy).xyz;
|
||||
float3 dt = float3(1.0, 1.0, 1.0);
|
||||
|
||||
float md1 = dot(abs(c00 - c22), dt);
|
||||
float md2 = dot(abs(c02 - c20), dt);
|
||||
|
||||
float w1 = dot(abs(c22 - c11), dt) * md2;
|
||||
float w2 = dot(abs(c02 - c11), dt) * md1;
|
||||
float w3 = dot(abs(c00 - c11), dt) * md2;
|
||||
float w4 = dot(abs(c20 - c11), dt) * md1;
|
||||
|
||||
float t1 = w1 + w3;
|
||||
float t2 = w2 + w4;
|
||||
float ww = max(t1, t2) + 0.0001;
|
||||
|
||||
c11 = (w1 * c00 + w2 * c20 + w3 * c22 + w4 * c02 + ww * c11) / (t1 + t2 + ww);
|
||||
|
||||
float lc1 = k / (0.12 * dot(c10 + c12 + c11, dt) + lum_add);
|
||||
float lc2 = k / (0.12 * dot(c01 + c21 + c11, dt) + lum_add);
|
||||
|
||||
w1 = clamp(lc1 * dot(abs(c11 - c10), dt) + mx, min_w, max_w);
|
||||
w2 = clamp(lc2 * dot(abs(c11 - c21), dt) + mx, min_w, max_w);
|
||||
w3 = clamp(lc1 * dot(abs(c11 - c12), dt) + mx, min_w, max_w);
|
||||
w4 = clamp(lc2 * dot(abs(c11 - c01), dt) + mx, min_w, max_w);
|
||||
|
||||
output OUT;
|
||||
OUT.color = float4(w1 * c10 + w2 * c21 + w3 * c12 + w4 * c01 + (1.0 - w1 - w2 - w3 - w4) * c11, 1.0);
|
||||
return OUT;
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
Author: Themaister
|
||||
License: Public domain
|
||||
*/
|
||||
|
||||
/* Default Vertex shader */
|
||||
void main_vertex
|
||||
(
|
||||
float4 position : POSITION,
|
||||
float4 color : COLOR,
|
||||
float2 texCoord : TEXCOORD0,
|
||||
|
||||
uniform float4x4 modelViewProj,
|
||||
|
||||
out float4 oPosition : POSITION,
|
||||
out float4 oColor : COLOR,
|
||||
out float2 otexCoord : TEXCOORD
|
||||
)
|
||||
{
|
||||
oPosition = mul(modelViewProj, position);
|
||||
oColor = color;
|
||||
otexCoord = texCoord;
|
||||
}
|
||||
|
||||
struct output
|
||||
{
|
||||
float4 color : COLOR;
|
||||
};
|
||||
|
||||
struct input
|
||||
{
|
||||
float2 video_size;
|
||||
float2 texture_size;
|
||||
float2 output_size;
|
||||
};
|
||||
|
||||
struct deltas
|
||||
{
|
||||
float2 UL, UR, DL, DR;
|
||||
};
|
||||
|
||||
float3 quad_inter(float3 x0, float3 x1, float3 x2, float x)
|
||||
{
|
||||
float3 poly[3];
|
||||
poly[2] = 0.5*x0 - x1 + 0.5*x2;
|
||||
poly[1] = -1.5*x0 + 2.0*x1 - 0.5*x2;
|
||||
poly[0] = x0;
|
||||
return poly[2] * x * x + poly[1] * x + poly[0];
|
||||
}
|
||||
|
||||
output main_fragment (float2 tex : TEXCOORD0, uniform input IN, uniform sampler2D s0 : TEXUNIT0)
|
||||
{
|
||||
float2 texsize = IN.texture_size;
|
||||
float sharpness = 2.0;
|
||||
float dx = float(pow(sharpness * texsize.x, -1.0));
|
||||
float dy = float(pow(sharpness * texsize.y, -1.0));
|
||||
|
||||
float3 c00 = tex2D(s0, tex + float2(-dx, -dy)).xyz;
|
||||
float3 c01 = tex2D(s0, tex + float2(-dx, 0)).xyz;
|
||||
float3 c02 = tex2D(s0, tex + float2(-dx, dy)).xyz;
|
||||
float3 c10 = tex2D(s0, tex + float2(0, -dy)).xyz;
|
||||
float3 c11 = tex2D(s0, tex + float2(0, 0)).xyz;
|
||||
float3 c12 = tex2D(s0, tex + float2(0, dy)).xyz;
|
||||
float3 c20 = tex2D(s0, tex + float2(dx, -dy)).xyz;
|
||||
float3 c21 = tex2D(s0, tex + float2(dx, 0)).xyz;
|
||||
float3 c22 = tex2D(s0, tex + float2(dx, dy)).xyz;
|
||||
|
||||
float frac_amt_x = frac(tex.x * texsize.x);
|
||||
float frac_amt_y = frac(tex.y * texsize.y);
|
||||
float3 loval = quad_inter(c00, c10, c20, frac_amt_x + 0.5);
|
||||
float3 midval = quad_inter(c01, c11, c21, frac_amt_x + 0.5);
|
||||
float3 hival = quad_inter(c02, c12, c22, frac_amt_x + 0.5);
|
||||
float3 res = quad_inter(loval, midval, hival, frac_amt_y + 0.5);
|
||||
|
||||
output OUT;
|
||||
|
||||
// Bilinear!
|
||||
// float3 first = lerp(c00, c20, frac(tex.x * texsize.x + 0.5));
|
||||
// float3 second = lerp(c02, c22, frac(tex.x * texsize.x + 0.5));
|
||||
// float3 res = lerp(first, second, frac(tex.y * texsize.y + 0.5));
|
||||
// OUT.color = float4(res, 1.0);
|
||||
|
||||
|
||||
OUT.color = float4(res, 1.0);
|
||||
return OUT;
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
|
||||
/* Default Vertex shader */
|
||||
void main_vertex
|
||||
(
|
||||
float4 position : POSITION,
|
||||
float4 color : COLOR,
|
||||
float2 texCoord : TEXCOORD0,
|
||||
|
||||
uniform float4x4 modelViewProj,
|
||||
|
||||
out float4 oPosition : POSITION,
|
||||
out float4 oColor : COLOR,
|
||||
out float2 otexCoord : TEXCOORD
|
||||
)
|
||||
{
|
||||
oPosition = mul(modelViewProj, position);
|
||||
oColor = color;
|
||||
otexCoord = texCoord;
|
||||
}
|
||||
|
||||
|
||||
struct output
|
||||
{
|
||||
float4 color : COLOR;
|
||||
};
|
||||
|
||||
struct input
|
||||
{
|
||||
float2 video_size;
|
||||
float2 texture_size;
|
||||
float2 output_size;
|
||||
};
|
||||
|
||||
output main_fragment(float2 texCoord : TEXCOORD0, uniform sampler2D decal : TEXUNIT0, uniform input IN)
|
||||
{
|
||||
output OUT;
|
||||
|
||||
float winHeight = IN.output_size.y;
|
||||
float4 tex_color = tex2D(decal, texCoord);
|
||||
|
||||
OUT.color = (1.0 - 0.60 * floor(fmod(texCoord.y * winHeight, 2.0))) * tex_color;
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
|
||||
/* Default Vertex shader */
|
||||
void main_vertex
|
||||
(
|
||||
float4 position : POSITION,
|
||||
float4 color : COLOR,
|
||||
float2 texCoord : TEXCOORD0,
|
||||
|
||||
uniform float4x4 modelViewProj,
|
||||
|
||||
out float4 oPosition : POSITION,
|
||||
out float4 oColor : COLOR,
|
||||
out float2 otexCoord : TEXCOORD
|
||||
)
|
||||
{
|
||||
oPosition = mul(modelViewProj, position);
|
||||
oColor = color;
|
||||
otexCoord = texCoord;
|
||||
}
|
||||
|
||||
struct output
|
||||
{
|
||||
float4 color : COLOR;
|
||||
};
|
||||
|
||||
output main_fragment( in float2 Tex : TEXCOORD0, uniform sampler2D s0 : TEXUNIT0 )
|
||||
{
|
||||
float4 Color = tex2D( s0, Tex.xy );
|
||||
float Sharpenfactor = 30; //Make this between 10 and 50;
|
||||
|
||||
Color -= tex2D( s0, Tex.xy+0.0001)*Sharpenfactor;
|
||||
Color += tex2D( s0, Tex.xy-0.0001)*Sharpenfactor;
|
||||
|
||||
Color.a = 1.0;
|
||||
output OUT;
|
||||
OUT.color = Color;
|
||||
return OUT;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/* Very simple grayscale filter.
|
||||
* Author: Hans-Kristian Arntzen
|
||||
* License: Public domain
|
||||
*/
|
||||
|
||||
#include "grayscale.h"
|
||||
|
||||
// Input format 0RRRRRGGGGGBBBBB. Colors themselves could be switched around.
|
||||
void grayscale_filter(uint16_t *data, int width, int height)
|
||||
{
|
||||
for (int i = 0; i < width * height; i++ )
|
||||
{
|
||||
int r, g, b, color;
|
||||
r = (data[i] >> 10) & 0x1F;
|
||||
g = (data[i] >> 5) & 0x1F;
|
||||
b = (data[i] >> 0) & 0x1F;
|
||||
|
||||
color = (int)(r * 0.3 + g * 0.59 + b * 0.11);
|
||||
data[i] = (color << 10) | (color << 5) | color;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/* Very simple grayscale filter.
|
||||
* Author: Hans-Kristian Arntzen
|
||||
* License: Public domain
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
void grayscale_filter(uint16_t *data, int width, int height);
|
||||
-555
@@ -1,555 +0,0 @@
|
||||
/* This source code is Copyright (C) 2006-2009 by WolfWings. */
|
||||
/* It is, however, released under the ISC license. */
|
||||
/* en.wikipedia.org/wiki/ISC_Licence */
|
||||
/* --------------------------------------------------------- */
|
||||
/* It is a formula-level rederiviation of the HQ2x technique */
|
||||
/* and is, thus, not a derivative work of the original code, */
|
||||
/* only the original equations behind the code. */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pastlib.h"
|
||||
|
||||
static const uint8_t blends_2x[14*4] = {
|
||||
/* 5 2 4 1 */
|
||||
|
||||
8, 4, 4, 0,
|
||||
8, 4, 0, 4,
|
||||
8, 0, 4, 4,
|
||||
16, 0, 0, 0,
|
||||
12, 4, 0, 0,
|
||||
12, 0, 4, 0,
|
||||
12, 0, 0, 4,
|
||||
|
||||
4, 6, 6, 0, /* Interp9 */
|
||||
12, 2, 2, 0, /* Interp7 */
|
||||
14, 1, 1, 0, /* Interp10*/
|
||||
10, 4, 2, 0, /* Interp6 */
|
||||
10, 2, 4, 0, /* Interp6 */
|
||||
4, 6, 6, 0, /* Added for secondary blending used in HQ4x */
|
||||
8, 4, 4, 0, /* Added for secondary blending used in HQ4x */
|
||||
};
|
||||
|
||||
static const uint8_t blends_4x[14*16] = {
|
||||
/* 5 2 4 1 5 2 4 1 5 2 4 1 5 2 4 1 */
|
||||
|
||||
8, 4, 4, 0, 10, 4, 2, 0, 10, 2, 4, 0, 12, 2, 2, 0, /* Needs to be split. B */
|
||||
10, 0, 0, 6, 10, 4, 0, 2, 12, 0, 0, 4, 14, 0, 0, 2,
|
||||
10, 0, 0, 6, 10, 0, 4, 2, 12, 0, 0, 4, 14, 0, 0, 2,
|
||||
16, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, /* Solid colors */
|
||||
10, 6, 0, 0, 10, 6, 0, 0, 14, 2, 0, 0, 14, 2, 0, 0,
|
||||
10, 0, 6, 0, 14, 0, 2, 0, 10, 0, 6, 0, 14, 0, 2, 0,
|
||||
10, 0, 0, 6, 12, 0, 0, 4, 12, 0, 0, 4, 14, 0, 0, 2,
|
||||
0, 8, 8, 0, 4, 8, 4, 0, 0, 6,10, 0, 12, 2, 2, 0, /* Needs to be split. A */
|
||||
8, 4, 4, 0, 12, 4, 0, 0, 12, 0, 4, 0, 16, 0, 0, 0,
|
||||
8, 4, 4, 0, 16, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0,
|
||||
12, 4, 0, 0, 4,12, 0, 0, 10, 0, 4, 0, 14, 0, 2, 0,
|
||||
12, 0, 4, 0, 10, 4, 0, 0, 4, 0,12, 0, 14, 2, 0, 0,
|
||||
0, 8, 8, 0, 0,10, 6, 0, 4, 4, 8, 0, 12, 2, 2, 0, /* Needs to be split. A */
|
||||
0, 8, 8, 0, 8, 8, 0, 0, 8, 0, 8, 0, 16, 0, 0, 0, /* Needs to be split. B */
|
||||
};
|
||||
|
||||
static const uint8_t tree_hq[0x800] = {
|
||||
/* 6 6 6 6 6 6 6 6
|
||||
* 2 2 2 2 2 2 2 2
|
||||
* 4 4 4 4 4 4 4 4
|
||||
* 8 8 8 8 8 8 8 8
|
||||
*/
|
||||
0, 0, 2, 2, 1, 1,13,13, 0, 0, 2, 2, 1, 1,13, 8, /* */
|
||||
0, 0, 2, 2, 1, 1,12, 6, 0, 0, 2, 2, 1, 1, 8, 8, /* 3 */
|
||||
0, 0, 5,10, 4, 4,13,13, 0, 0, 5, 5,11, 4,13,13, /* 1 */
|
||||
0, 0, 5,10, 4, 4,12,13, 0, 0, 5, 5,11, 4,13,13, /* 31 */
|
||||
0, 0, 2, 2, 1, 1, 7, 8, 0, 0, 2, 2, 1, 1, 6, 8, /* 7 */
|
||||
0, 0, 2, 2, 1, 1, 8, 6, 0, 0, 2, 2, 1, 1, 6, 6, /* 3 7 */
|
||||
0, 0, 5,10, 4, 4, 7,13, 0, 0, 5, 5,11, 4,13,13, /* 17 */
|
||||
0, 0, 5,10, 4, 4, 9, 9, 0, 0, 5,10,11,11, 9, 9, /* 317 */
|
||||
0, 0, 2, 2, 1, 1,13, 8, 0, 0, 2, 2, 1, 1, 8, 8, /* 9 */
|
||||
0, 0, 2, 2, 1, 1,12, 8, 0, 0, 2, 2, 1, 1, 8, 6, /* 3 9 */
|
||||
0, 0, 5, 5, 4, 4,13,13, 0, 0, 5, 5, 4, 4,13,13, /* 1 9 */
|
||||
0, 0, 5, 5, 4, 4,12,13, 0, 0, 5, 5, 4, 4,12,13, /* 31 9 */
|
||||
0, 0, 2, 2, 1, 1, 7, 8, 0, 0, 2, 2, 1, 1, 8, 6, /* 79 */
|
||||
0, 0, 2, 2, 1, 1, 8, 6, 0, 0, 2, 2, 1, 1, 6, 6, /* 3 79 */
|
||||
0, 0, 5, 5, 4, 4, 7, 7, 0, 0, 5, 5, 4, 4,13,13, /* 179 */
|
||||
0, 0, 5, 5, 4, 4, 9, 9, 0, 0, 5, 5, 4, 4, 9, 9, /* 3179 */
|
||||
/* DIFF26 */
|
||||
0, 0, 2, 2, 1, 1,13,13, 0, 0, 2, 2, 1, 1,13, 8,
|
||||
0, 0, 2, 2, 1, 1,12, 6, 0, 0, 2, 2, 1, 1, 8, 8,
|
||||
0, 0, 5, 5, 4, 4,13,13, 0, 0, 5, 5,11, 4,13,13,
|
||||
0, 0, 5, 5, 4, 4,12,13, 0, 0, 5, 5,11, 4,13,13,
|
||||
0, 0, 2, 2, 1, 1, 7, 8, 0, 0, 2, 2, 1, 1, 6, 8,
|
||||
0, 0, 2, 2, 1, 1, 8, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5, 5, 4, 4, 7,13, 0, 0, 5, 5,11, 4,13,13,
|
||||
0, 0, 5, 5, 4, 4, 9, 9, 0, 0, 5, 5,11,11, 9, 9,
|
||||
0, 0, 2, 2, 1, 1,13, 8, 0, 0, 2, 2, 1, 1, 8, 8,
|
||||
0, 0, 2, 2, 1, 1,12, 8, 0, 0, 2, 2, 1, 1, 8, 6,
|
||||
0, 0, 5, 5, 4, 4,13,13, 0, 0, 5, 5, 4, 4,13,13,
|
||||
0, 0, 5, 5, 4, 4,12,13, 0, 0, 5, 5, 4, 4,12,13,
|
||||
0, 0, 2, 2, 1, 1, 7, 8, 0, 0, 2, 2, 1, 1, 8, 6,
|
||||
0, 0, 2, 2, 1, 1, 8, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5, 5, 4, 4, 7, 7, 0, 0, 5, 5, 4, 4,13,13,
|
||||
0, 0, 5, 5, 4, 4, 9, 9, 0, 0, 5, 5, 4, 4, 9, 9,
|
||||
/* DIFF24 */
|
||||
0, 0, 2, 2, 1, 1, 6, 3, 0, 0, 2, 2, 1, 1, 3, 6,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5,10, 4, 4, 3, 3, 0, 0, 5, 5,11, 4, 3, 3,
|
||||
0, 0, 5,10, 4, 4, 3, 3, 0, 0, 5, 5,11, 4, 3, 3,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5,10, 4, 4, 3, 3, 0, 0, 5, 5,11, 4, 3, 3,
|
||||
0, 0, 5,10, 4, 4, 3, 3, 0, 0, 5,10,11,11, 3, 3,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
/* DIFF26 DIFF24 */
|
||||
0, 0, 2, 2, 1, 1, 6, 3, 0, 0, 2, 2, 1, 1, 3, 6,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5,11, 4, 3, 3,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5,11, 4, 3, 3,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5,11, 4, 3, 3,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5,11,11, 3, 3,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
/* DIFF84 */
|
||||
0, 0, 2, 2, 1, 1,13,13, 0, 0, 2, 2, 1, 1,13, 8,
|
||||
0, 0, 2, 2, 1, 1,12, 6, 0, 0, 2, 2, 1, 1, 8, 8,
|
||||
0, 0, 5,10, 4, 4,13,13, 0, 0, 5, 5, 4, 4,13,13,
|
||||
0, 0, 5,10, 4, 4,12,13, 0, 0, 5, 5, 4, 4,13,13,
|
||||
0, 0, 2, 2, 1, 1, 7, 8, 0, 0, 2, 2, 1, 1, 6, 8,
|
||||
0, 0, 2, 2, 1, 1, 8, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5,10, 4, 4, 7,13, 0, 0, 5, 5, 4, 4,13,13,
|
||||
0, 0, 5,10, 4, 4, 9, 9, 0, 0, 5,10, 4, 4, 9, 9,
|
||||
0, 0, 2, 2, 1, 1,13, 8, 0, 0, 2, 2, 1, 1, 8, 8,
|
||||
0, 0, 2, 2, 1, 1,12, 8, 0, 0, 2, 2, 1, 1, 8, 6,
|
||||
0, 0, 5, 5, 4, 4,13,13, 0, 0, 5, 5, 4, 4,13,13,
|
||||
0, 0, 5, 5, 4, 4,12,13, 0, 0, 5, 5, 4, 4,12,13,
|
||||
0, 0, 2, 2, 1, 1, 7, 8, 0, 0, 2, 2, 1, 1, 8, 6,
|
||||
0, 0, 2, 2, 1, 1, 8, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5, 5, 4, 4, 7, 7, 0, 0, 5, 5, 4, 4,13,13,
|
||||
0, 0, 5, 5, 4, 4, 9, 9, 0, 0, 5, 5, 4, 4, 9, 9,
|
||||
/* DIFF26 DIFF84 */
|
||||
0, 0, 2, 2, 1, 1,13,13, 0, 0, 2, 2, 1, 1,13, 8,
|
||||
0, 0, 2, 2, 1, 1,12, 6, 0, 0, 2, 2, 1, 1, 8, 8,
|
||||
0, 0, 5, 5, 4, 4,13,13, 0, 0, 5, 5, 4, 4,13,13,
|
||||
0, 0, 5, 5, 4, 4,12,13, 0, 0, 5, 5, 4, 4,13,13,
|
||||
0, 0, 2, 2, 1, 1, 7, 8, 0, 0, 2, 2, 1, 1, 6, 8,
|
||||
0, 0, 2, 2, 1, 1, 8, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5, 5, 4, 4, 7,13, 0, 0, 5, 5, 4, 4,13,13,
|
||||
0, 0, 5, 5, 4, 4, 9, 9, 0, 0, 5, 5, 4, 4, 9, 9,
|
||||
0, 0, 2, 2, 1, 1,13, 8, 0, 0, 2, 2, 1, 1, 8, 8,
|
||||
0, 0, 2, 2, 1, 1,12, 8, 0, 0, 2, 2, 1, 1, 8, 6,
|
||||
0, 0, 5, 5, 4, 4,13,13, 0, 0, 5, 5, 4, 4,13,13,
|
||||
0, 0, 5, 5, 4, 4,12,13, 0, 0, 5, 5, 4, 4,12,13,
|
||||
0, 0, 2, 2, 1, 1, 7, 8, 0, 0, 2, 2, 1, 1, 8, 6,
|
||||
0, 0, 2, 2, 1, 1, 8, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5, 5, 4, 4, 7, 7, 0, 0, 5, 5, 4, 4,13,13,
|
||||
0, 0, 5, 5, 4, 4, 9, 9, 0, 0, 5, 5, 4, 4, 9, 9,
|
||||
/* DIFF24 DIFF84 */
|
||||
0, 0, 2, 2, 1, 1, 6, 3, 0, 0, 2, 2, 1, 1, 3, 6,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5,10, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 5,10, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5,10, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 5,10, 4, 4, 3, 3, 0, 0, 5,10, 4, 4, 3, 3,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
/* DIFF26 DIFF24 DIFF84 */
|
||||
0, 0, 2, 2, 1, 1, 6, 3, 0, 0, 2, 2, 1, 1, 3, 6,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 2, 2, 1, 1, 6, 6, 0, 0, 2, 2, 1, 1, 6, 6,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3,
|
||||
0, 0, 5, 5, 4, 4, 3, 3, 0, 0, 5, 5, 4, 4, 3, 3};
|
||||
|
||||
#define RB(y,u,v) (((y)&0x3FF)<<22)|(((u)&0x3FF)<<11)|((v)&0x3FF)
|
||||
#define GO(y,v) ((((y)+64)&0x3FF)<<22)|(16<<11)|(((v)+16)&0x3FF)
|
||||
static const uint32_t DiffTable[128] = {
|
||||
RB(-85, 160, 80), RB(-83, 155, 77), RB(-80, 150, 75), RB(-77, 145, 72),
|
||||
RB(-75, 140, 70), RB(-72, 135, 67), RB(-69, 130, 65), RB(-67, 125, 62),
|
||||
RB(-64, 120, 60), RB(-61, 115, 57), RB(-59, 110, 55), RB(-56, 105, 52),
|
||||
RB(-53, 100, 50), RB(-51, 95, 47), RB(-48, 90, 45), RB(-45, 85, 42),
|
||||
RB(-43, 80, 40), RB(-40, 75, 37), RB(-37, 70, 35), RB(-35, 65, 32),
|
||||
RB(-32, 60, 30), RB(-29, 55, 27), RB(-27, 50, 25), RB(-24, 45, 22),
|
||||
RB(-21, 40, 20), RB(-19, 35, 17), RB(-16, 30, 15), RB(-13, 25, 12),
|
||||
RB(-11, 20, 10), RB( -8, 15, 7), RB( -5, 10, 5), RB( -3, 5, 2),
|
||||
RB( 0, 0, 0), RB( 2, -5, -2), RB( 5, -10, -5), RB( 7, -15, -7),
|
||||
RB( 10, -20, -10), RB( 13, -25, -12), RB( 15, -30, -15), RB( 18, -35, -17),
|
||||
RB( 21, -40, -20), RB( 23, -45, -22), RB( 26, -50, -25), RB( 29, -55, -27),
|
||||
RB( 31, -60, -30), RB( 34, -65, -32), RB( 37, -70, -35), RB( 39, -75, -37),
|
||||
RB( 42, -80, -40), RB( 45, -85, -42), RB( 47, -90, -45), RB( 50, -95, -47),
|
||||
RB( 53,-100, -50), RB( 55,-105, -52), RB( 58,-110, -55), RB( 61,-115, -57),
|
||||
RB( 63,-120, -60), RB( 66,-125, -62), RB( 69,-130, -65), RB( 71,-135, -67),
|
||||
RB( 74,-140, -70), RB( 77,-145, -72), RB( 79,-150, -75), RB( 82,-155, -77),
|
||||
GO(-84, -160), GO(-81, -155), GO(-78, -150), GO(-76, -145),
|
||||
GO(-73, -140), GO(-70, -135), GO(-68, -130), GO(-65, -125),
|
||||
GO(-63, -120), GO(-60, -115), GO(-57, -110), GO(-55, -105),
|
||||
GO(-52, -100), GO(-49, -95), GO(-47, -90), GO(-44, -85),
|
||||
GO(-42, -80), GO(-39, -75), GO(-36, -70), GO(-34, -65),
|
||||
GO(-31, -60), GO(-28, -55), GO(-26, -50), GO(-23, -45),
|
||||
GO(-21, -40), GO(-18, -35), GO(-15, -30), GO(-13, -25),
|
||||
GO(-10, -20), GO( -7, -15), GO( -5, -10), GO( -2, -5),
|
||||
GO( 0, 0), GO( 2, 4), GO( 5, 9), GO( 7, 14),
|
||||
GO( 10, 19), GO( 13, 24), GO( 15, 29), GO( 18, 34),
|
||||
GO( 21, 39), GO( 23, 44), GO( 26, 49), GO( 28, 54),
|
||||
GO( 31, 59), GO( 34, 64), GO( 36, 69), GO( 39, 74),
|
||||
GO( 42, 79), GO( 44, 84), GO( 47, 89), GO( 49, 94),
|
||||
GO( 52, 99), GO( 55, 104), GO( 57, 109), GO( 60, 114),
|
||||
GO( 63, 119), GO( 65, 124), GO( 68, 129), GO( 70, 134),
|
||||
GO( 73, 139), GO( 76, 144), GO( 78, 149), GO( 81, 154)};
|
||||
|
||||
inline static int ExpandedDiff (uint32_t c0, uint32_t c1) {
|
||||
uint32_t r, g;
|
||||
g = c0;
|
||||
g += 0x4008020;
|
||||
g -= c1;
|
||||
r = DiffTable[(int)((unsigned char)g) + 0];
|
||||
r ^= (0x3FF << 11);
|
||||
g >>= 10;
|
||||
r += DiffTable[(int)((unsigned char)g) + 0];
|
||||
g >>= 11;
|
||||
r += DiffTable[(int)((unsigned char)g) + 64];
|
||||
r &= 0xE01F03E0;
|
||||
return r;
|
||||
}
|
||||
|
||||
#define DIFF56 0x001
|
||||
#define DIFF52 0x002
|
||||
#define DIFF54 0x004
|
||||
#define DIFF58 0x008
|
||||
#define DIFF53 0x010
|
||||
#define DIFF51 0x020
|
||||
#define DIFF57 0x040
|
||||
#define DIFF59 0x080
|
||||
#define DIFF26 0x100
|
||||
#define DIFF24 0x200
|
||||
#define DIFF84 0x400
|
||||
#define DIFF86 0x800
|
||||
|
||||
/* lastLineDiffs is previous line's 52, 53, 26, all >> 1 */
|
||||
|
||||
static uint8_t lastLineDiffs[__PAST_LIBRARY_WIDTH];
|
||||
|
||||
#define RotatePattern(x) ((((x) & 0x777) << 1) | (((x) & 0x888) >> 3))
|
||||
|
||||
void ProcessHQ2x(const pixel * restrict in, pixel * restrict out) {
|
||||
signed int y, x;
|
||||
unsigned int pattern, newpattern;
|
||||
uint32_t pixels[9];
|
||||
int prevline, nextline;
|
||||
|
||||
memset(lastLineDiffs, 0, sizeof(lastLineDiffs));
|
||||
prevline = 1;
|
||||
nextline = 1 + __PAST_LIBRARY_WIDTH;
|
||||
y = __PAST_LIBRARY_HEIGHT - 1;
|
||||
|
||||
do {
|
||||
pixels[1-1] =
|
||||
pixels[2-1] = RGBUnpack(in[prevline-1]); /* Pixel 2 */
|
||||
pixels[3-1] = RGBUnpack(in[prevline ]); /* Pixel 3 */
|
||||
|
||||
pixels[4-1] =
|
||||
pixels[5-1] = RGBUnpack(in[ 0]); /* Pixel 5 */
|
||||
pixels[6-1] = RGBUnpack(in[ 1]); /* Pixel 6 */
|
||||
|
||||
pixels[7-1] =
|
||||
pixels[8-1] = RGBUnpack(in[nextline-1]); /* Pixel 8 */
|
||||
pixels[9-1] = RGBUnpack(in[nextline ]); /* Pixel 9 */
|
||||
|
||||
pattern = 0;
|
||||
|
||||
x = __PAST_LIBRARY_WIDTH - 1;
|
||||
do {
|
||||
newpattern = 0;
|
||||
if (pattern & DIFF26) newpattern |= DIFF51;
|
||||
if (pattern & DIFF56) newpattern |= DIFF54;
|
||||
if (pattern & DIFF86) newpattern |= DIFF57;
|
||||
if (pattern & DIFF53) newpattern |= DIFF24;
|
||||
if (pattern & DIFF59) newpattern |= DIFF84;
|
||||
|
||||
if (lastLineDiffs[x] & (DIFF52 >> 1)) newpattern |= DIFF58;
|
||||
if (lastLineDiffs[x] & (DIFF26 >> 1)) newpattern |= DIFF59;
|
||||
if (lastLineDiffs[x] & (DIFF53 >> 1)) newpattern |= DIFF86;
|
||||
|
||||
pattern = newpattern;
|
||||
|
||||
if (ExpandedDiff(pixels[5-1], pixels[2-1])) pattern |= DIFF52;
|
||||
if (ExpandedDiff(pixels[5-1], pixels[3-1])) pattern |= DIFF53;
|
||||
if (ExpandedDiff(pixels[5-1], pixels[6-1])) pattern |= DIFF56;
|
||||
if (ExpandedDiff(pixels[2-1], pixels[6-1])) pattern |= DIFF26;
|
||||
lastLineDiffs[x] = pattern >> 1;
|
||||
|
||||
newpattern = tree_hq[pattern & 0x7FF];
|
||||
pattern = RotatePattern(pattern);
|
||||
out[ 0] =
|
||||
RGBPack(((pixels[5-1] * blends_2x[(newpattern * 4) + 0]) +
|
||||
(pixels[2-1] * blends_2x[(newpattern * 4) + 1]) +
|
||||
(pixels[4-1] * blends_2x[(newpattern * 4) + 2]) +
|
||||
(pixels[1-1] * blends_2x[(newpattern * 4) + 3])) / 16);
|
||||
|
||||
newpattern = tree_hq[pattern & 0x7FF];
|
||||
pattern = RotatePattern(pattern);
|
||||
out[ 1] =
|
||||
RGBPack(((pixels[5-1] * blends_2x[(newpattern * 4) + 0]) +
|
||||
(pixels[6-1] * blends_2x[(newpattern * 4) + 1]) +
|
||||
(pixels[2-1] * blends_2x[(newpattern * 4) + 2]) +
|
||||
(pixels[3-1] * blends_2x[(newpattern * 4) + 3])) / 16);
|
||||
|
||||
newpattern = tree_hq[pattern & 0x7FF];
|
||||
pattern = RotatePattern(pattern);
|
||||
out[(__PAST_LIBRARY_WIDTH*2)+1] =
|
||||
RGBPack(((pixels[5-1] * blends_2x[(newpattern * 4) + 0]) +
|
||||
(pixels[8-1] * blends_2x[(newpattern * 4) + 1]) +
|
||||
(pixels[6-1] * blends_2x[(newpattern * 4) + 2]) +
|
||||
(pixels[9-1] * blends_2x[(newpattern * 4) + 3])) / 16);
|
||||
|
||||
newpattern = tree_hq[pattern & 0x7FF];
|
||||
pattern = RotatePattern(pattern);
|
||||
out[(__PAST_LIBRARY_WIDTH*2) ] =
|
||||
RGBPack(((pixels[5-1] * blends_2x[(newpattern * 4) + 0]) +
|
||||
(pixels[4-1] * blends_2x[(newpattern * 4) + 1]) +
|
||||
(pixels[8-1] * blends_2x[(newpattern * 4) + 2]) +
|
||||
(pixels[7-1] * blends_2x[(newpattern * 4) + 3])) / 16);
|
||||
|
||||
out += 2;
|
||||
in++;
|
||||
x--;
|
||||
|
||||
if (x == 0) {
|
||||
pixels[1-1] = pixels[2-1];
|
||||
pixels[2-1] = pixels[3-1];
|
||||
pixels[4-1] = pixels[5-1];
|
||||
pixels[5-1] = pixels[6-1];
|
||||
pixels[7-1] = pixels[8-1];
|
||||
pixels[8-1] = pixels[9-1];
|
||||
continue;
|
||||
}
|
||||
|
||||
pixels[1-1] = pixels[2-1];
|
||||
pixels[2-1] = pixels[3-1];
|
||||
pixels[3-1] = RGBUnpack(in[prevline]); /* Pixel 3 */
|
||||
pixels[4-1] = pixels[5-1];
|
||||
pixels[5-1] = pixels[6-1];
|
||||
pixels[6-1] = RGBUnpack(in[1 ]); /* Pixel 6 */
|
||||
pixels[7-1] = pixels[8-1];
|
||||
pixels[8-1] = pixels[9-1];
|
||||
pixels[9-1] = RGBUnpack(in[nextline]); /* Pixel 9 */
|
||||
} while (x >= 0);
|
||||
|
||||
prevline = 1 - __PAST_LIBRARY_WIDTH;
|
||||
out += (__PAST_LIBRARY_WIDTH * 2);
|
||||
|
||||
y--;
|
||||
if (y > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nextline = 0;
|
||||
} while (y >= 0);
|
||||
}
|
||||
|
||||
void ProcessHQ4x(const pixel * restrict in, pixel * restrict out) {
|
||||
signed int y, x;
|
||||
unsigned int pattern, newpattern;
|
||||
uint32_t pixels[9];
|
||||
int prevline, nextline;
|
||||
|
||||
memset(lastLineDiffs, 0, sizeof(lastLineDiffs));
|
||||
prevline = 1;
|
||||
nextline = 1 + __PAST_LIBRARY_WIDTH;
|
||||
y = __PAST_LIBRARY_HEIGHT - 1;
|
||||
|
||||
do {
|
||||
pixels[1-1] =
|
||||
pixels[2-1] = RGBUnpack(in[prevline-1]); /* Pixel 2 */
|
||||
pixels[3-1] = RGBUnpack(in[prevline ]); /* Pixel 3 */
|
||||
|
||||
pixels[4-1] =
|
||||
pixels[5-1] = RGBUnpack(in[ 0]); /* Pixel 5 */
|
||||
pixels[6-1] = RGBUnpack(in[ 1]); /* Pixel 6 */
|
||||
|
||||
pixels[7-1] =
|
||||
pixels[8-1] = RGBUnpack(in[nextline-1]); /* Pixel 8 */
|
||||
pixels[9-1] = RGBUnpack(in[nextline ]); /* Pixel 9 */
|
||||
|
||||
pattern = 0;
|
||||
|
||||
x = __PAST_LIBRARY_WIDTH - 1;
|
||||
do {
|
||||
newpattern = 0;
|
||||
if (pattern & DIFF26) newpattern |= DIFF51;
|
||||
if (pattern & DIFF56) newpattern |= DIFF54;
|
||||
if (pattern & DIFF86) newpattern |= DIFF57;
|
||||
if (pattern & DIFF53) newpattern |= DIFF24;
|
||||
if (pattern & DIFF59) newpattern |= DIFF84;
|
||||
|
||||
if (lastLineDiffs[x] & (DIFF52 >> 1)) newpattern |= DIFF58;
|
||||
if (lastLineDiffs[x] & (DIFF26 >> 1)) newpattern |= DIFF59;
|
||||
if (lastLineDiffs[x] & (DIFF53 >> 1)) newpattern |= DIFF86;
|
||||
|
||||
pattern = newpattern;
|
||||
|
||||
if (ExpandedDiff(pixels[5-1], pixels[2-1])) pattern |= DIFF52;
|
||||
if (ExpandedDiff(pixels[5-1], pixels[3-1])) pattern |= DIFF53;
|
||||
if (ExpandedDiff(pixels[5-1], pixels[6-1])) pattern |= DIFF56;
|
||||
if (ExpandedDiff(pixels[2-1], pixels[6-1])) pattern |= DIFF26;
|
||||
lastLineDiffs[x] = pattern >> 1;
|
||||
|
||||
newpattern = tree_hq[pattern & 0x7FF];
|
||||
pattern = RotatePattern(pattern);
|
||||
out[ 0] =
|
||||
RGBPack(((pixels[5-1] * blends_4x[(newpattern * 16) + 0 + 0x0]) +
|
||||
(pixels[2-1] * blends_4x[(newpattern * 16) + 1 + 0x0]) +
|
||||
(pixels[4-1] * blends_4x[(newpattern * 16) + 2 + 0x0]) +
|
||||
(pixels[1-1] * blends_4x[(newpattern * 16) + 3 + 0x0])) / 16);
|
||||
out[ 1] =
|
||||
RGBPack(((pixels[5-1] * blends_4x[(newpattern * 16) + 0 + 0x4]) +
|
||||
(pixels[2-1] * blends_4x[(newpattern * 16) + 1 + 0x4]) +
|
||||
(pixels[4-1] * blends_4x[(newpattern * 16) + 2 + 0x4]) +
|
||||
(pixels[1-1] * blends_4x[(newpattern * 16) + 3 + 0x4])) / 16);
|
||||
out[(__PAST_LIBRARY_WIDTH*0x4) ] =
|
||||
RGBPack(((pixels[5-1] * blends_4x[(newpattern * 16) + 0 + 0x8]) +
|
||||
(pixels[2-1] * blends_4x[(newpattern * 16) + 1 + 0x8]) +
|
||||
(pixels[4-1] * blends_4x[(newpattern * 16) + 2 + 0x8]) +
|
||||
(pixels[1-1] * blends_4x[(newpattern * 16) + 3 + 0x8])) / 16);
|
||||
out[(__PAST_LIBRARY_WIDTH*0x4)+1] =
|
||||
RGBPack(((pixels[5-1] * blends_4x[(newpattern * 16) + 0 + 0xC]) +
|
||||
(pixels[2-1] * blends_4x[(newpattern * 16) + 1 + 0xC]) +
|
||||
(pixels[4-1] * blends_4x[(newpattern * 16) + 2 + 0xC]) +
|
||||
(pixels[1-1] * blends_4x[(newpattern * 16) + 3 + 0xC])) / 16);
|
||||
|
||||
newpattern = tree_hq[pattern & 0x7FF];
|
||||
pattern = RotatePattern(pattern);
|
||||
out[ 3] =
|
||||
RGBPack(((pixels[5-1] * blends_4x[(newpattern * 16) + 0 + 0x0]) +
|
||||
(pixels[6-1] * blends_4x[(newpattern * 16) + 1 + 0x0]) +
|
||||
(pixels[2-1] * blends_4x[(newpattern * 16) + 2 + 0x0]) +
|
||||
(pixels[3-1] * blends_4x[(newpattern * 16) + 3 + 0x0])) / 16);
|
||||
out[(__PAST_LIBRARY_WIDTH*0x4)+3] =
|
||||
RGBPack(((pixels[5-1] * blends_4x[(newpattern * 16) + 0 + 0x4]) +
|
||||
(pixels[6-1] * blends_4x[(newpattern * 16) + 1 + 0x4]) +
|
||||
(pixels[2-1] * blends_4x[(newpattern * 16) + 2 + 0x4]) +
|
||||
(pixels[3-1] * blends_4x[(newpattern * 16) + 3 + 0x4])) / 16);
|
||||
out[ 2] =
|
||||
RGBPack(((pixels[5-1] * blends_4x[(newpattern * 16) + 0 + 0x8]) +
|
||||
(pixels[6-1] * blends_4x[(newpattern * 16) + 1 + 0x8]) +
|
||||
(pixels[2-1] * blends_4x[(newpattern * 16) + 2 + 0x8]) +
|
||||
(pixels[3-1] * blends_4x[(newpattern * 16) + 3 + 0x8])) / 16);
|
||||
out[(__PAST_LIBRARY_WIDTH*0x4)+2] =
|
||||
RGBPack(((pixels[5-1] * blends_4x[(newpattern * 16) + 0 + 0xC]) +
|
||||
(pixels[6-1] * blends_4x[(newpattern * 16) + 1 + 0xC]) +
|
||||
(pixels[2-1] * blends_4x[(newpattern * 16) + 2 + 0xC]) +
|
||||
(pixels[3-1] * blends_4x[(newpattern * 16) + 3 + 0xC])) / 16);
|
||||
|
||||
newpattern = tree_hq[pattern & 0x7FF];
|
||||
pattern = RotatePattern(pattern);
|
||||
out[(__PAST_LIBRARY_WIDTH*0xC)+3] =
|
||||
RGBPack(((pixels[5-1] * blends_4x[(newpattern * 16) + 0 + 0x0]) +
|
||||
(pixels[8-1] * blends_4x[(newpattern * 16) + 1 + 0x0]) +
|
||||
(pixels[6-1] * blends_4x[(newpattern * 16) + 2 + 0x0]) +
|
||||
(pixels[9-1] * blends_4x[(newpattern * 16) + 3 + 0x0])) / 16);
|
||||
out[(__PAST_LIBRARY_WIDTH*0xC)+2] =
|
||||
RGBPack(((pixels[5-1] * blends_4x[(newpattern * 16) + 0 + 0x4]) +
|
||||
(pixels[8-1] * blends_4x[(newpattern * 16) + 1 + 0x4]) +
|
||||
(pixels[6-1] * blends_4x[(newpattern * 16) + 2 + 0x4]) +
|
||||
(pixels[9-1] * blends_4x[(newpattern * 16) + 3 + 0x4])) / 16);
|
||||
out[(__PAST_LIBRARY_WIDTH*0x8)+3] =
|
||||
RGBPack(((pixels[5-1] * blends_4x[(newpattern * 16) + 0 + 0x8]) +
|
||||
(pixels[8-1] * blends_4x[(newpattern * 16) + 1 + 0x8]) +
|
||||
(pixels[6-1] * blends_4x[(newpattern * 16) + 2 + 0x8]) +
|
||||
(pixels[9-1] * blends_4x[(newpattern * 16) + 3 + 0x8])) / 16);
|
||||
out[(__PAST_LIBRARY_WIDTH*0x8)+2] =
|
||||
RGBPack(((pixels[5-1] * blends_4x[(newpattern * 16) + 0 + 0xC]) +
|
||||
(pixels[8-1] * blends_4x[(newpattern * 16) + 1 + 0xC]) +
|
||||
(pixels[6-1] * blends_4x[(newpattern * 16) + 2 + 0xC]) +
|
||||
(pixels[9-1] * blends_4x[(newpattern * 16) + 3 + 0xC])) / 16);
|
||||
|
||||
newpattern = tree_hq[pattern & 0x7FF];
|
||||
pattern = RotatePattern(pattern);
|
||||
out[(__PAST_LIBRARY_WIDTH*0xC) ] =
|
||||
RGBPack(((pixels[5-1] * blends_4x[(newpattern * 16) + 0 + 0x0]) +
|
||||
(pixels[4-1] * blends_4x[(newpattern * 16) + 1 + 0x0]) +
|
||||
(pixels[8-1] * blends_4x[(newpattern * 16) + 2 + 0x0]) +
|
||||
(pixels[7-1] * blends_4x[(newpattern * 16) + 3 + 0x0])) / 16);
|
||||
out[(__PAST_LIBRARY_WIDTH*0x8) ] =
|
||||
RGBPack(((pixels[5-1] * blends_4x[(newpattern * 16) + 0 + 0x4]) +
|
||||
(pixels[4-1] * blends_4x[(newpattern * 16) + 1 + 0x4]) +
|
||||
(pixels[8-1] * blends_4x[(newpattern * 16) + 2 + 0x4]) +
|
||||
(pixels[7-1] * blends_4x[(newpattern * 16) + 3 + 0x4])) / 16);
|
||||
out[(__PAST_LIBRARY_WIDTH*0xC)+1] =
|
||||
RGBPack(((pixels[5-1] * blends_4x[(newpattern * 16) + 0 + 0x8]) +
|
||||
(pixels[4-1] * blends_4x[(newpattern * 16) + 1 + 0x8]) +
|
||||
(pixels[8-1] * blends_4x[(newpattern * 16) + 2 + 0x8]) +
|
||||
(pixels[7-1] * blends_4x[(newpattern * 16) + 3 + 0x8])) / 16);
|
||||
out[(__PAST_LIBRARY_WIDTH*0x8)+1] =
|
||||
RGBPack(((pixels[5-1] * blends_4x[(newpattern * 16) + 0 + 0xC]) +
|
||||
(pixels[4-1] * blends_4x[(newpattern * 16) + 1 + 0xC]) +
|
||||
(pixels[8-1] * blends_4x[(newpattern * 16) + 2 + 0xC]) +
|
||||
(pixels[7-1] * blends_4x[(newpattern * 16) + 3 + 0xC])) / 16);
|
||||
|
||||
out += 4;
|
||||
in++;
|
||||
x--;
|
||||
|
||||
if (x == 0) {
|
||||
pixels[1-1] = pixels[2-1];
|
||||
pixels[2-1] = pixels[3-1];
|
||||
pixels[4-1] = pixels[5-1];
|
||||
pixels[5-1] = pixels[6-1];
|
||||
pixels[7-1] = pixels[8-1];
|
||||
pixels[8-1] = pixels[9-1];
|
||||
continue;
|
||||
}
|
||||
|
||||
pixels[1-1] = pixels[2-1];
|
||||
pixels[2-1] = pixels[3-1];
|
||||
pixels[3-1] = RGBUnpack(in[prevline]); /* Pixel 3 */
|
||||
pixels[4-1] = pixels[5-1];
|
||||
pixels[5-1] = pixels[6-1];
|
||||
pixels[6-1] = RGBUnpack(in[1 ]); /* Pixel 6 */
|
||||
pixels[7-1] = pixels[8-1];
|
||||
pixels[8-1] = pixels[9-1];
|
||||
pixels[9-1] = RGBUnpack(in[nextline]); /* Pixel 9 */
|
||||
} while (x >= 0);
|
||||
|
||||
prevline = 1 - __PAST_LIBRARY_WIDTH;
|
||||
out += (__PAST_LIBRARY_WIDTH * 0xC);
|
||||
|
||||
y--;
|
||||
if (y > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nextline = 0;
|
||||
} while (y >= 0);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#include "ntsc.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
void ntsc_filter(uint16_t * restrict out, const uint16_t * restrict in, int width, int height)
|
||||
{
|
||||
static int phase = 0;
|
||||
static snes_ntsc_t ntsc;
|
||||
static bool inited = false;
|
||||
|
||||
if (inited == false)
|
||||
{
|
||||
snes_ntsc_init(&ntsc, &snes_ntsc_composite);
|
||||
inited = true;
|
||||
}
|
||||
|
||||
snes_ntsc_blit(&ntsc, in, width, phase, width, height, out, SNES_NTSC_OUT_WIDTH(width) * sizeof(uint16_t));
|
||||
phase ^= 1;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
|
||||
#ifndef _NTSC_FILTER_H
|
||||
#define _NTSC_FILTER_H
|
||||
#include <stdint.h>
|
||||
#include "snes_ntsc/snes_ntsc.h"
|
||||
|
||||
void ntsc_filter(uint16_t * restrict out, const uint16_t * restrict in, int width, int height);
|
||||
|
||||
#endif
|
||||
@@ -1,54 +0,0 @@
|
||||
/* This source code is Copyright (C) 2006-2009 by WolfWings. */
|
||||
/* It is, however, released under the ISC license. */
|
||||
/* en.wikipedia.org/wiki/ISC_Licence */
|
||||
/* --------------------------------------------------------- */
|
||||
/* It is a formula-level rederiviation of the HQ2x technique */
|
||||
/* and is, thus, not a derivative work of the original code, */
|
||||
/* only the original equations behind the code. */
|
||||
|
||||
#ifndef __PAST_LIBRARY_H
|
||||
#define __PAST_LIBRARY_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef __PAST_LIBRARY_WIDTH
|
||||
#define __PAST_LIBRARY_WIDTH 256
|
||||
#endif
|
||||
|
||||
#ifndef __PAST_LIBRARY_HEIGHT
|
||||
#define __PAST_LIBRARY_HEIGHT 224
|
||||
#endif
|
||||
|
||||
typedef uint16_t pixel;
|
||||
|
||||
/* These two functions are what need to be modified to interface other
|
||||
* pixel formats with the library. They need to return values inside a
|
||||
* format like this: (Red/Blue can be reversed.)
|
||||
*
|
||||
* 0000-00GG-GGG0-0000-0RRR-RR00-000B-BBBB
|
||||
*
|
||||
* This is VERY fast to create from 15-bit (NOT 16-bit) RGB/BGR, as
|
||||
* below.
|
||||
*
|
||||
* If you can't return data in this format, you'll have to rebuild the
|
||||
* ExpandedDiff function and DiffTable array to compensate, and possibly
|
||||
* change the blending functions in the Process* functions to handle any
|
||||
* guard-bits and masking.
|
||||
*/
|
||||
|
||||
static inline uint32_t RGBUnpack(pixel i) {
|
||||
uint32_t o = i;
|
||||
o = (o * 0x10001);
|
||||
o = o & 0x03E07C1F;
|
||||
return o;
|
||||
}
|
||||
static inline pixel RGBPack(uint32_t x) {
|
||||
x &= 0x03E07C1F;
|
||||
x |= (x >> 16);
|
||||
return x;
|
||||
}
|
||||
|
||||
void ProcessHQ2x(const pixel * restrict inbuffer, pixel * restrict outbuffer);
|
||||
void ProcessHQ4x(const pixel * restrict inbuffer, pixel * restrict outbuffer);
|
||||
|
||||
#endif /* __PAST_LIBRARY_H */
|
||||
@@ -1,251 +0,0 @@
|
||||
/* snes_ntsc 0.2.2. http://www.slack.net/~ant/ */
|
||||
|
||||
#include "snes_ntsc.h"
|
||||
|
||||
/* Copyright (C) 2006-2007 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
snes_ntsc_setup_t const snes_ntsc_monochrome = { 0,-1, 0, 0,.2, 0,.2,-.2,-.2,-1, 1, 0, 0 };
|
||||
snes_ntsc_setup_t const snes_ntsc_composite = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 };
|
||||
snes_ntsc_setup_t const snes_ntsc_svideo = { 0, 0, 0, 0,.2, 0,.2, -1, -1, 0, 1, 0, 0 };
|
||||
snes_ntsc_setup_t const snes_ntsc_rgb = { 0, 0, 0, 0,.2, 0,.7, -1, -1,-1, 1, 0, 0 };
|
||||
|
||||
#define alignment_count 3
|
||||
#define burst_count 3
|
||||
#define rescale_in 8
|
||||
#define rescale_out 7
|
||||
|
||||
#define artifacts_mid 1.0f
|
||||
#define fringing_mid 1.0f
|
||||
#define std_decoder_hue 0
|
||||
|
||||
#define rgb_bits 7 /* half normal range to allow for doubled hires pixels */
|
||||
#define gamma_size 32
|
||||
|
||||
#include "snes_ntsc_impl.h"
|
||||
|
||||
/* 3 input pixels -> 8 composite samples */
|
||||
pixel_info_t const snes_ntsc_pixels [alignment_count] = {
|
||||
{ PIXEL_OFFSET( -4, -9 ), { 1, 1, .6667f, 0 } },
|
||||
{ PIXEL_OFFSET( -2, -7 ), { .3333f, 1, 1, .3333f } },
|
||||
{ PIXEL_OFFSET( 0, -5 ), { 0, .6667f, 1, 1 } },
|
||||
};
|
||||
|
||||
static void merge_kernel_fields( snes_ntsc_rgb_t* io )
|
||||
{
|
||||
int n;
|
||||
for ( n = burst_size; n; --n )
|
||||
{
|
||||
snes_ntsc_rgb_t p0 = io [burst_size * 0] + rgb_bias;
|
||||
snes_ntsc_rgb_t p1 = io [burst_size * 1] + rgb_bias;
|
||||
snes_ntsc_rgb_t p2 = io [burst_size * 2] + rgb_bias;
|
||||
/* merge colors without losing precision */
|
||||
io [burst_size * 0] =
|
||||
((p0 + p1 - ((p0 ^ p1) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias;
|
||||
io [burst_size * 1] =
|
||||
((p1 + p2 - ((p1 ^ p2) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias;
|
||||
io [burst_size * 2] =
|
||||
((p2 + p0 - ((p2 ^ p0) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias;
|
||||
++io;
|
||||
}
|
||||
}
|
||||
|
||||
static void correct_errors( snes_ntsc_rgb_t color, snes_ntsc_rgb_t* out )
|
||||
{
|
||||
int n;
|
||||
for ( n = burst_count; n; --n )
|
||||
{
|
||||
unsigned i;
|
||||
for ( i = 0; i < rgb_kernel_size / 2; i++ )
|
||||
{
|
||||
snes_ntsc_rgb_t error = color -
|
||||
out [i ] - out [(i+12)%14+14] - out [(i+10)%14+28] -
|
||||
out [i + 7] - out [i + 5 +14] - out [i + 3 +28];
|
||||
DISTRIBUTE_ERROR( i+3+28, i+5+14, i+7 );
|
||||
}
|
||||
out += alignment_count * rgb_kernel_size;
|
||||
}
|
||||
}
|
||||
|
||||
void snes_ntsc_init( snes_ntsc_t* ntsc, snes_ntsc_setup_t const* setup )
|
||||
{
|
||||
int merge_fields;
|
||||
int entry;
|
||||
init_t impl;
|
||||
if ( !setup )
|
||||
setup = &snes_ntsc_composite;
|
||||
init( &impl, setup );
|
||||
|
||||
merge_fields = setup->merge_fields;
|
||||
if ( setup->artifacts <= -1 && setup->fringing <= -1 )
|
||||
merge_fields = 1;
|
||||
|
||||
for ( entry = 0; entry < snes_ntsc_palette_size; entry++ )
|
||||
{
|
||||
/* Reduce number of significant bits of source color. Clearing the
|
||||
low bits of R and B were least notictable. Modifying green was too
|
||||
noticeable. */
|
||||
int ir = entry >> 8 & 0x1E;
|
||||
int ig = entry >> 4 & 0x1F;
|
||||
int ib = entry << 1 & 0x1E;
|
||||
|
||||
#if SNES_NTSC_BSNES_COLORTBL
|
||||
if ( setup->bsnes_colortbl )
|
||||
{
|
||||
int bgr15 = (ib << 10) | (ig << 5) | ir;
|
||||
unsigned long rgb16 = setup->bsnes_colortbl [bgr15];
|
||||
ir = rgb16 >> 11 & 0x1E;
|
||||
ig = rgb16 >> 6 & 0x1F;
|
||||
ib = rgb16 & 0x1E;
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
float rr = impl.to_float [ir];
|
||||
float gg = impl.to_float [ig];
|
||||
float bb = impl.to_float [ib];
|
||||
|
||||
float y, i, q = RGB_TO_YIQ( rr, gg, bb, y, i );
|
||||
|
||||
int r, g, b = YIQ_TO_RGB( y, i, q, impl.to_rgb, int, r, g );
|
||||
snes_ntsc_rgb_t rgb = PACK_RGB( r, g, b );
|
||||
|
||||
snes_ntsc_rgb_t* out = ntsc->table [entry];
|
||||
gen_kernel( &impl, y, i, q, out );
|
||||
if ( merge_fields )
|
||||
merge_kernel_fields( out );
|
||||
correct_errors( rgb, out );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef SNES_NTSC_NO_BLITTERS
|
||||
|
||||
void snes_ntsc_blit( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width,
|
||||
int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch )
|
||||
{
|
||||
int chunk_count = (in_width - 1) / snes_ntsc_in_chunk;
|
||||
for ( ; in_height; --in_height )
|
||||
{
|
||||
SNES_NTSC_IN_T const* line_in = input;
|
||||
SNES_NTSC_BEGIN_ROW( ntsc, burst_phase,
|
||||
snes_ntsc_black, snes_ntsc_black, SNES_NTSC_ADJ_IN( *line_in ) );
|
||||
snes_ntsc_out_t* restrict line_out = (snes_ntsc_out_t*) rgb_out;
|
||||
int n;
|
||||
++line_in;
|
||||
|
||||
for ( n = chunk_count; n; --n )
|
||||
{
|
||||
/* order of input and output pixels must not be altered */
|
||||
SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) );
|
||||
SNES_NTSC_RGB_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_RGB_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) );
|
||||
SNES_NTSC_RGB_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_RGB_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) );
|
||||
SNES_NTSC_RGB_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_RGB_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_RGB_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
line_in += 3;
|
||||
line_out += 7;
|
||||
}
|
||||
|
||||
/* finish final pixels */
|
||||
SNES_NTSC_COLOR_IN( 0, snes_ntsc_black );
|
||||
SNES_NTSC_RGB_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_RGB_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 1, snes_ntsc_black );
|
||||
SNES_NTSC_RGB_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_RGB_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 2, snes_ntsc_black );
|
||||
SNES_NTSC_RGB_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_RGB_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_RGB_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
burst_phase = (burst_phase + 1) % snes_ntsc_burst_count;
|
||||
input += in_row_width;
|
||||
rgb_out = (char*) rgb_out + out_pitch;
|
||||
}
|
||||
}
|
||||
|
||||
void snes_ntsc_blit_hires( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width,
|
||||
int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch )
|
||||
{
|
||||
int chunk_count = (in_width - 2) / (snes_ntsc_in_chunk * 2);
|
||||
for ( ; in_height; --in_height )
|
||||
{
|
||||
SNES_NTSC_IN_T const* line_in = input;
|
||||
SNES_NTSC_HIRES_ROW( ntsc, burst_phase,
|
||||
snes_ntsc_black, snes_ntsc_black, snes_ntsc_black,
|
||||
SNES_NTSC_ADJ_IN( line_in [0] ),
|
||||
SNES_NTSC_ADJ_IN( line_in [1] ) );
|
||||
snes_ntsc_out_t* restrict line_out = (snes_ntsc_out_t*) rgb_out;
|
||||
int n;
|
||||
line_in += 2;
|
||||
|
||||
for ( n = chunk_count; n; --n )
|
||||
{
|
||||
/* twice as many input pixels per chunk */
|
||||
SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) );
|
||||
SNES_NTSC_HIRES_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) );
|
||||
SNES_NTSC_HIRES_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) );
|
||||
SNES_NTSC_HIRES_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 3, SNES_NTSC_ADJ_IN( line_in [3] ) );
|
||||
SNES_NTSC_HIRES_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 4, SNES_NTSC_ADJ_IN( line_in [4] ) );
|
||||
SNES_NTSC_HIRES_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 5, SNES_NTSC_ADJ_IN( line_in [5] ) );
|
||||
SNES_NTSC_HIRES_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_HIRES_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
line_in += 6;
|
||||
line_out += 7;
|
||||
}
|
||||
|
||||
SNES_NTSC_COLOR_IN( 0, snes_ntsc_black );
|
||||
SNES_NTSC_HIRES_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 1, snes_ntsc_black );
|
||||
SNES_NTSC_HIRES_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 2, snes_ntsc_black );
|
||||
SNES_NTSC_HIRES_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 3, snes_ntsc_black );
|
||||
SNES_NTSC_HIRES_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 4, snes_ntsc_black );
|
||||
SNES_NTSC_HIRES_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
SNES_NTSC_COLOR_IN( 5, snes_ntsc_black );
|
||||
SNES_NTSC_HIRES_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
|
||||
SNES_NTSC_HIRES_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
|
||||
|
||||
burst_phase = (burst_phase + 1) % snes_ntsc_burst_count;
|
||||
input += in_row_width;
|
||||
rgb_out = (char*) rgb_out + out_pitch;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,236 +0,0 @@
|
||||
/* SNES NTSC video filter */
|
||||
|
||||
/* snes_ntsc 0.2.2 */
|
||||
#ifndef SNES_NTSC_H
|
||||
#define SNES_NTSC_H
|
||||
|
||||
#include "snes_ntsc_config.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Image parameters, ranging from -1.0 to 1.0. Actual internal values shown
|
||||
in parenthesis and should remain fairly stable in future versions. */
|
||||
typedef struct snes_ntsc_setup_t
|
||||
{
|
||||
/* Basic parameters */
|
||||
double hue; /* -1 = -180 degrees +1 = +180 degrees */
|
||||
double saturation; /* -1 = grayscale (0.0) +1 = oversaturated colors (2.0) */
|
||||
double contrast; /* -1 = dark (0.5) +1 = light (1.5) */
|
||||
double brightness; /* -1 = dark (0.5) +1 = light (1.5) */
|
||||
double sharpness; /* edge contrast enhancement/blurring */
|
||||
|
||||
/* Advanced parameters */
|
||||
double gamma; /* -1 = dark (1.5) +1 = light (0.5) */
|
||||
double resolution; /* image resolution */
|
||||
double artifacts; /* artifacts caused by color changes */
|
||||
double fringing; /* color artifacts caused by brightness changes */
|
||||
double bleed; /* color bleed (color resolution reduction) */
|
||||
int merge_fields; /* if 1, merges even and odd fields together to reduce flicker */
|
||||
float const* decoder_matrix; /* optional RGB decoder matrix, 6 elements */
|
||||
|
||||
unsigned long const* bsnes_colortbl; /* undocumented; set to 0 */
|
||||
} snes_ntsc_setup_t;
|
||||
|
||||
/* Video format presets */
|
||||
extern snes_ntsc_setup_t const snes_ntsc_composite; /* color bleeding + artifacts */
|
||||
extern snes_ntsc_setup_t const snes_ntsc_svideo; /* color bleeding only */
|
||||
extern snes_ntsc_setup_t const snes_ntsc_rgb; /* crisp image */
|
||||
extern snes_ntsc_setup_t const snes_ntsc_monochrome;/* desaturated + artifacts */
|
||||
|
||||
/* Initializes and adjusts parameters. Can be called multiple times on the same
|
||||
snes_ntsc_t object. Can pass NULL for either parameter. */
|
||||
typedef struct snes_ntsc_t snes_ntsc_t;
|
||||
void snes_ntsc_init( snes_ntsc_t* ntsc, snes_ntsc_setup_t const* setup );
|
||||
|
||||
/* Filters one or more rows of pixels. Input pixel format is set by SNES_NTSC_IN_FORMAT
|
||||
and output RGB depth is set by SNES_NTSC_OUT_DEPTH. Both default to 16-bit RGB.
|
||||
In_row_width is the number of pixels to get to the next input row. Out_pitch
|
||||
is the number of *bytes* to get to the next output row. */
|
||||
void snes_ntsc_blit( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input,
|
||||
long in_row_width, int burst_phase, int in_width, int in_height,
|
||||
void* rgb_out, long out_pitch );
|
||||
|
||||
void snes_ntsc_blit_hires( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input,
|
||||
long in_row_width, int burst_phase, int in_width, int in_height,
|
||||
void* rgb_out, long out_pitch );
|
||||
|
||||
/* Number of output pixels written by low-res blitter for given input width. Width
|
||||
might be rounded down slightly; use SNES_NTSC_IN_WIDTH() on result to find rounded
|
||||
value. Guaranteed not to round 256 down at all. */
|
||||
#define SNES_NTSC_OUT_WIDTH( in_width ) \
|
||||
((((in_width) - 1) / snes_ntsc_in_chunk + 1) * snes_ntsc_out_chunk)
|
||||
|
||||
/* Number of low-res input pixels that will fit within given output width. Might be
|
||||
rounded down slightly; use SNES_NTSC_OUT_WIDTH() on result to find rounded
|
||||
value. */
|
||||
#define SNES_NTSC_IN_WIDTH( out_width ) \
|
||||
(((out_width) / snes_ntsc_out_chunk - 1) * snes_ntsc_in_chunk + 1)
|
||||
|
||||
|
||||
/* Interface for user-defined custom blitters */
|
||||
|
||||
enum { snes_ntsc_in_chunk = 3 }; /* number of input pixels read per chunk */
|
||||
enum { snes_ntsc_out_chunk = 7 }; /* number of output pixels generated per chunk */
|
||||
enum { snes_ntsc_black = 0 }; /* palette index for black */
|
||||
enum { snes_ntsc_burst_count = 3 }; /* burst phase cycles through 0, 1, and 2 */
|
||||
|
||||
/* Begins outputting row and starts three pixels. First pixel will be cut off a bit.
|
||||
Use snes_ntsc_black for unused pixels. Declares variables, so must be before first
|
||||
statement in a block (unless you're using C++). */
|
||||
#define SNES_NTSC_BEGIN_ROW( ntsc, burst, pixel0, pixel1, pixel2 ) \
|
||||
char const* ktable = \
|
||||
(char const*) (ntsc)->table + burst * (snes_ntsc_burst_size * sizeof (snes_ntsc_rgb_t));\
|
||||
SNES_NTSC_BEGIN_ROW_6_( pixel0, pixel1, pixel2, SNES_NTSC_IN_FORMAT, ktable )
|
||||
|
||||
/* Begins input pixel */
|
||||
#define SNES_NTSC_COLOR_IN( index, color ) \
|
||||
SNES_NTSC_COLOR_IN_( index, color, SNES_NTSC_IN_FORMAT, ktable )
|
||||
|
||||
/* Generates output pixel. Bits can be 24, 16, 15, 14, 32 (treated as 24), or 0:
|
||||
24: RRRRRRRR GGGGGGGG BBBBBBBB (8-8-8 RGB)
|
||||
16: RRRRRGGG GGGBBBBB (5-6-5 RGB)
|
||||
15: RRRRRGG GGGBBBBB (5-5-5 RGB)
|
||||
14: BBBBBGG GGGRRRRR (5-5-5 BGR, native SNES format)
|
||||
0: xxxRRRRR RRRxxGGG GGGGGxxB BBBBBBBx (native internal format; x = junk bits) */
|
||||
#define SNES_NTSC_RGB_OUT( index, rgb_out, bits ) \
|
||||
SNES_NTSC_RGB_OUT_14_( index, rgb_out, bits, 1 )
|
||||
|
||||
/* Hires equivalents */
|
||||
#define SNES_NTSC_HIRES_ROW( ntsc, burst, pixel1, pixel2, pixel3, pixel4, pixel5 ) \
|
||||
char const* ktable = \
|
||||
(char const*) (ntsc)->table + burst * (snes_ntsc_burst_size * sizeof (snes_ntsc_rgb_t));\
|
||||
unsigned const snes_ntsc_pixel1_ = (pixel1);\
|
||||
snes_ntsc_rgb_t const* kernel1 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel1_ );\
|
||||
unsigned const snes_ntsc_pixel2_ = (pixel2);\
|
||||
snes_ntsc_rgb_t const* kernel2 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel2_ );\
|
||||
unsigned const snes_ntsc_pixel3_ = (pixel3);\
|
||||
snes_ntsc_rgb_t const* kernel3 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel3_ );\
|
||||
unsigned const snes_ntsc_pixel4_ = (pixel4);\
|
||||
snes_ntsc_rgb_t const* kernel4 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel4_ );\
|
||||
unsigned const snes_ntsc_pixel5_ = (pixel5);\
|
||||
snes_ntsc_rgb_t const* kernel5 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel5_ );\
|
||||
snes_ntsc_rgb_t const* kernel0 = kernel1;\
|
||||
snes_ntsc_rgb_t const* kernelx0;\
|
||||
snes_ntsc_rgb_t const* kernelx1 = kernel1;\
|
||||
snes_ntsc_rgb_t const* kernelx2 = kernel1;\
|
||||
snes_ntsc_rgb_t const* kernelx3 = kernel1;\
|
||||
snes_ntsc_rgb_t const* kernelx4 = kernel1;\
|
||||
snes_ntsc_rgb_t const* kernelx5 = kernel1
|
||||
|
||||
#define SNES_NTSC_HIRES_OUT( x, rgb_out, bits ) {\
|
||||
snes_ntsc_rgb_t raw_ =\
|
||||
kernel0 [ x ] + kernel2 [(x+5)%7+14] + kernel4 [(x+3)%7+28] +\
|
||||
kernelx0 [(x+7)%7+7] + kernelx2 [(x+5)%7+21] + kernelx4 [(x+3)%7+35] +\
|
||||
kernel1 [(x+6)%7 ] + kernel3 [(x+4)%7+14] + kernel5 [(x+2)%7+28] +\
|
||||
kernelx1 [(x+6)%7+7] + kernelx3 [(x+4)%7+21] + kernelx5 [(x+2)%7+35];\
|
||||
SNES_NTSC_CLAMP_( raw_, 0 );\
|
||||
SNES_NTSC_RGB_OUT_( rgb_out, (bits), 0 );\
|
||||
}
|
||||
|
||||
|
||||
/* private */
|
||||
enum { snes_ntsc_entry_size = 128 };
|
||||
enum { snes_ntsc_palette_size = 0x2000 };
|
||||
typedef unsigned long snes_ntsc_rgb_t;
|
||||
struct snes_ntsc_t {
|
||||
snes_ntsc_rgb_t table [snes_ntsc_palette_size] [snes_ntsc_entry_size];
|
||||
};
|
||||
enum { snes_ntsc_burst_size = snes_ntsc_entry_size / snes_ntsc_burst_count };
|
||||
|
||||
#define SNES_NTSC_RGB16( ktable, n ) \
|
||||
(snes_ntsc_rgb_t const*) (ktable + ((n & 0x001E) | (n >> 1 & 0x03E0) | (n >> 2 & 0x3C00)) * \
|
||||
(snes_ntsc_entry_size / 2 * sizeof (snes_ntsc_rgb_t)))
|
||||
|
||||
#define SNES_NTSC_BGR15( ktable, n ) \
|
||||
(snes_ntsc_rgb_t const*) (ktable + ((n << 9 & 0x3C00) | (n & 0x03E0) | (n >> 10 & 0x001E)) * \
|
||||
(snes_ntsc_entry_size / 2 * sizeof (snes_ntsc_rgb_t)))
|
||||
|
||||
/* common 3->7 ntsc macros */
|
||||
#define SNES_NTSC_BEGIN_ROW_6_( pixel0, pixel1, pixel2, ENTRY, table ) \
|
||||
unsigned const snes_ntsc_pixel0_ = (pixel0);\
|
||||
snes_ntsc_rgb_t const* kernel0 = ENTRY( table, snes_ntsc_pixel0_ );\
|
||||
unsigned const snes_ntsc_pixel1_ = (pixel1);\
|
||||
snes_ntsc_rgb_t const* kernel1 = ENTRY( table, snes_ntsc_pixel1_ );\
|
||||
unsigned const snes_ntsc_pixel2_ = (pixel2);\
|
||||
snes_ntsc_rgb_t const* kernel2 = ENTRY( table, snes_ntsc_pixel2_ );\
|
||||
snes_ntsc_rgb_t const* kernelx0;\
|
||||
snes_ntsc_rgb_t const* kernelx1 = kernel0;\
|
||||
snes_ntsc_rgb_t const* kernelx2 = kernel0
|
||||
|
||||
#define SNES_NTSC_RGB_OUT_14_( x, rgb_out, bits, shift ) {\
|
||||
snes_ntsc_rgb_t raw_ =\
|
||||
kernel0 [x ] + kernel1 [(x+12)%7+14] + kernel2 [(x+10)%7+28] +\
|
||||
kernelx0 [(x+7)%14] + kernelx1 [(x+ 5)%7+21] + kernelx2 [(x+ 3)%7+35];\
|
||||
SNES_NTSC_CLAMP_( raw_, shift );\
|
||||
SNES_NTSC_RGB_OUT_( rgb_out, bits, shift );\
|
||||
}
|
||||
|
||||
/* common ntsc macros */
|
||||
#define snes_ntsc_rgb_builder ((1L << 21) | (1 << 11) | (1 << 1))
|
||||
#define snes_ntsc_clamp_mask (snes_ntsc_rgb_builder * 3 / 2)
|
||||
#define snes_ntsc_clamp_add (snes_ntsc_rgb_builder * 0x101)
|
||||
#define SNES_NTSC_CLAMP_( io, shift ) {\
|
||||
snes_ntsc_rgb_t sub = (io) >> (9-(shift)) & snes_ntsc_clamp_mask;\
|
||||
snes_ntsc_rgb_t clamp = snes_ntsc_clamp_add - sub;\
|
||||
io |= clamp;\
|
||||
clamp -= sub;\
|
||||
io &= clamp;\
|
||||
}
|
||||
|
||||
#define SNES_NTSC_COLOR_IN_( index, color, ENTRY, table ) {\
|
||||
unsigned color_;\
|
||||
kernelx##index = kernel##index;\
|
||||
kernel##index = (color_ = (color), ENTRY( table, color_ ));\
|
||||
}
|
||||
|
||||
/* x is always zero except in snes_ntsc library */
|
||||
/* original routine */
|
||||
#if 0
|
||||
#define SNES_NTSC_RGB_OUT_( rgb_out, bits, x ) {\
|
||||
if ( bits == 16 )\
|
||||
rgb_out = (raw_>>(13-x)& 0xF800)|(raw_>>(8-x)&0x07E0)|(raw_>>(4-x)&0x001F);\
|
||||
if ( bits == 24 || bits == 32 )\
|
||||
rgb_out = (raw_>>(5-x)&0xFF0000)|(raw_>>(3-x)&0xFF00)|(raw_>>(1-x)&0xFF);\
|
||||
if ( bits == 15 )\
|
||||
rgb_out = (raw_>>(14-x)& 0x7C00)|(raw_>>(9-x)&0x03E0)|(raw_>>(4-x)&0x001F);\
|
||||
if ( bits == 14 )\
|
||||
rgb_out = (raw_>>(24-x)& 0x001F)|(raw_>>(9-x)&0x03E0)|(raw_<<(6+x)&0x7C00);\
|
||||
if ( bits == 0 )\
|
||||
rgb_out = raw_ << x;\
|
||||
}
|
||||
#endif
|
||||
|
||||
#define SNES_NTSC_RGB_OUT_( rgb_out, bits, x ) {\
|
||||
rgb_out = (raw_>>(14-x)& 0x7C00)|(raw_>>(9-x)&0x03E0)|(raw_>>(4-x)&0x001F);\
|
||||
}
|
||||
|
||||
// Custom SSNES routine for 15 bit.
|
||||
|
||||
/* custom bsnes routine -- hooks into bsnes colortable */
|
||||
#if 0
|
||||
#define SNES_NTSC_RGB_OUT_( rgb_out, bits, x ) {\
|
||||
if ( bits == 16 ) {\
|
||||
rgb_out = (raw_>>(13-x)& 0xF800)|(raw_>>(8-x)&0x07E0)|(raw_>>(4-x)&0x001F);\
|
||||
rgb_out = ((rgb_out&0xf800)>>11)|((rgb_out&0x07c0)>>1)|((rgb_out&0x001f)<<10);\
|
||||
rgb_out = colortable[rgb_out];\
|
||||
} else if ( bits == 24 || bits == 32 ) {\
|
||||
rgb_out = (raw_>>(5-x)&0xFF0000)|(raw_>>(3-x)&0xFF00)|(raw_>>(1-x)&0xFF);\
|
||||
rgb_out = ((rgb_out&0xf80000)>>19)|((rgb_out&0x00f800)>>6)|((rgb_out&0x0000f8)<<7);\
|
||||
rgb_out = colortable[rgb_out];\
|
||||
} else if ( bits == 15 ) {\
|
||||
rgb_out = (raw_>>(14-x)& 0x7C00)|(raw_>>(9-x)&0x03E0)|(raw_>>(4-x)&0x001F);\
|
||||
rgb_out = ((rgb_out&0x7c00)>>10)|((rgb_out&0x03e0))|((rgb_out&0x001f)<<10);\
|
||||
rgb_out = colortable[rgb_out];\
|
||||
} else {\
|
||||
rgb_out = raw_ << x;\
|
||||
}\
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,27 +0,0 @@
|
||||
/* Configure library by modifying this file */
|
||||
|
||||
#ifndef SNES_NTSC_CONFIG_H
|
||||
#define SNES_NTSC_CONFIG_H
|
||||
|
||||
/* Format of source pixels */
|
||||
/* #define SNES_NTSC_IN_FORMAT SNES_NTSC_RGB16 */
|
||||
#define SNES_NTSC_IN_FORMAT SNES_NTSC_BGR15
|
||||
|
||||
/* The following affect the built-in blitter only; a custom blitter can
|
||||
handle things however it wants. */
|
||||
|
||||
/* Bits per pixel of output. Can be 15, 16, 32, or 24 (same as 32). */
|
||||
#define SNES_NTSC_OUT_DEPTH 15
|
||||
|
||||
/* Type of input pixel values */
|
||||
#define SNES_NTSC_IN_T unsigned short
|
||||
|
||||
/* Each raw pixel input value is passed through this. You might want to mask
|
||||
the pixel index if you use the high bits as flags, etc. */
|
||||
#define SNES_NTSC_ADJ_IN( in ) (((in >> 10) & 0x1F) | (in & 0x3E0) | ((in << 10) & 0x7C00))
|
||||
|
||||
|
||||
/* For each pixel, this is the basic operation:
|
||||
output_color = SNES_NTSC_ADJ_IN( SNES_NTSC_IN_T ) */
|
||||
|
||||
#endif
|
||||
@@ -1,439 +0,0 @@
|
||||
/* snes_ntsc 0.2.2. http://www.slack.net/~ant/ */
|
||||
|
||||
/* Common implementation of NTSC filters */
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
/* Copyright (C) 2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module 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 Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#define DISABLE_CORRECTION 0
|
||||
|
||||
#undef PI
|
||||
#define PI 3.14159265358979323846f
|
||||
|
||||
#ifndef LUMA_CUTOFF
|
||||
#define LUMA_CUTOFF 0.20
|
||||
#endif
|
||||
#ifndef gamma_size
|
||||
#define gamma_size 1
|
||||
#endif
|
||||
#ifndef rgb_bits
|
||||
#define rgb_bits 8
|
||||
#endif
|
||||
#ifndef artifacts_max
|
||||
#define artifacts_max (artifacts_mid * 1.5f)
|
||||
#endif
|
||||
#ifndef fringing_max
|
||||
#define fringing_max (fringing_mid * 2)
|
||||
#endif
|
||||
#ifndef STD_HUE_CONDITION
|
||||
#define STD_HUE_CONDITION( setup ) 1
|
||||
#endif
|
||||
|
||||
#define ext_decoder_hue (std_decoder_hue + 15)
|
||||
#define rgb_unit (1 << rgb_bits)
|
||||
#define rgb_offset (rgb_unit * 2 + 0.5f)
|
||||
|
||||
enum { burst_size = snes_ntsc_entry_size / burst_count };
|
||||
enum { kernel_half = 16 };
|
||||
enum { kernel_size = kernel_half * 2 + 1 };
|
||||
|
||||
typedef struct init_t
|
||||
{
|
||||
float to_rgb [burst_count * 6];
|
||||
float to_float [gamma_size];
|
||||
float contrast;
|
||||
float brightness;
|
||||
float artifacts;
|
||||
float fringing;
|
||||
float kernel [rescale_out * kernel_size * 2];
|
||||
} init_t;
|
||||
|
||||
#define ROTATE_IQ( i, q, sin_b, cos_b ) {\
|
||||
float t;\
|
||||
t = i * cos_b - q * sin_b;\
|
||||
q = i * sin_b + q * cos_b;\
|
||||
i = t;\
|
||||
}
|
||||
|
||||
static void init_filters( init_t* impl, snes_ntsc_setup_t const* setup )
|
||||
{
|
||||
#if rescale_out > 1
|
||||
float kernels [kernel_size * 2];
|
||||
#else
|
||||
float* const kernels = impl->kernel;
|
||||
#endif
|
||||
|
||||
/* generate luma (y) filter using sinc kernel */
|
||||
{
|
||||
/* sinc with rolloff (dsf) */
|
||||
float const rolloff = 1 + (float) setup->sharpness * (float) 0.032;
|
||||
float const maxh = 32;
|
||||
float const pow_a_n = (float) pow( rolloff, maxh );
|
||||
float sum;
|
||||
int i;
|
||||
/* quadratic mapping to reduce negative (blurring) range */
|
||||
float to_angle = (float) setup->resolution + 1;
|
||||
to_angle = PI / maxh * (float) LUMA_CUTOFF * (to_angle * to_angle + 1);
|
||||
|
||||
kernels [kernel_size * 3 / 2] = maxh; /* default center value */
|
||||
for ( i = 0; i < kernel_half * 2 + 1; i++ )
|
||||
{
|
||||
int x = i - kernel_half;
|
||||
float angle = x * to_angle;
|
||||
/* instability occurs at center point with rolloff very close to 1.0 */
|
||||
if ( x || pow_a_n > (float) 1.056 || pow_a_n < (float) 0.981 )
|
||||
{
|
||||
float rolloff_cos_a = rolloff * (float) cos( angle );
|
||||
float num = 1 - rolloff_cos_a -
|
||||
pow_a_n * (float) cos( maxh * angle ) +
|
||||
pow_a_n * rolloff * (float) cos( (maxh - 1) * angle );
|
||||
float den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff;
|
||||
float dsf = num / den;
|
||||
kernels [kernel_size * 3 / 2 - kernel_half + i] = dsf - (float) 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* apply blackman window and find sum */
|
||||
sum = 0;
|
||||
for ( i = 0; i < kernel_half * 2 + 1; i++ )
|
||||
{
|
||||
float x = PI * 2 / (kernel_half * 2) * i;
|
||||
float blackman = 0.42f - 0.5f * (float) cos( x ) + 0.08f * (float) cos( x * 2 );
|
||||
sum += (kernels [kernel_size * 3 / 2 - kernel_half + i] *= blackman);
|
||||
}
|
||||
|
||||
/* normalize kernel */
|
||||
sum = 1.0f / sum;
|
||||
for ( i = 0; i < kernel_half * 2 + 1; i++ )
|
||||
{
|
||||
int x = kernel_size * 3 / 2 - kernel_half + i;
|
||||
kernels [x] *= sum;
|
||||
assert( kernels [x] == kernels [x] ); /* catch numerical instability */
|
||||
}
|
||||
}
|
||||
|
||||
/* generate chroma (iq) filter using gaussian kernel */
|
||||
{
|
||||
float const cutoff_factor = -0.03125f;
|
||||
float cutoff = (float) setup->bleed;
|
||||
int i;
|
||||
|
||||
if ( cutoff < 0 )
|
||||
{
|
||||
/* keep extreme value accessible only near upper end of scale (1.0) */
|
||||
cutoff *= cutoff;
|
||||
cutoff *= cutoff;
|
||||
cutoff *= cutoff;
|
||||
cutoff *= -30.0f / 0.65f;
|
||||
}
|
||||
cutoff = cutoff_factor - 0.65f * cutoff_factor * cutoff;
|
||||
|
||||
for ( i = -kernel_half; i <= kernel_half; i++ )
|
||||
kernels [kernel_size / 2 + i] = (float) exp( i * i * cutoff );
|
||||
|
||||
/* normalize even and odd phases separately */
|
||||
for ( i = 0; i < 2; i++ )
|
||||
{
|
||||
float sum = 0;
|
||||
int x;
|
||||
for ( x = i; x < kernel_size; x += 2 )
|
||||
sum += kernels [x];
|
||||
|
||||
sum = 1.0f / sum;
|
||||
for ( x = i; x < kernel_size; x += 2 )
|
||||
{
|
||||
kernels [x] *= sum;
|
||||
assert( kernels [x] == kernels [x] ); /* catch numerical instability */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
printf( "luma:\n" );
|
||||
for ( i = kernel_size; i < kernel_size * 2; i++ )
|
||||
printf( "%f\n", kernels [i] );
|
||||
printf( "chroma:\n" );
|
||||
for ( i = 0; i < kernel_size; i++ )
|
||||
printf( "%f\n", kernels [i] );
|
||||
*/
|
||||
|
||||
/* generate linear rescale kernels */
|
||||
#if rescale_out > 1
|
||||
{
|
||||
float weight = 1.0f;
|
||||
float* out = impl->kernel;
|
||||
int n = rescale_out;
|
||||
do
|
||||
{
|
||||
float remain = 0;
|
||||
int i;
|
||||
weight -= 1.0f / rescale_in;
|
||||
for ( i = 0; i < kernel_size * 2; i++ )
|
||||
{
|
||||
float cur = kernels [i];
|
||||
float m = cur * weight;
|
||||
*out++ = m + remain;
|
||||
remain = cur - m;
|
||||
}
|
||||
}
|
||||
while ( --n );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static float const default_decoder [6] =
|
||||
{ 0.956f, 0.621f, -0.272f, -0.647f, -1.105f, 1.702f };
|
||||
|
||||
static void init( init_t* impl, snes_ntsc_setup_t const* setup )
|
||||
{
|
||||
impl->brightness = (float) setup->brightness * (0.5f * rgb_unit) + rgb_offset;
|
||||
impl->contrast = (float) setup->contrast * (0.5f * rgb_unit) + rgb_unit;
|
||||
#ifdef default_palette_contrast
|
||||
if ( !setup->palette )
|
||||
impl->contrast *= default_palette_contrast;
|
||||
#endif
|
||||
|
||||
impl->artifacts = (float) setup->artifacts;
|
||||
if ( impl->artifacts > 0 )
|
||||
impl->artifacts *= artifacts_max - artifacts_mid;
|
||||
impl->artifacts = impl->artifacts * artifacts_mid + artifacts_mid;
|
||||
|
||||
impl->fringing = (float) setup->fringing;
|
||||
if ( impl->fringing > 0 )
|
||||
impl->fringing *= fringing_max - fringing_mid;
|
||||
impl->fringing = impl->fringing * fringing_mid + fringing_mid;
|
||||
|
||||
init_filters( impl, setup );
|
||||
|
||||
/* generate gamma table */
|
||||
if ( gamma_size > 1 )
|
||||
{
|
||||
float const to_float = 1.0f / (gamma_size - (gamma_size > 1));
|
||||
float const gamma = 1.1333f - (float) setup->gamma * 0.5f;
|
||||
/* match common PC's 2.2 gamma to TV's 2.65 gamma */
|
||||
int i;
|
||||
for ( i = 0; i < gamma_size; i++ )
|
||||
impl->to_float [i] =
|
||||
(float) pow( i * to_float, gamma ) * impl->contrast + impl->brightness;
|
||||
}
|
||||
|
||||
/* setup decoder matricies */
|
||||
{
|
||||
float hue = (float) setup->hue * PI + PI / 180 * ext_decoder_hue;
|
||||
float sat = (float) setup->saturation + 1;
|
||||
float const* decoder = setup->decoder_matrix;
|
||||
if ( !decoder )
|
||||
{
|
||||
decoder = default_decoder;
|
||||
if ( STD_HUE_CONDITION( setup ) )
|
||||
hue += PI / 180 * (std_decoder_hue - ext_decoder_hue);
|
||||
}
|
||||
|
||||
{
|
||||
float s = (float) sin( hue ) * sat;
|
||||
float c = (float) cos( hue ) * sat;
|
||||
float* out = impl->to_rgb;
|
||||
int n;
|
||||
|
||||
n = burst_count;
|
||||
do
|
||||
{
|
||||
float const* in = decoder;
|
||||
int n = 3;
|
||||
do
|
||||
{
|
||||
float i = *in++;
|
||||
float q = *in++;
|
||||
*out++ = i * c - q * s;
|
||||
*out++ = i * s + q * c;
|
||||
}
|
||||
while ( --n );
|
||||
if ( burst_count <= 1 )
|
||||
break;
|
||||
ROTATE_IQ( s, c, 0.866025f, -0.5f ); /* +120 degrees */
|
||||
}
|
||||
while ( --n );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* kernel generation */
|
||||
|
||||
#define RGB_TO_YIQ( r, g, b, y, i ) (\
|
||||
(y = (r) * 0.299f + (g) * 0.587f + (b) * 0.114f),\
|
||||
(i = (r) * 0.596f - (g) * 0.275f - (b) * 0.321f),\
|
||||
((r) * 0.212f - (g) * 0.523f + (b) * 0.311f)\
|
||||
)
|
||||
|
||||
#define YIQ_TO_RGB( y, i, q, to_rgb, type, r, g ) (\
|
||||
r = (type) (y + to_rgb [0] * i + to_rgb [1] * q),\
|
||||
g = (type) (y + to_rgb [2] * i + to_rgb [3] * q),\
|
||||
(type) (y + to_rgb [4] * i + to_rgb [5] * q)\
|
||||
)
|
||||
|
||||
#define PACK_RGB( r, g, b ) ((r) << 21 | (g) << 11 | (b) << 1)
|
||||
|
||||
enum { rgb_kernel_size = burst_size / alignment_count };
|
||||
enum { rgb_bias = rgb_unit * 2 * snes_ntsc_rgb_builder };
|
||||
|
||||
typedef struct pixel_info_t
|
||||
{
|
||||
int offset;
|
||||
float negate;
|
||||
float kernel [4];
|
||||
} pixel_info_t;
|
||||
|
||||
#if rescale_in > 1
|
||||
#define PIXEL_OFFSET_( ntsc, scaled ) \
|
||||
(kernel_size / 2 + ntsc + (scaled != 0) + (rescale_out - scaled) % rescale_out + \
|
||||
(kernel_size * 2 * scaled))
|
||||
|
||||
#define PIXEL_OFFSET( ntsc, scaled ) \
|
||||
PIXEL_OFFSET_( ((ntsc) - (scaled) / rescale_out * rescale_in),\
|
||||
(((scaled) + rescale_out * 10) % rescale_out) ),\
|
||||
(1.0f - (((ntsc) + 100) & 2))
|
||||
#else
|
||||
#define PIXEL_OFFSET( ntsc, scaled ) \
|
||||
(kernel_size / 2 + (ntsc) - (scaled)),\
|
||||
(1.0f - (((ntsc) + 100) & 2))
|
||||
#endif
|
||||
|
||||
extern pixel_info_t const snes_ntsc_pixels [alignment_count];
|
||||
|
||||
/* Generate pixel at all burst phases and column alignments */
|
||||
static void gen_kernel( init_t* impl, float y, float i, float q, snes_ntsc_rgb_t* out )
|
||||
{
|
||||
/* generate for each scanline burst phase */
|
||||
float const* to_rgb = impl->to_rgb;
|
||||
int burst_remain = burst_count;
|
||||
y -= rgb_offset;
|
||||
do
|
||||
{
|
||||
/* Encode yiq into *two* composite signals (to allow control over artifacting).
|
||||
Convolve these with kernels which: filter respective components, apply
|
||||
sharpening, and rescale horizontally. Convert resulting yiq to rgb and pack
|
||||
into integer. Based on algorithm by NewRisingSun. */
|
||||
pixel_info_t const* pixel = snes_ntsc_pixels;
|
||||
int alignment_remain = alignment_count;
|
||||
do
|
||||
{
|
||||
/* negate is -1 when composite starts at odd multiple of 2 */
|
||||
float const yy = y * impl->fringing * pixel->negate;
|
||||
float const ic0 = (i + yy) * pixel->kernel [0];
|
||||
float const qc1 = (q + yy) * pixel->kernel [1];
|
||||
float const ic2 = (i - yy) * pixel->kernel [2];
|
||||
float const qc3 = (q - yy) * pixel->kernel [3];
|
||||
|
||||
float const factor = impl->artifacts * pixel->negate;
|
||||
float const ii = i * factor;
|
||||
float const yc0 = (y + ii) * pixel->kernel [0];
|
||||
float const yc2 = (y - ii) * pixel->kernel [2];
|
||||
|
||||
float const qq = q * factor;
|
||||
float const yc1 = (y + qq) * pixel->kernel [1];
|
||||
float const yc3 = (y - qq) * pixel->kernel [3];
|
||||
|
||||
float const* k = &impl->kernel [pixel->offset];
|
||||
int n;
|
||||
++pixel;
|
||||
for ( n = rgb_kernel_size; n; --n )
|
||||
{
|
||||
float i = k[0]*ic0 + k[2]*ic2;
|
||||
float q = k[1]*qc1 + k[3]*qc3;
|
||||
float y = k[kernel_size+0]*yc0 + k[kernel_size+1]*yc1 +
|
||||
k[kernel_size+2]*yc2 + k[kernel_size+3]*yc3 + rgb_offset;
|
||||
if ( rescale_out <= 1 )
|
||||
k--;
|
||||
else if ( k < &impl->kernel [kernel_size * 2 * (rescale_out - 1)] )
|
||||
k += kernel_size * 2 - 1;
|
||||
else
|
||||
k -= kernel_size * 2 * (rescale_out - 1) + 2;
|
||||
{
|
||||
int r, g, b = YIQ_TO_RGB( y, i, q, to_rgb, int, r, g );
|
||||
*out++ = PACK_RGB( r, g, b ) - rgb_bias;
|
||||
}
|
||||
}
|
||||
}
|
||||
while ( alignment_count > 1 && --alignment_remain );
|
||||
|
||||
if ( burst_count <= 1 )
|
||||
break;
|
||||
|
||||
to_rgb += 6;
|
||||
|
||||
ROTATE_IQ( i, q, -0.866025f, -0.5f ); /* -120 degrees */
|
||||
}
|
||||
while ( --burst_remain );
|
||||
}
|
||||
|
||||
static void correct_errors( snes_ntsc_rgb_t color, snes_ntsc_rgb_t* out );
|
||||
|
||||
#if DISABLE_CORRECTION
|
||||
#define CORRECT_ERROR( a ) { out [i] += rgb_bias; }
|
||||
#define DISTRIBUTE_ERROR( a, b, c ) { out [i] += rgb_bias; }
|
||||
#else
|
||||
#define CORRECT_ERROR( a ) { out [a] += error; }
|
||||
#define DISTRIBUTE_ERROR( a, b, c ) {\
|
||||
snes_ntsc_rgb_t fourth = (error + 2 * snes_ntsc_rgb_builder) >> 2;\
|
||||
fourth &= (rgb_bias >> 1) - snes_ntsc_rgb_builder;\
|
||||
fourth -= rgb_bias >> 2;\
|
||||
out [a] += fourth;\
|
||||
out [b] += fourth;\
|
||||
out [c] += fourth;\
|
||||
out [i] += error - (fourth * 3);\
|
||||
}
|
||||
#endif
|
||||
|
||||
#define RGB_PALETTE_OUT( rgb, out_ )\
|
||||
{\
|
||||
unsigned char* out = (out_);\
|
||||
snes_ntsc_rgb_t clamped = (rgb);\
|
||||
SNES_NTSC_CLAMP_( clamped, (8 - rgb_bits) );\
|
||||
out [0] = (unsigned char) (clamped >> 21);\
|
||||
out [1] = (unsigned char) (clamped >> 11);\
|
||||
out [2] = (unsigned char) (clamped >> 1);\
|
||||
}
|
||||
|
||||
/* blitter related */
|
||||
|
||||
#ifndef restrict
|
||||
#if defined (__GNUC__)
|
||||
#define restrict __restrict__
|
||||
#elif defined (_MSC_VER) && _MSC_VER > 1300
|
||||
#define restrict __restrict
|
||||
#else
|
||||
/* no support for restricted pointers */
|
||||
#define restrict
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#if SNES_NTSC_OUT_DEPTH <= 16
|
||||
#if USHRT_MAX == 0xFFFF
|
||||
typedef unsigned short snes_ntsc_out_t;
|
||||
#else
|
||||
#error "Need 16-bit int type"
|
||||
#endif
|
||||
|
||||
#else
|
||||
#if UINT_MAX == 0xFFFFFFFF
|
||||
typedef unsigned int snes_ntsc_out_t;
|
||||
#elif ULONG_MAX == 0xFFFFFFFF
|
||||
typedef unsigned long snes_ntsc_out_t;
|
||||
#else
|
||||
#error "Need 32-bit int type"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+62
-59
@@ -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,7 +70,9 @@ static void* sdl_input_init(void)
|
||||
sdl->num_hats[i] = SDL_JoystickNumHats(sdl->joysticks[i]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
sdl->use_keyboard = true;
|
||||
return sdl;
|
||||
}
|
||||
|
||||
@@ -76,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))
|
||||
{
|
||||
@@ -118,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_key_pressed(key->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;
|
||||
}
|
||||
@@ -157,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]);
|
||||
@@ -166,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:
|
||||
@@ -246,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:
|
||||
@@ -271,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,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;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
#ifdef HAVE_DINPUT
|
||||
sdl_dinput_poll(sdl->di);
|
||||
#else
|
||||
SDL_JoystickUpdate();
|
||||
#endif
|
||||
|
||||
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
|
||||
+11
-5
@@ -20,19 +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;
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
/* 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 "driver.h"
|
||||
|
||||
#include "SDL.h"
|
||||
#include <stdbool.h>
|
||||
#include "general.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "ssnes_sdl_input.h"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/keysym.h>
|
||||
|
||||
typedef struct x11_input
|
||||
{
|
||||
sdl_input_t *sdl;
|
||||
Display *display;
|
||||
char state[32];
|
||||
} x11_input_t;
|
||||
|
||||
struct key_bind
|
||||
{
|
||||
int x;
|
||||
int sdl;
|
||||
};
|
||||
|
||||
static int keysym_lut[SDLK_LAST];
|
||||
static const struct key_bind lut_binds[] = {
|
||||
{ XK_Left, SDLK_LEFT },
|
||||
{ XK_Right, SDLK_RIGHT },
|
||||
{ XK_Up, SDLK_UP },
|
||||
{ XK_Down, SDLK_DOWN },
|
||||
{ XK_Return, SDLK_RETURN },
|
||||
{ XK_Tab, SDLK_TAB },
|
||||
{ XK_Insert, SDLK_INSERT },
|
||||
{ XK_Delete, SDLK_DELETE },
|
||||
{ XK_Shift_R, SDLK_RSHIFT },
|
||||
{ XK_Shift_L, SDLK_LSHIFT },
|
||||
{ XK_Control_L, SDLK_LCTRL },
|
||||
{ XK_Alt_L, SDLK_LALT },
|
||||
{ XK_space, SDLK_SPACE },
|
||||
{ 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 },
|
||||
{ XK_F4, SDLK_F4 },
|
||||
{ XK_F5, SDLK_F5 },
|
||||
{ XK_F6, SDLK_F6 },
|
||||
{ XK_F7, SDLK_F7 },
|
||||
{ XK_F8, SDLK_F8 },
|
||||
{ XK_F9, SDLK_F9 },
|
||||
{ XK_F10, SDLK_F10 },
|
||||
{ XK_F11, SDLK_F11 },
|
||||
{ XK_F12, SDLK_F12 },
|
||||
{ XK_a, SDLK_a },
|
||||
{ XK_b, SDLK_b },
|
||||
{ XK_c, SDLK_c },
|
||||
{ XK_d, SDLK_d },
|
||||
{ XK_e, SDLK_e },
|
||||
{ XK_f, SDLK_f },
|
||||
{ XK_g, SDLK_g },
|
||||
{ XK_h, SDLK_h },
|
||||
{ XK_i, SDLK_i },
|
||||
{ XK_j, SDLK_j },
|
||||
{ XK_k, SDLK_k },
|
||||
{ XK_l, SDLK_l },
|
||||
{ XK_m, SDLK_m },
|
||||
{ XK_n, SDLK_n },
|
||||
{ XK_o, SDLK_o },
|
||||
{ XK_p, SDLK_p },
|
||||
{ XK_q, SDLK_q },
|
||||
{ XK_r, SDLK_r },
|
||||
{ XK_s, SDLK_s },
|
||||
{ XK_t, SDLK_t },
|
||||
{ XK_u, SDLK_u },
|
||||
{ XK_v, SDLK_v },
|
||||
{ XK_w, SDLK_w },
|
||||
{ XK_x, SDLK_x },
|
||||
{ XK_y, SDLK_y },
|
||||
{ XK_z, SDLK_z },
|
||||
};
|
||||
|
||||
static void init_lut(void)
|
||||
{
|
||||
memset(keysym_lut, 0, sizeof(keysym_lut));
|
||||
for (unsigned i = 0; i < sizeof(lut_binds) / sizeof(lut_binds[0]); i++)
|
||||
keysym_lut[lut_binds[i].sdl] = lut_binds[i].x;
|
||||
}
|
||||
|
||||
static void* x_input_init(void)
|
||||
{
|
||||
x11_input_t *x11 = calloc(1, sizeof(*x11));
|
||||
if (!x11)
|
||||
return NULL;
|
||||
|
||||
x11->display = XOpenDisplay(NULL);
|
||||
if (!x11->display)
|
||||
{
|
||||
free(x11);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
x11->sdl = input_sdl.init();
|
||||
if (!x11->sdl)
|
||||
{
|
||||
free(x11);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
init_lut();
|
||||
|
||||
x11->sdl->use_keyboard = false;
|
||||
return x11;
|
||||
}
|
||||
|
||||
static bool x_key_pressed(x11_input_t *x11, int key)
|
||||
{
|
||||
key = keysym_lut[key];
|
||||
int keycode = XKeysymToKeycode(x11->display, key);
|
||||
bool ret = x11->state[keycode >> 3] & (1 << (keycode & 7));
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool x_is_pressed(x11_input_t *x11, const struct snes_keybind *binds, unsigned id)
|
||||
{
|
||||
for (int i = 0; binds[i].id != -1; i++)
|
||||
{
|
||||
if (binds[i].id == id)
|
||||
return x_key_pressed(x11, binds[i].key);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool x_bind_button_pressed(void *data, int key)
|
||||
{
|
||||
x11_input_t *x11 = data;
|
||||
bool pressed = x_is_pressed(x11, g_settings.input.binds[0], key);
|
||||
if (!pressed)
|
||||
return input_sdl.key_pressed(x11->sdl, key);
|
||||
return pressed;
|
||||
}
|
||||
|
||||
static int16_t x_input_state(void *data, const struct snes_keybind **binds, bool port, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
x11_input_t *x11 = data;
|
||||
bool pressed = false;
|
||||
|
||||
switch (device)
|
||||
{
|
||||
case SNES_DEVICE_JOYPAD:
|
||||
pressed = x_is_pressed(x11, binds[(port == SNES_PORT_1) ? 0 : 1], id);
|
||||
if (!pressed)
|
||||
pressed = input_sdl.input_state(x11->sdl, binds, port, device, index, id);
|
||||
return pressed;
|
||||
|
||||
case SNES_DEVICE_MULTITAP:
|
||||
pressed = x_is_pressed(x11, binds[(port == SNES_PORT_2) ? 1 + index : 0], id);
|
||||
if (!pressed)
|
||||
pressed = input_sdl.input_state(x11->sdl, binds, port, device, index, id);
|
||||
return pressed;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void x_input_free(void *data)
|
||||
{
|
||||
x11_input_t *x11 = data;
|
||||
input_sdl.free(x11->sdl);
|
||||
XCloseDisplay(x11->display);
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void x_input_poll(void *data)
|
||||
{
|
||||
x11_input_t *x11 = data;
|
||||
XQueryKeymap(x11->display, x11->state);
|
||||
input_sdl.poll(x11->sdl);
|
||||
}
|
||||
|
||||
const input_driver_t input_x = {
|
||||
.init = x_input_init,
|
||||
.poll = x_input_poll,
|
||||
.input_state = x_input_state,
|
||||
.key_pressed = x_bind_button_pressed,
|
||||
.free = x_input_free,
|
||||
.ident = "x"
|
||||
};
|
||||
|
||||
Executable
+133
@@ -0,0 +1,133 @@
|
||||
#ifndef LIBSNES_HPP
|
||||
#define LIBSNES_HPP
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define SNES_PORT_1 0
|
||||
#define SNES_PORT_2 1
|
||||
|
||||
#define SNES_DEVICE_NONE 0
|
||||
#define SNES_DEVICE_JOYPAD 1
|
||||
#define SNES_DEVICE_MULTITAP 2
|
||||
#define SNES_DEVICE_MOUSE 3
|
||||
#define SNES_DEVICE_SUPER_SCOPE 4
|
||||
#define SNES_DEVICE_JUSTIFIER 5
|
||||
#define SNES_DEVICE_JUSTIFIERS 6
|
||||
|
||||
#define SNES_DEVICE_ID_JOYPAD_B 0
|
||||
#define SNES_DEVICE_ID_JOYPAD_Y 1
|
||||
#define SNES_DEVICE_ID_JOYPAD_SELECT 2
|
||||
#define SNES_DEVICE_ID_JOYPAD_START 3
|
||||
#define SNES_DEVICE_ID_JOYPAD_UP 4
|
||||
#define SNES_DEVICE_ID_JOYPAD_DOWN 5
|
||||
#define SNES_DEVICE_ID_JOYPAD_LEFT 6
|
||||
#define SNES_DEVICE_ID_JOYPAD_RIGHT 7
|
||||
#define SNES_DEVICE_ID_JOYPAD_A 8
|
||||
#define SNES_DEVICE_ID_JOYPAD_X 9
|
||||
#define SNES_DEVICE_ID_JOYPAD_L 10
|
||||
#define SNES_DEVICE_ID_JOYPAD_R 11
|
||||
|
||||
#define SNES_DEVICE_ID_MOUSE_X 0
|
||||
#define SNES_DEVICE_ID_MOUSE_Y 1
|
||||
#define SNES_DEVICE_ID_MOUSE_LEFT 2
|
||||
#define SNES_DEVICE_ID_MOUSE_RIGHT 3
|
||||
|
||||
#define SNES_DEVICE_ID_SUPER_SCOPE_X 0
|
||||
#define SNES_DEVICE_ID_SUPER_SCOPE_Y 1
|
||||
#define SNES_DEVICE_ID_SUPER_SCOPE_TRIGGER 2
|
||||
#define SNES_DEVICE_ID_SUPER_SCOPE_CURSOR 3
|
||||
#define SNES_DEVICE_ID_SUPER_SCOPE_TURBO 4
|
||||
#define SNES_DEVICE_ID_SUPER_SCOPE_PAUSE 5
|
||||
|
||||
#define SNES_DEVICE_ID_JUSTIFIER_X 0
|
||||
#define SNES_DEVICE_ID_JUSTIFIER_Y 1
|
||||
#define SNES_DEVICE_ID_JUSTIFIER_TRIGGER 2
|
||||
#define SNES_DEVICE_ID_JUSTIFIER_START 3
|
||||
|
||||
#define SNES_REGION_NTSC 0
|
||||
#define SNES_REGION_PAL 1
|
||||
|
||||
#define SNES_MEMORY_CARTRIDGE_RAM 0
|
||||
#define SNES_MEMORY_CARTRIDGE_RTC 1
|
||||
#define SNES_MEMORY_BSX_RAM 2
|
||||
#define SNES_MEMORY_BSX_PRAM 3
|
||||
#define SNES_MEMORY_SUFAMI_TURBO_A_RAM 4
|
||||
#define SNES_MEMORY_SUFAMI_TURBO_B_RAM 5
|
||||
#define SNES_MEMORY_GAME_BOY_RAM 6
|
||||
#define SNES_MEMORY_GAME_BOY_RTC 7
|
||||
|
||||
#define SNES_MEMORY_WRAM 100
|
||||
#define SNES_MEMORY_APURAM 101
|
||||
#define SNES_MEMORY_VRAM 102
|
||||
#define SNES_MEMORY_OAM 103
|
||||
#define SNES_MEMORY_CGRAM 104
|
||||
|
||||
typedef void (*snes_video_refresh_t)(const uint16_t *data, unsigned width, unsigned height);
|
||||
typedef void (*snes_audio_sample_t)(uint16_t left, uint16_t right);
|
||||
typedef void (*snes_input_poll_t)(void);
|
||||
typedef int16_t (*snes_input_state_t)(bool port, unsigned device, unsigned index, unsigned id);
|
||||
|
||||
unsigned snes_library_revision_major(void);
|
||||
unsigned snes_library_revision_minor(void);
|
||||
|
||||
void snes_set_video_refresh(snes_video_refresh_t);
|
||||
void snes_set_audio_sample(snes_audio_sample_t);
|
||||
void snes_set_input_poll(snes_input_poll_t);
|
||||
void snes_set_input_state(snes_input_state_t);
|
||||
|
||||
void snes_set_controller_port_device(bool port, unsigned device);
|
||||
void snes_set_cartridge_basename(const char *basename);
|
||||
|
||||
void snes_init(void);
|
||||
void snes_term(void);
|
||||
void snes_power(void);
|
||||
void snes_reset(void);
|
||||
void snes_run(void);
|
||||
|
||||
unsigned snes_serialize_size(void);
|
||||
bool snes_serialize(uint8_t *data, unsigned size);
|
||||
bool snes_unserialize(const uint8_t *data, unsigned size);
|
||||
|
||||
void snes_cheat_reset(void);
|
||||
void snes_cheat_set(unsigned index, bool enabled, const char *code);
|
||||
|
||||
bool snes_load_cartridge_normal(
|
||||
const char *rom_xml, const uint8_t *rom_data, unsigned rom_size
|
||||
);
|
||||
|
||||
bool snes_load_cartridge_bsx_slotted(
|
||||
const char *rom_xml, const uint8_t *rom_data, unsigned rom_size,
|
||||
const char *bsx_xml, const uint8_t *bsx_data, unsigned bsx_size
|
||||
);
|
||||
|
||||
bool snes_load_cartridge_bsx(
|
||||
const char *rom_xml, const uint8_t *rom_data, unsigned rom_size,
|
||||
const char *bsx_xml, const uint8_t *bsx_data, unsigned bsx_size
|
||||
);
|
||||
|
||||
bool snes_load_cartridge_sufami_turbo(
|
||||
const char *rom_xml, const uint8_t *rom_data, unsigned rom_size,
|
||||
const char *sta_xml, const uint8_t *sta_data, unsigned sta_size,
|
||||
const char *stb_xml, const uint8_t *stb_data, unsigned stb_size
|
||||
);
|
||||
|
||||
bool snes_load_cartridge_super_game_boy(
|
||||
const char *rom_xml, const uint8_t *rom_data, unsigned rom_size,
|
||||
const char *dmg_xml, const uint8_t *dmg_data, unsigned dmg_size
|
||||
);
|
||||
|
||||
void snes_unload_cartridge(void);
|
||||
|
||||
bool snes_get_region(void);
|
||||
uint8_t* snes_get_memory_data(unsigned id);
|
||||
unsigned snes_get_memory_size(unsigned id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,302 @@
|
||||
/* 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 "movie.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "general.h"
|
||||
#include "dynamic.h"
|
||||
#include <assert.h>
|
||||
|
||||
// CRC32 implementation taken from BSNES source :)
|
||||
|
||||
static const uint32_t crc32_table[256] = {
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
|
||||
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
||||
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
|
||||
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
|
||||
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
|
||||
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
|
||||
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
|
||||
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
|
||||
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
|
||||
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
|
||||
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
||||
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
|
||||
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
||||
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
||||
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
|
||||
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
|
||||
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
||||
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
|
||||
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
|
||||
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
|
||||
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
|
||||
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
|
||||
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
||||
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
|
||||
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
|
||||
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
|
||||
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
|
||||
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
|
||||
};
|
||||
|
||||
uint32_t crc32_adjust(uint32_t crc32, uint8_t input)
|
||||
{
|
||||
return ((crc32 >> 8) & 0x00ffffff) ^ crc32_table[(crc32 ^ input) & 0xff];
|
||||
}
|
||||
|
||||
uint32_t crc32_calculate(const uint8_t *data, unsigned length)
|
||||
{
|
||||
uint32_t crc32 = ~0;
|
||||
for(unsigned i = 0; i < length; i++)
|
||||
crc32 = crc32_adjust(crc32, data[i]);
|
||||
return ~crc32;
|
||||
}
|
||||
|
||||
struct bsv_movie
|
||||
{
|
||||
FILE *file;
|
||||
uint8_t *state;
|
||||
|
||||
long *frame_pos; // A ring buffer keeping track of many key presses were requested per frame.
|
||||
size_t frame_mask;
|
||||
uint16_t current_frame_count;
|
||||
size_t frame_ptr;
|
||||
|
||||
bool playback;
|
||||
unsigned min_file_pos;
|
||||
|
||||
bool first_rewind;
|
||||
bool did_rewind;
|
||||
};
|
||||
|
||||
#define BSV_MAGIC 0x42535631
|
||||
|
||||
#define MAGIC_INDEX 0
|
||||
#define SERIALIZER_INDEX 1
|
||||
#define CRC_INDEX 2
|
||||
#define STATE_SIZE_INDEX 3
|
||||
|
||||
// Convert to big-endian if needed
|
||||
static inline uint32_t swap_if_big32(uint32_t val)
|
||||
{
|
||||
if (is_little_endian()) // Little-endian
|
||||
return val;
|
||||
else
|
||||
return (val >> 24) | ((val >> 8) & 0xFF00) | ((val << 8) & 0xFF0000) | (val << 24);
|
||||
}
|
||||
|
||||
static inline uint16_t swap_if_big16(uint16_t val)
|
||||
{
|
||||
if (is_little_endian())
|
||||
return val;
|
||||
else
|
||||
return (val >> 8) | (val << 8);
|
||||
}
|
||||
|
||||
static bool init_playback(bsv_movie_t *handle, const char *path)
|
||||
{
|
||||
handle->playback = true;
|
||||
handle->file = fopen(path, "rb");
|
||||
if (!handle->file)
|
||||
{
|
||||
SSNES_ERR("Couldn't open BSV file \"%s\" for playback.\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t header[4] = {0};
|
||||
if (fread(header, sizeof(uint32_t), 4, handle->file) != 4)
|
||||
{
|
||||
SSNES_ERR("Couldn't read movie header!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (swap_if_big32(header[MAGIC_INDEX]) != BSV_MAGIC)
|
||||
{
|
||||
SSNES_ERR("Movie file is not a valid BSV1 file!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (swap_if_big32(header[CRC_INDEX]) != g_extern.cart_crc)
|
||||
{
|
||||
SSNES_ERR("Cart CRC32s differ! Cannot play back!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t state_size = swap_if_big32(header[STATE_SIZE_INDEX]);
|
||||
handle->state = malloc(state_size);
|
||||
if (!handle->state)
|
||||
return false;
|
||||
|
||||
handle->min_file_pos = sizeof(header) + state_size;
|
||||
|
||||
if (fread(handle->state, 1, state_size, handle->file) != state_size)
|
||||
{
|
||||
SSNES_ERR("Couldn't read state from movie.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (psnes_serialize_size() != state_size)
|
||||
{
|
||||
SSNES_ERR("Movie format seems to have a different serializer version. Cannot continue.\n");
|
||||
exit(1);
|
||||
}
|
||||
// Unserialize to start playback.
|
||||
psnes_unserialize(handle->state, state_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool init_record(bsv_movie_t *handle, const char *path)
|
||||
{
|
||||
handle->file = fopen(path, "wb");
|
||||
if (!handle->file)
|
||||
{
|
||||
SSNES_ERR("Couldn't open BSV \"%s\" for recording.\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t header[4] = {0};
|
||||
header[MAGIC_INDEX] = swap_if_big32(BSV_MAGIC);
|
||||
//header[SERIALIZER_INDEX] = 0; // TODO: Fix this.
|
||||
header[CRC_INDEX] = swap_if_big32(g_extern.cart_crc);
|
||||
|
||||
uint32_t state_size = psnes_serialize_size();
|
||||
header[STATE_SIZE_INDEX] = swap_if_big32(state_size);
|
||||
fwrite(header, 4, sizeof(uint32_t), handle->file);
|
||||
|
||||
handle->state = malloc(state_size);
|
||||
if (!handle->state)
|
||||
return false;
|
||||
|
||||
handle->min_file_pos = sizeof(header) + state_size;
|
||||
|
||||
psnes_serialize(handle->state, state_size);
|
||||
fwrite(handle->state, 1, state_size, handle->file);
|
||||
return true;
|
||||
}
|
||||
|
||||
void bsv_movie_free(bsv_movie_t *handle)
|
||||
{
|
||||
if (handle)
|
||||
{
|
||||
if (handle->file)
|
||||
fclose(handle->file);
|
||||
free(handle->state);
|
||||
free(handle->frame_pos);
|
||||
free(handle);
|
||||
}
|
||||
}
|
||||
|
||||
bool bsv_movie_get_input(bsv_movie_t *handle, int16_t *input)
|
||||
{
|
||||
if (fread(input, sizeof(int16_t), 1, handle->file) != 1)
|
||||
return false;
|
||||
|
||||
*input = swap_if_big16(*input);
|
||||
return true;
|
||||
}
|
||||
|
||||
void bsv_movie_set_input(bsv_movie_t *handle, int16_t input)
|
||||
{
|
||||
fwrite(&input, sizeof(int16_t), 1, handle->file);
|
||||
}
|
||||
|
||||
bsv_movie_t *bsv_movie_init(const char *path, enum ssnes_movie_type type)
|
||||
{
|
||||
bsv_movie_t *handle = calloc(1, sizeof(*handle));
|
||||
if (!handle)
|
||||
return NULL;
|
||||
|
||||
if (type == SSNES_MOVIE_PLAYBACK)
|
||||
{
|
||||
if (!init_playback(handle, path))
|
||||
goto error;
|
||||
}
|
||||
else if (!init_record(handle, path))
|
||||
goto error;
|
||||
|
||||
if (!(handle->frame_pos = calloc((1 << 20), sizeof(long)))) // Just pick something really large :D
|
||||
goto error;
|
||||
handle->frame_mask = (1 << 20) - 1;
|
||||
|
||||
return handle;
|
||||
|
||||
error:
|
||||
bsv_movie_free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void bsv_movie_set_frame_start(bsv_movie_t *handle)
|
||||
{
|
||||
//fprintf(stderr, "Starting frame: %u, Pos: %ld\n", (unsigned)handle->frame_ptr, ftell(handle->file));
|
||||
handle->frame_pos[handle->frame_ptr] = ftell(handle->file);
|
||||
|
||||
}
|
||||
|
||||
void bsv_movie_set_frame_end(bsv_movie_t *handle)
|
||||
{
|
||||
//fprintf(stderr, "Frame++\n");
|
||||
handle->frame_ptr = (handle->frame_ptr + 1) & handle->frame_mask;
|
||||
|
||||
handle->first_rewind = !handle->did_rewind;
|
||||
handle->did_rewind = false;
|
||||
}
|
||||
|
||||
void bsv_movie_frame_rewind(bsv_movie_t *handle)
|
||||
{
|
||||
//fprintf(stderr, "Frame--\n");
|
||||
|
||||
handle->did_rewind = true;
|
||||
if (handle->frame_ptr <= 1)
|
||||
{
|
||||
handle->frame_ptr = 0;
|
||||
fseek(handle->file, handle->min_file_pos, SEEK_SET);
|
||||
}
|
||||
else
|
||||
{
|
||||
handle->frame_ptr = (handle->frame_ptr - (handle->first_rewind ? 1 : 2)) & handle->frame_mask;
|
||||
fseek(handle->file, handle->frame_pos[handle->frame_ptr], SEEK_SET);
|
||||
}
|
||||
|
||||
if (ftell(handle->file) <= (long)handle->min_file_pos)
|
||||
{
|
||||
if (!handle->playback)
|
||||
{
|
||||
fseek(handle->file, 4 * sizeof(uint32_t), SEEK_SET);
|
||||
psnes_serialize(handle->state, psnes_serialize_size());
|
||||
fwrite(handle->state, 1, psnes_serialize_size(), handle->file);
|
||||
}
|
||||
else
|
||||
fseek(handle->file, handle->min_file_pos, SEEK_SET);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/* 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_MOVIE_H
|
||||
#define __SSNES_MOVIE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct bsv_movie bsv_movie_t;
|
||||
|
||||
enum ssnes_movie_type
|
||||
{
|
||||
SSNES_MOVIE_PLAYBACK,
|
||||
SSNES_MOVIE_RECORD,
|
||||
};
|
||||
|
||||
bsv_movie_t *bsv_movie_init(const char *path, enum ssnes_movie_type type);
|
||||
|
||||
// Playback
|
||||
bool bsv_movie_get_input(bsv_movie_t *handle, int16_t *input);
|
||||
|
||||
// Recording
|
||||
void bsv_movie_set_input(bsv_movie_t *handle, int16_t input);
|
||||
|
||||
// Used for rewinding while playback/record.
|
||||
void bsv_movie_set_frame_start(bsv_movie_t *handle); // Debugging purposes.
|
||||
void bsv_movie_set_frame_end(bsv_movie_t *handle);
|
||||
void bsv_movie_frame_rewind(bsv_movie_t *handle);
|
||||
|
||||
void bsv_movie_free(bsv_movie_t *handle);
|
||||
|
||||
uint32_t crc32_calculate(const uint8_t *data, unsigned length);
|
||||
uint32_t crc32_adjust(uint32_t crc32, uint8_t input);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,701 @@
|
||||
/* 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 "netplay.h"
|
||||
#include "general.h"
|
||||
#include "autosave.h"
|
||||
#include "dynamic.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define _WIN32_WINNT 0x0501
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
// Woohoo, Winsock has headers from the STONE AGE! :D
|
||||
#define close(x) closesocket(x)
|
||||
#define CONST_CAST (const char*)
|
||||
#define NONCONST_CAST (char*)
|
||||
#else
|
||||
#include <sys/select.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <signal.h>
|
||||
#define CONST_CAST
|
||||
#define NONCONST_CAST
|
||||
#endif
|
||||
|
||||
|
||||
#define PREV_PTR(x) ((x) == 0 ? handle->buffer_size - 1 : (x) - 1)
|
||||
#define NEXT_PTR(x) ((x + 1) % handle->buffer_size)
|
||||
|
||||
struct delta_frame
|
||||
{
|
||||
uint8_t *state;
|
||||
|
||||
uint16_t real_input_state;
|
||||
uint16_t simulated_input_state;
|
||||
bool is_simulated;
|
||||
uint16_t self_state;
|
||||
bool used_real;
|
||||
};
|
||||
|
||||
#define UDP_FRAME_PACKETS 8
|
||||
|
||||
struct netplay
|
||||
{
|
||||
struct snes_callbacks cbs;
|
||||
int fd; // TCP connection for state sending, etc. Could perhaps be used for messaging later on. :)
|
||||
int udp_fd; // UDP connection for game state updates.
|
||||
unsigned port; // Which port is governed by netplay?
|
||||
bool has_connection;
|
||||
|
||||
struct delta_frame *buffer;
|
||||
size_t buffer_size;
|
||||
|
||||
size_t self_ptr; // Ptr where we are now.
|
||||
size_t other_ptr; // Points to the last reliable state that self ever had.
|
||||
size_t read_ptr; // Ptr to where we are reading. Generally, other_ptr <= read_ptr <= self_ptr.
|
||||
size_t tmp_ptr; // A temporary pointer used on replay.
|
||||
|
||||
size_t state_size;
|
||||
|
||||
size_t is_replay; // Are we replaying old frames?
|
||||
bool can_poll; // We don't want to poll several times on a frame.
|
||||
|
||||
uint32_t packet_buffer[UDP_FRAME_PACKETS * 2]; // To compat UDP packet loss we also send old data along with the packets.
|
||||
uint32_t frame_count;
|
||||
uint32_t read_frame_count;
|
||||
struct addrinfo *addr;
|
||||
struct sockaddr_storage their_addr;
|
||||
bool has_client_addr;
|
||||
};
|
||||
|
||||
void input_poll_net(void)
|
||||
{
|
||||
if (!netplay_should_skip(g_extern.netplay) && netplay_can_poll(g_extern.netplay))
|
||||
netplay_poll(g_extern.netplay);
|
||||
}
|
||||
|
||||
void video_frame_net(const uint16_t *data, unsigned width, unsigned height)
|
||||
{
|
||||
if (!netplay_should_skip(g_extern.netplay))
|
||||
netplay_callbacks(g_extern.netplay)->frame_cb(data, width, height);
|
||||
}
|
||||
|
||||
void audio_sample_net(uint16_t left, uint16_t right)
|
||||
{
|
||||
if (!netplay_should_skip(g_extern.netplay))
|
||||
netplay_callbacks(g_extern.netplay)->sample_cb(left, right);
|
||||
}
|
||||
|
||||
int16_t input_state_net(bool port, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
if (netplay_is_alive(g_extern.netplay))
|
||||
return netplay_input_state(g_extern.netplay, port, device, index, id);
|
||||
else
|
||||
return netplay_callbacks(g_extern.netplay)->state_cb(port, device, index, id);
|
||||
}
|
||||
|
||||
static bool init_tcp_socket(netplay_t *handle, const char *server, uint16_t port)
|
||||
{
|
||||
struct addrinfo hints, *res = NULL;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
#ifdef _WIN32 // Lolol, no AF_UNSPEC, wtf.
|
||||
hints.ai_family = AF_INET;
|
||||
#else
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
#endif
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
if (!server)
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
|
||||
char port_buf[16];
|
||||
snprintf(port_buf, sizeof(port_buf), "%hu", (unsigned short)port);
|
||||
if (getaddrinfo(server, port_buf, &hints, &res) < 0)
|
||||
return false;
|
||||
|
||||
if (!res)
|
||||
return false;
|
||||
|
||||
handle->fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
||||
if (handle->fd < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to init socket...\n");
|
||||
|
||||
if (res)
|
||||
freeaddrinfo(res);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (server)
|
||||
{
|
||||
if (connect(handle->fd, res->ai_addr, res->ai_addrlen) < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to connect to server.\n");
|
||||
close(handle->fd);
|
||||
freeaddrinfo(res);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int yes = 1;
|
||||
setsockopt(handle->fd, SOL_SOCKET, SO_REUSEADDR, CONST_CAST &yes, sizeof(int));
|
||||
|
||||
if (bind(handle->fd, res->ai_addr, res->ai_addrlen) < 0 || listen(handle->fd, 1) < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to bind socket.\n");
|
||||
close(handle->fd);
|
||||
freeaddrinfo(res);
|
||||
return false;
|
||||
}
|
||||
int new_fd = accept(handle->fd, NULL, NULL);
|
||||
if (new_fd < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to accept socket.\n");
|
||||
close(handle->fd);
|
||||
freeaddrinfo(res);
|
||||
return false;
|
||||
}
|
||||
close(handle->fd);
|
||||
handle->fd = new_fd;
|
||||
}
|
||||
|
||||
freeaddrinfo(res);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool init_udp_socket(netplay_t *handle, const char *server, uint16_t port)
|
||||
{
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
#ifdef _WIN32 // Lolol, no AF_UNSPEC, wtf.
|
||||
hints.ai_family = AF_INET;
|
||||
#else
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
#endif
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
if (!server)
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
|
||||
char port_buf[16];
|
||||
snprintf(port_buf, sizeof(port_buf), "%hu", (unsigned short)port);
|
||||
if (getaddrinfo(server, port_buf, &hints, &handle->addr) < 0)
|
||||
return false;
|
||||
|
||||
if (!handle->addr)
|
||||
return false;
|
||||
|
||||
handle->udp_fd = socket(handle->addr->ai_family, handle->addr->ai_socktype, handle->addr->ai_protocol);
|
||||
if (handle->udp_fd < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to init socket...\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!server)
|
||||
{
|
||||
// Note sure if we have to do this for UDP, but hey :)
|
||||
int yes = 1;
|
||||
setsockopt(handle->udp_fd, SOL_SOCKET, SO_REUSEADDR, CONST_CAST &yes, sizeof(int));
|
||||
|
||||
if (bind(handle->udp_fd, handle->addr->ai_addr, handle->addr->ai_addrlen) < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to bind socket.\n");
|
||||
close(handle->udp_fd);
|
||||
}
|
||||
|
||||
freeaddrinfo(handle->addr);
|
||||
handle->addr = NULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool init_socket(netplay_t *handle, const char *server, uint16_t port)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
|
||||
{
|
||||
WSACleanup();
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
signal(SIGPIPE, SIG_IGN); // Do not like SIGPIPE killing our app :(
|
||||
#endif
|
||||
|
||||
if (!init_tcp_socket(handle, server, port))
|
||||
return false;
|
||||
if (!init_udp_socket(handle, server, port))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool netplay_can_poll(netplay_t *handle)
|
||||
{
|
||||
return handle->can_poll;
|
||||
}
|
||||
|
||||
static bool send_info(netplay_t *handle)
|
||||
{
|
||||
uint32_t header[3] = { htonl(g_extern.cart_crc), htonl(psnes_serialize_size()), htonl(psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM)) };
|
||||
if (send(handle->fd, CONST_CAST header, sizeof(header), 0) != sizeof(header))
|
||||
return false;
|
||||
|
||||
// Get SRAM data from Player 1 :)
|
||||
uint8_t *sram = psnes_get_memory_data(SNES_MEMORY_CARTRIDGE_RAM);
|
||||
unsigned sram_size = psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM);
|
||||
while (sram_size > 0)
|
||||
{
|
||||
ssize_t ret = recv(handle->fd, NONCONST_CAST sram, sram_size, 0);
|
||||
if (ret <= 0)
|
||||
{
|
||||
SSNES_ERR("Failed to receive SRAM data from host.\n");
|
||||
return false;
|
||||
}
|
||||
sram += ret;
|
||||
sram_size -= ret;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool get_info(netplay_t *handle)
|
||||
{
|
||||
uint32_t header[3];
|
||||
if (recv(handle->fd, NONCONST_CAST header, sizeof(header), 0) != sizeof(header))
|
||||
{
|
||||
SSNES_ERR("Failed to receive header from client.\n");
|
||||
return false;
|
||||
}
|
||||
if (g_extern.cart_crc != ntohl(header[0]))
|
||||
{
|
||||
SSNES_ERR("Cart CRC32s differ! Cannot use different games!\n");
|
||||
return false;
|
||||
}
|
||||
if (psnes_serialize_size() != ntohl(header[1]))
|
||||
{
|
||||
SSNES_ERR("Serialization sizes differ, make sure you're using exact same libsnes implementations!\n");
|
||||
return false;
|
||||
}
|
||||
if (psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM) != ntohl(header[2]))
|
||||
{
|
||||
SSNES_ERR("Cartridge SRAM sizes do not correspond!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send SRAM data to our Player 2 :)
|
||||
const uint8_t *sram = psnes_get_memory_data(SNES_MEMORY_CARTRIDGE_RAM);
|
||||
unsigned sram_size = psnes_get_memory_size(SNES_MEMORY_CARTRIDGE_RAM);
|
||||
while (sram_size > 0)
|
||||
{
|
||||
ssize_t ret = send(handle->fd, CONST_CAST sram, sram_size, 0);
|
||||
if (ret <= 0)
|
||||
{
|
||||
SSNES_ERR("Failed to send SRAM data to client.\n");
|
||||
return false;
|
||||
}
|
||||
sram += ret;
|
||||
sram_size -= ret;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void init_buffers(netplay_t *handle)
|
||||
{
|
||||
handle->buffer = calloc(handle->buffer_size, sizeof(*handle->buffer));
|
||||
handle->state_size = psnes_serialize_size();
|
||||
for (unsigned i = 0; i < handle->buffer_size; i++)
|
||||
{
|
||||
handle->buffer[i].state = malloc(handle->state_size);
|
||||
handle->buffer[i].is_simulated = true;
|
||||
}
|
||||
}
|
||||
|
||||
netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const struct snes_callbacks *cb)
|
||||
{
|
||||
netplay_t *handle = calloc(1, sizeof(*handle));
|
||||
if (!handle)
|
||||
return NULL;
|
||||
|
||||
handle->cbs = *cb;
|
||||
handle->port = server ? 0 : 1;
|
||||
|
||||
if (!init_socket(handle, server, port))
|
||||
{
|
||||
free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (server)
|
||||
{
|
||||
if (!send_info(handle))
|
||||
{
|
||||
close(handle->fd);
|
||||
free(handle);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!get_info(handle))
|
||||
{
|
||||
close(handle->fd);
|
||||
free(handle);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
handle->buffer_size = frames + 1;
|
||||
|
||||
init_buffers(handle);
|
||||
handle->has_connection = true;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
||||
bool netplay_is_alive(netplay_t *handle)
|
||||
{
|
||||
return handle->has_connection;
|
||||
}
|
||||
|
||||
static bool send_chunk(netplay_t *handle)
|
||||
{
|
||||
const struct sockaddr *addr = NULL;
|
||||
if (handle->addr)
|
||||
addr = handle->addr->ai_addr;
|
||||
else if (handle->has_client_addr)
|
||||
addr = (const struct sockaddr*)&handle->their_addr;
|
||||
|
||||
if (addr)
|
||||
{
|
||||
if (sendto(handle->udp_fd, CONST_CAST handle->packet_buffer, sizeof(handle->packet_buffer), 0, addr, sizeof(struct sockaddr)) != sizeof(handle->packet_buffer))
|
||||
{
|
||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||
handle->has_connection = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#define MAX_RETRIES 64
|
||||
#define RETRY_MS 500
|
||||
|
||||
static int poll_input(netplay_t *handle, bool block)
|
||||
{
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(handle->udp_fd, &fds);
|
||||
FD_SET(handle->fd, &fds);
|
||||
int max_fd = (handle->fd > handle->udp_fd ? handle->fd : handle->udp_fd) + 1;
|
||||
|
||||
struct timeval tv = {
|
||||
.tv_sec = 0,
|
||||
.tv_usec = block ? (RETRY_MS * 1000) : 0
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
do
|
||||
{
|
||||
if (select(max_fd, &fds, NULL, NULL, &tv) < 0)
|
||||
return -1;
|
||||
|
||||
// Somewhat hacky, but we aren't using the TCP connection for anything useful atm.
|
||||
// Will probably add some proper messaging system here later.
|
||||
if (FD_ISSET(handle->fd, &fds))
|
||||
return -1;
|
||||
|
||||
if (FD_ISSET(handle->udp_fd, &fds))
|
||||
return 1;
|
||||
|
||||
if (block && !send_chunk(handle))
|
||||
{
|
||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||
handle->has_connection = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (block)
|
||||
SSNES_LOG("Network lag of %d ms, resending packet... Attempt %d of %d ...\n", RETRY_MS, i, MAX_RETRIES);
|
||||
|
||||
// Seems to be necessary on Win32.
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(handle->udp_fd, &fds);
|
||||
FD_SET(handle->fd, &fds);
|
||||
|
||||
} while ((i++ < MAX_RETRIES) && block);
|
||||
|
||||
if (block)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Grab our own input state and send this over the network.
|
||||
static bool get_self_input_state(netplay_t *handle)
|
||||
{
|
||||
struct delta_frame *ptr = &handle->buffer[handle->self_ptr];
|
||||
|
||||
uint32_t state = 0;
|
||||
if (handle->frame_count > 0) // First frame we always give zero input since relying on input from first frame screws up when we use -F 0.
|
||||
{
|
||||
snes_input_state_t cb = handle->cbs.state_cb;
|
||||
for (unsigned i = 0; i <= 11; i++)
|
||||
{
|
||||
int16_t tmp = cb(g_settings.input.netplay_client_swap_input ? 0 : !handle->port, SNES_DEVICE_JOYPAD, 0, i);
|
||||
state |= tmp ? 1 << i : 0;
|
||||
}
|
||||
}
|
||||
|
||||
memmove(handle->packet_buffer, handle->packet_buffer + 2, sizeof (handle->packet_buffer) - 2 * sizeof(uint32_t));
|
||||
handle->packet_buffer[(UDP_FRAME_PACKETS - 1) * 2] = htonl(handle->frame_count);
|
||||
handle->packet_buffer[(UDP_FRAME_PACKETS - 1) * 2 + 1] = htonl(state);
|
||||
|
||||
if (!send_chunk(handle))
|
||||
{
|
||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||
handle->has_connection = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr->self_state = state;
|
||||
handle->self_ptr = NEXT_PTR(handle->self_ptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Somewhat better prediction. :P
|
||||
static void simulate_input(netplay_t *handle)
|
||||
{
|
||||
size_t ptr = PREV_PTR(handle->self_ptr);
|
||||
size_t prev = PREV_PTR(handle->read_ptr);
|
||||
|
||||
handle->buffer[ptr].simulated_input_state = handle->buffer[prev].real_input_state;
|
||||
handle->buffer[ptr].is_simulated = true;
|
||||
handle->buffer[ptr].used_real = false;
|
||||
}
|
||||
|
||||
static void parse_packet(netplay_t *handle, uint32_t *buffer, unsigned size)
|
||||
{
|
||||
for (unsigned i = 0; i < size * 2; i++)
|
||||
buffer[i] = ntohl(buffer[i]);
|
||||
|
||||
for (unsigned i = 0; i < size && handle->read_frame_count <= handle->frame_count; i++)
|
||||
{
|
||||
uint32_t frame = buffer[2 * i];
|
||||
uint32_t state = buffer[2 * i + 1];
|
||||
|
||||
if (frame == handle->read_frame_count)
|
||||
{
|
||||
handle->buffer[handle->read_ptr].is_simulated = false;
|
||||
handle->buffer[handle->read_ptr].real_input_state = state;
|
||||
handle->read_ptr = NEXT_PTR(handle->read_ptr);
|
||||
handle->read_frame_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool receive_data(netplay_t *handle, uint32_t *buffer, size_t size)
|
||||
{
|
||||
socklen_t addrlen = sizeof(handle->their_addr);
|
||||
if (recvfrom(handle->udp_fd, NONCONST_CAST buffer, size, 0, (struct sockaddr*)&handle->their_addr, &addrlen) != size)
|
||||
return false;
|
||||
handle->has_client_addr = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Poll network to see if we have anything new. If our network buffer is full, we simply have to block for new input data.
|
||||
bool netplay_poll(netplay_t *handle)
|
||||
{
|
||||
if (!handle->has_connection)
|
||||
return false;
|
||||
|
||||
handle->can_poll = false;
|
||||
|
||||
if (!get_self_input_state(handle))
|
||||
return false;
|
||||
|
||||
// We skip reading the first frame so the host has a change to grab our host info so we don't block forever :')
|
||||
if (handle->frame_count == 0)
|
||||
{
|
||||
handle->buffer[0].used_real = true;
|
||||
handle->buffer[0].is_simulated = false;
|
||||
handle->buffer[0].real_input_state = 0;
|
||||
handle->read_ptr = NEXT_PTR(handle->read_ptr);
|
||||
handle->read_frame_count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
//fprintf(stderr, "Other ptr: %lu, Read ptr: %lu, Self ptr: %lu\n", handle->other_ptr, handle->read_ptr, handle->self_ptr);
|
||||
if (handle->buffer_size > 1)
|
||||
{
|
||||
assert(handle->other_ptr != handle->self_ptr);
|
||||
assert(handle->read_ptr != handle->self_ptr);
|
||||
}
|
||||
assert(handle->other_ptr == handle->read_ptr);
|
||||
|
||||
// We might have reached the end of the buffer, where we simply have to block.
|
||||
int res = poll_input(handle, handle->other_ptr == NEXT_PTR(handle->self_ptr));
|
||||
if (res == -1)
|
||||
{
|
||||
handle->has_connection = false;
|
||||
SSNES_WARN("Netplay connection timed out. Will continue without netplay.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (res == 1)
|
||||
{
|
||||
uint32_t first_read = handle->read_frame_count;
|
||||
do
|
||||
{
|
||||
uint32_t buffer[UDP_FRAME_PACKETS * 2];
|
||||
if (!receive_data(handle, buffer, sizeof(buffer)))
|
||||
{
|
||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||
handle->has_connection = false;
|
||||
return false;
|
||||
}
|
||||
parse_packet(handle, buffer, UDP_FRAME_PACKETS);
|
||||
|
||||
} while ((handle->read_frame_count <= handle->frame_count) &&
|
||||
poll_input(handle, (handle->other_ptr == NEXT_PTR(handle->self_ptr)) &&
|
||||
(first_read == handle->read_frame_count)) == 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cannot allow this. Should not happen though.
|
||||
if (NEXT_PTR(handle->self_ptr) == handle->other_ptr)
|
||||
{
|
||||
SSNES_WARN("Netplay connection hung up. Will continue without netplay.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (handle->read_ptr != handle->self_ptr)
|
||||
simulate_input(handle);
|
||||
else
|
||||
handle->buffer[PREV_PTR(handle->self_ptr)].used_real = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int16_t netplay_input_state(netplay_t *handle, bool port, unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
uint16_t input_state = 0;
|
||||
size_t ptr = 0;
|
||||
|
||||
if (handle->is_replay)
|
||||
ptr = handle->tmp_ptr;
|
||||
else
|
||||
ptr = PREV_PTR(handle->self_ptr);
|
||||
|
||||
if ((port ? 1 : 0) == handle->port)
|
||||
{
|
||||
if (handle->buffer[ptr].is_simulated)
|
||||
input_state = handle->buffer[ptr].simulated_input_state;
|
||||
else
|
||||
input_state = handle->buffer[ptr].real_input_state;
|
||||
}
|
||||
else
|
||||
input_state = handle->buffer[ptr].self_state;
|
||||
|
||||
return ((1 << id) & input_state) ? 1 : 0;
|
||||
}
|
||||
|
||||
void netplay_free(netplay_t *handle)
|
||||
{
|
||||
close(handle->fd);
|
||||
close(handle->udp_fd);
|
||||
|
||||
for (unsigned i = 0; i < handle->buffer_size; i++)
|
||||
free(handle->buffer[i].state);
|
||||
|
||||
free(handle->buffer);
|
||||
if (handle->addr)
|
||||
freeaddrinfo(handle->addr);
|
||||
free(handle);
|
||||
}
|
||||
|
||||
const struct snes_callbacks* netplay_callbacks(netplay_t *handle)
|
||||
{
|
||||
return &handle->cbs;
|
||||
}
|
||||
|
||||
bool netplay_should_skip(netplay_t *handle)
|
||||
{
|
||||
return handle->is_replay && handle->has_connection;
|
||||
}
|
||||
|
||||
void netplay_pre_frame(netplay_t *handle)
|
||||
{
|
||||
psnes_serialize(handle->buffer[handle->self_ptr].state, handle->state_size);
|
||||
handle->can_poll = true;
|
||||
|
||||
input_poll_net();
|
||||
}
|
||||
|
||||
// Here we check if we have new input and replay from recorded input.
|
||||
void netplay_post_frame(netplay_t *handle)
|
||||
{
|
||||
handle->frame_count++;
|
||||
|
||||
// Nothing to do...
|
||||
if (handle->other_ptr == handle->read_ptr)
|
||||
return;
|
||||
|
||||
// Skip ahead if we predicted correctly. Skip until our simulation failed.
|
||||
while (handle->other_ptr != handle->read_ptr)
|
||||
{
|
||||
struct delta_frame *ptr = &handle->buffer[handle->other_ptr];
|
||||
if ((ptr->simulated_input_state != ptr->real_input_state) && !ptr->used_real)
|
||||
break;
|
||||
handle->other_ptr = NEXT_PTR(handle->other_ptr);
|
||||
}
|
||||
|
||||
if (handle->other_ptr != handle->read_ptr)
|
||||
{
|
||||
// Replay frames
|
||||
handle->is_replay = true;
|
||||
handle->tmp_ptr = handle->other_ptr;
|
||||
psnes_unserialize(handle->buffer[handle->other_ptr].state, handle->state_size);
|
||||
while (handle->tmp_ptr != handle->self_ptr)
|
||||
{
|
||||
psnes_serialize(handle->buffer[handle->tmp_ptr].state, handle->state_size);
|
||||
lock_autosave();
|
||||
psnes_run();
|
||||
unlock_autosave();
|
||||
handle->tmp_ptr = NEXT_PTR(handle->tmp_ptr);
|
||||
}
|
||||
handle->other_ptr = handle->read_ptr;
|
||||
handle->is_replay = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/* 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_NETPLAY_H
|
||||
#define __SSNES_NETPLAY_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <libsnes.hpp>
|
||||
|
||||
void input_poll_net(void);
|
||||
int16_t input_state_net(bool port, unsigned device, unsigned index, unsigned id);
|
||||
void video_frame_net(const uint16_t *data, unsigned width, unsigned height);
|
||||
void audio_sample_net(uint16_t left, uint16_t right);
|
||||
|
||||
typedef struct netplay netplay_t;
|
||||
|
||||
struct snes_callbacks
|
||||
{
|
||||
snes_video_refresh_t frame_cb;
|
||||
snes_audio_sample_t sample_cb;
|
||||
snes_input_state_t state_cb;
|
||||
};
|
||||
|
||||
// Creates a new netplay handle. A NULL host means we're hosting (player 1). :)
|
||||
netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, const struct snes_callbacks *cb);
|
||||
void netplay_free(netplay_t *handle);
|
||||
|
||||
// Call this before running snes_run()
|
||||
void netplay_pre_frame(netplay_t *handle);
|
||||
// Call this after running snes_run()
|
||||
void netplay_post_frame(netplay_t *handle);
|
||||
|
||||
// Checks if input port/index is controlled by netplay or not.
|
||||
bool netplay_is_alive(netplay_t *handle);
|
||||
|
||||
bool netplay_poll(netplay_t *handle);
|
||||
int16_t netplay_input_state(netplay_t *handle, bool port, unsigned device, unsigned index, unsigned id);
|
||||
|
||||
// If we're fast-forward replaying to resync, check if we should actually show frame.
|
||||
bool netplay_should_skip(netplay_t *handle);
|
||||
bool netplay_can_poll(netplay_t *handle);
|
||||
const struct snes_callbacks* netplay_callbacks(netplay_t *handle);
|
||||
|
||||
#endif
|
||||
File renamed without changes.
+48
-12
@@ -3,6 +3,12 @@
|
||||
check_switch_c C99 -std=gnu99
|
||||
check_critical C99 "Cannot find C99 compatible compiler."
|
||||
|
||||
if [ "$OS" = BSD ]; then
|
||||
DYLIB=-lc
|
||||
else
|
||||
DYLIB=-ldl
|
||||
fi
|
||||
|
||||
if [ "$HAVE_DYNAMIC" = "yes" ] && [ "$HAVE_CONFIGFILE" = "no" ]; then
|
||||
echo "Cannot have dynamic loading of libsnes and no configfile support."
|
||||
echo "Dynamic loading requires config file support."
|
||||
@@ -10,44 +16,74 @@ if [ "$HAVE_DYNAMIC" = "yes" ] && [ "$HAVE_CONFIGFILE" = "no" ]; then
|
||||
fi
|
||||
|
||||
if [ $HAVE_DYNAMIC != yes ]; then
|
||||
check_lib_cxx SNES $LIBSNES snes_init -ldl
|
||||
check_lib_cxx SNES $LIBSNES snes_init $DYLIB
|
||||
check_critical SNES "Cannot find libsnes."
|
||||
add_define_make libsnes $LIBSNES
|
||||
fi
|
||||
|
||||
check_lib ALSA -lasound snd_pcm_open
|
||||
check_lib DYLIB $DYLIB dlopen
|
||||
check_lib NETPLAY -lc socket
|
||||
|
||||
check_pkgconf ALSA alsa
|
||||
check_header OSS sys/soundcard.h
|
||||
check_lib AL -lopenal alcOpenDevice
|
||||
check_lib RSOUND -lrsound rsd_init
|
||||
check_lib ROAR -lroar roar_vs_new
|
||||
check_lib JACK -ljack jack_client_open
|
||||
check_header OSS_BSD soundcard.h
|
||||
check_lib OSS_LIB -lossaudio
|
||||
|
||||
if [ "$OS" = "Darwin" ]; then
|
||||
check_lib AL "-framework OpenAL" alcOpenDevice
|
||||
else
|
||||
check_lib AL -lopenal alcOpenDevice
|
||||
fi
|
||||
|
||||
if [ "$OS" = "Darwin" ]; then
|
||||
check_lib FBO "-framework OpenGL" glFramebufferTexture2D
|
||||
else
|
||||
check_lib FBO -lGL glFramebufferTexture2D
|
||||
fi
|
||||
|
||||
check_pkgconf RSOUND rsound 1.1
|
||||
check_pkgconf ROAR libroar
|
||||
check_pkgconf JACK jack 0.120.1
|
||||
check_pkgconf PULSE libpulse
|
||||
|
||||
check_lib COREAUDIO "-framework AudioUnit" AudioUnitInitialize
|
||||
|
||||
check_pkgconf SDL sdl 1.2.10
|
||||
check_critical SDL "Cannot find SDL library."
|
||||
check_critical SDL "Cannot find SDL 1.2 library."
|
||||
check_pkgconf SDL_NEW sdl 1.3
|
||||
if [ $HAVE_SDL_NEW = yes ]; then
|
||||
echo "SSNES is not compatible with SDL 1.3 API, please use a recent 1.2 release."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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
|
||||
check_pkgconf AVFORMAT libavformat
|
||||
check_pkgconf AVCORE libavcore
|
||||
check_pkgconf AVUTIL libavutil
|
||||
check_pkgconf SWSCALE libswscale
|
||||
|
||||
( [ $HAVE_FFMPEG = auto ] && ( [ $HAVE_AVCODEC = no ] || [ $HAVE_AVFORMAT = no ] || [ $HAVE_AVCORE = no ] || [ $HAVE_AVUTIL = no ] || [ $HAVE_SWSCALE = no ] ) && HAVE_FFMPEG=no ) || HAVE_FFMPEG=yes
|
||||
( [ $HAVE_FFMPEG = auto ] && ( [ $HAVE_AVCODEC = no ] || [ $HAVE_AVFORMAT = no ] || [ $HAVE_AVUTIL = no ] || [ $HAVE_SWSCALE = no ] ) && HAVE_FFMPEG=no ) || HAVE_FFMPEG=yes
|
||||
fi
|
||||
|
||||
check_pkgconf SRC samplerate
|
||||
check_critical SRC "Cannot find libsamplerate."
|
||||
|
||||
check_lib DYNAMIC -ldl dlopen
|
||||
check_lib DYNAMIC $DYLIB dlopen
|
||||
|
||||
check_pkgconf FREETYPE freetype2
|
||||
check_pkgconf XVIDEO xv
|
||||
|
||||
check_lib STRL -lc strlcpy
|
||||
|
||||
check_pkgconf PYTHON python3
|
||||
|
||||
add_define_make OS $OS
|
||||
|
||||
# Creates config.mk and config.h.
|
||||
VARS="ALSA OSS AL RSOUND ROAR JACK PULSE SDL FILTER CG XML DYNAMIC FFMPEG AVCODEC AVFORMAT AVCORE AVUTIL SWSCALE SRC CONFIGFILE FREETYPE"
|
||||
VARS="ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK COREAUDIO PULSE SDL DYLIB CG XML SDL_IMAGE DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL SWSCALE SRC CONFIGFILE FREETYPE XVIDEO NETPLAY FBO STRL PYTHON"
|
||||
create_config_make config.mk $VARS
|
||||
create_config_header config.h $VARS
|
||||
|
||||
+9
-2
@@ -1,7 +1,7 @@
|
||||
. qb/qb.params.sh
|
||||
|
||||
PACKAGE_NAME=ssnes
|
||||
PACKAGE_VERSION=0.2.1
|
||||
PACKAGE_VERSION=0.7
|
||||
|
||||
# Adds a command line opt to ./configure --help
|
||||
# $1: Variable (HAVE_ALSA, HAVE_OSS, etc)
|
||||
@@ -10,15 +10,22 @@ PACKAGE_VERSION=0.2.1
|
||||
add_command_line_enable DYNAMIC "Enable dynamic loading of libsnes library." no
|
||||
add_command_line_string LIBSNES "libsnes library used" "-lsnes"
|
||||
add_command_line_enable FFMPEG "Enable FFmpeg recording support" no
|
||||
add_command_line_enable FILTER "Enable CPU filter support" no
|
||||
add_command_line_enable 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
|
||||
add_command_line_enable CG "Enable Cg shader support" auto
|
||||
add_command_line_enable XML "Enable bSNES-style XML shader support" auto
|
||||
add_command_line_enable FBO "Enable render-to-texture (FBO) support" auto
|
||||
add_command_line_enable ALSA "Enable ALSA support" auto
|
||||
add_command_line_enable OSS "Enable OSS support" auto
|
||||
add_command_line_enable RSOUND "Enable RSound support" auto
|
||||
add_command_line_enable ROAR "Enable RoarAudio support" auto
|
||||
add_command_line_enable AL "Enable OpenAL support" auto
|
||||
add_command_line_enable JACK "Enable JACK support" auto
|
||||
add_command_line_enable COREAUDIO "Enable CoreAudio 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
|
||||
+15
-14
@@ -1,24 +1,25 @@
|
||||
. qb/conf.comp.sh
|
||||
. qb/config.comp.sh
|
||||
|
||||
TEMP_C=.tmp.c
|
||||
TEMP_CXX=.tmp.cxx
|
||||
TEMP_EXE=.tmp
|
||||
|
||||
echo -n "Checking operating system ... "
|
||||
ECHOBUF="Checking operating system ... "
|
||||
OS="Win32" # whatever ;D
|
||||
unamestr="`uname -o`"
|
||||
if [ ! -z "`echo $unamestr | grep -i Linux`" ]; then
|
||||
unamestr="`uname -a`"
|
||||
if [ ! -z "`echo "$unamestr" | grep -i Linux`" ]; then
|
||||
OS="Linux"
|
||||
elif [ ! -z "`echo $unamestr | grep -i Darwin`" ]; then
|
||||
elif [ ! -z "`echo "$unamestr" | grep -i Darwin`" ]; then
|
||||
OS="Darwin"
|
||||
elif [ ! -z "`echo $unamestr | grep -i BSD`" ]; then
|
||||
elif [ ! -z "`echo "$unamestr" | grep -i BSD`" ]; then
|
||||
OS="BSD"
|
||||
elif [ ! -z "`echo $unamestr | grep -i NT`" ]; then
|
||||
elif [ ! -z "`echo "$unamestr" | grep -i MINGW32`" ]; then
|
||||
OS="MinGW"
|
||||
elif [ ! -z "`echo "$unamestr" | grep -i NT`" ]; then
|
||||
OS="Cygwin"
|
||||
fi
|
||||
|
||||
echo $OS
|
||||
|
||||
echo $ECHOBUF $OS
|
||||
|
||||
# Checking for working C compiler
|
||||
if [ "$USE_LANG_C" = yes ]; then
|
||||
@@ -31,12 +32,12 @@ if [ "$USE_LANG_C" = yes ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -n "Checking if $CC is a suitable compiler ... "
|
||||
ECHOBUF="Checking if $CC is a suitable compiler ..."
|
||||
answer=no
|
||||
echo "#include <stdio.h>" > $TEMP_C
|
||||
echo "int main(void) { puts(\"Hai world!\"); return 0; }" >> $TEMP_C
|
||||
$CC -o $TEMP_EXE $TEMP_C 2>/dev/null >/dev/null && answer=yes
|
||||
echo $answer
|
||||
echo $ECHOBUF $answer
|
||||
|
||||
rm -rf $TEMP_C $TEMP_EXE
|
||||
|
||||
@@ -50,16 +51,16 @@ if [ "$USE_LANG_CXX" = "yes" ]; then
|
||||
CXX=`which g++ c++ 2> /dev/null | grep ^/ | head -n 1`
|
||||
fi
|
||||
if [ -z $CXX ]; then
|
||||
echo "Could not find C compiler in path. Exiting ..."
|
||||
echo "Could not find C++ compiler in path. Exiting ..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -n "Checking if $CXX is a suitable compiler ... "
|
||||
ECHOBUF="Checking if $CXX is a suitable compiler ..."
|
||||
answer=no
|
||||
echo "#include <iostream>" > $TEMP_CXX
|
||||
echo "int main() { std::cout << \"Hai guise\" << std::endl; return 0; }" >> $TEMP_CXX
|
||||
$CXX -o $TEMP_EXE $TEMP_CXX 2>/dev/null >/dev/null && answer=yes
|
||||
echo $answer
|
||||
echo $ECHOBUF $answer
|
||||
|
||||
rm -rf $TEMP_CXX $TEMP_EXE
|
||||
|
||||
|
||||
+63
-25
@@ -41,20 +41,24 @@ check_lib()
|
||||
eval tmpval=\$$tmpval
|
||||
[ "$tmpval" = "no" ] && return 0
|
||||
|
||||
echo -n "Checking function $3 in $2 ... "
|
||||
echo "void $3(void); int main(void) { $3(); return 0; }" > $TEMP_C
|
||||
|
||||
if [ -z "$3" ]; then
|
||||
ECHOBUF="Checking existence of $2 ..."
|
||||
echo "int main(void) { return 0; }" > $TEMP_C
|
||||
else
|
||||
ECHOBUF="Checking function $3 in $2 ..."
|
||||
echo "void $3(void); int main(void) { $3(); return 0; }" > $TEMP_C
|
||||
fi
|
||||
|
||||
eval HAVE_$1=no
|
||||
answer=no
|
||||
|
||||
extralibs="$4"
|
||||
|
||||
$CC -o $TEMP_EXE $TEMP_C $INCLUDE_DIRS $LIBRARY_DIRS $extralibs $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
$CC -o $TEMP_EXE $TEMP_C $INCLUDE_DIRS $LIBRARY_DIRS $extralibs $CFLAGS $LDFLAGS $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
|
||||
echo $answer
|
||||
echo $ECHOBUF $answer
|
||||
|
||||
rm -rf $TEMP_C $TEMP_EXE
|
||||
rm -f $TEMP_C $TEMP_EXE
|
||||
if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then
|
||||
echo "Forced to build with library $2, but cannot locate. Exiting ..."
|
||||
exit 1
|
||||
@@ -67,35 +71,70 @@ check_lib_cxx()
|
||||
eval tmpval=\$$tmpval
|
||||
[ "$tmpval" = "no" ] && return 0
|
||||
|
||||
echo -n "Checking function $3 in $2 ... "
|
||||
echo "extern \"C\" { void $3(void); } int main() { $3(); }" > $TEMP_CXX
|
||||
if [ -z "$3" ]; then
|
||||
ECHOBUF="Checking existence of $2 ..."
|
||||
echo "int main() { return 0; }" > $TEMP_CXX
|
||||
else
|
||||
ECHOBUF="Checking function $3 in $2 ..."
|
||||
echo "extern \"C\" { void $3(void); } int main() { $3(); }" > $TEMP_CXX
|
||||
fi
|
||||
|
||||
eval HAVE_$1=no
|
||||
answer=no
|
||||
|
||||
extralibs="$4"
|
||||
|
||||
$CXX -o $TEMP_EXE $TEMP_CXX $INCLUDE_DIRS $LIBRARY_DIRS $extralibs $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
$CXX -o $TEMP_EXE $TEMP_CXX $INCLUDE_DIRS $LIBRARY_DIRS $extralibs $CFLAGS $LDFLAGS $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
|
||||
echo $answer
|
||||
echo $ECHOBUF $answer
|
||||
|
||||
rm -rf $TEMP_CXX $TEMP_EXE
|
||||
rm -f $TEMP_CXX $TEMP_EXE
|
||||
if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then
|
||||
echo "Forced to build with library $2, but cannot locate. Exiting ..."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_code_c()
|
||||
{
|
||||
tmpval="HAVE_$1"
|
||||
eval tmpval=\$$tmpval
|
||||
[ "$tmpval" = "no" ] && return 0
|
||||
|
||||
ECHOBUF="Checking C code snippet \"$3\" ..."
|
||||
eval HAVE_$1=no
|
||||
answer=no
|
||||
$CC -o $TEMP_EXE $TEMP_C $INCLUDE_DIRS $LIBRARY_DIRS $2 $CFLAGS $LDFLAGS 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
|
||||
echo $ECHOBUF $answer
|
||||
rm -f $TEMP_C $TEMP_EXE
|
||||
}
|
||||
|
||||
check_code_cxx()
|
||||
{
|
||||
tmpval="HAVE_$1"
|
||||
eval tmpval=\$$tmpval
|
||||
[ "$tmpval" = "no" ] && return 0
|
||||
|
||||
ECHOBUF="Checking C++ code snippet \"$3\" ..."
|
||||
eval HAVE_$1=no
|
||||
answer=no
|
||||
$CXX -o $TEMP_EXE $TEMP_CXX $INCLUDE_DIRS $LIBRARY_DIRS $2 $CXXFLAGS $LDFLAGS 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
|
||||
echo $ECHOBUF $answer
|
||||
rm -f $TEMP_CXX $TEMP_EXE
|
||||
}
|
||||
|
||||
locate_pkg_conf()
|
||||
{
|
||||
echo -n "Checking for pkg-config ... "
|
||||
ECHOBUF="Checking for pkg-config ... "
|
||||
PKG_CONF_PATH="`which pkg-config | grep ^/ | head -n1`"
|
||||
if [ -z $PKG_CONF_PATH ]; then
|
||||
echo "not found"
|
||||
echo "Cannot locate pkg-config. Exiting ..."
|
||||
exit 1
|
||||
fi
|
||||
echo "$PKG_CONF_PATH"
|
||||
echo $ECHOBUF $PKG_CONF_PATH
|
||||
}
|
||||
|
||||
check_pkgconf()
|
||||
@@ -106,16 +145,16 @@ check_pkgconf()
|
||||
eval tmpval=\$$tmpval
|
||||
[ "$tmpval" = "no" ] && return 0
|
||||
|
||||
echo -n "Checking presence of package $2"
|
||||
ECHOBUF="Checking presence of package $2"
|
||||
eval HAVE_$1=no
|
||||
eval $1_CFLAGS=""
|
||||
eval $1_LIBS=""
|
||||
answer=no
|
||||
minver=0.0
|
||||
[ ! -z $3 ] && minver=$3 && echo -n " with minimum version $minver"
|
||||
echo -n " ... "
|
||||
[ ! -z $3 ] && minver=$3 && ECHOBUF="$ECHOBUF with minimum version $minver"
|
||||
ECHOBUF="$ECHOBUF ... "
|
||||
pkg-config --atleast-version=$minver "$2" && eval HAVE_$1=yes && eval $1_CFLAGS='"`pkg-config $2 --cflags`"' && eval $1_LIBS='"`pkg-config $2 --libs`"' && answer=yes
|
||||
echo $answer
|
||||
echo $ECHOBUF $answer
|
||||
|
||||
PKG_CONF_USED="$PKG_CONF_USED $1"
|
||||
|
||||
@@ -131,7 +170,7 @@ check_header()
|
||||
eval tmpval=\$$tmpval
|
||||
[ "$tmpval" = "no" ] && return 0
|
||||
|
||||
echo -n "Checking presence of header file $2 ... "
|
||||
ECHOBUF="Checking presence of header file $2 ..."
|
||||
echo "#include<$2>" > $TEMP_C
|
||||
echo "int main(void) { return 0; }" >> $TEMP_C
|
||||
eval HAVE_$1=no
|
||||
@@ -139,7 +178,7 @@ check_header()
|
||||
|
||||
$CC -o $TEMP_EXE $TEMP_C $INCLUDE_DIRS 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
|
||||
echo $answer
|
||||
echo $ECHOBUF $answer
|
||||
|
||||
rm -rf $TEMP_C $TEMP_EXE
|
||||
if [ "$tmpval" = "yes" ] && [ "$answer" = "no" ]; then
|
||||
@@ -150,7 +189,7 @@ check_header()
|
||||
|
||||
check_switch_c()
|
||||
{
|
||||
echo -n "Checking for availability of switch $2 in $CC ... "
|
||||
ECHOBUF="Checking for availability of switch $2 in $CC ..."
|
||||
if [ -z "$CC" ]; then
|
||||
echo "No C compiler, cannot check ..."
|
||||
exit 1
|
||||
@@ -160,14 +199,14 @@ check_switch_c()
|
||||
answer=no
|
||||
$CC -o $TEMP_EXE $TEMP_C $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
|
||||
echo $answer
|
||||
echo $ECHOBUF $answer
|
||||
|
||||
rm -rf $TEMP_C $TEMP_EXE
|
||||
}
|
||||
|
||||
check_switch_cxx()
|
||||
{
|
||||
echo -n "Checking for availability of switch $2 in $CXX ... "
|
||||
ECHOBUF="Checking for availability of switch $2 in $CXX ... "
|
||||
if [ -z "$CXX" ]; then
|
||||
echo "No C++ compiler, cannot check ..."
|
||||
exit 1
|
||||
@@ -177,7 +216,7 @@ check_switch_cxx()
|
||||
answer=no
|
||||
$CXX -o $TEMP_EXE $TEMP_CXX $2 2>/dev/null >/dev/null && answer=yes && eval HAVE_$1=yes
|
||||
|
||||
echo $answer
|
||||
echo $ECHOBUF $answer
|
||||
|
||||
rm -rf $TEMP_CXX $TEMP_EXE
|
||||
}
|
||||
@@ -207,7 +246,7 @@ create_config_header()
|
||||
|
||||
echo "Creating config header: $outfile"
|
||||
|
||||
name="`echo __$outfile | sed 's|[\./]|_|g' | tr '[a-z]' '[A-Z]'`"
|
||||
name="`echo QB_${outfile}__ | sed 's|[\./]|_|g' | tr '[a-z]' '[A-Z]'`"
|
||||
echo "#ifndef $name" > "$outfile"
|
||||
echo "#define $name" >> "$outfile"
|
||||
echo "" >> "$outfile"
|
||||
@@ -251,7 +290,6 @@ output_define_make()
|
||||
|
||||
create_config_make()
|
||||
{
|
||||
|
||||
outfile="$1"
|
||||
shift
|
||||
|
||||
|
||||
+13
-10
@@ -64,8 +64,7 @@ print_sub_opt()
|
||||
lowertext="`echo $arg1 | tr '[A-Z]' '[a-z]'`"
|
||||
|
||||
if [ "$arg3" = "auto" ]; then
|
||||
echo -n "--enable-$lowertext: "
|
||||
echo $arg2
|
||||
echo "--enable-$lowertext: $arg2"
|
||||
echo "--disable-$lowertext"
|
||||
elif [ "$arg3" = "yes" ]; then
|
||||
echo "--disable-$lowertext: $arg2"
|
||||
@@ -95,7 +94,7 @@ parse_input()
|
||||
case "$1" in
|
||||
|
||||
--prefix=*)
|
||||
prefix="`echo $1 | sed -e 's|^--prefix=\(\S\S*\)$|\1|' -e 's|\(\S\S*\)/$|\1|'`"
|
||||
prefix="`echo $1 | sed -e 's|^--prefix=||' -e 's|^\(.*\)/$|\1|'`"
|
||||
|
||||
if [ "$prefix" != "$1" ]; then
|
||||
PREFIX="$prefix"
|
||||
@@ -103,8 +102,9 @@ parse_input()
|
||||
;;
|
||||
|
||||
--enable-*)
|
||||
enable=`echo $1 | sed 's|^--enable-||'`
|
||||
if [ -z "`echo $COMMAND_LINE_OPTS_ENABLE | grep -i $enable`" ]; then
|
||||
tmp="$1"
|
||||
enable="${tmp#--enable-}"
|
||||
if [ -z "`echo $COMMAND_LINE_OPTS_ENABLE | grep -i -- $enable`" ]; then
|
||||
print_help
|
||||
exit 1
|
||||
fi
|
||||
@@ -112,8 +112,9 @@ parse_input()
|
||||
;;
|
||||
|
||||
--disable-*)
|
||||
disable=`echo $1 | sed 's|^--disable-||'`
|
||||
if [ -z "`echo $COMMAND_LINE_OPTS_ENABLE | grep -i $disable`" ]; then
|
||||
tmp="$1"
|
||||
disable="${tmp#--disable-}"
|
||||
if [ -z "`echo $COMMAND_LINE_OPTS_ENABLE | grep -i -- $disable`" ]; then
|
||||
print_help
|
||||
exit 1
|
||||
fi
|
||||
@@ -121,9 +122,11 @@ parse_input()
|
||||
;;
|
||||
|
||||
--with-*)
|
||||
arg="`echo $1 | sed 's|^--with-\S\S*=||'`"
|
||||
with=`echo $1 | sed 's|^--with-\(\S\S*\)=.*$|\1|'`
|
||||
if [ -z "`echo $COMMAND_LINE_OPTS_STRINGS | grep -i $with`" ]; then
|
||||
tmp="$1"
|
||||
arg="${tmp#--with-*=}"
|
||||
tmp="${tmp#--with-}"
|
||||
with="${tmp%%=*}"
|
||||
if [ -z "`echo $COMMAND_LINE_OPTS_STRINGS | grep -i -- $with`" ]; then
|
||||
print_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
+360
-157
@@ -2,18 +2,21 @@
|
||||
#include <libavutil/mathematics.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/avstring.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libavutil/avconfig.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "ffemu.h"
|
||||
#include "fifo_buffer.h"
|
||||
#include "SDL_thread.h"
|
||||
|
||||
struct video_info
|
||||
{
|
||||
bool enabled;
|
||||
AVCodecContext *codec;
|
||||
|
||||
AVFrame *conv_frame;
|
||||
@@ -23,13 +26,16 @@ struct video_info
|
||||
uint8_t *outbuf;
|
||||
size_t outbuf_size;
|
||||
|
||||
int fmt;
|
||||
int pix_size;
|
||||
|
||||
AVFormatContext *format;
|
||||
|
||||
struct SwsContext *sws_ctx;
|
||||
} video;
|
||||
|
||||
struct audio_info
|
||||
{
|
||||
bool enabled;
|
||||
AVCodecContext *codec;
|
||||
|
||||
int16_t *buffer;
|
||||
@@ -55,158 +61,185 @@ struct ffemu
|
||||
struct muxer_info muxer;
|
||||
|
||||
struct ffemu_params params;
|
||||
|
||||
SDL_cond *cond;
|
||||
SDL_mutex *cond_lock;
|
||||
SDL_mutex *lock;
|
||||
fifo_buffer_t *audio_fifo;
|
||||
fifo_buffer_t *video_fifo;
|
||||
fifo_buffer_t *attr_fifo;
|
||||
SDL_Thread *thread;
|
||||
volatile bool alive;
|
||||
volatile bool can_sleep;
|
||||
};
|
||||
|
||||
// Currently hardcoded atm. :)
|
||||
static int map_audio_codec(ffemu_audio_codec codec)
|
||||
static bool init_audio(struct audio_info *audio, struct ffemu_params *param)
|
||||
{
|
||||
(void)codec;
|
||||
return CODEC_ID_VORBIS;
|
||||
}
|
||||
|
||||
// Currently hardcoded atm. :)
|
||||
static int map_video_codec(ffemu_video_codec codec)
|
||||
{
|
||||
(void)codec;
|
||||
return CODEC_ID_H264;
|
||||
}
|
||||
|
||||
static int init_audio(struct audio_info *audio, struct ffemu_params *param)
|
||||
{
|
||||
AVCodec *codec = avcodec_find_encoder(map_audio_codec(param->acodec));
|
||||
AVCodec *codec = avcodec_find_encoder(CODEC_ID_FLAC);
|
||||
if (!codec)
|
||||
return -1;
|
||||
return false;
|
||||
|
||||
audio->codec = avcodec_alloc_context();
|
||||
|
||||
avcodec_get_context_defaults(audio->codec);
|
||||
|
||||
// Hardcode this atm.
|
||||
audio->codec->global_quality = 100000;
|
||||
audio->codec->flags |= CODEC_FLAG_QSCALE;
|
||||
audio->codec->sample_rate = param->samplerate;
|
||||
audio->codec->time_base = (AVRational) { 1, param->samplerate };
|
||||
audio->codec->channels = param->channels;
|
||||
audio->codec->sample_fmt = AV_SAMPLE_FMT_S16;
|
||||
if (avcodec_open(audio->codec, codec) != 0)
|
||||
return -1;
|
||||
|
||||
return false;
|
||||
|
||||
audio->buffer = av_malloc(audio->codec->frame_size * param->channels * sizeof(int16_t));
|
||||
if (!audio->buffer)
|
||||
return -1;
|
||||
return false;
|
||||
|
||||
audio->outbuf_size = 50000;
|
||||
audio->outbuf_size = 2000000;
|
||||
audio->outbuf = av_malloc(audio->outbuf_size);
|
||||
if (!audio->outbuf)
|
||||
return -1;
|
||||
return false;
|
||||
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hardcode.
|
||||
static void init_x264_param(AVCodecContext *c)
|
||||
static bool init_video(struct video_info *video, struct ffemu_params *param)
|
||||
{
|
||||
c->coder_type = 1; // coder = 1
|
||||
c->flags|=CODEC_FLAG_LOOP_FILTER; // flags=+loop
|
||||
c->me_cmp|= 1; // cmp=+chroma, where CHROMA = 1
|
||||
c->partitions|=X264_PART_I8X8+X264_PART_I4X4+X264_PART_P8X8+X264_PART_B8X8; // partitions=+parti8x8+parti4x4+partp8x8+partb8x8
|
||||
c->me_method=ME_HEX; // me_method=hex
|
||||
c->me_subpel_quality = 7; // subq=7
|
||||
c->me_range = 16; // me_range=16
|
||||
c->gop_size = 250; // g=250
|
||||
c->keyint_min = 25; // keyint_min=25
|
||||
c->scenechange_threshold = 40; // sc_threshold=40
|
||||
c->i_quant_factor = 0.71; // i_qfactor=0.71
|
||||
c->b_frame_strategy = 1; // b_strategy=1
|
||||
c->qcompress = 0.6; // qcomp=0.6
|
||||
c->qmin = 10; // qmin=10
|
||||
c->qmax = 51; // qmax=51
|
||||
c->max_qdiff = 4; // qdiff=4
|
||||
c->max_b_frames = 3; // bf=3
|
||||
c->refs = 3; // refs=3
|
||||
c->directpred = 1; // directpred=1
|
||||
c->trellis = 1; // trellis=1
|
||||
c->flags2|=CODEC_FLAG2_BPYRAMID+CODEC_FLAG2_MIXED_REFS+CODEC_FLAG2_WPRED+CODEC_FLAG2_8X8DCT+CODEC_FLAG2_FASTPSKIP; // flags2=+bpyramid+mixed_refs+wpred+dct8x8+fastpskip
|
||||
c->weighted_p_pred = 2; // wpredp=2
|
||||
|
||||
// libx264-main.ffpreset preset
|
||||
c->flags2|=CODEC_FLAG2_8X8DCT;
|
||||
c->flags2^=CODEC_FLAG2_8X8DCT;
|
||||
}
|
||||
|
||||
static int init_video(struct video_info *video, struct ffemu_params *param)
|
||||
{
|
||||
AVCodec *codec = avcodec_find_encoder(map_video_codec(param->vcodec));
|
||||
AVCodec *codec = avcodec_find_encoder(CODEC_ID_FFV1);
|
||||
if (!codec)
|
||||
return -1;
|
||||
return false;
|
||||
|
||||
#if AV_HAVE_BIGENDIAN
|
||||
video->fmt = PIX_FMT_RGB555BE;
|
||||
#else
|
||||
video->fmt = PIX_FMT_RGB555LE;
|
||||
#endif
|
||||
video->pix_size = sizeof(uint16_t);
|
||||
if (param->rgb32)
|
||||
{
|
||||
video->fmt = PIX_FMT_RGB32;
|
||||
video->pix_size = sizeof(uint32_t);
|
||||
}
|
||||
|
||||
video->codec = avcodec_alloc_context();
|
||||
video->codec->width = param->out_width;
|
||||
video->codec->height = param->out_height;
|
||||
video->codec->time_base = (AVRational) {param->fps.den, param->fps.num};
|
||||
video->codec->crf = 25;
|
||||
// Might have to change this later to account for RGB codecs.
|
||||
video->codec->pix_fmt = PIX_FMT_YUV420P;
|
||||
///// Is this element in all recent ffmpeg versions?
|
||||
video->codec->thread_count = 4;
|
||||
/////
|
||||
video->codec->pix_fmt = PIX_FMT_RGB32;
|
||||
video->codec->sample_aspect_ratio = av_d2q(param->aspect_ratio * param->out_height / param->out_width, 255);
|
||||
init_x264_param(video->codec);
|
||||
|
||||
if (avcodec_open(video->codec, codec) != 0)
|
||||
return -1;
|
||||
return false;
|
||||
|
||||
// Allocate a big buffer :p ffmpeg API doesn't seem to give us some clues how big this buffer should be.
|
||||
video->outbuf_size = 1000000;
|
||||
video->outbuf_size = 1 << 23;
|
||||
video->outbuf = av_malloc(video->outbuf_size);
|
||||
|
||||
int size = avpicture_get_size(PIX_FMT_YUV420P, param->out_width, param->out_height);
|
||||
// Just to make sure we can handle the biggest frames. Seemed to crash with just 256 * 224.
|
||||
int size = avpicture_get_size(PIX_FMT_RGB32, param->fb_width, param->fb_height);
|
||||
video->conv_frame_buf = av_malloc(size);
|
||||
video->conv_frame = avcodec_alloc_frame();
|
||||
avpicture_fill((AVPicture*)video->conv_frame, video->conv_frame_buf, PIX_FMT_YUV420P, param->out_width, param->out_height);
|
||||
avpicture_fill((AVPicture*)video->conv_frame, video->conv_frame_buf, PIX_FMT_RGB32, param->fb_width, param->fb_height);
|
||||
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int init_muxer(ffemu_t *handle)
|
||||
static bool init_muxer(ffemu_t *handle)
|
||||
{
|
||||
AVFormatContext *ctx = avformat_alloc_context();
|
||||
av_strlcpy(ctx->filename, handle->params.filename, sizeof(ctx->filename));
|
||||
ctx->oformat = av_guess_format(NULL, ctx->filename, NULL);
|
||||
#ifdef AVIO_FLAG_WRITE
|
||||
if (avio_open(&ctx->pb, ctx->filename, AVIO_FLAG_WRITE) < 0)
|
||||
#else
|
||||
if (url_fopen(&ctx->pb, ctx->filename, URL_WRONLY) < 0)
|
||||
#endif
|
||||
{
|
||||
av_free(ctx);
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
int stream_cnt = 0;
|
||||
if (handle->video.enabled)
|
||||
{
|
||||
AVStream *stream = av_new_stream(ctx, stream_cnt++);
|
||||
stream->codec = handle->video.codec;
|
||||
AVStream *stream = av_new_stream(ctx, stream_cnt++);
|
||||
stream->codec = handle->video.codec;
|
||||
|
||||
if (ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
handle->video.codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
handle->muxer.vstream = stream;
|
||||
handle->muxer.vstream->sample_aspect_ratio = handle->video.codec->sample_aspect_ratio;
|
||||
}
|
||||
if (ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
handle->video.codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
handle->muxer.vstream = stream;
|
||||
handle->muxer.vstream->sample_aspect_ratio = handle->video.codec->sample_aspect_ratio;
|
||||
|
||||
if (handle->audio.enabled)
|
||||
{
|
||||
AVStream *stream = av_new_stream(ctx, stream_cnt++);
|
||||
stream->codec = handle->audio.codec;
|
||||
stream = av_new_stream(ctx, stream_cnt++);
|
||||
stream->codec = handle->audio.codec;
|
||||
|
||||
if (ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
handle->audio.codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
handle->muxer.astream = stream;
|
||||
}
|
||||
if (ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
handle->audio.codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
handle->muxer.astream = stream;
|
||||
|
||||
if (av_write_header(ctx) < 0)
|
||||
return -1;
|
||||
return false;
|
||||
|
||||
handle->muxer.ctx = ctx;
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
#define MAX_FRAMES 64
|
||||
|
||||
static int SDLCALL ffemu_thread(void *data);
|
||||
|
||||
static bool init_thread(ffemu_t *handle)
|
||||
{
|
||||
assert(handle->lock = SDL_CreateMutex());
|
||||
assert(handle->cond_lock = SDL_CreateMutex());
|
||||
assert(handle->cond = SDL_CreateCond());
|
||||
assert(handle->audio_fifo = fifo_new(32000 * sizeof(int16_t) * handle->params.channels * MAX_FRAMES / 60));
|
||||
assert(handle->attr_fifo = fifo_new(sizeof(struct ffemu_video_data) * MAX_FRAMES));
|
||||
assert(handle->video_fifo = fifo_new(handle->params.fb_width * handle->params.fb_height *
|
||||
handle->video.pix_size * MAX_FRAMES));
|
||||
|
||||
handle->alive = true;
|
||||
handle->can_sleep = true;
|
||||
assert(handle->thread = SDL_CreateThread(ffemu_thread, handle));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void deinit_thread(ffemu_t *handle)
|
||||
{
|
||||
if (handle->thread)
|
||||
{
|
||||
SDL_mutexP(handle->cond_lock);
|
||||
handle->alive = false;
|
||||
handle->can_sleep = false;
|
||||
SDL_mutexV(handle->cond_lock);
|
||||
|
||||
SDL_CondSignal(handle->cond);
|
||||
SDL_WaitThread(handle->thread, NULL);
|
||||
|
||||
SDL_DestroyMutex(handle->lock);
|
||||
SDL_DestroyMutex(handle->cond_lock);
|
||||
SDL_DestroyCond(handle->cond);
|
||||
|
||||
handle->thread = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void deinit_thread_buf(ffemu_t *handle)
|
||||
{
|
||||
if (handle->audio_fifo)
|
||||
{
|
||||
fifo_free(handle->audio_fifo);
|
||||
handle->audio_fifo = NULL;
|
||||
}
|
||||
|
||||
if (handle->attr_fifo)
|
||||
{
|
||||
fifo_free(handle->attr_fifo);
|
||||
handle->attr_fifo = NULL;
|
||||
}
|
||||
|
||||
if (handle->video_fifo)
|
||||
{
|
||||
fifo_free(handle->video_fifo);
|
||||
handle->video_fifo = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ffemu_t *ffemu_new(const struct ffemu_params *params)
|
||||
@@ -219,20 +252,17 @@ ffemu_t *ffemu_new(const struct ffemu_params *params)
|
||||
goto error;
|
||||
|
||||
handle->params = *params;
|
||||
if (handle->params.vcodec != FFEMU_VIDEO_NONE)
|
||||
handle->video.enabled = true;
|
||||
if (handle->params.acodec != FFEMU_AUDIO_NONE)
|
||||
handle->audio.enabled = true;
|
||||
|
||||
if (handle->video.enabled)
|
||||
if (init_video(&handle->video, &handle->params) < 0)
|
||||
goto error;
|
||||
if (!init_video(&handle->video, &handle->params))
|
||||
goto error;
|
||||
|
||||
if (handle->audio.enabled)
|
||||
if (init_audio(&handle->audio, &handle->params) < 0)
|
||||
goto error;
|
||||
if (!init_audio(&handle->audio, &handle->params))
|
||||
goto error;
|
||||
|
||||
if (init_muxer(handle) < 0)
|
||||
if (!init_muxer(handle))
|
||||
goto error;
|
||||
|
||||
if (!init_thread(handle))
|
||||
goto error;
|
||||
|
||||
return handle;
|
||||
@@ -246,6 +276,9 @@ void ffemu_free(ffemu_t *handle)
|
||||
{
|
||||
if (handle)
|
||||
{
|
||||
deinit_thread(handle);
|
||||
deinit_thread_buf(handle);
|
||||
|
||||
if (handle->audio.codec)
|
||||
{
|
||||
avcodec_close(handle->audio.codec);
|
||||
@@ -267,43 +300,112 @@ void ffemu_free(ffemu_t *handle)
|
||||
if (handle->video.conv_frame_buf)
|
||||
av_free(handle->video.conv_frame_buf);
|
||||
|
||||
if (handle->video.sws_ctx)
|
||||
sws_freeContext(handle->video.sws_ctx);
|
||||
|
||||
free(handle);
|
||||
}
|
||||
}
|
||||
|
||||
// Need to make this thread based, but hey.
|
||||
int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data)
|
||||
{
|
||||
if (!handle->video.enabled)
|
||||
return -1;
|
||||
for (;;)
|
||||
{
|
||||
SDL_mutexP(handle->lock);
|
||||
unsigned avail = fifo_write_avail(handle->attr_fifo);
|
||||
SDL_mutexV(handle->lock);
|
||||
|
||||
// This is deprecated, can't find a replacement... :(
|
||||
// Hardcode pixel format for now (SNES)
|
||||
struct SwsContext *conv_ctx = sws_getContext(data->width, data->height, PIX_FMT_RGB555LE,
|
||||
handle->params.out_width, handle->params.out_height, PIX_FMT_YUV420P, handle->params.rescaler == FFEMU_RESCALER_LANCZOS ? SWS_LANCZOS : SWS_POINT,
|
||||
if (!handle->alive)
|
||||
return -1;
|
||||
|
||||
if (avail >= sizeof(*data))
|
||||
break;
|
||||
|
||||
SDL_mutexP(handle->cond_lock);
|
||||
if (handle->can_sleep)
|
||||
{
|
||||
handle->can_sleep = false;
|
||||
SDL_CondWait(handle->cond, handle->cond_lock);
|
||||
handle->can_sleep = true;
|
||||
}
|
||||
else
|
||||
SDL_CondSignal(handle->cond);
|
||||
|
||||
SDL_mutexV(handle->cond_lock);
|
||||
}
|
||||
|
||||
SDL_mutexP(handle->lock);
|
||||
fifo_write(handle->attr_fifo, data, sizeof(*data));
|
||||
fifo_write(handle->video_fifo, data->data, data->pitch * data->height);
|
||||
SDL_mutexV(handle->lock);
|
||||
SDL_CondSignal(handle->cond);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
SDL_mutexP(handle->lock);
|
||||
unsigned avail = fifo_write_avail(handle->audio_fifo);
|
||||
SDL_mutexV(handle->lock);
|
||||
|
||||
if (!handle->alive)
|
||||
return -1;
|
||||
|
||||
if (avail >= data->frames * handle->params.channels * sizeof(int16_t))
|
||||
break;
|
||||
|
||||
SDL_mutexP(handle->cond_lock);
|
||||
if (handle->can_sleep)
|
||||
{
|
||||
handle->can_sleep = false;
|
||||
SDL_CondWait(handle->cond, handle->cond_lock);
|
||||
handle->can_sleep = true;
|
||||
}
|
||||
else
|
||||
SDL_CondSignal(handle->cond);
|
||||
|
||||
SDL_mutexV(handle->cond_lock);
|
||||
}
|
||||
|
||||
SDL_mutexP(handle->lock);
|
||||
fifo_write(handle->audio_fifo, data->data, data->frames * handle->params.channels * sizeof(int16_t));
|
||||
SDL_mutexV(handle->lock);
|
||||
SDL_CondSignal(handle->cond);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ffemu_push_video_thread(ffemu_t *handle, const struct ffemu_video_data *data)
|
||||
{
|
||||
handle->video.sws_ctx = sws_getCachedContext(handle->video.sws_ctx, data->width, data->height, handle->video.fmt,
|
||||
handle->params.out_width, handle->params.out_height, PIX_FMT_RGB32, SWS_POINT,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
int linesize = data->pitch;
|
||||
|
||||
sws_scale(conv_ctx, (const uint8_t* const*)&data->data, &linesize, 0, handle->params.out_width, handle->video.conv_frame->data, handle->video.conv_frame->linesize);
|
||||
sws_scale(handle->video.sws_ctx, (const uint8_t* const*)&data->data, &linesize, 0,
|
||||
handle->video.codec->height, handle->video.conv_frame->data, handle->video.conv_frame->linesize);
|
||||
|
||||
handle->video.conv_frame->pts = handle->video.frame_cnt;
|
||||
handle->video.conv_frame->display_picture_number = handle->video.frame_cnt;
|
||||
|
||||
int outsize = avcodec_encode_video(handle->video.codec, handle->video.outbuf, handle->video.outbuf_size, handle->video.conv_frame);
|
||||
int outsize = avcodec_encode_video(handle->video.codec, handle->video.outbuf,
|
||||
handle->video.outbuf_size, handle->video.conv_frame);
|
||||
|
||||
if (outsize < 0)
|
||||
return -1;
|
||||
|
||||
sws_freeContext(conv_ctx);
|
||||
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
pkt.stream_index = handle->muxer.vstream->index;
|
||||
pkt.data = handle->video.outbuf;
|
||||
pkt.size = outsize;
|
||||
|
||||
pkt.pts = av_rescale_q(handle->video.codec->coded_frame->pts, handle->video.codec->time_base, handle->muxer.vstream->time_base);
|
||||
pkt.pts = av_rescale_q(handle->video.codec->coded_frame->pts, handle->video.codec->time_base,
|
||||
handle->muxer.vstream->time_base);
|
||||
|
||||
if (handle->video.codec->coded_frame->key_frame)
|
||||
pkt.flags |= AV_PKT_FLAG_KEY;
|
||||
@@ -319,16 +421,8 @@ int ffemu_push_video(ffemu_t *handle, const struct ffemu_video_data *data)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
static int ffemu_push_audio_thread(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
{
|
||||
if (!handle->audio.enabled)
|
||||
return -1;
|
||||
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
pkt.stream_index = handle->muxer.astream->index;
|
||||
pkt.data = handle->audio.outbuf;
|
||||
|
||||
size_t written_frames = 0;
|
||||
while (written_frames < data->frames)
|
||||
{
|
||||
@@ -344,6 +438,10 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
|
||||
if (handle->audio.frames_in_buffer == (size_t)handle->audio.codec->frame_size)
|
||||
{
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
pkt.data = handle->audio.outbuf;
|
||||
pkt.stream_index = handle->muxer.astream->index;
|
||||
|
||||
int out_size = avcodec_encode_audio(handle->audio.codec, handle->audio.outbuf, handle->audio.outbuf_size, handle->audio.buffer);
|
||||
if (out_size < 0)
|
||||
@@ -351,13 +449,9 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
|
||||
pkt.size = out_size;
|
||||
if (handle->audio.codec->coded_frame && handle->audio.codec->coded_frame->pts != AV_NOPTS_VALUE)
|
||||
{
|
||||
pkt.pts = av_rescale_q(handle->audio.codec->coded_frame->pts, handle->audio.codec->time_base, handle->muxer.astream->time_base);
|
||||
}
|
||||
else
|
||||
{
|
||||
pkt.pts = av_rescale_q(handle->audio.frame_cnt, handle->audio.codec->time_base, handle->muxer.astream->time_base);
|
||||
}
|
||||
|
||||
pkt.flags |= AV_PKT_FLAG_KEY;
|
||||
handle->audio.frames_in_buffer = 0;
|
||||
@@ -375,38 +469,147 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data)
|
||||
|
||||
int ffemu_finalize(ffemu_t *handle)
|
||||
{
|
||||
// Push out delayed frames. (MPEG codecs)
|
||||
if (handle->video.enabled)
|
||||
deinit_thread(handle);
|
||||
|
||||
// Push out frames still stuck in queue.
|
||||
void *video_buf = av_malloc(2 * handle->params.fb_width * handle->params.fb_height * handle->video.pix_size);
|
||||
if (video_buf)
|
||||
{
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
pkt.stream_index = handle->muxer.vstream->index;
|
||||
pkt.data = handle->video.outbuf;
|
||||
|
||||
int out_size = 0;
|
||||
do
|
||||
struct ffemu_video_data attr_buf;
|
||||
while (fifo_read_avail(handle->attr_fifo) >= sizeof(attr_buf))
|
||||
{
|
||||
out_size = avcodec_encode_video(handle->video.codec, handle->video.outbuf, handle->video.outbuf_size, NULL);
|
||||
pkt.pts = av_rescale_q(handle->video.codec->coded_frame->pts, handle->video.codec->time_base, handle->muxer.vstream->time_base);
|
||||
|
||||
if (handle->video.codec->coded_frame->key_frame)
|
||||
pkt.flags |= AV_PKT_FLAG_KEY;
|
||||
|
||||
pkt.size = out_size;
|
||||
|
||||
if (pkt.size > 0)
|
||||
{
|
||||
int err = av_interleaved_write_frame(handle->muxer.ctx, &pkt);
|
||||
if (err < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
} while (out_size > 0);
|
||||
fifo_read(handle->attr_fifo, &attr_buf, sizeof(attr_buf));
|
||||
fifo_read(handle->video_fifo, video_buf, attr_buf.height * attr_buf.pitch);
|
||||
attr_buf.data = video_buf;
|
||||
ffemu_push_video_thread(handle, &attr_buf);
|
||||
}
|
||||
av_free(video_buf);
|
||||
}
|
||||
|
||||
size_t audio_buf_size = 128 * handle->params.channels * sizeof(int16_t);
|
||||
int16_t *audio_buf = av_malloc(audio_buf_size);
|
||||
if (audio_buf)
|
||||
{
|
||||
while (fifo_read_avail(handle->audio_fifo) >= audio_buf_size)
|
||||
{
|
||||
fifo_read(handle->audio_fifo, audio_buf, sizeof(audio_buf_size));
|
||||
|
||||
struct ffemu_audio_data aud = {
|
||||
.frames = 128,
|
||||
.data = audio_buf
|
||||
};
|
||||
|
||||
ffemu_push_audio_thread(handle, &aud);
|
||||
}
|
||||
av_free(audio_buf);
|
||||
}
|
||||
|
||||
deinit_thread_buf(handle);
|
||||
|
||||
// Push out delayed frames. (MPEG codecs)
|
||||
AVPacket pkt;
|
||||
av_init_packet(&pkt);
|
||||
pkt.stream_index = handle->muxer.vstream->index;
|
||||
pkt.data = handle->video.outbuf;
|
||||
|
||||
int out_size = 0;
|
||||
do
|
||||
{
|
||||
out_size = avcodec_encode_video(handle->video.codec, handle->video.outbuf, handle->video.outbuf_size, NULL);
|
||||
pkt.pts = av_rescale_q(handle->video.codec->coded_frame->pts, handle->video.codec->time_base, handle->muxer.vstream->time_base);
|
||||
|
||||
if (handle->video.codec->coded_frame->key_frame)
|
||||
pkt.flags |= AV_PKT_FLAG_KEY;
|
||||
|
||||
pkt.size = out_size;
|
||||
|
||||
if (pkt.size > 0)
|
||||
{
|
||||
int err = av_interleaved_write_frame(handle->muxer.ctx, &pkt);
|
||||
if (err < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
} while (out_size > 0);
|
||||
|
||||
// Write final data.
|
||||
av_write_trailer(handle->muxer.ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int SDLCALL ffemu_thread(void *data)
|
||||
{
|
||||
ffemu_t *ff = data;
|
||||
|
||||
// For some reason, FFmpeg has a tendency to crash if we don't overallocate a bit. :s
|
||||
void *video_buf = av_malloc(2 * ff->params.fb_width * ff->params.fb_height * ff->video.pix_size);
|
||||
assert(video_buf);
|
||||
|
||||
size_t audio_buf_size = 128 * ff->params.channels * sizeof(int16_t);
|
||||
int16_t *audio_buf = av_malloc(audio_buf_size);
|
||||
assert(audio_buf);
|
||||
|
||||
struct ffemu_video_data attr_buf;
|
||||
|
||||
while (ff->alive)
|
||||
{
|
||||
bool avail_video = false;
|
||||
bool avail_audio = false;
|
||||
|
||||
SDL_mutexP(ff->lock);
|
||||
if (fifo_read_avail(ff->attr_fifo) >= sizeof(attr_buf))
|
||||
avail_video = true;
|
||||
|
||||
if (fifo_read_avail(ff->audio_fifo) >= audio_buf_size)
|
||||
avail_audio = true;
|
||||
SDL_mutexV(ff->lock);
|
||||
|
||||
if (!avail_video && !avail_audio)
|
||||
{
|
||||
SDL_mutexP(ff->cond_lock);
|
||||
if (ff->can_sleep)
|
||||
{
|
||||
ff->can_sleep = false;
|
||||
SDL_CondWait(ff->cond, ff->cond_lock);
|
||||
ff->can_sleep = true;
|
||||
}
|
||||
else
|
||||
SDL_CondSignal(ff->cond);
|
||||
|
||||
SDL_mutexV(ff->cond_lock);
|
||||
}
|
||||
|
||||
if (avail_video)
|
||||
{
|
||||
SDL_mutexP(ff->lock);
|
||||
fifo_read(ff->attr_fifo, &attr_buf, sizeof(attr_buf));
|
||||
fifo_read(ff->video_fifo, video_buf, attr_buf.height * attr_buf.pitch);
|
||||
SDL_mutexV(ff->lock);
|
||||
SDL_CondSignal(ff->cond);
|
||||
|
||||
attr_buf.data = video_buf;
|
||||
ffemu_push_video_thread(ff, &attr_buf);
|
||||
}
|
||||
|
||||
if (avail_audio)
|
||||
{
|
||||
SDL_mutexP(ff->lock);
|
||||
fifo_read(ff->audio_fifo, audio_buf, audio_buf_size);
|
||||
SDL_mutexV(ff->lock);
|
||||
SDL_CondSignal(ff->cond);
|
||||
|
||||
struct ffemu_audio_data aud = {
|
||||
.frames = 128,
|
||||
.data = audio_buf
|
||||
};
|
||||
|
||||
ffemu_push_audio_thread(ff, &aud);
|
||||
}
|
||||
}
|
||||
|
||||
av_free(video_buf);
|
||||
av_free(audio_buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
+7
-43
@@ -8,36 +8,6 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Available video codecs
|
||||
typedef enum ffemu_video_codec
|
||||
{
|
||||
FFEMU_VIDEO_NONE,
|
||||
FFEMU_VIDEO_H264,
|
||||
FFEMU_VIDEO_MPEG4,
|
||||
} ffemu_video_codec;
|
||||
|
||||
// Available audio codecs
|
||||
typedef enum ffemu_audio_codec
|
||||
{
|
||||
FFEMU_AUDIO_NONE,
|
||||
FFEMU_AUDIO_VORBIS,
|
||||
FFEMU_AUDIO_MP3,
|
||||
FFEMU_AUDIO_AAC,
|
||||
} ffemu_audio_codec;
|
||||
|
||||
// Available pixel formats
|
||||
typedef enum ffemu_pixel_format
|
||||
{
|
||||
FFEMU_FMT_XBGR1555,
|
||||
FFEMU_FMT_RGB888,
|
||||
} ffemu_pixel_format;
|
||||
|
||||
typedef enum ffemu_rescaler
|
||||
{
|
||||
FFEMU_RESCALER_LANCZOS,
|
||||
FFEMU_RESCALER_POINT
|
||||
} ffemu_rescaler;
|
||||
|
||||
struct ffemu_rational
|
||||
{
|
||||
unsigned num;
|
||||
@@ -47,20 +17,15 @@ struct ffemu_rational
|
||||
// Parameters passed to ffemu_new()
|
||||
struct ffemu_params
|
||||
{
|
||||
// Video codec to use. If not recording video, select FFEMU_VIDEO_NONE.
|
||||
ffemu_video_codec vcodec;
|
||||
|
||||
// Desired output resolution.
|
||||
unsigned out_width;
|
||||
unsigned out_height;
|
||||
|
||||
// Total size of framebuffer used in input.
|
||||
unsigned fb_width;
|
||||
unsigned fb_height;
|
||||
float aspect_ratio;
|
||||
|
||||
// Rescaler for video.
|
||||
ffemu_rescaler rescaler;
|
||||
|
||||
// Pixel format for video input.
|
||||
ffemu_pixel_format format;
|
||||
|
||||
// FPS of video input.
|
||||
struct ffemu_rational fps;
|
||||
|
||||
@@ -71,15 +36,15 @@ struct ffemu_params
|
||||
// Define some video codec dependent option. (E.g. h264 profiles)
|
||||
uint64_t video_opt;
|
||||
|
||||
// Audio codec. If not recording audio, select FFEMU_AUDIO_NONE.
|
||||
ffemu_audio_codec acodec;
|
||||
|
||||
// Audio sample rate.
|
||||
unsigned samplerate;
|
||||
|
||||
// Audio channels.
|
||||
unsigned channels;
|
||||
|
||||
// If input is ARGB or XRGB1555.
|
||||
bool rgb32;
|
||||
|
||||
// Audio bits. Sample format is always signed PCM in native byte order.
|
||||
//unsigned bits;
|
||||
|
||||
@@ -119,7 +84,6 @@ int ffemu_push_audio(ffemu_t *handle, const struct ffemu_audio_data *data);
|
||||
int ffemu_finalize(ffemu_t *handle);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
Loaded 100 of 115 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user