mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 08:10:30 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2476e3dee3 | ||
|
|
c05a5c9fdd | ||
|
|
503c51d633 | ||
|
|
46054de4f8 | ||
|
|
604eb9b973 | ||
|
|
0e72120bdc | ||
|
|
b8064f5d42 | ||
|
|
379b14a0f3 | ||
|
|
2a598c0b97 | ||
|
|
aa124b4c40 | ||
|
|
bb5e31df0d | ||
|
|
e49d216884 | ||
|
|
598d06fcb5 | ||
|
|
7f13ec88e6 | ||
|
|
c5c60c6f53 | ||
|
|
81b0e6d0a3 | ||
|
|
da97b29d0b | ||
|
|
69b7f92285 | ||
|
|
3a9dbbc8b7 | ||
|
|
bf5db10459 | ||
|
|
4fa27a5f0f | ||
|
|
3e7c412738 | ||
|
|
88c870dcfa | ||
|
|
66e1179503 | ||
|
|
4ecea5d0a4 | ||
|
|
57816edfd3 | ||
|
|
17cf0985c5 | ||
|
|
776b0a1e2d | ||
|
|
4a6392aefa | ||
|
|
29dd4727b8 | ||
|
|
face42a888 | ||
|
|
45fa6795c3 | ||
|
|
a9d339efb4 | ||
|
|
60841b01a5 | ||
|
|
ad67511483 | ||
|
|
104fb4fb16 | ||
|
|
816ef095ed | ||
|
|
48e8472f9c | ||
|
|
1755500b19 | ||
|
|
4bfc827eb2 | ||
|
|
ddfeacbd83 | ||
|
|
79353f861c | ||
|
|
f3f4efe304 | ||
|
|
51721b4cb6 | ||
|
|
477091ce8f | ||
|
|
2707b87767 | ||
|
|
b3d422b5cd | ||
|
|
d1dc65f202 | ||
|
|
7815940a97 | ||
|
|
4f841c208d | ||
|
|
b4f0d9b8f8 | ||
|
|
b96ab2982c | ||
|
|
825c1de7d1 | ||
|
|
6ada4d87d3 | ||
|
|
53abc92322 | ||
|
|
a816082ac3 | ||
|
|
47e63f1c6b | ||
|
|
cdf3d05c8e | ||
|
|
389e9ba0dc | ||
|
|
a6f7a2311f | ||
|
|
f19d99d523 | ||
|
|
e7082fd6ea | ||
|
|
4806d2fe88 | ||
|
|
f85ff7124b | ||
|
|
0f010cf3d2 | ||
|
|
95636d4f04 | ||
|
|
43905e410e | ||
|
|
d23a7e3ddf | ||
|
|
652f4c5856 | ||
|
|
90febc4ecf | ||
|
|
34b5994842 | ||
|
|
0c3f6ed66b | ||
|
|
f3e487557e | ||
|
|
ee12266a2b | ||
|
|
1981b14de9 | ||
|
|
65665f36cc | ||
|
|
a48dc55c41 | ||
|
|
202a458524 | ||
|
|
b4de704a09 | ||
|
|
fef5edfac8 | ||
|
|
a6e74775b1 | ||
|
|
ee66a28d05 | ||
|
|
5e28d82af2 | ||
|
|
c2a06bfa09 | ||
|
|
66510e21aa | ||
|
|
ec5e3355b9 | ||
|
|
0522a2d6a0 | ||
|
|
fad89f9d2d | ||
|
|
97f937ec6d | ||
|
|
9647006a2b | ||
|
|
418ba455f8 | ||
|
|
197d62860b | ||
|
|
402451626f | ||
|
|
3739cbfc70 | ||
|
|
235355ffc7 | ||
|
|
4856d0cac3 | ||
|
|
77a2723bb0 | ||
|
|
41cd6e21c3 | ||
|
|
1fc99c7123 | ||
|
|
fddcc3447c | ||
|
|
70e9f084b7 | ||
|
|
824790a80d | ||
|
|
23d23983bc | ||
|
|
c42655ed91 | ||
|
|
f46ee78c76 | ||
|
|
5d1d188231 | ||
|
|
c55ce543c2 | ||
|
|
de8bc62474 | ||
|
|
397da887a7 | ||
|
|
f44733d7f6 | ||
|
|
5faa28a1a2 | ||
|
|
efaa85ff9b | ||
|
|
8f5580be10 | ||
|
|
c7acd99548 | ||
|
|
64ee13e6f4 | ||
|
|
e134f8afc7 | ||
|
|
dd89e6fbe0 | ||
|
|
a79a1b46f7 | ||
|
|
6467f51ea8 | ||
|
|
141eefb03b | ||
|
|
068c224d83 | ||
|
|
5bc8090ea7 | ||
|
|
466ab1ef8d | ||
|
|
a1c19ed1f5 | ||
|
|
cce4b7d20c | ||
|
|
80925ffc11 | ||
|
|
9eb14e328d | ||
|
|
5ae0cb1821 | ||
|
|
fc594f7f0f | ||
|
|
0c4e406ee7 | ||
|
|
a082ec5251 | ||
|
|
953b5daf0a | ||
|
|
b4ff622f32 | ||
|
|
f26b54c998 | ||
|
|
d7b3f39136 | ||
|
|
5bcf601a40 | ||
|
|
e91ebdac97 | ||
|
|
b2dd12d186 | ||
|
|
9c79bf0ba2 | ||
|
|
f48247a18b | ||
|
|
48e015558e | ||
|
|
0ff3cbc93b | ||
|
|
25f0e162a4 | ||
|
|
0810812cd9 | ||
|
|
15e0b721e6 | ||
|
|
9d12c3ac87 | ||
|
|
a7c3d8d17a | ||
|
|
8fbfdee011 | ||
|
|
bc999e72fa | ||
|
|
c7f8110670 | ||
|
|
b0dbc67281 | ||
|
|
4916f7b08b | ||
|
|
1058d94ec1 | ||
|
|
340dca3c6d | ||
|
|
4ae0f3da22 | ||
|
|
7d2d1ce49e | ||
|
|
fa26f6a10c | ||
|
|
db71a5cdba | ||
|
|
5058fdcd1a | ||
|
|
ef04f199d5 | ||
|
|
d26dd9d9e7 | ||
|
|
d2bc4096de | ||
|
|
08ca1a54d3 | ||
|
|
869eddfc1a | ||
|
|
fbced43f11 | ||
|
|
c996f816b8 | ||
|
|
33ecedb462 | ||
|
|
a58676c9e1 | ||
|
|
1ee9722e5f | ||
|
|
81250657f0 | ||
|
|
7d33cccc8e | ||
|
|
3db79ab2f0 | ||
|
|
ea0f6813d2 | ||
|
|
f6d4d32c03 | ||
|
|
892e82141f | ||
|
|
65ec7fae9c | ||
|
|
a0acb75a19 | ||
|
|
bcae973d82 | ||
|
|
d01a4fe61b | ||
|
|
8d4620b680 | ||
|
|
c2cedb1b6f | ||
|
|
157dcad87b | ||
|
|
8ea5ba171a | ||
|
|
fa876b10a3 | ||
|
|
3c58d10eae | ||
|
|
a3ee8c8849 | ||
|
|
5e14b302bc | ||
|
|
efe3f943d5 | ||
|
|
38e7fc6659 | ||
|
|
a1ceb534de | ||
|
|
0d897dd65d | ||
|
|
721d73f451 | ||
|
|
e8bd64e898 | ||
|
|
7da7ab2ce7 | ||
|
|
3f6183ff15 | ||
|
|
642316a1e2 | ||
|
|
21c6b75839 | ||
|
|
87d34deb0c | ||
|
|
1b3408919c | ||
|
|
23516572b7 | ||
|
|
924848fdad | ||
|
|
4c40e56060 | ||
|
|
cf34444505 | ||
|
|
9ca5e4e470 | ||
|
|
32f2689b6a | ||
|
|
13ae61031f | ||
|
|
1ced96c5e9 | ||
|
|
0d905eb3f6 | ||
|
|
32a5460ea7 | ||
|
|
be5acff69c | ||
|
|
1b0ef5793e | ||
|
|
2bd0caae2e | ||
|
|
3045f2c478 | ||
|
|
1282a142d6 | ||
|
|
ac1a539d22 | ||
|
|
6f47775239 | ||
|
|
0823d72dee | ||
|
|
407ad94f58 | ||
|
|
b2b103362d | ||
|
|
231680944f | ||
|
|
ec7dab05e8 | ||
|
|
9588f6061f | ||
|
|
57c25dcb0b | ||
|
|
38763c4554 | ||
|
|
fac2ea00dd | ||
|
|
74782a1bea | ||
|
|
c1265e7227 | ||
|
|
f0841256e8 | ||
|
|
600efab1bd | ||
|
|
dc9dd2e6bf | ||
|
|
de8fe0e88c | ||
|
|
338432c35f | ||
|
|
11aeb05a6e | ||
|
|
e28a32d922 | ||
|
|
edac39cb2b | ||
|
|
c1954aa4a6 | ||
|
|
ef048fcfd5 | ||
|
|
48a1531d7c | ||
|
|
4b4686ae04 | ||
|
|
cb4a54fefe | ||
|
|
131b2386cd | ||
|
|
4d6d01228c | ||
|
|
a050955113 | ||
|
|
b02789d280 | ||
|
|
6f92d0588d | ||
|
|
45df48ab67 | ||
|
|
aba5c7722e | ||
|
|
07a1a1ee2f | ||
|
|
3b952973de | ||
|
|
ac4adebdf6 | ||
|
|
1bfd9716c5 | ||
|
|
702cf9c804 | ||
|
|
5993fffb64 | ||
|
|
58758d2818 | ||
|
|
21f88fcf39 | ||
|
|
e3f029a446 | ||
|
|
0694d8884f | ||
|
|
0730a34a5b | ||
|
|
f827bb4cbd | ||
|
|
eafd8d91d7 | ||
|
|
56a4d1ddac | ||
|
|
8d3f15602c | ||
|
|
7877c7939d | ||
|
|
c55263b956 | ||
|
|
96ce288120 | ||
|
|
e5326467a1 | ||
|
|
596d8d9985 | ||
|
|
b4b0be9118 | ||
|
|
00e5f0fe97 | ||
|
|
cb140c3691 | ||
|
|
ad85ccb42f | ||
|
|
97e50f15de | ||
|
|
071c03e194 | ||
|
|
6f47c21ce9 | ||
|
|
68bfda24ff | ||
|
|
4f2fbcb9bc | ||
|
|
90ab5dfc2f | ||
|
|
362cbe5deb | ||
|
|
e133662346 | ||
|
|
1fe83ab25a | ||
|
|
1d95b5ee22 | ||
|
|
9dc603d405 | ||
|
|
5cb2863e6e | ||
|
|
98cf4223ab | ||
|
|
ff7843cbe9 | ||
|
|
c4d6b0b7c1 | ||
|
|
fb739621ac | ||
|
|
24e8ca6856 | ||
|
|
1ff9b6a375 | ||
|
|
cb6086635c | ||
|
|
bce679df78 | ||
|
|
22e8c3d895 | ||
|
|
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 | ||
|
|
43dc30c3a5 | ||
|
|
98e6613df0 | ||
|
|
3ce07891e4 | ||
|
|
2ff47ab7c4 | ||
|
|
8ba639c7d5 | ||
|
|
e0403e4dae | ||
|
|
7776525c96 | ||
|
|
25fe9b7f58 | ||
|
|
fff7aba8ec | ||
|
|
e83f3ed444 | ||
|
|
334f11d5bc | ||
|
|
5bcde44684 | ||
|
|
2459793f9c | ||
|
|
cadd7b9728 | ||
|
|
644f247026 | ||
|
|
8c1ef3051d | ||
|
|
d296e15f26 | ||
|
|
5a5cd46f9b | ||
|
|
20d9345a4e |
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,51 +2,108 @@ include config.mk
|
||||
|
||||
TARGET = ssnes tools/ssnes-joyconfig
|
||||
|
||||
OBJ = ssnes.o file.o driver.o settings.o dynamic.o message.o
|
||||
JOYCONFIG_OBJ = tools/ssnes-joyconfig.o conf/config_file.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 bps.o strl.o screenshot.o thread.o audio/hermite.o
|
||||
JOYCONFIG_OBJ = tools/ssnes-joyconfig.o conf/config_file.o strl.o
|
||||
HEADERS = $(wildcard */*.h) $(wildcard *.h)
|
||||
|
||||
LIBS =
|
||||
LIBS = -lm -lpthread
|
||||
DEFINES = -DHAVE_CONFIG_H
|
||||
|
||||
ifeq ($(HAVE_SRC), 1)
|
||||
LIBS += $(SRC_LIBS)
|
||||
DEFINES += $(SRC_CFLAGS)
|
||||
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
|
||||
|
||||
ifneq ($(findstring Linux,$(OS)),)
|
||||
LIBS += -lrt
|
||||
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
|
||||
LIBS += $(PULSE_LIBS)
|
||||
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/sdlwrap.o input/sdl.o audio/sdl.o fifo_buffer.o
|
||||
DEFINES += $(SDL_CFLAGS) $(BSD_LOCAL_INC)
|
||||
LIBS += $(SDL_LIBS)
|
||||
|
||||
ifeq ($(HAVE_OPENGL), 1)
|
||||
OBJ += gfx/gl.o
|
||||
ifeq ($(OSX),1)
|
||||
LIBS += -framework OpenGL
|
||||
else
|
||||
LIBS += -lGL
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XVIDEO), 1)
|
||||
OBJ += gfx/xvideo.o input/x11_input.o
|
||||
LIBS += $(XVIDEO_LIBS) $(X11_LIBS) $(XEXT_LIBS)
|
||||
DEFINES += $(XVIDEO_CFLAGS) $(X11_CFLAGS) $(XEXT_CFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_CG), 1)
|
||||
@@ -55,17 +112,24 @@ ifeq ($(HAVE_CG), 1)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XML), 1)
|
||||
OBJ += gfx/shader_glsl.o
|
||||
OBJ += sha256.o cheats.o
|
||||
LIBS += $(XML_LIBS)
|
||||
DEFINES += $(XML_CFLAGS)
|
||||
|
||||
ifeq ($(HAVE_OPENGL), 1)
|
||||
OBJ += gfx/shader_glsl.o
|
||||
endif
|
||||
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_XML), 1)
|
||||
OBJ += gfx/snes_state.o gfx/image.o
|
||||
else ifeq ($(HAVE_CG), 1)
|
||||
OBJ += gfx/snes_state.o gfx/image.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_DYLIB), 1)
|
||||
OBJ += gfx/ext.o audio/ext.o
|
||||
LIBS += $(DYLIB_LIB)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_FREETYPE), 1)
|
||||
@@ -74,23 +138,48 @@ 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.
|
||||
OPTIMIZE_FLAG = -O3
|
||||
ifeq ($(DEBUG), 1)
|
||||
OPTIMIZE_FLAG = -O0
|
||||
endif
|
||||
|
||||
CFLAGS += -Wall $(OPTIMIZE_FLAG) -g -I. -pedantic
|
||||
ifneq ($(findstring icc,$(CC)),)
|
||||
CFLAGS += -std=c99 -D_GNU_SOURCE
|
||||
else
|
||||
CFLAGS += -std=gnu99
|
||||
endif
|
||||
|
||||
all: $(TARGET) config.mk
|
||||
|
||||
@@ -99,11 +188,11 @@ config.mk: configure qb/*
|
||||
@exit 1
|
||||
|
||||
ssnes: $(OBJ)
|
||||
$(Q)$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS)
|
||||
$(Q)$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS) $(LIBRARY_DIRS)
|
||||
@$(if $(Q), $(shell echo echo LD $@),)
|
||||
|
||||
tools/ssnes-joyconfig: $(JOYCONFIG_OBJ)
|
||||
$(Q)$(CC) -o $@ $(JOYCONFIG_OBJ) $(SDL_LIBS) $(LDFLAGS)
|
||||
$(Q)$(CC) -o $@ $(JOYCONFIG_OBJ) $(SDL_LIBS) $(LDFLAGS) $(LIBRARY_DIRS)
|
||||
@$(if $(Q), $(shell echo echo LD $@),)
|
||||
|
||||
%.o: %.c config.h config.mk $(HEADERS)
|
||||
@@ -111,26 +200,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)
|
||||
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
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 bps.o ups.o strl.o screenshot.o audio/hermite.o thread.o
|
||||
JOBJ = conf/config_file.o tools/main-stub.o tools/ssnes-joyconfig.o strl.o
|
||||
|
||||
CC = gcc
|
||||
CXX = g++
|
||||
|
||||
HAVE_DINPUT = 1
|
||||
HAVE_XAUDIO = 1
|
||||
HAVE_DSOUND = 1
|
||||
HAVE_SDL = 1
|
||||
HAVE_OPENGL = 1
|
||||
HAVE_DYLIB = 1
|
||||
HAVE_NETPLAY = 1
|
||||
DYNAMIC = 1
|
||||
|
||||
ifeq ($(SLIM),)
|
||||
HAVE_SDL_IMAGE = 1
|
||||
HAVE_XML = 1
|
||||
HAVE_FREETYPE = 1
|
||||
HAVE_RSOUND = 1
|
||||
HAVE_FBO = 1
|
||||
HAVE_CG = 1
|
||||
HAVE_PYTHON = 1
|
||||
HAVE_FFMPEG = 1
|
||||
endif
|
||||
|
||||
libsnes ?= -lsnes
|
||||
|
||||
LIBS = -lm -lshlwapi
|
||||
DEFINES = -I. -DHAVE_CONFIGFILE
|
||||
LDFLAGS = -L. -static-libgcc
|
||||
LDCXXFLAGS = -static-libstdc++ -s
|
||||
|
||||
ifeq ($(HAVE_SDL), 1)
|
||||
OBJ += gfx/sdl.o gfx/gl.o gfx/sdlwrap.o input/sdl.o audio/sdl.o fifo_buffer.o
|
||||
LIBS += -lSDL
|
||||
DEFINES += -ISDL -DHAVE_SDL
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_OPENGL), 1)
|
||||
DEFINES += -DHAVE_OPENGL
|
||||
LIBS += -lopengl32
|
||||
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 audio/xaudio-c/xaudio-c.o
|
||||
DEFINES += -DHAVE_XAUDIO
|
||||
LIBS += -lole32
|
||||
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 += -Ilibxml2 -DHAVE_XML
|
||||
LIBS += -lxml2 -liconv
|
||||
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 += -lfreetype -lz
|
||||
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
|
||||
|
||||
ifeq ($(HAVE_FFMPEG), 1)
|
||||
LIBS += -lavformat -lavcodec -lavutil -lswscale -lws2_32 -lz
|
||||
DEFINES += -DHAVE_FFMPEG -Iffmpeg
|
||||
DEFINES += -DHAVE_FFMPEG_ALLOC_CONTEXT3
|
||||
DEFINES += -DHAVE_FFMPEG_AVCODEC_OPEN2
|
||||
DEFINES += -DHAVE_FFMPEG_AVIO_OPEN
|
||||
DEFINES += -DHAVE_FFMPEG_AVFORMAT_WRITE_HEADER
|
||||
DEFINES += -DHAVE_FFMPEG_AVFORMAT_NEW_STREAM
|
||||
DEFINES += -DHAVE_X264RGB
|
||||
OBJ += record/ffemu.o
|
||||
endif
|
||||
|
||||
ifneq ($(V),1)
|
||||
Q := @
|
||||
endif
|
||||
|
||||
CFLAGS += -Wall -O3 -std=gnu99 -I.
|
||||
CXXFLAGS += -Wall -O3 -pedantic -I.
|
||||
|
||||
all: $(TARGET) $(JTARGET)
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
$(Q)$(CXX) -o $@ $(OBJ) $(LIBS) $(LDFLAGS) $(LDCXXFLAGS)
|
||||
@$(if $(Q), $(shell echo echo LD $@),)
|
||||
|
||||
%.o: %.c
|
||||
$(Q)$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $<
|
||||
@$(if $(Q), $(shell echo echo CC $<),)
|
||||
|
||||
%.o: %.cpp
|
||||
$(Q)$(CXX) $(CXXFLAGS) $(DEFINES) -c -o $@ $<
|
||||
@$(if $(Q), $(shell echo echo CXX $<),)
|
||||
|
||||
|
||||
$(JTARGET): $(JOBJ)
|
||||
$(Q)$(CC) -o $@ $(JOBJ) -lSDL $(LDFLAGS)
|
||||
@$(if $(Q), $(shell echo echo LD $@),)
|
||||
|
||||
clean:
|
||||
rm -f *.o
|
||||
rm -f audio/*.o
|
||||
rm -f audio/xaudio-c/*.o
|
||||
rm -f conf/*.o
|
||||
rm -f gfx/*.o
|
||||
rm -f gfx/py_state/*.o
|
||||
rm -f record/*.o
|
||||
rm -f input/*.o
|
||||
rm -f $(TARGET)
|
||||
rm -f ssnes-joyconfig.exe
|
||||
rm -f tools/*.o
|
||||
|
||||
dist_x86: all
|
||||
zip -r ssnes-win32-0.9.2.zip $(TARGET) $(JTARGET) ssnes.cfg snes.dll SDL.dll
|
||||
|
||||
dist_x86_64: all
|
||||
zip -r ssnes-win64-0.9.2.zip $(TARGET) $(JTARGET) ssnes.cfg snes.dll SDL.dll
|
||||
|
||||
libs_x86:
|
||||
wget https://github.com/downloads/Themaister/SSNES/SSNES-win32-libs.zip --no-check-certificate
|
||||
unzip SSNES-win32-libs.zip
|
||||
wget https://github.com/downloads/Themaister/SSNES/SSNES-win32-headers.zip --no-check-certificate
|
||||
unzip SSNES-win32-headers.zip
|
||||
|
||||
libs_x86_64:
|
||||
wget https://github.com/downloads/Themaister/SSNES/SSNES-win64-libs.zip --no-check-certificate
|
||||
unzip SSNES-win64-libs.zip
|
||||
wget https://github.com/downloads/Themaister/SSNES/SSNES-win64-headers.zip --no-check-certificate
|
||||
unzip SSNES-win64-headers.zip
|
||||
|
||||
.PHONY: all install uninstall clean dist_x86 dist_x86_64 libs_x86 libs_x86_64
|
||||
@@ -1,93 +0,0 @@
|
||||
TARGET = ssnes.exe
|
||||
JTARGET = ssnes-joyconfig.exe
|
||||
OBJ = ssnes.o file.o driver.o conf/config_file.o settings.o dynamic.o message.o
|
||||
JOBJ = conf/config_file.o tools/main-stub.o tools/ssnes-joyconfig.o
|
||||
|
||||
CC = gcc
|
||||
CXX = g++
|
||||
|
||||
HAVE_SRC = 1
|
||||
HAVE_SDL = 1
|
||||
HAVE_XML = 1
|
||||
HAVE_FREETYPE = 1
|
||||
libsnes ?= -lsnes
|
||||
|
||||
LIBS =
|
||||
DEFINES = -I. -DHAVE_CONFIGFILE
|
||||
LDFLAGS = -L. -static-libgcc -s
|
||||
|
||||
SRC_LIBS = -lsamplerate-0
|
||||
SDL_LIBS = -lSDL
|
||||
SDL_CFLAGS = -ISDL
|
||||
|
||||
ifeq ($(HAVE_SRC), 1)
|
||||
LIBS += $(SRC_LIBS)
|
||||
DEFINES += $(SRC_CFLAGS) -DHAVE_SRC
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL), 1)
|
||||
OBJ += gfx/gl.o input/sdl.o audio/sdl.o audio/buffer.o
|
||||
LIBS += $(SDL_LIBS) -lopengl32
|
||||
DEFINES += $(SDL_CFLAGS) -DHAVE_SDL
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_XML), 1)
|
||||
OBJ += gfx/shader_glsl.o
|
||||
DEFINES += $(XML_CFLAGS) -DHAVE_XML
|
||||
LIBS += -lxml2
|
||||
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
|
||||
|
||||
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-win32-0.2.zip $(TARGET) ssnes.cfg snes.dll libxml2.dll iconv.dll zlib1.dll SDL.dll libsamplerate-0.dll freetype6.dll $(JTARGET)
|
||||
|
||||
libs:
|
||||
wget https://github.com/downloads/Themaister/SSNES/SSNES-win32-libs.zip --no-check-certificate
|
||||
unzip SSNES-win32-libs.zip
|
||||
|
||||
.PHONY: all install uninstall clean dist libs
|
||||
@@ -7,6 +7,10 @@ SSNES is a simple frontend for the libsnes library.
|
||||
libsnes is the emulator core of [bSNES](http://www.byuu.org), the most accurate SNES emulator to date, in library form. The libsnes API has been implemented by SNES9x as well, allowing multiple different cores to be used for an emulator that supports libsnes.
|
||||
This enables the possibility of custom front-ends for the emulator.
|
||||
|
||||
# Binaries
|
||||
|
||||
Latest Windows binaries are currently hosted on my [homepage](http://themaister.net/ssnes.html).
|
||||
|
||||
# Philosophy
|
||||
|
||||
SSNES attempts to be very small and lean, while still having all the useful core features expected from an emulator.
|
||||
@@ -18,15 +22,15 @@ 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 one of these audio driver libraries:
|
||||
SSNES needs at least one of these audio driver libraries:
|
||||
|
||||
- ALSA
|
||||
- OSS
|
||||
@@ -35,6 +39,8 @@ SSNES needs one of these audio driver libraries:
|
||||
- OpenAL
|
||||
- JACK
|
||||
- SDL
|
||||
- XAudio2 (Win32)
|
||||
- PulseAudio
|
||||
|
||||
# Building libsnes
|
||||
|
||||
|
||||
+24
-29
@@ -18,10 +18,10 @@
|
||||
|
||||
#include "driver.h"
|
||||
#include <stdlib.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <asoundlib.h>
|
||||
#include "general.h"
|
||||
|
||||
#define TRY_ALSA(x) if ( x < 0 ) { \
|
||||
#define TRY_ALSA(x) if (x < 0) { \
|
||||
goto error; \
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ typedef struct alsa
|
||||
bool has_float;
|
||||
} alsa_t;
|
||||
|
||||
static bool __alsa_use_float(void *data)
|
||||
static bool alsa_use_float(void *data)
|
||||
{
|
||||
alsa_t *alsa = data;
|
||||
return alsa->has_float;
|
||||
@@ -49,17 +49,17 @@ 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 )
|
||||
if (!alsa)
|
||||
return NULL;
|
||||
|
||||
snd_pcm_hw_params_t *params = NULL;
|
||||
snd_pcm_sw_params_t *sw_params = NULL;
|
||||
|
||||
const char *alsa_dev = "default";
|
||||
if ( device != NULL )
|
||||
if (device)
|
||||
alsa_dev = device;
|
||||
|
||||
TRY_ALSA(snd_pcm_open(&alsa->pcm, alsa_dev, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK));
|
||||
@@ -117,7 +117,7 @@ error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ssize_t __alsa_write(void* data, const void* buf, size_t size)
|
||||
static ssize_t alsa_write(void *data, const void *buf, size_t size)
|
||||
{
|
||||
alsa_t *alsa = data;
|
||||
|
||||
@@ -141,16 +141,16 @@ static ssize_t __alsa_write(void* data, const void* buf, size_t size)
|
||||
|
||||
frames = snd_pcm_writei(alsa->pcm, (const char*)buf + written * 2 * (alsa->has_float ? sizeof(float) : sizeof(int16_t)), size - written);
|
||||
|
||||
if ( frames == -EPIPE || frames == -EINTR || frames == -ESTRPIPE )
|
||||
if (frames == -EPIPE || frames == -EINTR || frames == -ESTRPIPE)
|
||||
{
|
||||
if ( snd_pcm_recover(alsa->pcm, frames, 1) < 0 )
|
||||
if (snd_pcm_recover(alsa->pcm, frames, 1) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
else if ( frames == -EAGAIN && alsa->nonblock )
|
||||
else if (frames == -EAGAIN && alsa->nonblock)
|
||||
return 0;
|
||||
else if ( frames < 0 )
|
||||
else if (frames < 0)
|
||||
return -1;
|
||||
|
||||
written += frames;
|
||||
@@ -159,28 +159,28 @@ static ssize_t __alsa_write(void* data, const void* buf, size_t size)
|
||||
return snd_pcm_frames_to_bytes(alsa->pcm, size);
|
||||
}
|
||||
|
||||
static bool __alsa_stop(void *data)
|
||||
static bool alsa_stop(void *data)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __alsa_set_nonblock_state(void *data, bool state)
|
||||
static void alsa_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
alsa_t *alsa = data;
|
||||
alsa->nonblock = state;
|
||||
}
|
||||
|
||||
static bool __alsa_start(void *data)
|
||||
static bool alsa_start(void *data)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __alsa_free(void *data)
|
||||
static void alsa_free(void *data)
|
||||
{
|
||||
alsa_t *alsa = data;
|
||||
if ( alsa )
|
||||
if (alsa)
|
||||
{
|
||||
if ( alsa->pcm )
|
||||
if (alsa->pcm)
|
||||
{
|
||||
snd_pcm_drop(alsa->pcm);
|
||||
snd_pcm_close(alsa->pcm);
|
||||
@@ -190,18 +190,13 @@ static void __alsa_free(void *data)
|
||||
}
|
||||
|
||||
const audio_driver_t audio_alsa = {
|
||||
.init = __alsa_init,
|
||||
.write = __alsa_write,
|
||||
.stop = __alsa_stop,
|
||||
.start = __alsa_start,
|
||||
.use_float = __alsa_use_float,
|
||||
.set_nonblock_state = __alsa_set_nonblock_state,
|
||||
.free = __alsa_free,
|
||||
.init = alsa_init,
|
||||
.write = alsa_write,
|
||||
.stop = alsa_stop,
|
||||
.start = alsa_start,
|
||||
.use_float = alsa_use_float,
|
||||
.set_nonblock_state = alsa_set_nonblock_state,
|
||||
.free = alsa_free,
|
||||
.ident = "alsa"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,98 @@
|
||||
/* 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 CHANNELS 2
|
||||
|
||||
struct hermite_resampler
|
||||
{
|
||||
float chan_data[CHANNELS][4];
|
||||
double r_frac;
|
||||
};
|
||||
|
||||
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(void)
|
||||
{
|
||||
hermite_resampler_t *re = calloc(1, sizeof(*re));
|
||||
if (!re)
|
||||
return NULL;
|
||||
return re;
|
||||
}
|
||||
|
||||
// We make sure to allocate enough output data beforehand ... ;)
|
||||
void hermite_process(hermite_resampler_t *re, struct hermite_data *data)
|
||||
{
|
||||
double r_step = 1.0 / data->ratio;
|
||||
size_t processed_out = 0;
|
||||
|
||||
size_t in_frames = data->input_frames;
|
||||
const float *in_data = data->data_in;
|
||||
float *out_data = data->data_out;
|
||||
|
||||
for (size_t i = 0; i < in_frames; i++)
|
||||
{
|
||||
while (re->r_frac <= 1.0)
|
||||
{
|
||||
re->r_frac += r_step;
|
||||
for (unsigned i = 0; i < CHANNELS; 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++;
|
||||
}
|
||||
|
||||
re->r_frac -= 1.0;
|
||||
for (unsigned i = 0; i < CHANNELS; 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++;
|
||||
}
|
||||
}
|
||||
|
||||
data->output_frames = processed_out;
|
||||
}
|
||||
|
||||
void hermite_free(hermite_resampler_t *re)
|
||||
{
|
||||
free(re);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/* 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(void);
|
||||
|
||||
struct hermite_data
|
||||
{
|
||||
const float *data_in;
|
||||
float *data_out;
|
||||
|
||||
size_t input_frames;
|
||||
size_t output_frames;
|
||||
|
||||
double ratio;
|
||||
};
|
||||
|
||||
void hermite_process(hermite_resampler_t *re, struct hermite_data *data);
|
||||
void hermite_free(hermite_resampler_t *re);
|
||||
|
||||
#endif
|
||||
|
||||
+40
-33
@@ -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 *ja_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;
|
||||
|
||||
@@ -239,32 +246,32 @@ static size_t write_buffer(jack_t *jd, const float *buf, size_t size)
|
||||
return written * sizeof(float) * 2;
|
||||
}
|
||||
|
||||
static ssize_t __jack_write(void* data, const void* buf, size_t size)
|
||||
static ssize_t ja_write(void *data, const void *buf, size_t size)
|
||||
{
|
||||
jack_t *jd = data;
|
||||
|
||||
return write_buffer(jd, buf, size);
|
||||
}
|
||||
|
||||
static bool __jack_stop(void *data)
|
||||
static bool ja_stop(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __jack_set_nonblock_state(void *data, bool state)
|
||||
static void ja_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
jack_t *jd = data;
|
||||
jd->nonblock = state;
|
||||
}
|
||||
|
||||
static bool __jack_start(void *data)
|
||||
static bool ja_start(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __jack_free(void *data)
|
||||
static void ja_free(void *data)
|
||||
{
|
||||
jack_t *jd = data;
|
||||
|
||||
@@ -285,20 +292,20 @@ static void __jack_free(void *data)
|
||||
free(jd);
|
||||
}
|
||||
|
||||
static bool __jack_use_float(void *data)
|
||||
static bool ja_use_float(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
const audio_driver_t audio_jack = {
|
||||
.init = __jack_init,
|
||||
.write = __jack_write,
|
||||
.stop = __jack_stop,
|
||||
.start = __jack_start,
|
||||
.set_nonblock_state = __jack_set_nonblock_state,
|
||||
.free = __jack_free,
|
||||
.use_float = __jack_use_float,
|
||||
.init = ja_init,
|
||||
.write = ja_write,
|
||||
.stop = ja_stop,
|
||||
.start = ja_start,
|
||||
.set_nonblock_state = ja_set_nonblock_state,
|
||||
.free = ja_free,
|
||||
.use_float = ja_use_float,
|
||||
.ident = "jack"
|
||||
};
|
||||
|
||||
|
||||
+23
-17
@@ -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,11 +56,11 @@ 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));
|
||||
if ( al == NULL )
|
||||
if (!al)
|
||||
return NULL;
|
||||
|
||||
al->handle = alcOpenDevice(NULL);
|
||||
@@ -126,7 +133,7 @@ static bool al_get_buffer(al_t *al, ALuint *buffer)
|
||||
};
|
||||
#endif
|
||||
|
||||
for(;;)
|
||||
for (;;)
|
||||
{
|
||||
if (al_unqueue_buffers(al))
|
||||
break;
|
||||
@@ -146,7 +153,7 @@ static bool al_get_buffer(al_t *al, ALuint *buffer)
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t al_fill_internal_buf(al_t *al, const void* buf, size_t size)
|
||||
static size_t al_fill_internal_buf(al_t *al, const void *buf, size_t size)
|
||||
{
|
||||
size_t read_size = (BUFSIZE - al->tmpbuf_ptr > size) ? size : (BUFSIZE - al->tmpbuf_ptr);
|
||||
memcpy(al->tmpbuf + al->tmpbuf_ptr, buf, read_size);
|
||||
@@ -154,7 +161,7 @@ static size_t al_fill_internal_buf(al_t *al, const void* buf, size_t size)
|
||||
return read_size;
|
||||
}
|
||||
|
||||
static ssize_t __al_write(void* data, const void* buf, size_t size)
|
||||
static ssize_t al_write(void *data, const void *buf, size_t size)
|
||||
{
|
||||
al_t *al = data;
|
||||
|
||||
@@ -189,32 +196,32 @@ static ssize_t __al_write(void* data, const void* buf, size_t size)
|
||||
return size;
|
||||
}
|
||||
|
||||
static bool __al_stop(void *data)
|
||||
static bool al_stop(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __al_set_nonblock_state(void *data, bool state)
|
||||
static void al_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
al_t *al = data;
|
||||
al->nonblock = state;
|
||||
}
|
||||
|
||||
static bool __al_start(void *data)
|
||||
static bool al_start(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __al_free(void *data)
|
||||
static void al_free(void *data)
|
||||
{
|
||||
al_t *al= data;
|
||||
if (al)
|
||||
{
|
||||
alSourceStop(al->source);
|
||||
alDeleteSources(1, &al->source);
|
||||
if ( al->buffers )
|
||||
if (al->buffers)
|
||||
{
|
||||
alDeleteBuffers(al->num_buffers, al->buffers);
|
||||
free(al->buffers);
|
||||
@@ -228,13 +235,12 @@ static void __al_free(void *data)
|
||||
}
|
||||
|
||||
const audio_driver_t audio_openal = {
|
||||
.init = __al_init,
|
||||
.write = __al_write,
|
||||
.stop = __al_stop,
|
||||
.start = __al_start,
|
||||
.set_nonblock_state = __al_set_nonblock_state,
|
||||
.free = __al_free,
|
||||
.init = al_init,
|
||||
.write = al_write,
|
||||
.stop = al_stop,
|
||||
.start = al_start,
|
||||
.set_nonblock_state = al_set_nonblock_state,
|
||||
.free = al_free,
|
||||
.ident = "openal"
|
||||
};
|
||||
|
||||
|
||||
+41
-29
@@ -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);
|
||||
@@ -79,17 +96,17 @@ static void* __oss_init(const char* device, int rate, int latency)
|
||||
return fd;
|
||||
}
|
||||
|
||||
static ssize_t __oss_write(void* data, const void* buf, size_t size)
|
||||
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;
|
||||
}
|
||||
@@ -97,19 +114,19 @@ static ssize_t __oss_write(void* data, const void* buf, size_t size)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool __oss_stop(void *data)
|
||||
static bool oss_stop(void *data)
|
||||
{
|
||||
int *fd = data;
|
||||
ioctl(*fd, SNDCTL_DSP_RESET, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool __oss_start(void *data)
|
||||
static bool oss_start(void *data)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __oss_set_nonblock_state(void *data, bool state)
|
||||
static void oss_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
int *fd = data;
|
||||
int rc;
|
||||
@@ -121,7 +138,7 @@ static void __oss_set_nonblock_state(void *data, bool state)
|
||||
fprintf(stderr, "SSNES [ERROR]: Could not set nonblocking on OSS file descriptor. Will not be able to fast-forward.\n");
|
||||
}
|
||||
|
||||
static void __oss_free(void *data)
|
||||
static void oss_free(void *data)
|
||||
{
|
||||
int *fd = data;
|
||||
|
||||
@@ -131,17 +148,12 @@ static void __oss_free(void *data)
|
||||
}
|
||||
|
||||
const audio_driver_t audio_oss = {
|
||||
.init = __oss_init,
|
||||
.write = __oss_write,
|
||||
.stop = __oss_stop,
|
||||
.start = __oss_start,
|
||||
.set_nonblock_state = __oss_set_nonblock_state,
|
||||
.free = __oss_free,
|
||||
.init = oss_init,
|
||||
.write = oss_write,
|
||||
.stop = oss_stop,
|
||||
.start = oss_start,
|
||||
.set_nonblock_state = oss_set_nonblock_state,
|
||||
.free = oss_free,
|
||||
.ident = "oss"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
/* 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 <pulse/pulseaudio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
pa_threaded_mainloop *mainloop;
|
||||
pa_context *context;
|
||||
pa_stream *stream;
|
||||
bool nonblock;
|
||||
} pa_t;
|
||||
|
||||
static void pulse_free(void *data)
|
||||
{
|
||||
pa_t *pa = data;
|
||||
if (pa)
|
||||
{
|
||||
if (pa->mainloop)
|
||||
pa_threaded_mainloop_stop(pa->mainloop);
|
||||
|
||||
if (pa->stream)
|
||||
{
|
||||
pa_stream_disconnect(pa->stream);
|
||||
pa_stream_unref(pa->stream);
|
||||
}
|
||||
|
||||
if (pa->context)
|
||||
{
|
||||
pa_context_disconnect(pa->context);
|
||||
pa_context_unref(pa->context);
|
||||
}
|
||||
|
||||
if (pa->mainloop)
|
||||
pa_threaded_mainloop_free(pa->mainloop);
|
||||
|
||||
free(pa);
|
||||
}
|
||||
}
|
||||
|
||||
static void context_state_cb(pa_context *c, void *data)
|
||||
{
|
||||
pa_t *pa = data;
|
||||
switch (pa_context_get_state(c))
|
||||
{
|
||||
case PA_CONTEXT_READY:
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
case PA_CONTEXT_FAILED:
|
||||
pa_threaded_mainloop_signal(pa->mainloop, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void stream_state_cb(pa_stream *s, void *data)
|
||||
{
|
||||
pa_t *pa = data;
|
||||
switch (pa_stream_get_state(s)) {
|
||||
case PA_STREAM_READY:
|
||||
case PA_STREAM_FAILED:
|
||||
case PA_STREAM_TERMINATED:
|
||||
pa_threaded_mainloop_signal(pa->mainloop, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void stream_request_cb(pa_stream *s, size_t length, void *data)
|
||||
{
|
||||
(void)length;
|
||||
(void)s;
|
||||
pa_t *pa = data;
|
||||
pa_threaded_mainloop_signal(pa->mainloop, 0);
|
||||
}
|
||||
|
||||
static void stream_latency_update_cb(pa_stream *s, void *data)
|
||||
{
|
||||
(void)s;
|
||||
pa_t *pa = data;
|
||||
pa_threaded_mainloop_signal(pa->mainloop, 0);
|
||||
}
|
||||
|
||||
static void *pulse_init(const char *device, unsigned rate, unsigned latency)
|
||||
{
|
||||
pa_t *pa = calloc(1, sizeof(*pa));
|
||||
if (!pa)
|
||||
goto error;
|
||||
|
||||
pa->mainloop = pa_threaded_mainloop_new();
|
||||
if (!pa->mainloop)
|
||||
goto error;
|
||||
|
||||
pa->context = pa_context_new(pa_threaded_mainloop_get_api(pa->mainloop), "SSNES");
|
||||
if (!pa->context)
|
||||
goto error;
|
||||
|
||||
pa_context_set_state_callback(pa->context, context_state_cb, pa);
|
||||
|
||||
if (pa_context_connect(pa->context, device, PA_CONTEXT_NOFLAGS, NULL) < 0)
|
||||
goto error;
|
||||
|
||||
pa_threaded_mainloop_lock(pa->mainloop);
|
||||
if (pa_threaded_mainloop_start(pa->mainloop) < 0)
|
||||
goto error;
|
||||
|
||||
pa_threaded_mainloop_wait(pa->mainloop);
|
||||
|
||||
if (pa_context_get_state(pa->context) != PA_CONTEXT_READY)
|
||||
goto unlock_error;
|
||||
|
||||
pa_sample_spec spec = {
|
||||
.format = is_little_endian() ? PA_SAMPLE_FLOAT32LE : PA_SAMPLE_FLOAT32BE,
|
||||
.channels = 2,
|
||||
.rate = rate
|
||||
};
|
||||
|
||||
pa->stream = pa_stream_new(pa->context, "audio", &spec, NULL);
|
||||
if (!pa->stream)
|
||||
goto unlock_error;
|
||||
|
||||
pa_stream_set_state_callback(pa->stream, stream_state_cb, pa);
|
||||
pa_stream_set_write_callback(pa->stream, stream_request_cb, pa);
|
||||
pa_stream_set_latency_update_callback(pa->stream, stream_latency_update_cb, pa);
|
||||
|
||||
pa_buffer_attr buffer_attr = {
|
||||
.maxlength = -1,
|
||||
.tlength = pa_usec_to_bytes(latency * PA_USEC_PER_MSEC, &spec),
|
||||
.prebuf = -1,
|
||||
.minreq = -1,
|
||||
.fragsize = -1
|
||||
};
|
||||
|
||||
if (pa_stream_connect_playback(pa->stream, NULL, &buffer_attr, PA_STREAM_ADJUST_LATENCY, NULL, NULL) < 0)
|
||||
goto error;
|
||||
|
||||
pa_threaded_mainloop_wait(pa->mainloop);
|
||||
|
||||
if (pa_stream_get_state(pa->stream) != PA_STREAM_READY)
|
||||
goto unlock_error;
|
||||
|
||||
pa_threaded_mainloop_unlock(pa->mainloop);
|
||||
|
||||
return pa;
|
||||
|
||||
unlock_error:
|
||||
pa_threaded_mainloop_unlock(pa->mainloop);
|
||||
error:
|
||||
pulse_free(pa);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ssize_t pulse_write(void *data, const void *buf, size_t size)
|
||||
{
|
||||
pa_t *pa = data;
|
||||
|
||||
pa_threaded_mainloop_lock(pa->mainloop);
|
||||
unsigned length = pa_stream_writable_size(pa->stream);
|
||||
pa_threaded_mainloop_unlock(pa->mainloop);
|
||||
while (length < size)
|
||||
{
|
||||
pa_threaded_mainloop_wait(pa->mainloop);
|
||||
pa_threaded_mainloop_lock(pa->mainloop);
|
||||
length = pa_stream_writable_size(pa->stream);
|
||||
pa_threaded_mainloop_unlock(pa->mainloop);
|
||||
|
||||
if (pa->nonblock)
|
||||
break;
|
||||
}
|
||||
|
||||
size_t write_size = length < size ? length : size;
|
||||
|
||||
pa_threaded_mainloop_lock(pa->mainloop);
|
||||
pa_stream_write(pa->stream, buf, write_size, NULL, 0LL, PA_SEEK_RELATIVE);
|
||||
pa_threaded_mainloop_unlock(pa->mainloop);
|
||||
return write_size;
|
||||
}
|
||||
|
||||
static bool pulse_stop(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool pulse_start(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void pulse_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
pa_t *pa = data;
|
||||
pa->nonblock = state;
|
||||
}
|
||||
|
||||
static bool pulse_use_float(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
const audio_driver_t audio_pulse = {
|
||||
.init = pulse_init,
|
||||
.write = pulse_write,
|
||||
.stop = pulse_stop,
|
||||
.start = pulse_start,
|
||||
.set_nonblock_state = pulse_set_nonblock_state,
|
||||
.use_float = pulse_use_float,
|
||||
.free = pulse_free,
|
||||
.ident = "pulse"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+23
-19
@@ -22,6 +22,7 @@
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include "general.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
@@ -29,44 +30,47 @@ typedef struct
|
||||
bool nonblocking;
|
||||
} roar_t;
|
||||
|
||||
static void* __roar_init(const char* device, int rate, int latency)
|
||||
static void *ra_init(const char *device, unsigned rate, unsigned latency)
|
||||
{
|
||||
(void)latency;
|
||||
int err;
|
||||
roar_t *roar = calloc(1, sizeof(roar_t));
|
||||
if (roar == NULL)
|
||||
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;
|
||||
}
|
||||
|
||||
static ssize_t __roar_write(void* data, const void* buf, size_t size)
|
||||
static ssize_t ra_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;
|
||||
}
|
||||
@@ -74,13 +78,13 @@ static ssize_t __roar_write(void* data, const void* buf, size_t size)
|
||||
return size;
|
||||
}
|
||||
|
||||
static bool __roar_stop(void *data)
|
||||
static bool ra_stop(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __roar_set_nonblock_state(void *data, bool state)
|
||||
static void ra_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
roar_t *roar = data;
|
||||
if (roar_vs_blocking(roar->vss, (state) ? ROAR_VS_FALSE : ROAR_VS_TRUE, NULL) < 0)
|
||||
@@ -88,13 +92,13 @@ static void __roar_set_nonblock_state(void *data, bool state)
|
||||
roar->nonblocking = state;
|
||||
}
|
||||
|
||||
static bool __roar_start(void *data)
|
||||
static bool ra_start(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __roar_free(void *data)
|
||||
static void ra_free(void *data)
|
||||
{
|
||||
roar_t *roar = data;
|
||||
roar_vs_close(roar->vss, ROAR_VS_TRUE, NULL);
|
||||
@@ -102,12 +106,12 @@ static void __roar_free(void *data)
|
||||
}
|
||||
|
||||
const audio_driver_t audio_roar = {
|
||||
.init = __roar_init,
|
||||
.write = __roar_write,
|
||||
.stop = __roar_stop,
|
||||
.start = __roar_start,
|
||||
.set_nonblock_state = __roar_set_nonblock_state,
|
||||
.free = __roar_free,
|
||||
.init = ra_init,
|
||||
.write = ra_write,
|
||||
.stop = ra_stop,
|
||||
.start = ra_start,
|
||||
.set_nonblock_state = ra_set_nonblock_state,
|
||||
.free = ra_free,
|
||||
.ident = "roar"
|
||||
};
|
||||
|
||||
|
||||
+94
-47
@@ -19,85 +19,132 @@
|
||||
#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 *rs_init(const char *device, unsigned rate, unsigned latency)
|
||||
{
|
||||
rsd_t *rsd = calloc(1, sizeof(rsd_t));
|
||||
if ( rsd == NULL )
|
||||
if (!rsd)
|
||||
return NULL;
|
||||
|
||||
rsound_t *rd;
|
||||
|
||||
if ( rsd_init(&rd) < 0 )
|
||||
if (rsd_init(&rd) < 0)
|
||||
{
|
||||
free(rsd);
|
||||
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 )
|
||||
if (device)
|
||||
rsd_set_param(rd, RSD_HOST, (void*)device);
|
||||
|
||||
rsd_set_param(rd, RSD_FORMAT, &format);
|
||||
|
||||
if ( rsd_start(rd) < 0 )
|
||||
rsd_set_callback(rd, audio_cb, err_cb, 256, rsd);
|
||||
|
||||
if (rsd_start(rd) < 0)
|
||||
{
|
||||
free(rsd);
|
||||
rsd_free(rd);
|
||||
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;
|
||||
}
|
||||
|
||||
static ssize_t __rsd_write(void* data, const void* buf, size_t size)
|
||||
static ssize_t rs_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)
|
||||
static bool rs_stop(void *data)
|
||||
{
|
||||
rsd_t *rsd = data;
|
||||
rsd_stop(rsd->rd);
|
||||
@@ -105,42 +152,42 @@ static bool __rsd_stop(void *data)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __rsd_set_nonblock_state(void *data, bool state)
|
||||
static void rs_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
rsd_t *rsd = data;
|
||||
rsd->nonblock = state;
|
||||
}
|
||||
|
||||
static bool __rsd_start(void *data)
|
||||
static bool rs_start(void *data)
|
||||
{
|
||||
rsd_t *rsd = data;
|
||||
if ( rsd_start(rsd->rd) < 0)
|
||||
if (rsd_start(rsd->rd) < 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __rsd_free(void *data)
|
||||
static void rs_free(void *data)
|
||||
{
|
||||
rsd_t *rsd = data;
|
||||
|
||||
rsd_stop(rsd->rd);
|
||||
rsd_free(rsd->rd);
|
||||
|
||||
fifo_free(rsd->buffer);
|
||||
SDL_DestroyMutex(rsd->cond_lock);
|
||||
SDL_DestroyCond(rsd->cond);
|
||||
|
||||
free(rsd);
|
||||
}
|
||||
|
||||
const audio_driver_t audio_rsound = {
|
||||
.init = __rsd_init,
|
||||
.write = __rsd_write,
|
||||
.stop = __rsd_stop,
|
||||
.start = __rsd_start,
|
||||
.set_nonblock_state = __rsd_set_nonblock_state,
|
||||
.free = __rsd_free,
|
||||
.init = rs_init,
|
||||
.write = rs_write,
|
||||
.stop = rs_stop,
|
||||
.start = rs_start,
|
||||
.set_nonblock_state = rs_set_nonblock_state,
|
||||
.free = rs_free,
|
||||
.ident = "rsound"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+13
-31
@@ -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);
|
||||
}
|
||||
|
||||
@@ -122,7 +109,7 @@ static void* sdl_audio_init(const char* device, int rate, int latency)
|
||||
return sdl;
|
||||
}
|
||||
|
||||
static ssize_t sdl_audio_write(void* data, const void* buf, size_t size)
|
||||
static ssize_t sdl_audio_write(void *data, const void *buf, size_t size)
|
||||
{
|
||||
sdl_audio_t *sdl = data;
|
||||
|
||||
@@ -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"
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#ifndef AUDIO_UTILS_H
|
||||
#define AUDIO_UTILS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if __SSE2__
|
||||
#include <mmintrin.h>
|
||||
#include <emmintrin.h>
|
||||
#define audio_convert_s16_to_float audio_convert_s16_to_float_SSE2
|
||||
#define audio_convert_float_to_s16 audio_convert_float_to_s16_SSE2
|
||||
#else
|
||||
#define audio_convert_s16_to_float audio_convert_s16_to_float_C
|
||||
#define audio_convert_float_to_s16 audio_convert_float_to_s16_C
|
||||
#endif
|
||||
|
||||
static inline void audio_convert_s16_to_float_C(float *out,
|
||||
const int16_t *in, unsigned samples)
|
||||
{
|
||||
for (unsigned i = 0; i < samples; i++)
|
||||
out[i] = (float)in[i] / 0x8000;
|
||||
}
|
||||
|
||||
static inline void audio_convert_float_to_s16_C(int16_t *out,
|
||||
const float *in, unsigned samples)
|
||||
{
|
||||
for (unsigned i = 0; i < samples; i++)
|
||||
{
|
||||
int32_t val = in[i] * 0x8000;
|
||||
out[i] = (val > 0x7FFF) ? 0x7FFF : (val < -0x8000 ? -0x8000 : (int16_t)val);
|
||||
}
|
||||
}
|
||||
|
||||
#if __SSE2__
|
||||
static inline void audio_convert_s16_to_float_SSE2(float *out,
|
||||
const int16_t *in, unsigned samples)
|
||||
{
|
||||
__m128 factor = _mm_set1_ps(1.0f / (0x7fff * 0x10000));
|
||||
unsigned i;
|
||||
for (i = 0; i + 8 <= samples; i += 8, in += 8, out += 8)
|
||||
{
|
||||
__m128i input = _mm_loadu_si128((const __m128i *)in);
|
||||
__m128i regs[2] = {
|
||||
_mm_unpacklo_epi16(_mm_setzero_si128(), input),
|
||||
_mm_unpackhi_epi16(_mm_setzero_si128(), input),
|
||||
};
|
||||
|
||||
__m128 output[2] = {
|
||||
_mm_mul_ps(_mm_cvtepi32_ps(regs[0]), factor),
|
||||
_mm_mul_ps(_mm_cvtepi32_ps(regs[1]), factor),
|
||||
};
|
||||
|
||||
_mm_storeu_ps(out + 0, output[0]);
|
||||
_mm_storeu_ps(out + 4, output[1]);
|
||||
}
|
||||
|
||||
audio_convert_s16_to_float_C(out, in, samples - i);
|
||||
}
|
||||
|
||||
static inline void audio_convert_float_to_s16_SSE2(int16_t *out,
|
||||
const float *in, unsigned samples)
|
||||
{
|
||||
__m128 factor = _mm_set1_ps((float)0x7fff);
|
||||
unsigned i;
|
||||
for (i = 0; i + 8 <= samples; i += 8, in += 8, out += 8)
|
||||
{
|
||||
__m128 input[2] = { _mm_loadu_ps(in + 0), _mm_loadu_ps(in + 4) };
|
||||
__m128 res[2] = { _mm_mul_ps(input[0], factor), _mm_mul_ps(input[1], factor) };
|
||||
|
||||
__m128i ints[2] = { _mm_cvtps_epi32(res[0]), _mm_cvtps_epi32(res[1]) };
|
||||
__m128i packed = _mm_packs_epi32(ints[0], ints[1]);
|
||||
|
||||
_mm_storeu_si128((__m128i *)out, packed);
|
||||
}
|
||||
|
||||
audio_convert_float_to_s16_C(out, in, samples - i);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
Simple C interface for XAudio2
|
||||
Author: Hans-Kristian Arntzen
|
||||
License: Public Domain
|
||||
*/
|
||||
|
||||
#include "xaudio-c.h"
|
||||
#include "xaudio2.hpp"
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
|
||||
struct xaudio2 : public IXAudio2VoiceCallback
|
||||
{
|
||||
xaudio2(unsigned samplerate, unsigned channels, unsigned size) :
|
||||
buf(0), pXAudio2(0), pMasterVoice(0), pSourceVoice(0),
|
||||
buffers(0), bufptr(0)
|
||||
{
|
||||
CoInitializeEx(0, COINIT_MULTITHREADED);
|
||||
if (FAILED(XAudio2Create(&pXAudio2, 0)))
|
||||
throw -1;
|
||||
if (FAILED(pXAudio2->CreateMasteringVoice(
|
||||
&pMasterVoice, channels, samplerate, 0, 0, 0)))
|
||||
throw -1;
|
||||
|
||||
WAVEFORMATEX wfx;
|
||||
wfx.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
|
||||
wfx.nChannels = channels;
|
||||
wfx.nSamplesPerSec = samplerate;
|
||||
wfx.nBlockAlign = channels * sizeof(float);
|
||||
wfx.wBitsPerSample = sizeof(float) * 8;
|
||||
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
|
||||
wfx.cbSize = 0;
|
||||
if (FAILED(pXAudio2->CreateSourceVoice(
|
||||
&pSourceVoice, &wfx, XAUDIO2_VOICE_NOSRC, XAUDIO2_DEFAULT_FREQ_RATIO, this, 0, 0)))
|
||||
throw -1;
|
||||
|
||||
hEvent = CreateEvent(0, FALSE, FALSE, 0);
|
||||
if (!hEvent)
|
||||
throw -1;
|
||||
|
||||
pSourceVoice->Start(0);
|
||||
|
||||
bufsize = size / max_buffers;
|
||||
buf = new uint8_t[bufsize * max_buffers];
|
||||
write_buffer = 0;
|
||||
}
|
||||
|
||||
virtual ~xaudio2()
|
||||
{
|
||||
if (pSourceVoice)
|
||||
{
|
||||
pSourceVoice->Stop(0);
|
||||
pSourceVoice->DestroyVoice();
|
||||
}
|
||||
|
||||
if (pMasterVoice)
|
||||
pMasterVoice->DestroyVoice();
|
||||
|
||||
if (pXAudio2)
|
||||
pXAudio2->Release();
|
||||
|
||||
if (hEvent)
|
||||
CloseHandle(hEvent);
|
||||
|
||||
if (buf)
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
size_t write_avail() const
|
||||
{
|
||||
return bufsize * (max_buffers - buffers - 1);
|
||||
}
|
||||
|
||||
size_t write(const void *buffer_, size_t bytes_)
|
||||
{
|
||||
unsigned bytes = bytes_;
|
||||
const uint8_t *buffer = reinterpret_cast<const uint8_t*>(buffer_);
|
||||
while (bytes > 0)
|
||||
{
|
||||
unsigned need = std::min(bytes, bufsize - bufptr);
|
||||
memcpy(buf + write_buffer * bufsize + bufptr,
|
||||
buffer, need);
|
||||
|
||||
bufptr += need;
|
||||
buffer += need;
|
||||
bytes -= need;
|
||||
|
||||
if (bufptr == bufsize)
|
||||
{
|
||||
WaitForSingleObject(hEvent, 0); // Clear out ready state.
|
||||
while (buffers == max_buffers - 1)
|
||||
WaitForSingleObject(hEvent, INFINITE);
|
||||
|
||||
XAUDIO2_BUFFER xa2buffer = {0};
|
||||
xa2buffer.AudioBytes = bufsize;
|
||||
xa2buffer.pAudioData = buf + write_buffer * bufsize;
|
||||
xa2buffer.pContext = 0;
|
||||
if (FAILED(pSourceVoice->SubmitSourceBuffer(&xa2buffer)))
|
||||
return 0;
|
||||
|
||||
InterlockedIncrement(&buffers);
|
||||
bufptr = 0;
|
||||
write_buffer = (write_buffer + 1) & (max_buffers_mask);
|
||||
}
|
||||
}
|
||||
|
||||
return bytes_;
|
||||
}
|
||||
|
||||
enum { max_buffers = 16, max_buffers_mask = max_buffers - 1 };
|
||||
|
||||
STDMETHOD_(void, OnBufferStart) (void *) {}
|
||||
STDMETHOD_(void, OnBufferEnd) (void *)
|
||||
{
|
||||
InterlockedDecrement(&buffers);
|
||||
SetEvent(hEvent);
|
||||
}
|
||||
STDMETHOD_(void, OnLoopEnd) (void *) {}
|
||||
STDMETHOD_(void, OnStreamEnd) () {}
|
||||
STDMETHOD_(void, OnVoiceError) (void *, HRESULT) {}
|
||||
STDMETHOD_(void, OnVoiceProcessingPassEnd) () {}
|
||||
STDMETHOD_(void, OnVoiceProcessingPassStart) (UINT32) {}
|
||||
|
||||
uint8_t *buf;
|
||||
IXAudio2 *pXAudio2;
|
||||
IXAudio2MasteringVoice *pMasterVoice;
|
||||
IXAudio2SourceVoice *pSourceVoice;
|
||||
HANDLE hEvent;
|
||||
|
||||
volatile long buffers;
|
||||
unsigned bufsize;
|
||||
unsigned bufptr;
|
||||
unsigned write_buffer;
|
||||
};
|
||||
|
||||
xaudio2_t *xaudio2_new(unsigned samplerate, unsigned channels, size_t size)
|
||||
{
|
||||
xaudio2 *handle = 0;
|
||||
try {
|
||||
handle = new xaudio2(samplerate, channels, size);
|
||||
} catch(...) { delete handle; return 0; }
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
size_t xaudio2_write_avail(xaudio2_t *handle)
|
||||
{
|
||||
try {
|
||||
return handle->write_avail();
|
||||
} catch(...) { return 0; }
|
||||
}
|
||||
|
||||
size_t xaudio2_write(xaudio2_t *handle, const void *buf, size_t bytes)
|
||||
{
|
||||
try {
|
||||
return handle->write(buf, bytes);
|
||||
} catch(...) { return 0; }
|
||||
}
|
||||
|
||||
void xaudio2_free(xaudio2_t *handle)
|
||||
{
|
||||
delete handle;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Simple C interface for XAudio2
|
||||
Author: Hans-Kristian Arntzen
|
||||
License: Public Domain
|
||||
*/
|
||||
|
||||
#ifndef XAUDIO_C_H
|
||||
#define XAUDIO_C_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct xaudio2 xaudio2_t;
|
||||
|
||||
xaudio2_t* xaudio2_new(unsigned samplerate, unsigned channels, size_t bufsize);
|
||||
size_t xaudio2_write_avail(xaudio2_t *handle);
|
||||
size_t xaudio2_write(xaudio2_t *handle, const void *data, size_t bytes);
|
||||
void xaudio2_free(xaudio2_t *handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Executable
+329
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
xaudio2.hpp (2010-08-14)
|
||||
author: OV2
|
||||
*/
|
||||
|
||||
#ifndef XAUDIO2_MINGW_H
|
||||
#define XAUDIO2_MINGW_H
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#undef min
|
||||
#undef max
|
||||
#include <basetyps.h>
|
||||
#include <objbase.h>
|
||||
#include <mmreg.h>
|
||||
|
||||
// 64-bit GCC fix
|
||||
#define GUID_EXT EXTERN_C
|
||||
#define GUID_SECT
|
||||
|
||||
#define DEFINE_GUID_X(n,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) GUID_EXT const GUID n GUID_SECT = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
|
||||
#define DEFINE_CLSID_X(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
||||
DEFINE_GUID_X(CLSID_##className, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8)
|
||||
#define DEFINE_IID_X(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
||||
DEFINE_GUID_X(IID_##interfaceName, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8)
|
||||
#define X2DEFAULT(x) =x
|
||||
|
||||
DEFINE_CLSID_X(XAudio2, e21a7345, eb21, 468e, be, 50, 80, 4d, b9, 7c, f7, 08);
|
||||
DEFINE_CLSID_X(XAudio2_Debug, f7a76c21, 53d4, 46bb, ac, 53, 8b, 45, 9c, ae, 46, bd);
|
||||
DEFINE_IID_X(IXAudio2, 8bcf1f58, 9fe7, 4583, 8a, c6, e2, ad, c4, 65, c8, bb);
|
||||
|
||||
DECLARE_INTERFACE(IXAudio2Voice);
|
||||
|
||||
#define XAUDIO2_COMMIT_NOW 0
|
||||
#define XAUDIO2_DEFAULT_CHANNELS 0
|
||||
#define XAUDIO2_DEFAULT_SAMPLERATE 0
|
||||
#define XAUDIO2_DEFAULT_FREQ_RATIO 4.0f
|
||||
#define XAUDIO2_DEBUG_ENGINE 0x0001
|
||||
#define XAUDIO2_VOICE_NOSRC 0x0004
|
||||
|
||||
typedef enum XAUDIO2_DEVICE_ROLE
|
||||
{
|
||||
NotDefaultDevice = 0x0,
|
||||
DefaultConsoleDevice = 0x1,
|
||||
DefaultMultimediaDevice = 0x2,
|
||||
DefaultCommunicationsDevice = 0x4,
|
||||
DefaultGameDevice = 0x8,
|
||||
GlobalDefaultDevice = 0xf,
|
||||
InvalidDeviceRole = ~GlobalDefaultDevice
|
||||
} XAUDIO2_DEVICE_ROLE;
|
||||
|
||||
typedef struct XAUDIO2_DEVICE_DETAILS
|
||||
{
|
||||
WCHAR DeviceID[256];
|
||||
WCHAR DisplayName[256];
|
||||
XAUDIO2_DEVICE_ROLE Role;
|
||||
WAVEFORMATEXTENSIBLE OutputFormat;
|
||||
} XAUDIO2_DEVICE_DETAILS;
|
||||
|
||||
typedef struct XAUDIO2_VOICE_DETAILS
|
||||
{
|
||||
UINT32 CreationFlags;
|
||||
UINT32 InputChannels;
|
||||
UINT32 InputSampleRate;
|
||||
} XAUDIO2_VOICE_DETAILS;
|
||||
|
||||
typedef enum XAUDIO2_WINDOWS_PROCESSOR_SPECIFIER
|
||||
{
|
||||
Processor1 = 0x00000001,
|
||||
Processor2 = 0x00000002,
|
||||
Processor3 = 0x00000004,
|
||||
Processor4 = 0x00000008,
|
||||
Processor5 = 0x00000010,
|
||||
Processor6 = 0x00000020,
|
||||
Processor7 = 0x00000040,
|
||||
Processor8 = 0x00000080,
|
||||
Processor9 = 0x00000100,
|
||||
Processor10 = 0x00000200,
|
||||
Processor11 = 0x00000400,
|
||||
Processor12 = 0x00000800,
|
||||
Processor13 = 0x00001000,
|
||||
Processor14 = 0x00002000,
|
||||
Processor15 = 0x00004000,
|
||||
Processor16 = 0x00008000,
|
||||
Processor17 = 0x00010000,
|
||||
Processor18 = 0x00020000,
|
||||
Processor19 = 0x00040000,
|
||||
Processor20 = 0x00080000,
|
||||
Processor21 = 0x00100000,
|
||||
Processor22 = 0x00200000,
|
||||
Processor23 = 0x00400000,
|
||||
Processor24 = 0x00800000,
|
||||
Processor25 = 0x01000000,
|
||||
Processor26 = 0x02000000,
|
||||
Processor27 = 0x04000000,
|
||||
Processor28 = 0x08000000,
|
||||
Processor29 = 0x10000000,
|
||||
Processor30 = 0x20000000,
|
||||
Processor31 = 0x40000000,
|
||||
Processor32 = 0x80000000,
|
||||
XAUDIO2_ANY_PROCESSOR = 0xffffffff,
|
||||
XAUDIO2_DEFAULT_PROCESSOR = XAUDIO2_ANY_PROCESSOR
|
||||
} XAUDIO2_WINDOWS_PROCESSOR_SPECIFIER, XAUDIO2_PROCESSOR;
|
||||
|
||||
typedef struct XAUDIO2_VOICE_SENDS
|
||||
{
|
||||
UINT32 OutputCount;
|
||||
IXAudio2Voice** pOutputVoices;
|
||||
} XAUDIO2_VOICE_SENDS;
|
||||
|
||||
typedef struct XAUDIO2_EFFECT_DESCRIPTOR
|
||||
{
|
||||
IUnknown* pEffect;
|
||||
BOOL InitialState;
|
||||
UINT32 OutputChannels;
|
||||
} XAUDIO2_EFFECT_DESCRIPTOR;
|
||||
|
||||
typedef struct XAUDIO2_EFFECT_CHAIN
|
||||
{
|
||||
UINT32 EffectCount;
|
||||
const XAUDIO2_EFFECT_DESCRIPTOR* pEffectDescriptors;
|
||||
} XAUDIO2_EFFECT_CHAIN;
|
||||
|
||||
typedef enum XAUDIO2_FILTER_TYPE
|
||||
{
|
||||
LowPassFilter,
|
||||
BandPassFilter,
|
||||
HighPassFilter
|
||||
} XAUDIO2_FILTER_TYPE;
|
||||
|
||||
typedef struct XAUDIO2_FILTER_PARAMETERS
|
||||
{
|
||||
XAUDIO2_FILTER_TYPE Type;
|
||||
float Frequency;
|
||||
float OneOverQ;
|
||||
|
||||
} XAUDIO2_FILTER_PARAMETERS;
|
||||
|
||||
typedef struct XAUDIO2_BUFFER
|
||||
{
|
||||
UINT32 Flags;
|
||||
UINT32 AudioBytes;
|
||||
const BYTE* pAudioData;
|
||||
UINT32 PlayBegin;
|
||||
UINT32 PlayLength;
|
||||
UINT32 LoopBegin;
|
||||
UINT32 LoopLength;
|
||||
UINT32 LoopCount;
|
||||
void *pContext;
|
||||
} XAUDIO2_BUFFER;
|
||||
|
||||
typedef struct XAUDIO2_BUFFER_WMA
|
||||
{
|
||||
const UINT32* pDecodedPacketCumulativeBytes;
|
||||
UINT32 PacketCount;
|
||||
} XAUDIO2_BUFFER_WMA;
|
||||
|
||||
typedef struct XAUDIO2_VOICE_STATE
|
||||
{
|
||||
void *pCurrentBufferContext;
|
||||
UINT32 BuffersQueued;
|
||||
UINT64 SamplesPlayed;
|
||||
} XAUDIO2_VOICE_STATE;
|
||||
|
||||
typedef struct XAUDIO2_PERFORMANCE_DATA
|
||||
{
|
||||
UINT64 AudioCyclesSinceLastQuery;
|
||||
UINT64 TotalCyclesSinceLastQuery;
|
||||
UINT32 MinimumCyclesPerQuantum;
|
||||
UINT32 MaximumCyclesPerQuantum;
|
||||
UINT32 MemoryUsageInBytes;
|
||||
UINT32 CurrentLatencyInSamples;
|
||||
UINT32 GlitchesSinceEngineStarted;
|
||||
UINT32 ActiveSourceVoiceCount;
|
||||
UINT32 TotalSourceVoiceCount;
|
||||
UINT32 ActiveSubmixVoiceCount;
|
||||
UINT32 TotalSubmixVoiceCount;
|
||||
UINT32 ActiveXmaSourceVoices;
|
||||
UINT32 ActiveXmaStreams;
|
||||
} XAUDIO2_PERFORMANCE_DATA;
|
||||
|
||||
typedef struct XAUDIO2_DEBUG_CONFIGURATION
|
||||
{
|
||||
UINT32 TraceMask;
|
||||
UINT32 BreakMask;
|
||||
BOOL LogThreadID;
|
||||
BOOL LogFileline;
|
||||
BOOL LogFunctionName;
|
||||
BOOL LogTiming;
|
||||
} XAUDIO2_DEBUG_CONFIGURATION;
|
||||
|
||||
DECLARE_INTERFACE(IXAudio2EngineCallback)
|
||||
{
|
||||
STDMETHOD_(void, OnProcessingPassStart) (THIS) PURE;
|
||||
STDMETHOD_(void, OnProcessingPassEnd) (THIS) PURE;
|
||||
STDMETHOD_(void, OnCriticalError) (THIS_ HRESULT Error) PURE;
|
||||
};
|
||||
|
||||
DECLARE_INTERFACE(IXAudio2VoiceCallback)
|
||||
{
|
||||
STDMETHOD_(void, OnVoiceProcessingPassStart) (THIS_ UINT32 BytesRequired) PURE;
|
||||
STDMETHOD_(void, OnVoiceProcessingPassEnd) (THIS) PURE;
|
||||
STDMETHOD_(void, OnStreamEnd) (THIS) PURE;
|
||||
STDMETHOD_(void, OnBufferStart) (THIS_ void *pBufferContext) PURE;
|
||||
STDMETHOD_(void, OnBufferEnd) (THIS_ void *pBufferContext) PURE;
|
||||
STDMETHOD_(void, OnLoopEnd) (THIS_ void *pBufferContext) PURE;
|
||||
STDMETHOD_(void, OnVoiceError) (THIS_ void *pBufferContext, HRESULT Error) PURE;
|
||||
};
|
||||
|
||||
DECLARE_INTERFACE(IXAudio2Voice)
|
||||
{
|
||||
#define Declare_IXAudio2Voice_Methods() \
|
||||
STDMETHOD_(void, GetVoiceDetails) (THIS_ XAUDIO2_VOICE_DETAILS* pVoiceDetails) PURE; \
|
||||
STDMETHOD(SetOutputVoices) (THIS_ const XAUDIO2_VOICE_SENDS* pSendList) PURE; \
|
||||
STDMETHOD(SetEffectChain) (THIS_ const XAUDIO2_EFFECT_CHAIN* pEffectChain) PURE; \
|
||||
STDMETHOD(EnableEffect) (THIS_ UINT32 EffectIndex, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD(DisableEffect) (THIS_ UINT32 EffectIndex, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD_(void, GetEffectState) (THIS_ UINT32 EffectIndex, BOOL* pEnabled) PURE; \
|
||||
STDMETHOD(SetEffectParameters) (THIS_ UINT32 EffectIndex, \
|
||||
const void *pParameters, \
|
||||
UINT32 ParametersByteSize, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD(GetEffectParameters) (THIS_ UINT32 EffectIndex, void *pParameters, \
|
||||
UINT32 ParametersByteSize) PURE; \
|
||||
STDMETHOD(SetFilterParameters) (THIS_ const XAUDIO2_FILTER_PARAMETERS* pParameters, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD_(void, GetFilterParameters) (THIS_ XAUDIO2_FILTER_PARAMETERS* pParameters) PURE; \
|
||||
STDMETHOD(SetVolume) (THIS_ float Volume, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD_(void, GetVolume) (THIS_ float* pVolume) PURE; \
|
||||
STDMETHOD(SetChannelVolumes) (THIS_ UINT32 Channels, const float* pVolumes, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD_(void, GetChannelVolumes) (THIS_ UINT32 Channels, float* pVolumes) PURE; \
|
||||
STDMETHOD(SetOutputMatrix) (THIS_ IXAudio2Voice* pDestinationVoice, \
|
||||
UINT32 SourceChannels, UINT32 DestinationChannels, \
|
||||
const float* pLevelMatrix, \
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \
|
||||
STDMETHOD_(void, GetOutputMatrix) (THIS_ IXAudio2Voice* pDestinationVoice, \
|
||||
UINT32 SourceChannels, UINT32 DestinationChannels, \
|
||||
float* pLevelMatrix) PURE; \
|
||||
STDMETHOD_(void, DestroyVoice) (THIS) PURE
|
||||
|
||||
Declare_IXAudio2Voice_Methods();
|
||||
};
|
||||
|
||||
|
||||
DECLARE_INTERFACE_(IXAudio2MasteringVoice, IXAudio2Voice)
|
||||
{
|
||||
Declare_IXAudio2Voice_Methods();
|
||||
};
|
||||
|
||||
DECLARE_INTERFACE_(IXAudio2SubmixVoice, IXAudio2Voice)
|
||||
{
|
||||
Declare_IXAudio2Voice_Methods();
|
||||
};
|
||||
|
||||
DECLARE_INTERFACE_(IXAudio2SourceVoice, IXAudio2Voice)
|
||||
{
|
||||
Declare_IXAudio2Voice_Methods();
|
||||
STDMETHOD(Start) (THIS_ UINT32 Flags, UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE;
|
||||
STDMETHOD(Stop) (THIS_ UINT32 Flags, UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE;
|
||||
STDMETHOD(SubmitSourceBuffer) (THIS_ const XAUDIO2_BUFFER* pBuffer, const XAUDIO2_BUFFER_WMA* pBufferWMA X2DEFAULT(NULL)) PURE;
|
||||
STDMETHOD(FlushSourceBuffers) (THIS) PURE;
|
||||
STDMETHOD(Discontinuity) (THIS) PURE;
|
||||
STDMETHOD(ExitLoop) (THIS_ UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE;
|
||||
STDMETHOD_(void, GetState) (THIS_ XAUDIO2_VOICE_STATE* pVoiceState) PURE;
|
||||
STDMETHOD(SetFrequencyRatio) (THIS_ float Ratio,
|
||||
UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE;
|
||||
STDMETHOD_(void, GetFrequencyRatio) (THIS_ float* pRatio) PURE;
|
||||
};
|
||||
|
||||
DECLARE_INTERFACE_(IXAudio2, IUnknown)
|
||||
{
|
||||
STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvInterface) PURE;
|
||||
STDMETHOD_(ULONG, AddRef) (THIS) PURE;
|
||||
STDMETHOD_(ULONG, Release) (THIS) PURE;
|
||||
STDMETHOD(GetDeviceCount) (THIS_ UINT32* pCount) PURE;
|
||||
STDMETHOD(GetDeviceDetails) (THIS_ UINT32 Index, XAUDIO2_DEVICE_DETAILS* pDeviceDetails) PURE;
|
||||
STDMETHOD(Initialize) (THIS_ UINT32 Flags X2DEFAULT(0),
|
||||
XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR)) PURE;
|
||||
STDMETHOD(RegisterForCallbacks) (IXAudio2EngineCallback* pCallback) PURE;
|
||||
STDMETHOD_(void, UnregisterForCallbacks) (IXAudio2EngineCallback* pCallback) PURE;
|
||||
STDMETHOD(CreateSourceVoice) (THIS_ IXAudio2SourceVoice** ppSourceVoice,
|
||||
const WAVEFORMATEX* pSourceFormat,
|
||||
UINT32 Flags X2DEFAULT(0),
|
||||
float MaxFrequencyRatio X2DEFAULT(XAUDIO2_DEFAULT_FREQ_RATIO),
|
||||
IXAudio2VoiceCallback* pCallback X2DEFAULT(NULL),
|
||||
const XAUDIO2_VOICE_SENDS* pSendList X2DEFAULT(NULL),
|
||||
const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE;
|
||||
STDMETHOD(CreateSubmixVoice) (THIS_ IXAudio2SubmixVoice** ppSubmixVoice,
|
||||
UINT32 InputChannels, UINT32 InputSampleRate,
|
||||
UINT32 Flags X2DEFAULT(0), UINT32 ProcessingStage X2DEFAULT(0),
|
||||
const XAUDIO2_VOICE_SENDS* pSendList X2DEFAULT(NULL),
|
||||
const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE;
|
||||
STDMETHOD(CreateMasteringVoice) (THIS_ IXAudio2MasteringVoice** ppMasteringVoice,
|
||||
UINT32 InputChannels X2DEFAULT(XAUDIO2_DEFAULT_CHANNELS),
|
||||
UINT32 InputSampleRate X2DEFAULT(XAUDIO2_DEFAULT_SAMPLERATE),
|
||||
UINT32 Flags X2DEFAULT(0), UINT32 DeviceIndex X2DEFAULT(0),
|
||||
const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE;
|
||||
STDMETHOD(StartEngine) (THIS) PURE;
|
||||
STDMETHOD_(void, StopEngine) (THIS) PURE;
|
||||
STDMETHOD(CommitChanges) (THIS_ UINT32 OperationSet) PURE;
|
||||
STDMETHOD_(void, GetPerformanceData) (THIS_ XAUDIO2_PERFORMANCE_DATA* pPerfData) PURE;
|
||||
STDMETHOD_(void, SetDebugConfiguration) (THIS_ const XAUDIO2_DEBUG_CONFIGURATION* pDebugConfiguration,
|
||||
void *pReserved X2DEFAULT(NULL)) PURE;
|
||||
};
|
||||
|
||||
static inline HRESULT XAudio2Create(IXAudio2** ppXAudio2, UINT32 Flags X2DEFAULT(0),
|
||||
XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR))
|
||||
{
|
||||
IXAudio2* pXAudio2;
|
||||
HRESULT hr = CoCreateInstance((Flags & XAUDIO2_DEBUG_ENGINE) ? CLSID_XAudio2_Debug : CLSID_XAudio2,
|
||||
NULL, CLSCTX_INPROC_SERVER, IID_IXAudio2, reinterpret_cast<void**>(&pXAudio2));
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = pXAudio2->Initialize(Flags, XAudio2Processor);
|
||||
if (SUCCEEDED(hr))
|
||||
*ppXAudio2 = pXAudio2;
|
||||
else
|
||||
pXAudio2->Release();
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/* 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 "xaudio-c/xaudio-c.h"
|
||||
#include "general.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
xaudio2_t *xa;
|
||||
bool nonblock;
|
||||
} xa_t;
|
||||
|
||||
static void *xa_init(const char *device, unsigned rate, unsigned latency)
|
||||
{
|
||||
if (latency < 8)
|
||||
latency = 8; // Do not allow shenanigans.
|
||||
|
||||
xa_t *xa = calloc(1, sizeof(xa_t));
|
||||
if (xa == NULL)
|
||||
return NULL;
|
||||
|
||||
size_t bufsize = latency * rate / 1000;
|
||||
|
||||
SSNES_LOG("XAudio2: Requesting %d ms latency, using %d ms latency.\n", latency, (int)bufsize * 1000 / rate);
|
||||
|
||||
xa->xa = xaudio2_new(rate, 2, bufsize * 2 * sizeof(float));
|
||||
if (!xa->xa)
|
||||
{
|
||||
SSNES_ERR("Failed to init XAudio2.\n");
|
||||
free(xa);
|
||||
return NULL;
|
||||
}
|
||||
return xa;
|
||||
}
|
||||
|
||||
static ssize_t xa_write(void *data, const void *buf, size_t size)
|
||||
{
|
||||
xa_t *xa = data;
|
||||
if (xa->nonblock)
|
||||
{
|
||||
size_t avail = xaudio2_write_avail(xa->xa);
|
||||
if (avail == 0)
|
||||
return 0;
|
||||
if (avail < size)
|
||||
size = avail;
|
||||
}
|
||||
size_t ret = xaudio2_write(xa->xa, buf, size);
|
||||
if (ret == 0)
|
||||
return -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool xa_stop(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void xa_set_nonblock_state(void *data, bool state)
|
||||
{
|
||||
xa_t *xa = data;
|
||||
xa->nonblock = state;
|
||||
}
|
||||
|
||||
static bool xa_start(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool xa_use_float(void *data)
|
||||
{
|
||||
(void)data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void xa_free(void *data)
|
||||
{
|
||||
xa_t *xa = data;
|
||||
if (xa)
|
||||
{
|
||||
if (xa->xa)
|
||||
xaudio2_free(xa->xa);
|
||||
free(xa);
|
||||
}
|
||||
}
|
||||
|
||||
const audio_driver_t audio_xa = {
|
||||
.init = xa_init,
|
||||
.write = xa_write,
|
||||
.stop = xa_stop,
|
||||
.start = xa_start,
|
||||
.set_nonblock_state = xa_set_nonblock_state,
|
||||
.use_float = xa_use_float,
|
||||
.free = xa_free,
|
||||
.ident = "xaudio"
|
||||
};
|
||||
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
/* 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 "thread.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "general.h"
|
||||
|
||||
struct autosave
|
||||
{
|
||||
volatile bool quit;
|
||||
slock_t *lock;
|
||||
|
||||
slock_t *cond_lock;
|
||||
scond_t *cond;
|
||||
sthread_t *thread;
|
||||
|
||||
void *buffer;
|
||||
const void *snes_buffer;
|
||||
const char *path;
|
||||
size_t bufsize;
|
||||
unsigned interval;
|
||||
};
|
||||
|
||||
static void autosave_thread(void *data)
|
||||
{
|
||||
autosave_t *save = data;
|
||||
|
||||
bool first_log = true;
|
||||
|
||||
while (!save->quit)
|
||||
{
|
||||
autosave_lock(save);
|
||||
bool differ = memcmp(save->buffer, save->snes_buffer, save->bufsize) != 0;
|
||||
if (differ)
|
||||
memcpy(save->buffer, save->snes_buffer, save->bufsize);
|
||||
autosave_unlock(save);
|
||||
|
||||
if (differ)
|
||||
{
|
||||
// Should probably deal with this more elegantly.
|
||||
FILE *file = fopen(save->path, "wb");
|
||||
if (file)
|
||||
{
|
||||
// Avoid spamming down stderr ... :)
|
||||
if (first_log)
|
||||
{
|
||||
SSNES_LOG("Autosaving SRAM to \"%s\", will continue to check every %u seconds ...\n", save->path, save->interval);
|
||||
first_log = false;
|
||||
}
|
||||
else
|
||||
SSNES_LOG("SRAM changed ... autosaving ...\n");
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
slock_lock(save->cond_lock);
|
||||
if (!save->quit)
|
||||
scond_wait_timeout(save->cond, save->cond_lock, save->interval * 1000);
|
||||
slock_unlock(save->cond_lock);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
memcpy(handle->buffer, handle->snes_buffer, handle->bufsize);
|
||||
|
||||
handle->lock = slock_new();
|
||||
handle->cond_lock = slock_new();
|
||||
handle->cond = scond_new();
|
||||
|
||||
handle->thread = sthread_create(autosave_thread, handle);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void autosave_lock(autosave_t *handle)
|
||||
{
|
||||
slock_lock(handle->lock);
|
||||
}
|
||||
|
||||
void autosave_unlock(autosave_t *handle)
|
||||
{
|
||||
slock_unlock(handle->lock);
|
||||
}
|
||||
|
||||
void autosave_free(autosave_t *handle)
|
||||
{
|
||||
slock_lock(handle->cond_lock);
|
||||
handle->quit = true;
|
||||
slock_unlock(handle->cond_lock);
|
||||
scond_signal(handle->cond);
|
||||
sthread_join(handle->thread);
|
||||
|
||||
slock_free(handle->lock);
|
||||
slock_free(handle->cond_lock);
|
||||
scond_free(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,175 @@
|
||||
/* 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 "bps.h"
|
||||
#include "movie.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum bps_mode
|
||||
{
|
||||
SOURCE_READ = 0,
|
||||
TARGET_READ,
|
||||
SOURCE_COPY,
|
||||
TARGET_COPY
|
||||
};
|
||||
|
||||
struct bps_data
|
||||
{
|
||||
const uint8_t *modify_data, *source_data;
|
||||
uint8_t *target_data;
|
||||
size_t modify_length, source_length, target_length;
|
||||
size_t modify_offset, source_offset, target_offset;
|
||||
uint32_t modify_checksum, source_checksum, target_checksum;
|
||||
|
||||
size_t source_relative_offset, target_relative_offset, output_offset;
|
||||
};
|
||||
|
||||
static uint8_t bps_read(struct bps_data *bps)
|
||||
{
|
||||
uint8_t data = bps->modify_data[bps->modify_offset++];
|
||||
bps->modify_checksum = crc32_adjust(bps->modify_checksum, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
static uint64_t bps_decode(struct bps_data *bps)
|
||||
{
|
||||
uint64_t data = 0, shift = 1;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
uint8_t x = bps_read(bps);
|
||||
data += (x & 0x7f) * shift;
|
||||
if (x & 0x80)
|
||||
break;
|
||||
shift <<= 7;
|
||||
data += shift;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static void bps_write(struct bps_data *bps, uint8_t data)
|
||||
{
|
||||
bps->target_data[bps->output_offset++] = data;
|
||||
bps->target_checksum = crc32_adjust(bps->target_checksum, data);
|
||||
}
|
||||
|
||||
bps_error_t bps_apply_patch(
|
||||
const uint8_t *modify_data, size_t modify_length,
|
||||
const uint8_t *source_data, size_t source_length,
|
||||
uint8_t *target_data, size_t *target_length)
|
||||
{
|
||||
if (modify_length < 19)
|
||||
return BPS_PATCH_TOO_SMALL;
|
||||
|
||||
struct bps_data bps = {
|
||||
.modify_data = modify_data,
|
||||
.modify_length = modify_length,
|
||||
.target_data = target_data,
|
||||
.target_length = *target_length,
|
||||
.source_data = source_data,
|
||||
.source_length = source_length,
|
||||
|
||||
.modify_checksum = ~0,
|
||||
.target_checksum = ~0,
|
||||
};
|
||||
|
||||
if ((bps_read(&bps) != 'B') || (bps_read(&bps) != 'P') || (bps_read(&bps) != 'S') || (bps_read(&bps) != '1'))
|
||||
return BPS_PATCH_INVALID_HEADER;
|
||||
|
||||
size_t modify_source_size = bps_decode(&bps);
|
||||
size_t modify_target_size = bps_decode(&bps);
|
||||
size_t modify_markup_size = bps_decode(&bps);
|
||||
for (size_t i = 0; i < modify_markup_size; i++)
|
||||
bps_read(&bps);
|
||||
|
||||
if (modify_source_size > bps.source_length)
|
||||
return BPS_SOURCE_TOO_SMALL;
|
||||
if (modify_target_size > bps.target_length)
|
||||
return BPS_TARGET_TOO_SMALL;
|
||||
|
||||
while (bps.modify_offset < bps.modify_length - 12)
|
||||
{
|
||||
size_t length = bps_decode(&bps);
|
||||
unsigned mode = length & 3;
|
||||
length = (length >> 2) + 1;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case SOURCE_READ:
|
||||
while (length--)
|
||||
bps_write(&bps, bps.source_data[bps.output_offset]);
|
||||
break;
|
||||
|
||||
case TARGET_READ:
|
||||
while (length--)
|
||||
bps_write(&bps, bps_read(&bps));
|
||||
break;
|
||||
|
||||
case SOURCE_COPY:
|
||||
case TARGET_COPY:
|
||||
{
|
||||
int offset = bps_decode(&bps);
|
||||
bool negative = offset & 1;
|
||||
offset >>= 1;
|
||||
if (negative)
|
||||
offset = -offset;
|
||||
|
||||
if (mode == SOURCE_COPY)
|
||||
{
|
||||
bps.source_offset += offset;
|
||||
while (length--)
|
||||
bps_write(&bps, bps.source_data[bps.source_offset++]);
|
||||
}
|
||||
else
|
||||
{
|
||||
bps.target_offset += offset;
|
||||
while (length--)
|
||||
bps_write(&bps, bps.target_data[bps.target_offset++]);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t modify_source_checksum = 0, modify_target_checksum = 0, modify_modify_checksum = 0;
|
||||
for (unsigned i = 0; i < 32; i += 8)
|
||||
modify_source_checksum |= bps_read(&bps) << i;
|
||||
for (unsigned i = 0; i < 32; i += 8)
|
||||
modify_target_checksum |= bps_read(&bps) << i;
|
||||
|
||||
uint32_t checksum = ~bps.modify_checksum;
|
||||
for (unsigned i = 0; i < 32; i += 8)
|
||||
modify_modify_checksum |= bps_read(&bps) << i;
|
||||
|
||||
bps.source_checksum = crc32_calculate(bps.source_data, bps.source_length);
|
||||
bps.target_checksum = ~bps.target_checksum;
|
||||
|
||||
if (bps.source_checksum != modify_source_checksum)
|
||||
return BPS_SOURCE_CHECKSUM_INVALID;
|
||||
if (bps.target_checksum != modify_target_checksum)
|
||||
return BPS_TARGET_CHECKSUM_INVALID;
|
||||
if (checksum != modify_modify_checksum)
|
||||
return BPS_PATCH_CHECKSUM_INVALID;
|
||||
|
||||
*target_length = modify_target_size;
|
||||
|
||||
return BPS_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/* 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 __BPS_H
|
||||
#define __BPS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// BPS implementation from bSNES (nall::).
|
||||
|
||||
typedef enum bps_error
|
||||
{
|
||||
BPS_UNKNOWN,
|
||||
BPS_SUCCESS,
|
||||
BPS_PATCH_TOO_SMALL,
|
||||
BPS_PATCH_INVALID_HEADER,
|
||||
BPS_SOURCE_TOO_SMALL,
|
||||
BPS_TARGET_TOO_SMALL,
|
||||
BPS_SOURCE_CHECKSUM_INVALID,
|
||||
BPS_TARGET_CHECKSUM_INVALID,
|
||||
BPS_PATCH_CHECKSUM_INVALID
|
||||
} bps_error_t;
|
||||
|
||||
bps_error_t bps_apply_patch(
|
||||
const uint8_t *patch_data, size_t patch_length,
|
||||
const uint8_t *source_data, size_t source_length,
|
||||
uint8_t *target_data, size_t *target_length);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,388 @@
|
||||
/* 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"
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
#include "conf/config_file.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <string.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);
|
||||
if (!output)
|
||||
return NULL;
|
||||
|
||||
if (dest)
|
||||
strlcat(output, input, required_len);
|
||||
else
|
||||
strlcpy(output, input, required_len);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static bool xml_grab_cheat(struct cheat *cht, xmlNodePtr ptr)
|
||||
{
|
||||
if (!ptr)
|
||||
return false;
|
||||
|
||||
memset(cht, 0, sizeof(struct cheat));
|
||||
bool first = true;
|
||||
|
||||
for (; ptr; ptr = ptr->next)
|
||||
{
|
||||
if (strcmp((const char*)ptr->name, "description") == 0)
|
||||
{
|
||||
cht->desc = (char*)xmlNodeGetContent(ptr);
|
||||
}
|
||||
else if (strcmp((const char*)ptr->name, "code") == 0)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
cht->code = strcat_alloc(cht->code, "+");
|
||||
if (!cht->code)
|
||||
return false;
|
||||
}
|
||||
|
||||
xmlChar *code = xmlNodeGetContent(ptr);
|
||||
if (!code)
|
||||
return false;
|
||||
|
||||
cht->code = strcat_alloc(cht->code, (const char*)code);
|
||||
xmlFree(code);
|
||||
if (!cht->code)
|
||||
return false;
|
||||
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool xml_grab_cheats(cheat_manager_t *handle, xmlNodePtr ptr)
|
||||
{
|
||||
for (; ptr; ptr = ptr->next)
|
||||
{
|
||||
if (strcmp((const char*)ptr->name, "name") == 0)
|
||||
{
|
||||
xmlChar *name = xmlNodeGetContent(ptr);
|
||||
if (name)
|
||||
{
|
||||
SSNES_LOG("Found cheat for game: \"%s\"\n", name);
|
||||
xmlFree(name);
|
||||
}
|
||||
}
|
||||
else if (strcmp((const char*)ptr->name, "cheat") == 0)
|
||||
{
|
||||
if (handle->size == handle->buf_size)
|
||||
{
|
||||
handle->buf_size *= 2;
|
||||
handle->cheats = realloc(handle->cheats, handle->buf_size * sizeof(struct cheat));
|
||||
if (!handle->cheats)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xml_grab_cheat(&handle->cheats[handle->size], ptr->children))
|
||||
handle->size++;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void cheat_manager_apply_cheats(cheat_manager_t *handle)
|
||||
{
|
||||
unsigned index = 0;
|
||||
psnes_cheat_reset();
|
||||
for (unsigned i = 0; i < handle->size; i++)
|
||||
{
|
||||
if (handle->cheats[i].state)
|
||||
psnes_cheat_set(index++, true, handle->cheats[i].code);
|
||||
}
|
||||
}
|
||||
|
||||
static void cheat_manager_load_config(cheat_manager_t *handle, const char *path, const char *sha256)
|
||||
{
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
if (!(*path))
|
||||
return;
|
||||
|
||||
config_file_t *conf = config_file_new(path);
|
||||
if (!conf)
|
||||
return;
|
||||
|
||||
char *str;
|
||||
if (!config_get_string(conf, sha256, &str))
|
||||
{
|
||||
config_file_free(conf);
|
||||
return;
|
||||
}
|
||||
|
||||
const char *num = strtok(str, ";");
|
||||
while (num)
|
||||
{
|
||||
unsigned index = strtoul(num, NULL, 0);
|
||||
if (index < handle->size)
|
||||
handle->cheats[index].state = true;
|
||||
|
||||
num = strtok(NULL, ";");
|
||||
}
|
||||
|
||||
free(str);
|
||||
config_file_free(conf);
|
||||
|
||||
cheat_manager_apply_cheats(handle);
|
||||
|
||||
#else
|
||||
(void)handle;
|
||||
(void)path;
|
||||
(void)sha256;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void cheat_manager_save_config(cheat_manager_t *handle, const char *path, const char *sha256)
|
||||
{
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
if (!(*path))
|
||||
return;
|
||||
|
||||
config_file_t *conf = config_file_new(path);
|
||||
if (!conf)
|
||||
conf = config_file_new(NULL);
|
||||
|
||||
if (!conf)
|
||||
{
|
||||
SSNES_ERR("Cannot save XML cheat settings!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
char conf_str[512] = {0};
|
||||
char tmp[32] = {0};
|
||||
|
||||
for (unsigned i = 0; i < handle->size; i++)
|
||||
{
|
||||
if (handle->cheats[i].state)
|
||||
{
|
||||
snprintf(tmp, sizeof(tmp), "%u;", i);
|
||||
strlcat(conf_str, tmp, sizeof(conf_str));
|
||||
}
|
||||
}
|
||||
|
||||
if (*conf_str)
|
||||
conf_str[strlen(conf_str) - 1] = '\0'; // Remove the trailing ';'
|
||||
|
||||
config_set_string(conf, sha256, conf_str);
|
||||
|
||||
if (!config_file_write(conf, path))
|
||||
SSNES_ERR("Failed to write XML cheat settings to \"%s\"! Check permissions!\n", path);
|
||||
|
||||
config_file_free(conf);
|
||||
|
||||
#else
|
||||
(void)handle;
|
||||
(void)path;
|
||||
(void)sha256;
|
||||
#endif
|
||||
}
|
||||
|
||||
cheat_manager_t* cheat_manager_new(const char *path)
|
||||
{
|
||||
LIBXML_TEST_VERSION;
|
||||
|
||||
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 = 1;
|
||||
handle->cheats = calloc(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 (strcmp((const char*)sha256, g_extern.sha256) == 0)
|
||||
{
|
||||
xmlFree(sha256);
|
||||
break;
|
||||
}
|
||||
|
||||
xmlFree(sha256);
|
||||
}
|
||||
}
|
||||
|
||||
if (!cur)
|
||||
goto error;
|
||||
|
||||
if (!xml_grab_cheats(handle, cur->children))
|
||||
{
|
||||
SSNES_ERR("Failed to grab cheats. This should not happen.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (handle->size == 0)
|
||||
{
|
||||
SSNES_ERR("Did not find any cheats in XML file: %s\n", path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
cheat_manager_load_config(handle, g_settings.cheat_settings_path, g_extern.sha256);
|
||||
|
||||
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)
|
||||
{
|
||||
cheat_manager_save_config(handle, g_settings.cheat_settings_path, g_extern.sha256);
|
||||
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;
|
||||
cheat_manager_apply_cheats(handle);
|
||||
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
|
||||
+363
-73
@@ -21,19 +21,52 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include "strl.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/param.h> // MAXPATHLEN
|
||||
#else
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifndef MAXPATHLEN
|
||||
#ifdef PATH_MAX
|
||||
#define MAXPATHLEN PATH_MAX
|
||||
#else
|
||||
#define MAXPATHLEN 512
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define MAX_INCLUDE_DEPTH 16
|
||||
|
||||
struct entry_list
|
||||
{
|
||||
bool readonly; // If we got this from an #include, do not allow write.
|
||||
char *key;
|
||||
char *value;
|
||||
struct entry_list *next;
|
||||
};
|
||||
|
||||
struct include_list
|
||||
{
|
||||
char *path;
|
||||
struct include_list *next;
|
||||
};
|
||||
|
||||
struct config_file
|
||||
{
|
||||
char *path;
|
||||
struct entry_list *entries;
|
||||
struct entry_list *tail;
|
||||
unsigned include_depth;
|
||||
|
||||
struct include_list *includes;
|
||||
};
|
||||
|
||||
static config_file_t *config_file_new_internal(const char *path, unsigned depth);
|
||||
|
||||
static char *getaline(FILE *file)
|
||||
{
|
||||
char *newline = malloc(9);
|
||||
@@ -56,13 +89,189 @@ static char *getaline(FILE *file)
|
||||
return newline;
|
||||
}
|
||||
|
||||
static bool parse_line(struct entry_list *list, char *line)
|
||||
static char *extract_value(char *line, bool is_value)
|
||||
{
|
||||
if (is_value)
|
||||
{
|
||||
while (isspace(*line))
|
||||
line++;
|
||||
|
||||
// If we don't have an equal sign here, we've got an invalid string...
|
||||
if (*line != '=')
|
||||
return false;
|
||||
|
||||
line++;
|
||||
}
|
||||
|
||||
while (isspace(*line))
|
||||
line++;
|
||||
|
||||
// We have a full string. Read until next ".
|
||||
if (*line == '"')
|
||||
{
|
||||
line++;
|
||||
char *tok = strtok(line, "\"");
|
||||
if (!tok)
|
||||
return false;
|
||||
return strdup(tok);
|
||||
}
|
||||
else if (*line == '\0') // Nothing :(
|
||||
return NULL;
|
||||
else // We don't have that... Read till next space.
|
||||
{
|
||||
char *tok = strtok(line, " \n\t\f\r\v");
|
||||
if (tok)
|
||||
return strdup(tok);
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void set_list_readonly(struct entry_list *list)
|
||||
{
|
||||
while (list)
|
||||
{
|
||||
list->readonly = true;
|
||||
list = list->next;
|
||||
}
|
||||
}
|
||||
|
||||
// Move semantics? :)
|
||||
static void add_child_list(config_file_t *parent, config_file_t *child)
|
||||
{
|
||||
if (parent->entries)
|
||||
{
|
||||
struct entry_list *head = parent->entries;
|
||||
while (head->next)
|
||||
head = head->next;
|
||||
|
||||
set_list_readonly(child->entries);
|
||||
head->next = child->entries;
|
||||
}
|
||||
else
|
||||
{
|
||||
set_list_readonly(child->entries);
|
||||
parent->entries = child->entries;
|
||||
}
|
||||
|
||||
child->entries = NULL;
|
||||
|
||||
// Rebase tail.
|
||||
if (parent->entries)
|
||||
{
|
||||
struct entry_list *head = parent->entries;
|
||||
while (head->next)
|
||||
head = head->next;
|
||||
parent->tail = head;
|
||||
}
|
||||
else
|
||||
parent->tail = NULL;
|
||||
}
|
||||
|
||||
static void add_include_list(config_file_t *conf, const char *path)
|
||||
{
|
||||
struct include_list *head = conf->includes;
|
||||
struct include_list *node = calloc(1, sizeof(*node));
|
||||
node->path = strdup(path);
|
||||
|
||||
if (head)
|
||||
{
|
||||
while (head->next)
|
||||
head = head->next;
|
||||
|
||||
head->next = node;
|
||||
}
|
||||
else
|
||||
conf->includes = node;
|
||||
}
|
||||
|
||||
static void add_sub_conf(config_file_t *conf, char *line)
|
||||
{
|
||||
char *path = extract_value(line, false);
|
||||
if (!path)
|
||||
return;
|
||||
|
||||
add_include_list(conf, path);
|
||||
|
||||
char real_path[MAXPATHLEN];
|
||||
|
||||
#ifndef _WIN32
|
||||
if (*path == '/')
|
||||
{
|
||||
strlcpy(real_path, path, sizeof(real_path));
|
||||
}
|
||||
else if (*path == '~')
|
||||
{
|
||||
const char *home = getenv("HOME");
|
||||
strlcpy(real_path, home ? home : "/", sizeof(real_path));
|
||||
strlcat(real_path, path + 1, sizeof(real_path));
|
||||
}
|
||||
else
|
||||
{
|
||||
strlcpy(real_path, conf->path, sizeof(real_path));
|
||||
char *split = strrchr(real_path, '/');
|
||||
if (split)
|
||||
{
|
||||
split[1] = '\0';
|
||||
strlcat(real_path, path, sizeof(real_path));
|
||||
}
|
||||
else
|
||||
strlcpy(real_path, path, sizeof(real_path));
|
||||
}
|
||||
#else
|
||||
// Accomodate POSIX systems on Win32.
|
||||
bool is_full_path = *path == '/';
|
||||
|
||||
if (is_full_path)
|
||||
strlcpy(real_path, path, sizeof(real_path));
|
||||
else
|
||||
GetFullPathNameA(path, sizeof(real_path), real_path, NULL);
|
||||
|
||||
if (strcmp(path, real_path) != 0)
|
||||
{
|
||||
strlcpy(real_path, conf->path, sizeof(real_path));
|
||||
char *split = strrchr(real_path, '/');
|
||||
if (!split)
|
||||
split = strrchr(real_path, '\\');
|
||||
|
||||
split[1] = '\0';
|
||||
strlcat(real_path, path, sizeof(real_path));
|
||||
}
|
||||
#endif
|
||||
|
||||
config_file_t *sub_conf = config_file_new_internal(real_path, conf->include_depth + 1);
|
||||
if (!sub_conf)
|
||||
{
|
||||
free(path);
|
||||
return;
|
||||
}
|
||||
|
||||
// Pilfer internal list! :D
|
||||
add_child_list(conf, sub_conf);
|
||||
config_file_free(sub_conf);
|
||||
free(path);
|
||||
}
|
||||
|
||||
static bool parse_line(config_file_t *conf, struct entry_list *list, char *line)
|
||||
{
|
||||
// Remove everything after comment.
|
||||
char *comment = strchr(line, '#');
|
||||
if (comment)
|
||||
*comment = '\0';
|
||||
|
||||
// Starting line with # and include includes config files. :)
|
||||
if ((comment == line) && (conf->include_depth < MAX_INCLUDE_DEPTH))
|
||||
{
|
||||
comment++;
|
||||
if (strstr(comment, "include ") == comment)
|
||||
{
|
||||
add_sub_conf(conf, comment + strlen("include "));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (conf->include_depth >= MAX_INCLUDE_DEPTH)
|
||||
fprintf(stderr, "!!! #include depth exceeded for config! Might be a cycle.\n");
|
||||
|
||||
// Skips to first character.
|
||||
while (isspace(*line))
|
||||
line++;
|
||||
@@ -84,66 +293,42 @@ static bool parse_line(struct entry_list *list, char *line)
|
||||
key[index] = '\0';
|
||||
list->key = key;
|
||||
|
||||
while (isspace(*line))
|
||||
line++;
|
||||
|
||||
// If we don't have an equal sign here, we've got an invalid string...
|
||||
if (*line != '=')
|
||||
list->value = extract_value(line, true);
|
||||
if (!list->value)
|
||||
{
|
||||
list->key = NULL;
|
||||
free(key);
|
||||
return false;
|
||||
}
|
||||
line++;
|
||||
|
||||
while (isspace(*line))
|
||||
line++;
|
||||
|
||||
// We have a full string. Read until next ".
|
||||
if (*line == '"')
|
||||
{
|
||||
char *tok = strtok(line + 1, "\"");
|
||||
if (tok == NULL)
|
||||
{
|
||||
list->key = NULL;
|
||||
free(key);
|
||||
return false;
|
||||
}
|
||||
list->value = strdup(tok);
|
||||
}
|
||||
else // We don't have that... Read till next space.
|
||||
{
|
||||
char *tok = strtok(line, " \t\f");
|
||||
if (tok == NULL)
|
||||
{
|
||||
list->key = NULL;
|
||||
free(key);
|
||||
return false;
|
||||
}
|
||||
list->value = strdup(tok);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
config_file_t *config_file_new(const char *path)
|
||||
static config_file_t *config_file_new_internal(const char *path, unsigned depth)
|
||||
{
|
||||
|
||||
struct config_file *conf = calloc(1, sizeof(*conf));
|
||||
if (conf == NULL)
|
||||
if (!conf)
|
||||
return NULL;
|
||||
|
||||
if (path == NULL)
|
||||
if (!path)
|
||||
return conf;
|
||||
|
||||
FILE *file = fopen(path, "r");
|
||||
if (!file)
|
||||
conf->path = strdup(path);
|
||||
if (!conf->path)
|
||||
{
|
||||
free(conf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct entry_list *tail = conf->entries;
|
||||
conf->include_depth = depth;
|
||||
|
||||
FILE *file = fopen(path, "r");
|
||||
if (!file)
|
||||
{
|
||||
free(conf->path);
|
||||
free(conf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (!feof(file))
|
||||
{
|
||||
@@ -152,50 +337,69 @@ config_file_t *config_file_new(const char *path)
|
||||
|
||||
if (line)
|
||||
{
|
||||
if (parse_line(list, line))
|
||||
if (parse_line(conf, list, line))
|
||||
{
|
||||
if (conf->entries == NULL)
|
||||
if (conf->entries)
|
||||
{
|
||||
conf->entries = list;
|
||||
tail = list;
|
||||
conf->tail->next = list;
|
||||
conf->tail = list;
|
||||
}
|
||||
else
|
||||
{
|
||||
tail->next = list;
|
||||
tail = list;
|
||||
conf->entries = list;
|
||||
conf->tail = list;
|
||||
}
|
||||
}
|
||||
|
||||
free(line);
|
||||
}
|
||||
|
||||
if (list != conf->tail)
|
||||
free(list);
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
return conf;
|
||||
}
|
||||
|
||||
config_file_t *config_file_new(const char *path)
|
||||
{
|
||||
return config_file_new_internal(path, 0);
|
||||
}
|
||||
|
||||
void config_file_free(config_file_t *conf)
|
||||
{
|
||||
if (conf != NULL)
|
||||
if (!conf)
|
||||
return;
|
||||
|
||||
struct entry_list *tmp = conf->entries;
|
||||
while (tmp)
|
||||
{
|
||||
struct entry_list *tmp = conf->entries;
|
||||
struct entry_list *old = tmp;
|
||||
while (tmp != NULL)
|
||||
{
|
||||
free(tmp->key);
|
||||
free(tmp->value);
|
||||
old = tmp;
|
||||
tmp = tmp->next;
|
||||
free(old);
|
||||
}
|
||||
free(conf);
|
||||
free(tmp->key);
|
||||
free(tmp->value);
|
||||
struct entry_list *hold = tmp;
|
||||
tmp = tmp->next;
|
||||
free(hold);
|
||||
}
|
||||
|
||||
struct include_list *inc_tmp = conf->includes;
|
||||
while (inc_tmp)
|
||||
{
|
||||
free(inc_tmp->path);
|
||||
struct include_list *hold = inc_tmp;
|
||||
inc_tmp = inc_tmp->next;
|
||||
free(hold);
|
||||
}
|
||||
|
||||
free(conf->path);
|
||||
free(conf);
|
||||
}
|
||||
|
||||
bool config_get_double(config_file_t *conf, const char *key, double *in)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list != NULL)
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
{
|
||||
@@ -211,12 +415,42 @@ bool config_get_int(config_file_t *conf, const char *key, int *in)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list != NULL)
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
{
|
||||
*in = strtol(list->value, NULL, 0);
|
||||
return true;
|
||||
errno = 0;
|
||||
int val = strtol(list->value, NULL, 0);
|
||||
if (errno == 0)
|
||||
{
|
||||
*in = val;
|
||||
return true;
|
||||
}
|
||||
return
|
||||
false;
|
||||
}
|
||||
list = list->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool config_get_hex(config_file_t *conf, const char *key, unsigned *in)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
{
|
||||
errno = 0;
|
||||
unsigned val = strtoul(list->value, NULL, 16);
|
||||
if (errno == 0)
|
||||
{
|
||||
*in = val;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
list = list->next;
|
||||
}
|
||||
@@ -227,11 +461,11 @@ bool config_get_char(config_file_t *conf, const char *key, char *in)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list != NULL)
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
{
|
||||
if (strlen(list->value) > 1)
|
||||
if (list->value[0] && list->value[1])
|
||||
return false;
|
||||
*in = *list->value;
|
||||
return true;
|
||||
@@ -245,7 +479,7 @@ bool config_get_string(config_file_t *conf, const char *key, char **str)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list != NULL)
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
{
|
||||
@@ -257,11 +491,27 @@ bool config_get_string(config_file_t *conf, const char *key, char **str)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool config_get_array(config_file_t *conf, const char *key, char *buf, size_t size)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
{
|
||||
strlcpy(buf, list->value, size);
|
||||
return true;
|
||||
}
|
||||
list = list->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool config_get_bool(config_file_t *conf, const char *key, bool *in)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list != NULL)
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
{
|
||||
@@ -287,14 +537,15 @@ void config_set_string(config_file_t *conf, const char *key, const char *val)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
struct entry_list *last = list;
|
||||
while (list != NULL)
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(key, list->key) == 0)
|
||||
if (!list->readonly && (strcmp(key, list->key) == 0))
|
||||
{
|
||||
free(list->value);
|
||||
list->value = strdup(val);
|
||||
return;
|
||||
}
|
||||
|
||||
last = list;
|
||||
list = list->next;
|
||||
}
|
||||
@@ -358,11 +609,50 @@ bool config_file_write(config_file_t *conf, const char *path)
|
||||
|
||||
void config_file_dump(config_file_t *conf, FILE *file)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list != NULL)
|
||||
struct include_list *includes = conf->includes;
|
||||
while (includes)
|
||||
{
|
||||
fprintf(file, "%s = \"%s\"\n", list->key, list->value);
|
||||
fprintf(file, "#include \"%s\"\n", includes->path);
|
||||
includes = includes->next;
|
||||
}
|
||||
|
||||
struct entry_list *list = conf->entries;
|
||||
while (list)
|
||||
{
|
||||
if (!list->readonly)
|
||||
fprintf(file, "%s = \"%s\"\n", list->key, list->value);
|
||||
list = list->next;
|
||||
}
|
||||
}
|
||||
|
||||
void config_file_dump_all(config_file_t *conf, FILE *file)
|
||||
{
|
||||
struct include_list *includes = conf->includes;
|
||||
while (includes)
|
||||
{
|
||||
fprintf(file, "#include \"%s\"\n", includes->path);
|
||||
includes = includes->next;
|
||||
}
|
||||
|
||||
struct entry_list *list = conf->entries;
|
||||
while (list)
|
||||
{
|
||||
fprintf(file, "%s = \"%s\" %s\n", list->key, list->value, list->readonly ? "(included)" : "");
|
||||
list = list->next;
|
||||
}
|
||||
}
|
||||
|
||||
bool config_entry_exists(config_file_t *conf, const char *entry)
|
||||
{
|
||||
struct entry_list *list = conf->entries;
|
||||
|
||||
while (list)
|
||||
{
|
||||
if (strcmp(entry, list->key) == 0)
|
||||
return true;
|
||||
list = list->next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
+20
-5
@@ -22,6 +22,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct config_file config_file_t;
|
||||
|
||||
@@ -30,26 +31,39 @@ typedef struct config_file config_file_t;
|
||||
// - # are treated as comments. Rest of the line is ignored.
|
||||
// - Format is: key = value. There can be as many spaces as you like in-between.
|
||||
// - Value can be wrapped inside "" for multiword strings. (foo = "hai u")
|
||||
//
|
||||
// - #include includes a config file in-place.
|
||||
// Path is relative to where config file was loaded unless an absolute path is chosen.
|
||||
// Key/value pairs from an #include are read-only, and cannot be modified.
|
||||
|
||||
// Loads a config file. Returns NULL if file doesn't exist. NULL path will create an empty config file.
|
||||
// Loads a config file. Returns NULL if file doesn't exist.
|
||||
// NULL path will create an empty config file.
|
||||
config_file_t *config_file_new(const char *path);
|
||||
// Frees config file.
|
||||
void config_file_free(config_file_t *conf);
|
||||
|
||||
// All extract functions return true when value is valid and exists. Returns false otherwise.
|
||||
// All extract functions return true when value is valid and exists.
|
||||
// Returns false otherwise.
|
||||
|
||||
bool config_entry_exists(config_file_t *conf, const char *entry);
|
||||
|
||||
// Extracts a double from config file.
|
||||
bool config_get_double(config_file_t *conf, const char *entry, double *in);
|
||||
// Extracts an int from config file.
|
||||
bool config_get_int(config_file_t *conf, const char *entry, int *in);
|
||||
// Extracts 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.
|
||||
bool config_get_string(config_file_t *conf, const char *entry, char **in);
|
||||
// Extracts a string to a preallocated buffer. Avoid memory allocation.
|
||||
bool config_get_array(config_file_t *conf, const char *entry, char *in, size_t size);
|
||||
// Extracts a boolean from config. Valid boolean true are "true" and "1". Valid false are "false" and "0". Other values will be treated as an error.
|
||||
bool config_get_bool(config_file_t *conf, const char *entry, bool *in);
|
||||
|
||||
// Setters. Similiar to the getters.
|
||||
// Setters. Similar to the getters. Will not write to entry if the entry
|
||||
// was obtained from an #include.
|
||||
void config_set_double(config_file_t *conf, const char *entry, double value);
|
||||
void config_set_int(config_file_t *conf, const char *entry, int val);
|
||||
void config_set_char(config_file_t *conf, const char *entry, char val);
|
||||
@@ -61,7 +75,8 @@ bool config_file_write(config_file_t *conf, const char *path);
|
||||
|
||||
// Dump the current config to an already opened file. Does not close the file.
|
||||
void config_file_dump(config_file_t *conf, FILE *file);
|
||||
|
||||
|
||||
// Also dumps inherited values, useful for logging.
|
||||
void config_file_dump_all(config_file_t *conf, FILE *file);
|
||||
|
||||
#endif
|
||||
|
||||
+169
-128
@@ -30,21 +30,13 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SDL
|
||||
#include <SDL/SDL.h>
|
||||
#else
|
||||
#error HAVE_SDL is not defined!
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SRC
|
||||
#include <samplerate.h>
|
||||
#else
|
||||
#error HAVE_SRC is not defined!
|
||||
#endif
|
||||
|
||||
#include "input/keysym.h"
|
||||
|
||||
///////////////// 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
|
||||
@@ -53,29 +45,63 @@
|
||||
#define AUDIO_AL 5
|
||||
#define AUDIO_JACK 6
|
||||
#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_OPENGL)
|
||||
#define VIDEO_DEFAULT_DRIVER VIDEO_GL
|
||||
|
||||
#if HAVE_ALSA
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_ALSA
|
||||
#elif HAVE_OSS
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_OSS
|
||||
#elif HAVE_JACK
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_JACK
|
||||
#elif HAVE_RSOUND
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_RSOUND
|
||||
#elif HAVE_ROAR
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_ROAR
|
||||
#elif HAVE_AL
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_AL
|
||||
#elif HAVE_SDL
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_SDL
|
||||
#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
|
||||
#elif defined(HAVE_PULSE)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_PULSE
|
||||
#elif defined(HAVE_OSS)
|
||||
#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)
|
||||
#define AUDIO_DEFAULT_DRIVER AUDIO_XAUDIO
|
||||
#elif defined(HAVE_RSOUND)
|
||||
#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!"
|
||||
#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
|
||||
|
||||
|
||||
////////////////
|
||||
@@ -83,13 +109,19 @@
|
||||
////////////////
|
||||
|
||||
// Windowed
|
||||
static const float xscale = 3.0; // Real x res = 296 * xscale
|
||||
static const float yscale = 3.0; // Real y res = 224 * yscale
|
||||
static const float xscale = 3.0; // Real x res = aspect * base_size * xscale
|
||||
static const float yscale = 3.0; // Real y res = base_size * yscale
|
||||
|
||||
// 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;
|
||||
@@ -100,14 +132,42 @@ 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 = true;
|
||||
|
||||
// Font size for on-screen messages.
|
||||
static const unsigned font_size = 48;
|
||||
|
||||
// Offset for where messages will be placed on-screen. Values are in range [0.0, 1.0].
|
||||
static const float message_pos_offset_x = 0.05;
|
||||
static const float message_pos_offset_y = 0.05;
|
||||
// Color of the message.
|
||||
static const uint32_t message_color = 0xffff00; // RGB hex value.
|
||||
|
||||
#define SNES_ASPECT_RATIO (4.0/3)
|
||||
// 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;
|
||||
|
||||
// OSD-messages
|
||||
static const bool font_enable = true;
|
||||
|
||||
// The accurate refresh rate of your monitor (Hz).
|
||||
// This is used to calculate audio input rate with the formula:
|
||||
// audio_input_rate = game_input_rate * display_refresh_rate / game_refresh_rate.
|
||||
// If the implementation does not report any values,
|
||||
// SNES NTSC defaults will be assumed for compatibility.
|
||||
// This value should stay close to 60Hz to avoid large pitch changes.
|
||||
// If your monitor does not run at 60Hz, or something close to it, disable VSync,
|
||||
// and leave this at its default.
|
||||
static const float refresh_rate = 59.95;
|
||||
|
||||
////////////////
|
||||
// Audio
|
||||
@@ -119,12 +179,11 @@ static const bool audio_enable = true;
|
||||
// Output samplerate
|
||||
static const unsigned out_rate = 48000;
|
||||
|
||||
// Input samplerate from libSNES.
|
||||
// Lower this (slightly) if you are experiencing frequent audio dropouts while vsync is enabled.
|
||||
static const unsigned in_rate = 31980;
|
||||
// When changing input rate on-the-fly
|
||||
static const float audio_rate_step = 0.25;
|
||||
|
||||
// Audio device (e.g. hw:0,0 or /dev/audio). If NULL, will use defaults.
|
||||
static const char* audio_device = NULL;
|
||||
static const char *audio_device = NULL;
|
||||
|
||||
// Desired audio latency in milliseconds. Might not be honored if driver can't provide given latency.
|
||||
static const int out_latency = 64;
|
||||
@@ -132,10 +191,32 @@ static const int out_latency = 64;
|
||||
// Will sync audio. (recommended)
|
||||
static const bool audio_sync = true;
|
||||
|
||||
// Defines the quality (and cpu reqirements) of samplerate conversion.
|
||||
#define SAMPLERATE_QUALITY SRC_LINEAR
|
||||
// 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 = false;
|
||||
|
||||
// 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;
|
||||
|
||||
// On save state load, block SRAM from being overwritten.
|
||||
// This could potentially lead to buggy games.
|
||||
static const bool block_sram_overwrite = false;
|
||||
|
||||
// When saving savestates, state index is automatically incremented before saving.
|
||||
// When the ROM is loaded, state index will be set to the highest existing value.
|
||||
static const bool savestate_auto_index = false;
|
||||
|
||||
|
||||
////////////////////
|
||||
@@ -144,105 +225,65 @@ static const bool audio_sync = true;
|
||||
|
||||
// Axis threshold (between 0.0 and 1.0)
|
||||
// How far an axis must be tilted to result in a button press
|
||||
#define AXIS_THRESHOLD 0.5
|
||||
static const float axis_threshold = 0.5;
|
||||
|
||||
// To figure out which joypad buttons to use, check jstest or similar.
|
||||
// SDL sometimes reverses the axes for some odd reason, but hey. :D
|
||||
|
||||
// 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 },
|
||||
{ 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 },
|
||||
{ SSNES_QUIT_KEY, SDLK_ESCAPE, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_STATE_SLOT_MINUS, SDLK_F6, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_STATE_SLOT_PLUS, SDLK_F7, NO_BTN, AXIS_NONE },
|
||||
// SNES button | keyboard key | js btn | js axis |
|
||||
{ SNES_DEVICE_ID_JOYPAD_A, SK_x, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SK_z, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SK_s, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SK_a, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SK_q, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SK_w, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SK_LEFT, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SK_RIGHT, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SK_UP, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SK_DOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SK_RETURN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SK_RSHIFT, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_FAST_FORWARD_KEY, SK_SPACE, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_FAST_FORWARD_HOLD_KEY, SK_l, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_SAVE_STATE_KEY, SK_F2, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_LOAD_STATE_KEY, SK_F4, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_FULLSCREEN_TOGGLE_KEY, SK_f, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_QUIT_KEY, SK_ESCAPE, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_STATE_SLOT_MINUS, SK_F6, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_STATE_SLOT_PLUS, SK_F7, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_AUDIO_INPUT_RATE_PLUS, SK_KP_PLUS, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_AUDIO_INPUT_RATE_MINUS, SK_KP_MINUS, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_REWIND, SK_r, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_MOVIE_RECORD_TOGGLE, SK_o, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_PAUSE_TOGGLE, SK_p, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_FRAMEADVANCE, SK_k, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_RESET, SK_h, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_SHADER_NEXT, SK_m, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_SHADER_PREV, SK_n, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_CHEAT_INDEX_PLUS, SK_y, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_CHEAT_INDEX_MINUS, SK_t, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_CHEAT_TOGGLE, SK_u, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_SCREENSHOT, SK_PRINT, NO_BTN, AXIS_NONE },
|
||||
{ SSNES_DSP_CONFIG, SK_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, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_B, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_X, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_Y, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_L, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_R, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_LEFT, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_RIGHT, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_UP, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_DOWN, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_START, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ SNES_DEVICE_ID_JOYPAD_SELECT, SK_UNKNOWN, NO_BTN, AXIS_NONE },
|
||||
{ -1 }
|
||||
};
|
||||
|
||||
|
||||
+55
-7
@@ -14,13 +14,25 @@ static const bool _sdl_supp = true;
|
||||
static const bool _sdl_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_OPENGL
|
||||
static const bool _opengl_supp = true;
|
||||
#else
|
||||
static const bool _opengl_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;
|
||||
@@ -50,10 +62,22 @@ static const bool _jack_supp = true;
|
||||
static const bool _jack_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FILTER
|
||||
static const bool _filter_supp = true;
|
||||
#ifdef HAVE_PULSE
|
||||
static const bool _pulse_supp = true;
|
||||
#else
|
||||
static const bool _filter_supp = false;
|
||||
static const bool _pulse_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_XAUDIO
|
||||
static const bool _xaudio_supp = true;
|
||||
#else
|
||||
static const bool _xaudio_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_DYLIB
|
||||
static const bool _dylib_supp = true;
|
||||
#else
|
||||
static const bool _dylib_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CG
|
||||
@@ -68,6 +92,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
|
||||
@@ -80,10 +116,10 @@ static const bool _ffmpeg_supp = true;
|
||||
static const bool _ffmpeg_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SRC
|
||||
static const bool _src_supp = true;
|
||||
#ifdef HAVE_X264RGB
|
||||
static const bool _x264rgb_supp = true;
|
||||
#else
|
||||
static const bool _src_supp = false;
|
||||
static const bool _x264rgb_supp = false;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
@@ -98,4 +134,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
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" ssnes-joyconfig.1:
|
||||
|
||||
.TH "SSNES-JOYCONFIG" "1" "January 2011" "SSNES-JOYCONFIG" "System Manager's Manual: ssnes-joyconfig"
|
||||
.TH "SSNES-JOYCONFIG" "1" "October 2011" "SSNES-JOYCONFIG" "System Manager's Manual: ssnes-joyconfig"
|
||||
|
||||
.SH NAME
|
||||
|
||||
@@ -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.
|
||||
|
||||
+87
-4
@@ -1,6 +1,6 @@
|
||||
.\" ssnes.1:
|
||||
|
||||
.TH "SSNES" "1" "January 2011" "SSNES" "System Manager's Manual: ssnes"
|
||||
.TH "SSNES" "1" "November 2011" "SSNES" "System Manager's Manual: ssnes"
|
||||
|
||||
.SH NAME
|
||||
|
||||
@@ -27,27 +27,48 @@ If no rom file path is defined on the command line, \fBssnes\fR will try to load
|
||||
\fB--help, -h\fR
|
||||
Prints help text.
|
||||
|
||||
.TP
|
||||
\fB--features\fR
|
||||
Prints available features compiled into SSNES, then exits.
|
||||
|
||||
.TP
|
||||
\fB-L PATH, --libsnes PATH\fR
|
||||
Path to a libsnes implementation which is to be used.
|
||||
This option will override any setting in a config file.
|
||||
This option is only available if SSNES is compiled with dynamic libsnes loading.
|
||||
|
||||
.TP
|
||||
\fB--save PATH, -s PATH\fR
|
||||
Overrides the path used for save ram (*.srm).
|
||||
Without this flag, the save ram path will be inferred from the rom path name, and put in the same directory as the rom file with the extention replaced with '.srm'.
|
||||
When rom is loaded from \fBstdin\fR, this flag is mandatory to define as no path can be inferred.
|
||||
If PATH is a directory, SSNES will treat this as the save file directory, where the save file name will be inferred from the rom name.
|
||||
When loading a rom from stdin, the path must be a full path, however.
|
||||
|
||||
.TP
|
||||
\fB--savestate PATH, -S path\fR
|
||||
\fB--savestate PATH, -S PATH\fR
|
||||
Overrides the path used for save states.
|
||||
Without this flag, the save state path will be inferred from the rom path name, and put in the same directory as the rom file with the extention replace with '.state'.
|
||||
When rom is loaded from \fBstdin\fR, this flag is mandatory to define as no path can be inferred.
|
||||
If PATH is a directory, SSNES will treat this as the save state directory, where the state file name will be inferred from the rom name.
|
||||
When loading a rom from stdin, the path must be a full path, however.
|
||||
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.
|
||||
Should this not be defined, \fBssnes\fR will look in platform specific paths to attempt finding the config file.
|
||||
/etc/ssnes.cfg (when installed), or ssnes.cfg in the source tarball serves as a skeleton configuration file.
|
||||
If PATH is a directory, SSNES will treat this as the config file directory, where the state file name will be inferred from the rom name (*.cfg).
|
||||
When loading a rom from stdin, the path must be a full path, however.
|
||||
If a config cannot be found when using directory path, the default config path will be used instead.
|
||||
|
||||
.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 +101,10 @@ 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.
|
||||
|
||||
.TP
|
||||
\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.
|
||||
@@ -98,11 +123,69 @@ Connects a four-way multitap into port 2 of the SNES. This allows for up to 5 pl
|
||||
|
||||
.TP
|
||||
\fB--record PATH, -r PATH\fR
|
||||
Activates video recording of gameplay into PATH. Video recording options can be configured in config file.
|
||||
Activates video recording of gameplay into PATH. Using .mkv extension is recommended.
|
||||
Codecs used are FFV1/FLAC, suitable for processing the material further.
|
||||
|
||||
.TP
|
||||
\fB--size WIDTHxHEIGHT\fR
|
||||
Allows specifying the exact output width and height of FFmpeg recording. This option will override any configuration settings.
|
||||
The video input is scaled with point filtering before being encoded at the correct size.
|
||||
|
||||
.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--bsvrecord PATH, -R PATH\fR
|
||||
Start recording a .bsv video to PATH immediately after startup.
|
||||
|
||||
.TP
|
||||
\fB--sram-mode MODE, -M MODE\fR
|
||||
MODE designates how to handle SRAM.
|
||||
The format of the second parameter is {no,}load-{no,}save.
|
||||
These two boolean values tell if SRAM loading and SRAM saving should take place.
|
||||
Note that noload-save implies that the SRAM will be overwritten with new data.
|
||||
|
||||
.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--bps PATCH\fR
|
||||
Attempts to apply a BPS patch to the current ROM image. No files are altered.
|
||||
If this flag is not specified, SSNES will look for a .bps 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
|
||||
@@ -49,23 +53,47 @@ static const audio_driver_t *audio_drivers[] = {
|
||||
#ifdef HAVE_SDL
|
||||
&audio_sdl,
|
||||
#endif
|
||||
#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
|
||||
#ifdef HAVE_OPENGL
|
||||
&video_gl,
|
||||
#endif
|
||||
#ifdef HAVE_SDL
|
||||
&video_sdl,
|
||||
#endif
|
||||
#ifdef HAVE_XVIDEO
|
||||
&video_xvideo,
|
||||
#endif
|
||||
#ifdef HAVE_DYLIB
|
||||
&video_ext,
|
||||
#endif
|
||||
};
|
||||
|
||||
static const input_driver_t *input_drivers[] = {
|
||||
#ifdef HAVE_SDL
|
||||
&input_sdl,
|
||||
#endif
|
||||
#ifdef HAVE_XVIDEO
|
||||
&input_x,
|
||||
#endif
|
||||
};
|
||||
|
||||
static void find_audio_driver(void)
|
||||
{
|
||||
for (int i = 0; i < sizeof(audio_drivers) / sizeof(audio_driver_t*); i++)
|
||||
for (unsigned i = 0; i < sizeof(audio_drivers) / sizeof(audio_driver_t*); i++)
|
||||
{
|
||||
if (strcasecmp(g_settings.audio.driver, audio_drivers[i]->ident) == 0)
|
||||
{
|
||||
@@ -75,7 +103,7 @@ static void find_audio_driver(void)
|
||||
}
|
||||
SSNES_ERR("Couldn't find any audio driver named \"%s\"\n", g_settings.audio.driver);
|
||||
fprintf(stderr, "Available audio drivers are:\n");
|
||||
for (int i = 0; i < sizeof(audio_drivers) / sizeof(audio_driver_t*); i++)
|
||||
for (unsigned i = 0; i < sizeof(audio_drivers) / sizeof(audio_driver_t*); i++)
|
||||
fprintf(stderr, "\t%s\n", audio_drivers[i]->ident);
|
||||
|
||||
exit(1);
|
||||
@@ -83,7 +111,7 @@ static void find_audio_driver(void)
|
||||
|
||||
static void find_video_driver(void)
|
||||
{
|
||||
for (int i = 0; i < sizeof(video_drivers) / sizeof(video_driver_t*); i++)
|
||||
for (unsigned i = 0; i < sizeof(video_drivers) / sizeof(video_driver_t*); i++)
|
||||
{
|
||||
if (strcasecmp(g_settings.video.driver, video_drivers[i]->ident) == 0)
|
||||
{
|
||||
@@ -101,7 +129,7 @@ static void find_video_driver(void)
|
||||
|
||||
static void find_input_driver(void)
|
||||
{
|
||||
for (int i = 0; i < sizeof(input_drivers) / sizeof(input_driver_t*); i++)
|
||||
for (unsigned i = 0; i < sizeof(input_drivers) / sizeof(input_driver_t*); i++)
|
||||
{
|
||||
if (strcasecmp(g_settings.input.driver, input_drivers[i]->ident) == 0)
|
||||
{
|
||||
@@ -112,7 +140,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);
|
||||
}
|
||||
@@ -129,24 +157,137 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
static void adjust_audio_input_rate(void)
|
||||
{
|
||||
static bool first = true;
|
||||
if (!first)
|
||||
return;
|
||||
|
||||
if (g_extern.system.timing_set)
|
||||
{
|
||||
float timing_skew = fabs(1.0f - g_extern.system.timing.fps / g_settings.video.refresh_rate);
|
||||
if (timing_skew > 0.05f) // We don't want to adjust pitch too much. If we have extreme cases, just don't readjust at all.
|
||||
{
|
||||
SSNES_LOG("Timings deviate too much. Will not adjust. (Display = %.2f Hz, Game = %.2lf Hz)\n",
|
||||
g_settings.video.refresh_rate,
|
||||
g_extern.system.timing.fps);
|
||||
|
||||
g_settings.video.refresh_rate = g_extern.system.timing.fps;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_extern.system.timing_set)
|
||||
{
|
||||
g_settings.audio.in_rate = g_extern.system.timing.sample_rate *
|
||||
(g_settings.video.refresh_rate / g_extern.system.timing.fps);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_settings.audio.in_rate = 32040.5 *
|
||||
(g_settings.video.refresh_rate / (21477272.0 / 357366.0)); // SNES metrics.
|
||||
}
|
||||
|
||||
SSNES_LOG("Set audio input rate to: %.2f Hz.\n", g_settings.audio.in_rate);
|
||||
first = false;
|
||||
}
|
||||
|
||||
#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
|
||||
void init_audio(void)
|
||||
{
|
||||
// Accomodate rewind since at some point we might have two full buffers.
|
||||
size_t max_bufsamples = AUDIO_CHUNK_SIZE_NONBLOCKING * 2;
|
||||
|
||||
// Used for recording even if audio isn't enabled.
|
||||
assert((g_extern.audio_data.conv_outsamples = malloc(max_bufsamples * sizeof(int16_t) * AUDIO_MAX_RATIO)));
|
||||
g_extern.audio_data.chunk_size = g_extern.audio_data.block_chunk_size;
|
||||
|
||||
// Needs to be able to hold full content of a full max_bufsamples in addition to its own.
|
||||
assert((g_extern.audio_data.rewind_buf = malloc(max_bufsamples * sizeof(int16_t))));
|
||||
g_extern.audio_data.rewind_size = max_bufsamples;
|
||||
|
||||
if (!g_settings.audio.enable)
|
||||
{
|
||||
g_extern.audio_active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
adjust_audio_input_rate();
|
||||
find_audio_driver();
|
||||
|
||||
g_extern.audio_data.block_chunk_size = AUDIO_CHUNK_SIZE_BLOCKING;
|
||||
g_extern.audio_data.nonblock_chunk_size = AUDIO_CHUNK_SIZE_NONBLOCKING;
|
||||
|
||||
driver.audio_data = driver.audio->init(strlen(g_settings.audio.device) ? g_settings.audio.device : NULL, g_settings.audio.out_rate, g_settings.audio.latency);
|
||||
if ( driver.audio_data == NULL )
|
||||
driver.audio_data = driver.audio->init(*g_settings.audio.device ? g_settings.audio.device : NULL,
|
||||
g_settings.audio.out_rate, g_settings.audio.latency);
|
||||
|
||||
if (!driver.audio_data)
|
||||
g_extern.audio_active = false;
|
||||
|
||||
if (g_extern.audio_active && driver.audio->use_float && driver.audio->use_float(driver.audio_data))
|
||||
@@ -157,82 +298,219 @@ void init_audio(void)
|
||||
driver.audio->set_nonblock_state(driver.audio_data, true);
|
||||
g_extern.audio_data.chunk_size = g_extern.audio_data.nonblock_chunk_size;
|
||||
}
|
||||
else
|
||||
g_extern.audio_data.chunk_size = g_extern.audio_data.block_chunk_size;
|
||||
|
||||
int err;
|
||||
g_extern.source = src_new(g_settings.audio.src_quality, 2, &err);
|
||||
if (!g_extern.source)
|
||||
g_extern.audio_data.source = hermite_new();
|
||||
if (!g_extern.audio_data.source)
|
||||
g_extern.audio_active = false;
|
||||
|
||||
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;
|
||||
|
||||
assert(g_settings.audio.out_rate < g_settings.audio.in_rate * AUDIO_MAX_RATIO);
|
||||
assert((g_extern.audio_data.data = malloc(max_bufsamples * sizeof(float))));
|
||||
g_extern.audio_data.data_ptr = 0;
|
||||
assert(g_settings.audio.out_rate < g_settings.audio.in_rate * AUDIO_MAX_RATIO);
|
||||
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)));
|
||||
|
||||
g_extern.audio_data.src_ratio =
|
||||
(double)g_settings.audio.out_rate / g_settings.audio.in_rate;
|
||||
|
||||
init_dsp_plugin();
|
||||
}
|
||||
|
||||
void uninit_audio(void)
|
||||
{
|
||||
free(g_extern.audio_data.conv_outsamples); g_extern.audio_data.conv_outsamples = NULL;
|
||||
g_extern.audio_data.data_ptr = 0;
|
||||
free(g_extern.audio_data.rewind_buf);
|
||||
g_extern.audio_data.rewind_buf = NULL;
|
||||
|
||||
if (!g_settings.audio.enable)
|
||||
{
|
||||
g_extern.audio_active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( driver.audio_data && driver.audio )
|
||||
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)
|
||||
hermite_free(g_extern.audio_data.source);
|
||||
|
||||
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;
|
||||
free(g_extern.audio_data.data);
|
||||
g_extern.audio_data.data = NULL;
|
||||
free(g_extern.audio_data.outsamples);
|
||||
g_extern.audio_data.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 = g_extern.system.geom.max_width;
|
||||
unsigned height = g_extern.system.geom.max_height;
|
||||
g_extern.filter.psize(&width, &height);
|
||||
|
||||
unsigned pow2_x = next_pow2(ceil(width));
|
||||
unsigned pow2_y = next_pow2(ceil(height));
|
||||
unsigned maxsize = pow2_x > pow2_y ? pow2_x : pow2_y;
|
||||
g_extern.filter.scale = maxsize / SSNES_SCALE_BASE;
|
||||
|
||||
g_extern.filter.buffer = malloc(SSNES_SCALE_BASE * SSNES_SCALE_BASE * g_extern.filter.scale * g_extern.filter.scale * sizeof(uint32_t));
|
||||
g_extern.filter.pitch = SSNES_SCALE_BASE * g_extern.filter.scale * sizeof(uint32_t);
|
||||
assert(g_extern.filter.buffer);
|
||||
|
||||
g_extern.filter.colormap = malloc(32768 * sizeof(uint32_t));
|
||||
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);
|
||||
g_extern.shader_dir.elems = NULL;
|
||||
g_extern.shader_dir.size = 0;
|
||||
g_extern.shader_dir.ptr = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
void init_video_input(void)
|
||||
{
|
||||
int scale = 2;
|
||||
#ifdef HAVE_DYLIB
|
||||
init_filter();
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_XML
|
||||
init_shader_dir();
|
||||
#endif
|
||||
|
||||
unsigned max_dim = max(g_extern.system.geom.max_width, g_extern.system.geom.max_height);
|
||||
unsigned scale = max_dim / SSNES_SCALE_BASE;
|
||||
scale = max(scale, 1);
|
||||
|
||||
find_video_driver();
|
||||
find_input_driver();
|
||||
|
||||
// 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;
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
if (g_settings.video.force_aspect && (g_settings.video.aspect_ratio > 0.0f))
|
||||
{
|
||||
width = roundf(g_extern.system.geom.base_height * g_settings.video.xscale * g_settings.video.aspect_ratio);
|
||||
height = roundf(g_extern.system.geom.base_height * g_settings.video.yscale);
|
||||
}
|
||||
else
|
||||
{
|
||||
width = roundf(g_extern.system.geom.base_width * g_settings.video.xscale);
|
||||
height = roundf(g_extern.system.geom.base_height * g_settings.video.yscale);
|
||||
}
|
||||
}
|
||||
|
||||
if (g_settings.video.aspect_ratio < 0.0f)
|
||||
{
|
||||
g_settings.video.aspect_ratio = (float)g_extern.system.geom.base_width / g_extern.system.geom.base_height;
|
||||
SSNES_LOG("Adjusting aspect ratio to %.2f\n", g_settings.video.aspect_ratio);
|
||||
}
|
||||
|
||||
SSNES_LOG("Video @ %ux%u\n", width, height);
|
||||
|
||||
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;
|
||||
driver.video_data = driver.video->init(&video, &driver.input, &driver.input_data);
|
||||
|
||||
if ( driver.video_data == NULL )
|
||||
if (driver.video_data == NULL)
|
||||
{
|
||||
SSNES_ERR("Cannot open video driver... Exiting ...\n");
|
||||
SSNES_ERR("Cannot open video driver ... Exiting ...\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -243,7 +521,7 @@ void init_video_input(void)
|
||||
if (driver.input != NULL)
|
||||
{
|
||||
driver.input_data = driver.input->init();
|
||||
if ( driver.input_data == NULL )
|
||||
if (driver.input_data == NULL)
|
||||
{
|
||||
SSNES_ERR("Cannot init input driver. Exiting ...\n");
|
||||
exit(1);
|
||||
@@ -259,36 +537,18 @@ void init_video_input(void)
|
||||
|
||||
void uninit_video_input(void)
|
||||
{
|
||||
if ( driver.video_data && driver.video )
|
||||
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;
|
||||
|
||||
#if 0
|
||||
driver_t driver = {
|
||||
#if VIDEO_DRIVER == VIDEO_GL
|
||||
.video = &video_gl,
|
||||
#else
|
||||
#error "Define a valid video driver in config.h"
|
||||
#endif
|
||||
|
||||
#if AUDIO_DRIVER == AUDIO_ALSA
|
||||
.audio = &audio_alsa,
|
||||
#elif AUDIO_DRIVER == AUDIO_RSOUND
|
||||
.audio = &audio_rsound,
|
||||
#elif AUDIO_DRIVER == AUDIO_OSS
|
||||
.audio = &audio_oss,
|
||||
#elif AUDIO_DRIVER == AUDIO_ROAR
|
||||
.audio = &audio_roar,
|
||||
#elif AUDIO_DRIVER == AUDIO_AL
|
||||
.audio = &audio_openal,
|
||||
#else
|
||||
#error "Define a valid audio driver in config.h"
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -24,58 +24,79 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include "input/keysym.h"
|
||||
|
||||
// SNES has 12 buttons from 0-11 (libsnes.hpp)
|
||||
#define SSNES_FIRST_META_KEY 12
|
||||
enum
|
||||
{
|
||||
SSNES_FAST_FORWARD_KEY = 0x666, // Hurr, durr
|
||||
SSNES_FAST_FORWARD_KEY = SSNES_FIRST_META_KEY,
|
||||
SSNES_FAST_FORWARD_HOLD_KEY,
|
||||
SSNES_LOAD_STATE_KEY,
|
||||
SSNES_SAVE_STATE_KEY,
|
||||
SSNES_FULLSCREEN_TOGGLE_KEY,
|
||||
SSNES_QUIT_KEY,
|
||||
SSNES_STATE_SLOT_PLUS,
|
||||
SSNES_STATE_SLOT_MINUS,
|
||||
SSNES_AUDIO_INPUT_RATE_PLUS,
|
||||
SSNES_AUDIO_INPUT_RATE_MINUS,
|
||||
SSNES_REWIND,
|
||||
SSNES_MOVIE_RECORD_TOGGLE,
|
||||
SSNES_PAUSE_TOGGLE,
|
||||
SSNES_FRAMEADVANCE,
|
||||
SSNES_RESET,
|
||||
SSNES_SHADER_NEXT,
|
||||
SSNES_SHADER_PREV,
|
||||
SSNES_CHEAT_INDEX_PLUS,
|
||||
SSNES_CHEAT_INDEX_MINUS,
|
||||
SSNES_CHEAT_TOGGLE,
|
||||
SSNES_SCREENSHOT,
|
||||
SSNES_DSP_CONFIG,
|
||||
|
||||
SSNES_BIND_LIST_END
|
||||
};
|
||||
|
||||
|
||||
struct snes_keybind
|
||||
{
|
||||
int id;
|
||||
uint16_t key;
|
||||
enum ssnes_key key;
|
||||
uint16_t joykey;
|
||||
uint32_t joyaxis;
|
||||
};
|
||||
|
||||
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);
|
||||
ssize_t (*write)(void* data, const void* buf, size_t size);
|
||||
bool (*stop)(void* data);
|
||||
bool (*start)(void* data);
|
||||
void (*set_nonblock_state)(void* data, bool toggle); // Should we care about blocking in audio thread? Fast forwarding.
|
||||
void (*free)(void* data);
|
||||
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);
|
||||
void (*set_nonblock_state)(void *data, bool toggle); // Should we care about blocking in audio thread? Fast forwarding.
|
||||
void (*free)(void *data);
|
||||
bool (*use_float)(void *data); // Defines if driver will take standard floating point samples, or int16_t samples.
|
||||
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)
|
||||
@@ -89,23 +110,25 @@ typedef struct audio_driver
|
||||
|
||||
typedef struct input_driver
|
||||
{
|
||||
void* (*init)(void);
|
||||
void (*poll)(void* data);
|
||||
int16_t (*input_state)(void* data, const struct snes_keybind **snes_keybinds, bool port, unsigned device, unsigned index, unsigned id);
|
||||
bool (*key_pressed)(void* data, int key);
|
||||
void (*free)(void* data);
|
||||
void *(*init)(void);
|
||||
void (*poll)(void *data);
|
||||
int16_t (*input_state)(void *data, const struct snes_keybind **snes_keybinds, bool port, unsigned device, unsigned index, unsigned id);
|
||||
bool (*key_pressed)(void *data, int key);
|
||||
void (*free)(void *data);
|
||||
const char *ident;
|
||||
} input_driver_t;
|
||||
|
||||
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.
|
||||
void (*set_nonblock_state)(void* data, bool toggle); // Should we care about syncing to vblank? Fast forwarding.
|
||||
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);
|
||||
void (*free)(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;
|
||||
|
||||
@@ -138,8 +161,17 @@ extern const audio_driver_t audio_roar;
|
||||
extern const audio_driver_t audio_openal;
|
||||
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
|
||||
@@ -17,7 +17,9 @@
|
||||
|
||||
#include "dynamic.h"
|
||||
#include "general.h"
|
||||
#include "strl.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
@@ -25,29 +27,25 @@
|
||||
|
||||
#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);
|
||||
@@ -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);
|
||||
|
||||
@@ -93,50 +96,59 @@ unsigned (*psnes_get_memory_size)(unsigned);
|
||||
void (*psnes_unload_cartridge)(void);
|
||||
void (*psnes_term)(void);
|
||||
|
||||
#ifdef HAVE_DYLIB
|
||||
static void set_environment(void);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_DYNAMIC
|
||||
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);
|
||||
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
|
||||
|
||||
#define SSYM(x) do { \
|
||||
p##x = x; \
|
||||
} while(0)
|
||||
} while (0)
|
||||
|
||||
#ifndef HAVE_DYNAMIC
|
||||
static void set_statics(void)
|
||||
@@ -148,6 +160,10 @@ static void set_statics(void)
|
||||
SSYM(snes_set_input_state);
|
||||
SSYM(snes_library_revision_minor);
|
||||
SSYM(snes_library_revision_major);
|
||||
SSYM(snes_library_id);
|
||||
SSYM(snes_cheat_reset);
|
||||
SSYM(snes_cheat_set);
|
||||
SSYM(snes_reset);
|
||||
SSYM(snes_run);
|
||||
SSYM(snes_get_region);
|
||||
SSYM(snes_load_cartridge_normal);
|
||||
@@ -169,16 +185,41 @@ 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();
|
||||
else
|
||||
|
||||
// Try to verify that -lsnes was not linked in from other modules
|
||||
// since loading it dynamically and with -l will fail hard.
|
||||
function_t sym = dylib_proc(NULL, "snes_init");
|
||||
if (sym)
|
||||
{
|
||||
SSNES_ERR("This binary is built to use runtime dynamic binding of libsnes. Set libsnes_path in config to load a libsnes library dynamically.\n");
|
||||
SSNES_ERR("Serious problem! SSNES wants to load libsnes dyamically, but it is already linked!\n");
|
||||
SSNES_ERR("This could happen if other modules SSNES depends on link against libsnes directly.\n");
|
||||
SSNES_ERR("Proceeding could cause a crash! Aborting ...\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!*g_settings.libsnes)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
strlcpy(g_settings.libsnes, "snes.dll", sizeof(g_settings.libsnes));
|
||||
#elif defined(__APPLE__)
|
||||
strlcpy(g_settings.libsnes, "libsnes.dylib", sizeof(g_settings.libsnes));
|
||||
#else
|
||||
set_statics();
|
||||
strlcpy(g_settings.libsnes, "libsnes.so", sizeof(g_settings.libsnes));
|
||||
#endif
|
||||
}
|
||||
|
||||
load_dynamic();
|
||||
#else
|
||||
set_statics();
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_DYLIB
|
||||
set_environment();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -186,12 +227,131 @@ 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 ? lib : GetModuleHandle(NULL), proc);
|
||||
#else
|
||||
void *ptr_sym = NULL;
|
||||
if (lib)
|
||||
ptr_sym = dlsym(lib, proc);
|
||||
else
|
||||
{
|
||||
void *handle = dlopen(NULL, RTLD_LAZY);
|
||||
if (handle)
|
||||
{
|
||||
ptr_sym = dlsym(handle, proc);
|
||||
dlclose(handle);
|
||||
}
|
||||
}
|
||||
|
||||
// Dirty hack to workaround the non-legality of (void*) -> fn-pointer casts.
|
||||
function_t sym;
|
||||
memcpy(&sym, &ptr_sym, sizeof(void*));
|
||||
#endif
|
||||
|
||||
return sym;
|
||||
}
|
||||
|
||||
void dylib_close(dylib_t lib)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
FreeLibrary(lib);
|
||||
#else
|
||||
dlclose(lib);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool environment_cb(unsigned cmd, void *data)
|
||||
{
|
||||
switch (cmd)
|
||||
{
|
||||
case SNES_ENVIRONMENT_GET_FULLPATH:
|
||||
*(const char**)data = g_extern.system.fullpath;
|
||||
SSNES_LOG("FULLPATH: \"%s\"\n", g_extern.system.fullpath);
|
||||
break;
|
||||
|
||||
case SNES_ENVIRONMENT_SET_GEOMETRY:
|
||||
g_extern.system.geom = *(const struct snes_geometry*)data;
|
||||
g_extern.system.geom.max_width = next_pow2(g_extern.system.geom.max_width);
|
||||
g_extern.system.geom.max_height = next_pow2(g_extern.system.geom.max_height);
|
||||
SSNES_LOG("SET_GEOMETRY: (%ux%u) / (%ux%u)\n",
|
||||
g_extern.system.geom.base_width,
|
||||
g_extern.system.geom.base_height,
|
||||
g_extern.system.geom.max_width,
|
||||
g_extern.system.geom.max_height);
|
||||
break;
|
||||
|
||||
case SNES_ENVIRONMENT_SET_PITCH:
|
||||
g_extern.system.pitch = *(const unsigned*)data;
|
||||
SSNES_LOG("SET_PITCH: %u\n", g_extern.system.pitch);
|
||||
break;
|
||||
|
||||
case SNES_ENVIRONMENT_GET_OVERSCAN:
|
||||
*(bool*)data = !g_settings.video.crop_overscan;
|
||||
SSNES_LOG("GET_OVERSCAN: %u\n", (unsigned)!g_settings.video.crop_overscan);
|
||||
break;
|
||||
|
||||
case SNES_ENVIRONMENT_SET_TIMING:
|
||||
g_extern.system.timing = *(const struct snes_system_timing*)data;
|
||||
g_extern.system.timing_set = true;
|
||||
break;
|
||||
|
||||
case SNES_ENVIRONMENT_GET_CAN_DUPE:
|
||||
*(bool*)data = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef HAVE_DYLIB
|
||||
// Assume SNES as defaults.
|
||||
static void set_environment_defaults(void)
|
||||
{
|
||||
g_extern.system.pitch = 0; // 0 is classic libsnes semantics.
|
||||
g_extern.system.geom = (struct snes_geometry) {
|
||||
.base_width = 256,
|
||||
.base_height = 224,
|
||||
.max_width = 512,
|
||||
.max_height = 512,
|
||||
};
|
||||
}
|
||||
|
||||
// SSNES extension hooks. Totally optional 'n shizz :)
|
||||
static void set_environment(void)
|
||||
{
|
||||
#ifdef HAVE_DYNAMIC
|
||||
dylib_t lib = lib_handle;
|
||||
#else
|
||||
dylib_t lib = NULL;
|
||||
#endif
|
||||
|
||||
void (*psnes_set_environment)(snes_environment_t) =
|
||||
(void (*)(snes_environment_t))dylib_proc(lib, "snes_set_environment");
|
||||
|
||||
if (psnes_set_environment)
|
||||
psnes_set_environment(environment_cb);
|
||||
|
||||
set_environment_defaults();
|
||||
}
|
||||
#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,19 +13,20 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "buffer.h"
|
||||
#include "fifo_buffer.h"
|
||||
#include <stdint.h>
|
||||
|
||||
struct rsound_fifo_buffer
|
||||
struct fifo_buffer
|
||||
{
|
||||
char *buffer;
|
||||
uint8_t *buffer;
|
||||
size_t bufsize;
|
||||
size_t first;
|
||||
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,20 +41,14 @@ 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);
|
||||
|
||||
free(buffer->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);
|
||||
|
||||
size_t first = buffer->first;
|
||||
size_t end = buffer->end;
|
||||
if (end < first)
|
||||
@@ -61,11 +56,8 @@ 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);
|
||||
|
||||
size_t first = buffer->first;
|
||||
size_t end = buffer->end;
|
||||
if (end < first)
|
||||
@@ -74,13 +66,8 @@ 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);
|
||||
|
||||
size_t first_write = size;
|
||||
size_t rest_write = 0;
|
||||
if (buffer->end + size > buffer->bufsize)
|
||||
@@ -91,19 +78,14 @@ void rsnd_fifo_write(rsound_fifo_buffer_t* buffer, const void* in_buf, size_t si
|
||||
|
||||
memcpy(buffer->buffer + buffer->end, in_buf, first_write);
|
||||
if (rest_write > 0)
|
||||
memcpy(buffer->buffer, (const char*)in_buf + first_write, rest_write);
|
||||
memcpy(buffer->buffer, (const uint8_t*)in_buf + first_write, rest_write);
|
||||
|
||||
buffer->end = (buffer->end + size) % buffer->bufsize;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
|
||||
size_t first_read = size;
|
||||
size_t rest_read = 0;
|
||||
if (buffer->first + size > buffer->bufsize)
|
||||
@@ -112,9 +94,9 @@ void rsnd_fifo_read(rsound_fifo_buffer_t* buffer, void* in_buf, size_t size)
|
||||
rest_read = size - first_read;
|
||||
}
|
||||
|
||||
memcpy(in_buf, (const char*)buffer->buffer + buffer->first, first_read);
|
||||
memcpy(in_buf, (const uint8_t*)buffer->buffer + buffer->first, first_read);
|
||||
if (rest_read > 0)
|
||||
memcpy((char*)in_buf + first_read, buffer->buffer, rest_read);
|
||||
memcpy((uint8_t*)in_buf + first_read, buffer->buffer, rest_read);
|
||||
|
||||
buffer->first = (buffer->first + size) % buffer->bufsize;
|
||||
}
|
||||
@@ -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
|
||||
@@ -21,25 +21,237 @@
|
||||
#include <stdbool.h>
|
||||
#include <libsnes.hpp>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
#include "dynamic.h"
|
||||
#include "movie.h"
|
||||
#include "ups.h"
|
||||
#include "bps.h"
|
||||
#include "strl.h"
|
||||
|
||||
#ifdef HAVE_XML
|
||||
#include "sha256.h"
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#include <windows.h>
|
||||
#include <shlwapi.h>
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.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;
|
||||
}
|
||||
|
||||
enum patch_type
|
||||
{
|
||||
PATCH_NONE,
|
||||
PATCH_UPS,
|
||||
PATCH_BPS
|
||||
};
|
||||
|
||||
static void patch_rom(uint8_t **buf, ssize_t *size)
|
||||
{
|
||||
uint8_t *ret_buf = *buf;
|
||||
ssize_t ret_size = *size;
|
||||
|
||||
ssize_t patch_size = 0;
|
||||
void *patch_data = NULL;
|
||||
enum patch_type type = PATCH_NONE;
|
||||
|
||||
if (g_extern.ups_pref && g_extern.bps_pref)
|
||||
{
|
||||
SSNES_WARN("Both UPS and BPS patch explicitly defined, ignoring both ...\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!g_extern.bps_pref && *g_extern.ups_name && (patch_size = read_file(g_extern.ups_name, &patch_data)) >= 0)
|
||||
type = PATCH_UPS;
|
||||
else if (!g_extern.ups_pref && *g_extern.bps_name && (patch_size = read_file(g_extern.bps_name, &patch_data)) >= 0)
|
||||
type = PATCH_BPS;
|
||||
|
||||
if (type == PATCH_NONE)
|
||||
{
|
||||
SSNES_LOG("Did not find a valid ROM patch.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case PATCH_UPS:
|
||||
SSNES_LOG("Found UPS file in \"%s\", attempting to patch ...\n", g_extern.ups_name);
|
||||
break;
|
||||
case PATCH_BPS:
|
||||
SSNES_LOG("Found BPS file in \"%s\", attempting to patch ...\n", g_extern.bps_name);
|
||||
break;
|
||||
|
||||
default:
|
||||
return; // Should not happen, but.
|
||||
}
|
||||
|
||||
size_t target_size = ret_size * 4; // Just to be sure ...
|
||||
uint8_t *patched_rom = malloc(target_size);
|
||||
if (!patched_rom)
|
||||
{
|
||||
SSNES_ERR("Failed to allocate memory for patched ROM ...\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
switch (type)
|
||||
{
|
||||
case PATCH_UPS:
|
||||
{
|
||||
ups_error_t err = ups_apply_patch(patch_data, patch_size, ret_buf, ret_size, patched_rom, &target_size);
|
||||
if (err == UPS_SUCCESS)
|
||||
{
|
||||
SSNES_LOG("ROM patched successfully (UPS)!\n");
|
||||
success = true;
|
||||
}
|
||||
else
|
||||
SSNES_ERR("Failed to patch UPS: Error #%u\n", (unsigned)err);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PATCH_BPS:
|
||||
{
|
||||
bps_error_t err = bps_apply_patch(patch_data, patch_size, ret_buf, ret_size, patched_rom, &target_size);
|
||||
if (err == BPS_SUCCESS)
|
||||
{
|
||||
SSNES_LOG("ROM patched successfully (BPS)!\n");
|
||||
success = true;
|
||||
}
|
||||
else
|
||||
SSNES_ERR("Failed to patch BPS: Error #%u\n", (unsigned)err);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
free(ret_buf);
|
||||
*buf = patched_rom;
|
||||
*size = target_size;
|
||||
}
|
||||
|
||||
if (patch_data)
|
||||
free(patch_data);
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
*buf = ret_buf;
|
||||
*size = ret_size;
|
||||
if (patch_data)
|
||||
free(patch_data);
|
||||
}
|
||||
|
||||
// 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
|
||||
setmode(0, O_BINARY);
|
||||
#endif
|
||||
|
||||
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");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for(;;)
|
||||
for (;;)
|
||||
{
|
||||
size_t ret = fread(rom_buf + buf_ptr, 1, buf_size - buf_ptr, stdin);
|
||||
buf_ptr += ret;
|
||||
@@ -58,100 +270,126 @@ 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;
|
||||
}
|
||||
|
||||
// Remove copier header if present (512 first bytes).
|
||||
if ((ret & 0x7fff) == 512)
|
||||
{
|
||||
memmove(ret_buf, ret_buf + 512, ret - 512);
|
||||
ret -= 512;
|
||||
}
|
||||
|
||||
// Attempt to apply a patch :)
|
||||
patch_rom(&ret_buf, &ret);
|
||||
|
||||
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)
|
||||
{
|
||||
SSNES_ERR("Couldn't open file: \"%s\"\n", path);
|
||||
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)
|
||||
{
|
||||
FILE *file = fopen(path, "wb");
|
||||
if (!file)
|
||||
{
|
||||
SSNES_ERR("Couldn't dump to file %s\n", path);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
fwrite(data, 1, size, file);
|
||||
bool ret = fwrite(data, 1, size, file) == size;
|
||||
fclose(file);
|
||||
return true;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
bool save_state(const char* path)
|
||||
static const char *ramtype2str(int type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case SNES_MEMORY_CARTRIDGE_RAM:
|
||||
case SNES_MEMORY_GAME_BOY_RAM:
|
||||
case SNES_MEMORY_BSX_RAM:
|
||||
return ".srm";
|
||||
|
||||
case SNES_MEMORY_CARTRIDGE_RTC:
|
||||
case SNES_MEMORY_GAME_BOY_RTC:
|
||||
return ".rtc";
|
||||
|
||||
case SNES_MEMORY_BSX_PRAM:
|
||||
return ".pram";
|
||||
|
||||
case SNES_MEMORY_SUFAMI_TURBO_A_RAM:
|
||||
return ".aram";
|
||||
case SNES_MEMORY_SUFAMI_TURBO_B_RAM:
|
||||
return ".bram";
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to save valuable RAM data somewhere ...
|
||||
static void dump_to_file_desperate(const void *data, size_t size, int type)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const char *base = getenv("APPDATA");
|
||||
#else
|
||||
const char *base = getenv("HOME");
|
||||
#endif
|
||||
|
||||
if (!base)
|
||||
goto error;
|
||||
|
||||
char path[MAXPATHLEN];
|
||||
snprintf(path, sizeof(path), "%s/SSNES-recovery-", base);
|
||||
char timebuf[MAXPATHLEN];
|
||||
|
||||
time_t time_;
|
||||
time(&time_);
|
||||
strftime(timebuf, sizeof(timebuf), "%Y-%m-%d-%H-%M-%S", localtime(&time_));
|
||||
strlcat(path, timebuf, sizeof(path));
|
||||
strlcat(path, ramtype2str(type), sizeof(path));
|
||||
|
||||
if (dump_to_file(path, data, size))
|
||||
SSNES_WARN("Succeeded in saving RAM data to \"%s\". Phew! :D\n", path);
|
||||
else
|
||||
goto error;
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
SSNES_WARN("Failed ... Tough luck ... :(\n");
|
||||
}
|
||||
|
||||
bool save_state(const char *path)
|
||||
{
|
||||
SSNES_LOG("Saving state: \"%s\".\n", path);
|
||||
size_t size = psnes_serialize_size();
|
||||
@@ -169,30 +407,99 @@ bool save_state(const char* path)
|
||||
psnes_serialize(data, size);
|
||||
bool ret = dump_to_file(path, data, size);
|
||||
free(data);
|
||||
if (!ret)
|
||||
SSNES_ERR("Failed to save state to \"%s\".\n", path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool load_state(const char* path)
|
||||
bool load_state(const char *path)
|
||||
{
|
||||
SSNES_LOG("Loading state: \"%s\".\n", path);
|
||||
void *buf = NULL;
|
||||
ssize_t size = read_file(path, &buf);
|
||||
if (size < 0)
|
||||
{
|
||||
SSNES_ERR("Failed to load state.\n");
|
||||
SSNES_ERR("Failed to load state from \"%s\".\n", path);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
SSNES_LOG("State size: %d bytes.\n", (int)size);
|
||||
|
||||
uint8_t *block_buf[2] = {NULL, NULL};
|
||||
int block_type[2] = {-1, -1};
|
||||
unsigned block_size[2] = {0};
|
||||
|
||||
if (g_settings.block_sram_overwrite)
|
||||
{
|
||||
SSNES_LOG("Blocking SRAM overwrite!\n");
|
||||
switch (g_extern.game_type)
|
||||
{
|
||||
case SSNES_CART_NORMAL:
|
||||
block_type[0] = SNES_MEMORY_CARTRIDGE_RAM;
|
||||
block_type[1] = SNES_MEMORY_CARTRIDGE_RTC;
|
||||
break;
|
||||
|
||||
case SSNES_CART_BSX:
|
||||
case SSNES_CART_BSX_SLOTTED:
|
||||
block_type[0] = SNES_MEMORY_BSX_RAM;
|
||||
block_type[1] = SNES_MEMORY_BSX_PRAM;
|
||||
break;
|
||||
|
||||
case SSNES_CART_SUFAMI:
|
||||
block_type[0] = SNES_MEMORY_SUFAMI_TURBO_A_RAM;
|
||||
block_type[1] = SNES_MEMORY_SUFAMI_TURBO_B_RAM;
|
||||
break;
|
||||
|
||||
case SSNES_CART_SGB:
|
||||
block_type[0] = SNES_MEMORY_GAME_BOY_RAM;
|
||||
block_type[1] = SNES_MEMORY_GAME_BOY_RTC;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
if (block_type[i] != -1)
|
||||
block_size[i] = psnes_get_memory_size(block_type[i]);
|
||||
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
if (block_size[i])
|
||||
block_buf[i] = malloc(block_size[i]);
|
||||
|
||||
// Backup current SRAM which is overwritten by unserialize.
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
if (block_buf[i])
|
||||
{
|
||||
const uint8_t *ptr = psnes_get_memory_data(block_type[i]);
|
||||
if (ptr)
|
||||
memcpy(block_buf[i], ptr, block_size[i]);
|
||||
}
|
||||
}
|
||||
|
||||
psnes_unserialize(buf, size);
|
||||
|
||||
// Flush back :D
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
if (block_buf[i])
|
||||
{
|
||||
uint8_t *ptr = psnes_get_memory_data(block_type[i]);
|
||||
if (ptr)
|
||||
memcpy(ptr, block_buf[i], block_size[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
if (block_buf[i])
|
||||
free(block_buf[i]);
|
||||
}
|
||||
|
||||
free(buf);
|
||||
return true;
|
||||
}
|
||||
|
||||
void load_ram_file(const char* path, int type)
|
||||
void load_ram_file(const char *path, int type)
|
||||
{
|
||||
size_t size = psnes_get_memory_size(type);
|
||||
uint8_t *data = psnes_get_memory_data(type);
|
||||
@@ -203,18 +510,39 @@ void load_ram_file(const char* path, int type)
|
||||
void *buf = NULL;
|
||||
ssize_t rc = read_file(path, &buf);
|
||||
if (rc <= size)
|
||||
memcpy(data, buf, size);
|
||||
memcpy(data, buf, rc);
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
void save_ram_file(const char* path, int type)
|
||||
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 )
|
||||
dump_to_file(path, data, size);
|
||||
if (data && size > 0)
|
||||
{
|
||||
if (!dump_to_file(path, data, size))
|
||||
{
|
||||
SSNES_ERR("Failed to save SNES RAM!\n");
|
||||
SSNES_WARN("Attempting to recover ...\n");
|
||||
dump_to_file_desperate(data, size, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -232,27 +560,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)
|
||||
@@ -286,28 +612,23 @@ 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,
|
||||
NULL, extra_rom_buf, extra_rom_len))
|
||||
{
|
||||
SSNES_ERR("Cannot load BSX slotted rom.\n");
|
||||
goto error;
|
||||
}
|
||||
xml_buf, rom_buf, rom_len,
|
||||
NULL, extra_rom_buf, extra_rom_len))
|
||||
{
|
||||
SSNES_ERR("Cannot load BSX slotted rom.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
@@ -321,6 +642,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)
|
||||
@@ -360,23 +684,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]))
|
||||
{
|
||||
@@ -384,6 +703,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);
|
||||
@@ -425,13 +746,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;
|
||||
}
|
||||
@@ -474,3 +800,185 @@ 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 cur_ptr = 0;
|
||||
size_t cur_size = 32;
|
||||
char **dir_list = NULL;
|
||||
|
||||
#ifdef _WIN32
|
||||
WIN32_FIND_DATAW ffd;
|
||||
HANDLE hFind = INVALID_HANDLE_VALUE;
|
||||
|
||||
wchar_t wchar_buf[MAXPATHLEN];
|
||||
char utf8_buf[MAXPATHLEN];
|
||||
|
||||
if (strlcpy(utf8_buf, dir, sizeof(utf8_buf)) >= sizeof(utf8_buf))
|
||||
goto error;
|
||||
if (strlcat(utf8_buf, "/*", sizeof(utf8_buf)) >= sizeof(utf8_buf))
|
||||
goto error;
|
||||
|
||||
if (ext)
|
||||
{
|
||||
if (strlcat(utf8_buf, ext, sizeof(utf8_buf)) >= sizeof(utf8_buf))
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (MultiByteToWideChar(CP_UTF8, 0, utf8_buf, -1, wchar_buf, MAXPATHLEN) == 0)
|
||||
goto error;
|
||||
|
||||
hFind = FindFirstFileW(wchar_buf, &ffd);
|
||||
if (hFind == INVALID_HANDLE_VALUE)
|
||||
goto error;
|
||||
#else
|
||||
DIR *directory = NULL;
|
||||
const struct dirent *entry = NULL;
|
||||
|
||||
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;
|
||||
if (WideCharToMultiByte(CP_UTF8, 0, ffd.cFileName, -1, utf8_buf, MAXPATHLEN, NULL, NULL) == 0)
|
||||
continue;
|
||||
if (ext && !strstr(utf8_buf, ext))
|
||||
continue;
|
||||
#else
|
||||
if (ext && !strstr(entry->d_name, ext))
|
||||
continue;
|
||||
#endif
|
||||
|
||||
dir_list[cur_ptr] = malloc(MAXPATHLEN);
|
||||
if (!dir_list[cur_ptr])
|
||||
goto error;
|
||||
|
||||
strlcpy(dir_list[cur_ptr], dir, MAXPATHLEN);
|
||||
strlcat(dir_list[cur_ptr], "/", MAXPATHLEN);
|
||||
#ifdef _WIN32
|
||||
strlcat(dir_list[cur_ptr], utf8_buf, MAXPATHLEN);
|
||||
#else
|
||||
strlcat(dir_list[cur_ptr], entry->d_name, MAXPATHLEN);
|
||||
#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);
|
||||
}
|
||||
|
||||
bool path_is_directory(const char *path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
wchar_t buf[MAXPATHLEN];
|
||||
if (MultiByteToWideChar(CP_UTF8, 0, path, -1, buf, MAXPATHLEN) == 0)
|
||||
return false;
|
||||
return PathIsDirectoryW(buf) == FILE_ATTRIBUTE_DIRECTORY;
|
||||
#else
|
||||
struct stat buf;
|
||||
if (stat(path, &buf) < 0)
|
||||
return false;
|
||||
|
||||
return S_ISDIR(buf.st_mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool path_file_exists(const char *path)
|
||||
{
|
||||
FILE *dummy = fopen(path, "rb");
|
||||
if (dummy)
|
||||
{
|
||||
fclose(dummy);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void fill_pathname(char *out_path, const char *in_path, const char *replace, size_t size)
|
||||
{
|
||||
char tmp_path[MAXPATHLEN];
|
||||
assert(strlcpy(tmp_path, in_path, sizeof(tmp_path)) < sizeof(tmp_path));
|
||||
char *tok = strrchr(tmp_path, '.');
|
||||
if (tok != NULL)
|
||||
*tok = '\0';
|
||||
assert(strlcpy(out_path, tmp_path, size) < size);
|
||||
assert(strlcat(out_path, replace, size) < size);
|
||||
}
|
||||
|
||||
void fill_pathname_noext(char *out_path, const char *in_path, const char *replace, size_t size)
|
||||
{
|
||||
assert(strlcpy(out_path, in_path, size) < size);
|
||||
assert(strlcat(out_path, replace, size) < size);
|
||||
}
|
||||
|
||||
void fill_pathname_dir(char *in_dir, const char *in_basename, const char *replace, size_t size)
|
||||
{
|
||||
assert(strlcat(in_dir, "/", size) < size);
|
||||
|
||||
const char *base = strrchr(in_basename, '/');
|
||||
if (!base)
|
||||
base = strrchr(in_basename, '\\');
|
||||
|
||||
if (base)
|
||||
base++;
|
||||
else
|
||||
base = in_basename;
|
||||
|
||||
assert(strlcat(in_dir, base, size) < size);
|
||||
assert(strlcat(in_dir, replace, size) < size);
|
||||
}
|
||||
@@ -26,12 +26,34 @@
|
||||
#include <sys/types.h>
|
||||
#include "general.h"
|
||||
|
||||
bool load_state(const char* path);
|
||||
bool save_state(const char* path);
|
||||
// Generic file, path and directory handling.
|
||||
|
||||
void load_ram_file(const char* path, int type);
|
||||
void save_ram_file(const char* path, int type);
|
||||
ssize_t read_file(const char *path, void **buf);
|
||||
|
||||
bool load_state(const char *path);
|
||||
bool save_state(const char *path);
|
||||
|
||||
void load_ram_file(const char *path, int type);
|
||||
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.
|
||||
// If ext is NULL, any file will be picked.
|
||||
// If non-NULL, only files with extension ext are added.
|
||||
char **dir_list_new(const char *dir, const char *ext);
|
||||
void dir_list_free(char **dir_list);
|
||||
|
||||
bool path_is_directory(const char *path);
|
||||
bool path_file_exists(const char *path);
|
||||
|
||||
// Path-name operations.
|
||||
// Replaces filename extension with replace.
|
||||
void fill_pathname(char *out_path, const char *in_path, const char *replace, size_t size);
|
||||
// Sets filename extension. Assumes in_path has no extension.
|
||||
void fill_pathname_noext(char *out_path, const char *in_path, const char *replace, size_t size);
|
||||
// Concatenates in_basename and replace to in_dir.
|
||||
void fill_pathname_dir(char *in_dir, const char *in_basename, const char *replace, size_t size);
|
||||
|
||||
|
||||
#endif
|
||||
@@ -20,23 +20,53 @@
|
||||
#define __SSNES_GENERAL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "driver.h"
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include "driver.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"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SRC
|
||||
#include <samplerate.h>
|
||||
#include "audio/hermite.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <sys/param.h> // MAXPATHLEN
|
||||
#else
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifndef MAXPATHLEN
|
||||
#ifdef PATH_MAX
|
||||
#define MAXPATHLEN PATH_MAX
|
||||
#else
|
||||
#define MAXPATHLEN 512
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define MAX_PLAYERS 5
|
||||
#define MAX_BINDS 20 // Needs to be increased every time there are new binds added.
|
||||
#define MAX_BINDS (SSNES_BIND_LIST_END + 1)
|
||||
#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 +80,37 @@ 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 cg_shader_path[MAXPATHLEN];
|
||||
char bsnes_shader_path[MAXPATHLEN];
|
||||
char filter_path[MAXPATHLEN];
|
||||
enum ssnes_shader_type shader_type;
|
||||
float refresh_rate;
|
||||
|
||||
char font_path[256];
|
||||
bool render_to_texture;
|
||||
double fbo_scale_x;
|
||||
double fbo_scale_y;
|
||||
char second_pass_shader[MAXPATHLEN];
|
||||
bool second_pass_smooth;
|
||||
char shader_dir[MAXPATHLEN];
|
||||
|
||||
char font_path[MAXPATHLEN];
|
||||
unsigned font_size;
|
||||
bool font_enable;
|
||||
float msg_pos_x;
|
||||
float msg_pos_y;
|
||||
float msg_color_r;
|
||||
float msg_color_g;
|
||||
float msg_color_b;
|
||||
|
||||
bool force_16bit;
|
||||
bool disable_composition;
|
||||
|
||||
bool hires_record;
|
||||
bool post_filter_record;
|
||||
|
||||
char external_driver[MAXPATHLEN];
|
||||
} video;
|
||||
|
||||
struct
|
||||
@@ -67,11 +118,14 @@ struct settings
|
||||
char driver[32];
|
||||
bool enable;
|
||||
unsigned out_rate;
|
||||
unsigned in_rate;
|
||||
char device[256];
|
||||
float in_rate;
|
||||
float rate_step;
|
||||
char device[MAXPATHLEN];
|
||||
unsigned latency;
|
||||
bool sync;
|
||||
int src_quality;
|
||||
|
||||
char dsp_plugin[MAXPATHLEN];
|
||||
char external_driver[MAXPATHLEN];
|
||||
} audio;
|
||||
|
||||
struct
|
||||
@@ -80,9 +134,24 @@ 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 libsnes[MAXPATHLEN];
|
||||
char cheat_database[MAXPATHLEN];
|
||||
char cheat_settings_path[MAXPATHLEN];
|
||||
|
||||
char screenshot_directory[MAXPATHLEN];
|
||||
|
||||
bool rewind_enable;
|
||||
size_t rewind_buffer_size;
|
||||
unsigned rewind_granularity;
|
||||
|
||||
bool pause_nonactive;
|
||||
unsigned autosave_interval;
|
||||
|
||||
bool block_sram_overwrite;
|
||||
bool savestate_auto_index;
|
||||
};
|
||||
|
||||
enum ssnes_game_type
|
||||
@@ -94,60 +163,169 @@ 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];
|
||||
char gb_rom_path[MAXPATHLEN];
|
||||
char bsx_rom_path[MAXPATHLEN];
|
||||
char sufami_rom_path[2][MAXPATHLEN];
|
||||
bool has_set_save_path;
|
||||
bool has_set_state_path;
|
||||
|
||||
#ifdef HAVE_CONFIGFILE
|
||||
char config_path[256];
|
||||
char config_path[MAXPATHLEN];
|
||||
#endif
|
||||
|
||||
char basename[256];
|
||||
char savefile_name_srm[256];
|
||||
char savefile_name_rtc[512]; // Make sure that fill_pathname has space.
|
||||
char savefile_name_psrm[512];
|
||||
char savefile_name_asrm[512];
|
||||
char savefile_name_bsrm[512];
|
||||
char savestate_name[256];
|
||||
char basename[MAXPATHLEN];
|
||||
char savefile_name_srm[MAXPATHLEN];
|
||||
char savefile_name_rtc[MAXPATHLEN]; // Make sure that fill_pathname has space.
|
||||
char savefile_name_psrm[MAXPATHLEN];
|
||||
char savefile_name_asrm[MAXPATHLEN];
|
||||
char savefile_name_bsrm[MAXPATHLEN];
|
||||
char savestate_name[MAXPATHLEN];
|
||||
char xml_name[MAXPATHLEN];
|
||||
|
||||
bool ups_pref;
|
||||
bool bps_pref;
|
||||
char ups_name[MAXPATHLEN];
|
||||
char bps_name[MAXPATHLEN];
|
||||
|
||||
unsigned state_slot;
|
||||
|
||||
struct
|
||||
{
|
||||
struct snes_geometry geom;
|
||||
unsigned pitch; // If 0, has classic libsnes semantics.
|
||||
char fullpath[MAXPATHLEN];
|
||||
struct snes_system_timing timing;
|
||||
bool timing_set;
|
||||
} system;
|
||||
|
||||
struct
|
||||
{
|
||||
hermite_resampler_t *source;
|
||||
|
||||
float *data;
|
||||
size_t data_ptr;
|
||||
size_t chunk_size;
|
||||
size_t nonblock_chunk_size;
|
||||
size_t block_chunk_size;
|
||||
|
||||
double src_ratio;
|
||||
|
||||
bool use_float;
|
||||
|
||||
float *outsamples;
|
||||
int16_t *conv_outsamples;
|
||||
|
||||
int16_t *rewind_buf;
|
||||
size_t rewind_ptr;
|
||||
size_t rewind_size;
|
||||
|
||||
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 playback/recording support.
|
||||
struct
|
||||
{
|
||||
bsv_movie_t *movie;
|
||||
char movie_path[MAXPATHLEN];
|
||||
bool movie_playback;
|
||||
|
||||
// Immediate playback/recording.
|
||||
char movie_start_path[MAXPATHLEN];
|
||||
bool movie_start_recording;
|
||||
bool movie_start_playback;
|
||||
bool movie_end;
|
||||
} bsv;
|
||||
|
||||
bool sram_load_disable;
|
||||
bool sram_save_disable;
|
||||
|
||||
// Pausing support
|
||||
bool is_paused;
|
||||
bool is_oneshot;
|
||||
|
||||
// Autosave support.
|
||||
autosave_t *autosave[2];
|
||||
|
||||
// Netplay.
|
||||
netplay_t *netplay;
|
||||
char netplay_server[MAXPATHLEN];
|
||||
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];
|
||||
char record_path[MAXPATHLEN];
|
||||
bool recording;
|
||||
unsigned record_width;
|
||||
unsigned record_height;
|
||||
#endif
|
||||
|
||||
struct
|
||||
{
|
||||
const uint16_t *data;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
} frame_cache;
|
||||
|
||||
char title_buf[64];
|
||||
|
||||
struct
|
||||
{
|
||||
char **elems;
|
||||
size_t size;
|
||||
size_t ptr;
|
||||
} shader_dir;
|
||||
|
||||
char sha256[64 + 1];
|
||||
|
||||
#ifdef HAVE_XML
|
||||
cheat_manager_t *cheat;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -156,20 +334,69 @@ 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)
|
||||
} 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)
|
||||
} 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)
|
||||
} while (0)
|
||||
|
||||
#ifndef max
|
||||
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
#ifndef min
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
#define SSNES_SCALE_BASE 256
|
||||
|
||||
static inline uint32_t next_pow2(uint32_t v)
|
||||
{
|
||||
v--;
|
||||
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];
|
||||
}
|
||||
|
||||
static inline void ssnes_sleep(unsigned usec)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
Sleep(10);
|
||||
#else
|
||||
struct timespec tv = {
|
||||
.tv_sec = 0,
|
||||
.tv_nsec = 10000000
|
||||
};
|
||||
nanosleep(&tv, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
/* 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"
|
||||
#include "sdlwrap.h"
|
||||
#include "gfx_common.h"
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
#include "fonts.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
#define PY_STATE_OMIT_DECLARATION
|
||||
#include "py_state/py_state.h"
|
||||
#endif
|
||||
|
||||
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;
|
||||
|
||||
int font_color_r = g_settings.video.msg_color_r * 255;
|
||||
int font_color_g = g_settings.video.msg_color_g * 255;
|
||||
int font_color_b = g_settings.video.msg_color_b * 255;
|
||||
font_color_r = font_color_r > 255 ? 255 : (font_color_r < 0 ? 0 : font_color_r);
|
||||
font_color_g = font_color_g > 255 ? 255 : (font_color_g < 0 ? 0 : font_color_g);
|
||||
font_color_b = font_color_b > 255 ? 255 : (font_color_b < 0 ? 0 : font_color_b);
|
||||
|
||||
const char *font = NULL;
|
||||
if (g_settings.video.font_enable)
|
||||
{
|
||||
#ifdef HAVE_FREETYPE
|
||||
if (*g_settings.video.font_path)
|
||||
font = g_settings.video.font_path;
|
||||
else
|
||||
font = font_renderer_get_default_font();
|
||||
#else
|
||||
font = *g_settings.video.font_path ?
|
||||
g_settings.video.font_path : NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
char title_buf[128];
|
||||
gfx_window_title_reset();
|
||||
gfx_window_title(title_buf, sizeof(title_buf));
|
||||
|
||||
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 = font,
|
||||
.ttf_font_size = g_settings.video.font_size,
|
||||
.ttf_font_color = (font_color_r << 16) | (font_color_g << 8) | (font_color_b << 0),
|
||||
.title_hint = title_buf,
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
.python_state_new = py_state_new,
|
||||
.python_state_get = py_state_get,
|
||||
.python_state_free = py_state_free,
|
||||
#endif
|
||||
};
|
||||
|
||||
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,278 @@
|
||||
/////
|
||||
// API header for external SSNES video and input plugins.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef __SSNES_VIDEO_DRIVER_H
|
||||
#define __SSNES_VIDEO_DRIVER_H
|
||||
|
||||
#include <stddef.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 2
|
||||
|
||||
// Since we don't want to rely on C++ or C99 for a proper boolean type,
|
||||
// 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
|
||||
|
||||
#define SSNES_INPUT_SCALE_BASE 256
|
||||
|
||||
typedef struct py_state py_state_t;
|
||||
|
||||
// Create a new runtime for Python.
|
||||
// py_script: The python script to be loaded. If is_file is true, this will be the full path to a file.
|
||||
// If false, it will be an UTF-8 encoded string of the script.
|
||||
// is_file: Tells if py_script is the path to a script, or a script itself.
|
||||
// py_class: name of the class being instantiated.
|
||||
typedef py_state_t *(*python_state_new_cb)(const char *py_script, unsigned is_file, const char *py_class);
|
||||
// Grabs a value from the Python runtime.
|
||||
// id: The uniform (class method) to be called.
|
||||
// frame_count: Passes frame_count as an argument to the script.
|
||||
typedef float (*python_state_get_cb)(py_state_t *handle, const char *id, unsigned frame_count);
|
||||
// Frees the runtime.
|
||||
typedef void (*python_state_free_cb)(py_state_t *handle);
|
||||
|
||||
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 (SSNES_INPUT_SCALE_BASE),
|
||||
// so an input scale of 2
|
||||
// means you should allocate a texture or of 512x512.
|
||||
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;
|
||||
unsigned ttf_font_color; // Font color, in format ARGB8888. Alpha should be disregarded.
|
||||
|
||||
// A title that should be displayed in the title bar of the window.
|
||||
const char *title_hint;
|
||||
|
||||
// Functions to peek into the python runtime for shaders.
|
||||
// Check typedefs above for explanation.
|
||||
// These may be NULL if SSNES is not built with Python support.
|
||||
python_state_new_cb python_state_new;
|
||||
python_state_get_cb python_state_get;
|
||||
python_state_free_cb python_state_free;
|
||||
} 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 (0xFFFFFFFFU)
|
||||
#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 1.2 keysyms,
|
||||
// which probably need to be transformed to the native format.
|
||||
// The actual keysyms SSNES uses are found in input/keysym.h.
|
||||
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/ABI 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
|
||||
+41
-5
@@ -16,10 +16,10 @@
|
||||
*/
|
||||
|
||||
#include "fonts.h"
|
||||
#include "file.h"
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
@@ -79,12 +79,16 @@ void font_renderer_msg(font_renderer_t *handle, const char *msg, struct font_out
|
||||
if (!err)
|
||||
{
|
||||
struct font_output *tmp = calloc(1, sizeof(*tmp));
|
||||
assert(tmp);
|
||||
|
||||
//fprintf(stderr, "Char: %c, off_x: %d, off_y: %d, bmp_left: %d, bmp_top: %d\n", msg[i], off_x, off_y, slot->bitmap_left, slot->bitmap_top);
|
||||
if (!tmp)
|
||||
break;
|
||||
|
||||
tmp->output = malloc(slot->bitmap.pitch * slot->bitmap.rows);
|
||||
assert(tmp->output);
|
||||
if (!tmp->output)
|
||||
{
|
||||
free(tmp);
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(tmp->output, slot->bitmap.buffer, slot->bitmap.pitch * slot->bitmap.rows);
|
||||
|
||||
tmp->width = slot->bitmap.width;
|
||||
@@ -128,3 +132,35 @@ void font_renderer_free(font_renderer_t *handle)
|
||||
if (handle->lib)
|
||||
FT_Done_FreeType(handle->lib);
|
||||
}
|
||||
|
||||
// Not the cleanest way to do things for sure, but should hopefully work ... :)
|
||||
|
||||
#if defined(_WIN32)
|
||||
static const char *font_paths[] = {
|
||||
"C:\\Windows\\Fonts\\consola.ttf",
|
||||
"C:\\Windows\\Fonts\\verdana.ttf",
|
||||
#elif defined(__APPLE__)
|
||||
static const char *font_paths[] = {
|
||||
"/Library/Fonts/Microsoft/Candara.ttf",
|
||||
"/Library/Fonts/Verdana.ttf",
|
||||
"/Library/Fonts/Tahoma.ttf",
|
||||
#else
|
||||
static const char *font_paths[] = {
|
||||
"/usr/share/fonts/TTF/DejaVuSansMono.ttf",
|
||||
"/usr/share/fonts/TTF/DejaVuSans.ttf",
|
||||
#endif
|
||||
"osd-font.ttf", // Magic font to search for, useful for distribution.
|
||||
};
|
||||
|
||||
// Highly OS/platform dependent.
|
||||
const char *font_renderer_get_default_font(void)
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(font_paths) / sizeof(font_paths[0]); i++)
|
||||
{
|
||||
if (path_file_exists(font_paths[i]))
|
||||
return font_paths[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+7
-3
@@ -25,10 +25,10 @@ typedef struct font_renderer font_renderer_t;
|
||||
|
||||
struct font_output
|
||||
{
|
||||
uint8_t *output;
|
||||
uint8_t *output; // 8-bit intensity.
|
||||
unsigned width, height, pitch;
|
||||
int off_x, off_y;
|
||||
struct font_output *next;
|
||||
struct font_output *next; // linked list.
|
||||
};
|
||||
|
||||
struct font_output_list
|
||||
@@ -37,8 +37,12 @@ struct font_output_list
|
||||
};
|
||||
|
||||
font_renderer_t *font_renderer_new(const char *font_path, unsigned font_size);
|
||||
void font_renderer_msg(font_renderer_t *handle, const char *msg, struct font_output_list *output);
|
||||
void font_renderer_msg(font_renderer_t *handle, const char *msg,
|
||||
struct font_output_list *output);
|
||||
|
||||
void font_renderer_free_output(struct font_output_list *list);
|
||||
void font_renderer_free(font_renderer_t *handle);
|
||||
|
||||
const char *font_renderer_get_default_font(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,114 @@
|
||||
/* 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.\n");
|
||||
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
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/* 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
|
||||
|
||||
#endif
|
||||
+60
-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,49 @@ 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];
|
||||
};
|
||||
|
||||
// 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] = (b << 24) | (g << 16) | (r << 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] = (b << 24) | (g << 16) | (r << 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 b = tmp[i * 4 + 0];
|
||||
uint32_t g = tmp[i * 4 + 1];
|
||||
uint32_t r = tmp[i * 4 + 2];
|
||||
uint32_t a = tmp[i * 4 + 3];
|
||||
|
||||
out_img->pixels[i] = (b << 24) | (g << 16) | (r << 8) | a;
|
||||
}
|
||||
}
|
||||
else if (bits == 24)
|
||||
{
|
||||
for (unsigned i = 0; i < width * height; i++)
|
||||
{
|
||||
uint32_t b = tmp[i * 3 + 0];
|
||||
uint32_t g = tmp[i * 3 + 1];
|
||||
uint32_t r = tmp[i * 3 + 2];
|
||||
uint32_t a = 0xff;
|
||||
|
||||
out_img->pixels[i] = (b << 24) | (g << 16) | (r << 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,350 @@
|
||||
/* 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"
|
||||
#include "file.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 || key >= SSNES_FIRST_META_KEY)
|
||||
return NULL;
|
||||
|
||||
static const struct snes_keybind *binds[MAX_PLAYERS] = {
|
||||
g_settings.input.binds[0],
|
||||
g_settings.input.binds[1],
|
||||
g_settings.input.binds[2],
|
||||
g_settings.input.binds[3],
|
||||
g_settings.input.binds[4],
|
||||
};
|
||||
|
||||
int16_t res = driver.input->input_state(driver.input_data,
|
||||
binds, player > 1,
|
||||
player > 2 ? SNES_DEVICE_MULTITAP : SNES_DEVICE_JOYPAD,
|
||||
player > 2 ? player - 2 : 0,
|
||||
key);
|
||||
|
||||
return PyBool_FromLong(res);
|
||||
}
|
||||
|
||||
static PyObject *py_read_input_meta(PyObject *self, PyObject *args)
|
||||
{
|
||||
(void)self;
|
||||
|
||||
if (!driver.input_data)
|
||||
return PyBool_FromLong(0);
|
||||
|
||||
unsigned key;
|
||||
if (!PyArg_ParseTuple(args, "I", &key))
|
||||
return NULL;
|
||||
|
||||
if (key < SSNES_FIRST_META_KEY)
|
||||
return NULL;
|
||||
|
||||
bool ret = driver.input->key_pressed(driver.input_data, key);
|
||||
return PyBool_FromLong(ret);
|
||||
}
|
||||
|
||||
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." },
|
||||
{ "input_meta", py_read_input_meta, METH_VARARGS, "Read SSNES specific input." },
|
||||
{ NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
#define DECL_ATTR_SNES(attr) PyObject_SetAttrString(mod, #attr, PyLong_FromLong(SNES_DEVICE_ID_JOYPAD_##attr))
|
||||
#define DECL_ATTR_SSNES(attr) PyObject_SetAttrString(mod, #attr, PyLong_FromLong(SSNES_##attr))
|
||||
static void py_set_attrs(PyObject *mod)
|
||||
{
|
||||
DECL_ATTR_SNES(B);
|
||||
DECL_ATTR_SNES(Y);
|
||||
DECL_ATTR_SNES(SELECT);
|
||||
DECL_ATTR_SNES(START);
|
||||
DECL_ATTR_SNES(UP);
|
||||
DECL_ATTR_SNES(DOWN);
|
||||
DECL_ATTR_SNES(LEFT);
|
||||
DECL_ATTR_SNES(RIGHT);
|
||||
DECL_ATTR_SNES(A);
|
||||
DECL_ATTR_SNES(X);
|
||||
DECL_ATTR_SNES(L);
|
||||
DECL_ATTR_SNES(R);
|
||||
|
||||
DECL_ATTR_SSNES(FAST_FORWARD_KEY);
|
||||
DECL_ATTR_SSNES(FAST_FORWARD_HOLD_KEY);
|
||||
DECL_ATTR_SSNES(LOAD_STATE_KEY);
|
||||
DECL_ATTR_SSNES(SAVE_STATE_KEY);
|
||||
DECL_ATTR_SSNES(FULLSCREEN_TOGGLE_KEY);
|
||||
DECL_ATTR_SSNES(QUIT_KEY);
|
||||
DECL_ATTR_SSNES(STATE_SLOT_PLUS);
|
||||
DECL_ATTR_SSNES(STATE_SLOT_MINUS);
|
||||
DECL_ATTR_SSNES(AUDIO_INPUT_RATE_PLUS);
|
||||
DECL_ATTR_SSNES(AUDIO_INPUT_RATE_MINUS);
|
||||
DECL_ATTR_SSNES(REWIND);
|
||||
DECL_ATTR_SSNES(MOVIE_RECORD_TOGGLE);
|
||||
DECL_ATTR_SSNES(PAUSE_TOGGLE);
|
||||
DECL_ATTR_SSNES(FRAMEADVANCE);
|
||||
DECL_ATTR_SSNES(RESET);
|
||||
DECL_ATTR_SSNES(SHADER_NEXT);
|
||||
DECL_ATTR_SSNES(SHADER_PREV);
|
||||
DECL_ATTR_SSNES(CHEAT_INDEX_PLUS);
|
||||
DECL_ATTR_SSNES(CHEAT_INDEX_MINUS);
|
||||
DECL_ATTR_SSNES(CHEAT_TOGGLE);
|
||||
DECL_ATTR_SSNES(SCREENSHOT);
|
||||
DECL_ATTR_SSNES(DSP_CONFIG);
|
||||
}
|
||||
|
||||
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, unsigned is_file, const char *pyclass)
|
||||
{
|
||||
SSNES_LOG("Initializing Python runtime ...\n");
|
||||
PyImport_AppendInittab("snes", &PyInit_SNES);
|
||||
Py_Initialize();
|
||||
SSNES_LOG("Initialized Python runtime!\n");
|
||||
|
||||
py_state_t *handle = calloc(1, sizeof(*handle));
|
||||
|
||||
handle->main = PyImport_AddModule("__main__");
|
||||
if (!handle->main)
|
||||
goto error;
|
||||
Py_INCREF(handle->main);
|
||||
|
||||
if (is_file)
|
||||
{
|
||||
// Have to hack around the fact that the
|
||||
// FILE struct isn't standardized across environments.
|
||||
// PyRun_SimpleFile() breaks on Windows.
|
||||
|
||||
char *script_ = NULL;
|
||||
if (read_file(script, (void**)&script_) < 0)
|
||||
{
|
||||
SSNES_ERR("Python: Failed to read script\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
PyRun_SimpleString(script_);
|
||||
free(script_);
|
||||
}
|
||||
else
|
||||
{
|
||||
char *script_ = align_program(script);
|
||||
if (script_)
|
||||
{
|
||||
PyRun_SimpleString(script_);
|
||||
free(script_);
|
||||
}
|
||||
}
|
||||
|
||||
SSNES_LOG("Python: Script loaded.\n");
|
||||
handle->dict = PyModule_GetDict(handle->main);
|
||||
if (!handle->dict)
|
||||
{
|
||||
SSNES_ERR("Python: PyModule_GetDict() failed.\n");
|
||||
goto error;
|
||||
}
|
||||
Py_INCREF(handle->dict);
|
||||
|
||||
PyObject *hook = PyDict_GetItemString(handle->dict, pyclass);
|
||||
if (!hook)
|
||||
{
|
||||
SSNES_ERR("Python: PyDict_GetItemString() failed.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
handle->inst = PyObject_CallFunction(hook, NULL);
|
||||
if (!handle->inst)
|
||||
{
|
||||
SSNES_ERR("Python: PyObject_CallFunction() failed.\n");
|
||||
goto error;
|
||||
}
|
||||
Py_INCREF(handle->inst);
|
||||
|
||||
return handle;
|
||||
|
||||
error:
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
py_state_free(handle);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void py_state_free(py_state_t *handle)
|
||||
{
|
||||
if (handle)
|
||||
{
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
|
||||
Py_CLEAR(handle->inst);
|
||||
Py_CLEAR(handle->dict);
|
||||
Py_CLEAR(handle->main);
|
||||
|
||||
free(handle);
|
||||
Py_Finalize();
|
||||
}
|
||||
}
|
||||
|
||||
float 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.0f;
|
||||
}
|
||||
|
||||
float retval = (float)PyFloat_AsDouble(ret);
|
||||
Py_DECREF(ret);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -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_PY_STATE_H
|
||||
#define __SSNES_PY_STATE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef PY_STATE_OMIT_DECLARATION
|
||||
typedef struct py_state py_state_t;
|
||||
#endif
|
||||
|
||||
py_state_t *py_state_new(const char *program, unsigned is_file, const char *pyclass);
|
||||
void py_state_free(py_state_t *handle);
|
||||
|
||||
float py_state_get(py_state_t *handle,
|
||||
const char *id, unsigned frame_count);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,564 @@
|
||||
/* 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;
|
||||
|
||||
bool render32;
|
||||
|
||||
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;
|
||||
uint8_t font_r;
|
||||
uint8_t font_g;
|
||||
uint8_t font_b;
|
||||
#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 (!g_settings.video.font_enable)
|
||||
return;
|
||||
|
||||
const char *path = font_path;
|
||||
if (!*path)
|
||||
path = font_renderer_get_default_font();
|
||||
|
||||
if (path)
|
||||
{
|
||||
vid->font = font_renderer_new(path, font_size);
|
||||
if (vid->font)
|
||||
{
|
||||
int r = g_settings.video.msg_color_r * 255;
|
||||
int g = g_settings.video.msg_color_g * 255;
|
||||
int b = g_settings.video.msg_color_b * 255;
|
||||
|
||||
r = r < 0 ? 0 : (r > 255 ? 255 : r);
|
||||
g = g < 0 ? 0 : (g > 255 ? 255 : g);
|
||||
b = b < 0 ? 0 : (b > 255 ? 255 : b);
|
||||
|
||||
// RGB888 -> RGB555
|
||||
if (!vid->render32)
|
||||
{
|
||||
r >>= 3;
|
||||
g >>= 3;
|
||||
b >>= 3;
|
||||
}
|
||||
|
||||
vid->font_r = r;
|
||||
vid->font_g = g;
|
||||
vid->font_b = b;
|
||||
}
|
||||
else
|
||||
SSNES_WARN("Failed to init font.\n");
|
||||
}
|
||||
else
|
||||
SSNES_LOG("Did not find default 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) + vid->font_r * blend) >> 8;
|
||||
unsigned out_g = (g * (256 - blend) + vid->font_g * blend) >> 8;
|
||||
unsigned out_b = (b * (256 - blend) + vid->font_b * 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) + vid->font_r * blend) >> 8;
|
||||
unsigned out_g = (g * (256 - blend) + vid->font_g * blend) >> 8;
|
||||
unsigned out_b = (b * (256 - blend) + vid->font_b * 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->render32 = video->rgb32 && !g_settings.video.force_16bit;
|
||||
vid->screen = SDL_SetVideoMode(video->width, video->height, vid->render32 ? 32 : 15, 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");
|
||||
vid->render32 = true;
|
||||
}
|
||||
|
||||
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 (vid->render32)
|
||||
{
|
||||
SSNES_LOG("SDL: Creating 32-bit buffer.\n");
|
||||
vid->buffer = SDL_CreateRGBSurface(SDL_SWSURFACE, SSNES_SCALE_BASE * video->input_scale,
|
||||
SSNES_SCALE_BASE * 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, SSNES_SCALE_BASE * video->input_scale,
|
||||
SSNES_SCALE_BASE * 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) // XRGB1555
|
||||
{
|
||||
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) // ARGB8888
|
||||
{
|
||||
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;
|
||||
|
||||
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 (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);
|
||||
|
||||
if (msg)
|
||||
{
|
||||
if ((!vid->rgb32 || g_settings.video.force_16bit) && !vid->upsample)
|
||||
sdl_render_msg_15(vid, vid->screen, msg, vid->screen->w, vid->screen->h, vid->screen->format);
|
||||
else
|
||||
sdl_render_msg_32(vid, vid->screen, msg, vid->screen->w, vid->screen->h, vid->screen->format);
|
||||
}
|
||||
|
||||
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;
|
||||
check_window(vid);
|
||||
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"
|
||||
};
|
||||
|
||||
+351
@@ -0,0 +1,351 @@
|
||||
/* 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 "sdlwrap.h"
|
||||
#include "SDL_syswm.h"
|
||||
#include "general.h"
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#include <OpenGL/gl.h>
|
||||
#endif
|
||||
|
||||
// SDL 1.2 is portable, sure, but you still need some platform specific workarounds ;)
|
||||
// Hopefully SDL 1.3 will solve this more cleanly :D
|
||||
// Welcome to #ifdef HELL! :D
|
||||
//
|
||||
|
||||
#if SDL_MODERN
|
||||
static SDL_Window* g_window;
|
||||
static SDL_GLContext g_ctx;
|
||||
#endif
|
||||
|
||||
static bool g_fullscreen;
|
||||
static unsigned g_interval;
|
||||
|
||||
void sdlwrap_set_swap_interval(unsigned interval, bool inited)
|
||||
{
|
||||
g_interval = interval;
|
||||
|
||||
#if SDL_MODERN
|
||||
if (g_window)
|
||||
SDL_GL_SetSwapInterval(g_interval);
|
||||
#else
|
||||
if (inited)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
static BOOL (APIENTRY *wgl_swap_interval)(int) = NULL;
|
||||
if (!wgl_swap_interval)
|
||||
{
|
||||
SDL_SYM_WRAP(wgl_swap_interval, "wglSwapIntervalEXT");
|
||||
}
|
||||
if (wgl_swap_interval) wgl_swap_interval(g_interval);
|
||||
|
||||
#elif defined(__APPLE__) && defined(HAVE_OPENGL)
|
||||
GLint val = g_interval;
|
||||
CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &val);
|
||||
#else
|
||||
static int (*glx_swap_interval)(int) = NULL;
|
||||
if (!glx_swap_interval)
|
||||
{
|
||||
SDL_SYM_WRAP(glx_swap_interval, "glXSwapIntervalSGI");
|
||||
}
|
||||
if (!glx_swap_interval)
|
||||
{
|
||||
SDL_SYM_WRAP(glx_swap_interval, "glXSwapIntervalMESA");
|
||||
}
|
||||
if (glx_swap_interval)
|
||||
glx_swap_interval(g_interval);
|
||||
else
|
||||
SSNES_WARN("Could not find GLX VSync call. :(\n");
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool sdlwrap_init(void)
|
||||
{
|
||||
#if SDL_MODERN
|
||||
return SDL_VideoInit(NULL) == 0;
|
||||
#else
|
||||
if (SDL_WasInit(SDL_INIT_VIDEO))
|
||||
return true;
|
||||
|
||||
return SDL_Init(SDL_INIT_VIDEO) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if SDL_MODERN
|
||||
void sdlwrap_destroy(void)
|
||||
{
|
||||
if (g_ctx)
|
||||
SDL_GL_DeleteContext(g_ctx);
|
||||
if (g_window)
|
||||
SDL_DestroyWindow(g_window);
|
||||
|
||||
g_ctx = NULL;
|
||||
g_window = NULL;
|
||||
SDL_VideoQuit();
|
||||
}
|
||||
#else
|
||||
void sdlwrap_destroy(void)
|
||||
{
|
||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool sdlwrap_set_video_mode(
|
||||
unsigned width, unsigned height,
|
||||
unsigned bits, bool fullscreen)
|
||||
{
|
||||
#if SDL_MODERN
|
||||
if (g_window)
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#if SDL_MODERN
|
||||
static const int resizable = SDL_WINDOW_RESIZABLE;
|
||||
#else
|
||||
#ifndef __APPLE__ // Resizing on OSX is broken in 1.2 it seems :)
|
||||
static const int resizable = SDL_RESIZABLE;
|
||||
#else
|
||||
static const int resizable = 0;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
|
||||
#if !SDL_MODERN
|
||||
SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, g_interval);
|
||||
#endif
|
||||
|
||||
#if SDL_MODERN
|
||||
if (bits == 15)
|
||||
{
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
|
||||
}
|
||||
g_window = SDL_CreateWindow("SSNES", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
||||
width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | (fullscreen ? SDL_WINDOW_FULLSCREEN : resizable));
|
||||
if (!g_window)
|
||||
return false;
|
||||
g_ctx = SDL_GL_CreateContext(g_window);
|
||||
#else
|
||||
if (!SDL_SetVideoMode(width, height, bits,
|
||||
SDL_OPENGL | (fullscreen ? SDL_FULLSCREEN : resizable)))
|
||||
return false;
|
||||
#endif
|
||||
|
||||
int attr = 0;
|
||||
#if SDL_MODERN
|
||||
SDL_GL_SetSwapInterval(g_interval);
|
||||
#else
|
||||
SDL_GL_GetAttribute(SDL_GL_SWAP_CONTROL, &attr);
|
||||
if (attr <= 0 && g_interval)
|
||||
{
|
||||
SSNES_WARN("SDL failed to setup VSync, attempting to recover using native calls!\n");
|
||||
sdlwrap_set_swap_interval(g_interval, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
g_fullscreen = fullscreen;
|
||||
|
||||
attr = 0;
|
||||
SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &attr);
|
||||
if (attr <= 0)
|
||||
SSNES_WARN("GL double buffer has not been enabled!\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// SDL 1.2 has an awkward model where you need to "confirm" window resizing.
|
||||
// SDL 1.3 luckily removes this quirk.
|
||||
void sdlwrap_set_resize(unsigned width, unsigned height)
|
||||
{
|
||||
#if SDL_MODERN
|
||||
(void)width;
|
||||
(void)height;
|
||||
#else
|
||||
#ifndef __APPLE__ // Resizing on OSX is broken in 1.2 it seems :)
|
||||
static const int resizable = SDL_RESIZABLE;
|
||||
#else
|
||||
static const int resizable = 0;
|
||||
#endif
|
||||
SDL_SetVideoMode(width, height, 0, SDL_OPENGL | (g_fullscreen ? SDL_FULLSCREEN : resizable));
|
||||
#endif
|
||||
}
|
||||
|
||||
void sdlwrap_wm_set_caption(const char *str)
|
||||
{
|
||||
#if SDL_MODERN
|
||||
SDL_SetWindowTitle(g_window, str);
|
||||
#else
|
||||
SDL_WM_SetCaption(str, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void sdlwrap_swap_buffers(void)
|
||||
{
|
||||
#if SDL_MODERN
|
||||
SDL_GL_SwapWindow(g_window);
|
||||
#else
|
||||
SDL_GL_SwapBuffers();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool sdlwrap_key_pressed(int key)
|
||||
{
|
||||
int num_keys;
|
||||
#if SDL_MODERN
|
||||
Uint8 *keymap = SDL_GetKeyboardState(&num_keys);
|
||||
key = SDL_GetScancodeFromKey(key);
|
||||
if (key >= num_keys)
|
||||
return false;
|
||||
|
||||
return keymap[key];
|
||||
#else
|
||||
Uint8 *keymap = SDL_GetKeyState(&num_keys);
|
||||
if (key >= num_keys)
|
||||
return false;
|
||||
|
||||
return keymap[key];
|
||||
#endif
|
||||
}
|
||||
|
||||
// 1.2 specific workaround for tiling WMs. In 1.3 we call GetSize directly, so we don't need to rely on
|
||||
// proper event handling (I hope).
|
||||
#if !defined(__APPLE__) && !defined(_WIN32) && !SDL_MODERN
|
||||
static void sdlwrap_get_window_size(unsigned *width, unsigned *height)
|
||||
{
|
||||
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
|
||||
|
||||
void sdlwrap_check_window(bool *quit,
|
||||
bool *resize, unsigned *width, unsigned *height, unsigned frame_count)
|
||||
{
|
||||
*quit = false;
|
||||
*resize = false;
|
||||
SDL_Event event;
|
||||
#if SDL_MODERN
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
*quit = true;
|
||||
return;
|
||||
|
||||
case SDL_WINDOWEVENT:
|
||||
switch (event.window.event)
|
||||
{
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
*quit = true;
|
||||
return;
|
||||
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
*resize = true;
|
||||
*width = event.window.data1;
|
||||
*height = event.window.data2;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!*resize)
|
||||
{
|
||||
int w, h;
|
||||
SDL_GetWindowSize(g_window, &w, &h);
|
||||
if (*width != (unsigned)w || *height != (unsigned)h)
|
||||
{
|
||||
*resize = true;
|
||||
*width = w;
|
||||
*height = h;
|
||||
}
|
||||
}
|
||||
#else
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
*quit = true;
|
||||
break;
|
||||
|
||||
case SDL_VIDEORESIZE:
|
||||
*resize = true;
|
||||
*width = event.resize.w;
|
||||
*height = event.resize.h;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(__APPLE__) && !defined(_WIN32)
|
||||
// Hack to workaround limitations in tiling WMs ...
|
||||
if (!*resize && !g_fullscreen)
|
||||
{
|
||||
unsigned new_width, new_height;
|
||||
sdlwrap_get_window_size(&new_width, &new_height);
|
||||
if ((new_width != *width || new_height != *height) || (frame_count == 10)) // Ugly hack :D
|
||||
{
|
||||
*resize = true;
|
||||
*width = new_width;
|
||||
*height = new_height;
|
||||
SSNES_LOG("GL: Verified window size: %u x %u\n", *width, *height);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
bool sdlwrap_get_wm_info(SDL_SysWMinfo *info)
|
||||
{
|
||||
#if SDL_MODERN
|
||||
if (g_window)
|
||||
return SDL_GetWindowWMInfo(g_window, info);
|
||||
else
|
||||
return SDL_GetWMInfo(info) == 1;
|
||||
#else
|
||||
return SDL_GetWMInfo(info) == 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool sdlwrap_window_has_focus(void)
|
||||
{
|
||||
#if SDL_MODERN
|
||||
Uint32 flags = SDL_GetWindowFlags(g_window);
|
||||
flags &= SDL_WINDOW_INPUT_FOCUS;
|
||||
return flags == SDL_WINDOW_INPUT_FOCUS;
|
||||
#else
|
||||
return (SDL_GetAppState() & (SDL_APPINPUTFOCUS | SDL_APPACTIVE)) == (SDL_APPINPUTFOCUS | SDL_APPACTIVE);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
// Compatibility wrapper between SDL 1.2/1.3 for OpenGL.
|
||||
// Wraps functions which differ in 1.2 and 1.3.
|
||||
|
||||
#ifndef __SDLWRAP_H
|
||||
#define __SDLWRAP_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "SDL.h"
|
||||
#include "SDL_version.h"
|
||||
#include "SDL_syswm.h"
|
||||
|
||||
#if SDL_VERSION_ATLEAST(1, 3, 0)
|
||||
#define SDL_MODERN 1
|
||||
#else
|
||||
#define SDL_MODERN 0
|
||||
#endif
|
||||
|
||||
// Not legal to cast (void*) to fn-pointer. Need workaround to be compliant.
|
||||
#define SDL_SYM_WRAP(sym, symbol) { \
|
||||
assert(sizeof(void*) == sizeof(void (*)(void))); \
|
||||
void *sym__ = SDL_GL_GetProcAddress(symbol); \
|
||||
memcpy(&(sym), &sym__, sizeof(void*)); \
|
||||
}
|
||||
|
||||
void sdlwrap_set_swap_interval(unsigned interval, bool inited);
|
||||
|
||||
bool sdlwrap_set_video_mode(
|
||||
unsigned width, unsigned height,
|
||||
unsigned bits, bool fullscreen);
|
||||
|
||||
bool sdlwrap_init(void);
|
||||
void sdlwrap_destroy(void);
|
||||
|
||||
void sdlwrap_wm_set_caption(const char *str);
|
||||
|
||||
void sdlwrap_swap_buffers(void);
|
||||
|
||||
bool sdlwrap_key_pressed(int key);
|
||||
|
||||
void sdlwrap_check_window(bool *quit,
|
||||
bool *resize, unsigned *width, unsigned *height, unsigned frame_count);
|
||||
|
||||
void sdlwrap_set_resize(unsigned width, unsigned height);
|
||||
|
||||
bool sdlwrap_get_wm_info(SDL_SysWMinfo *info);
|
||||
|
||||
bool sdlwrap_window_has_focus(void);
|
||||
|
||||
#endif
|
||||
|
||||
+1013
-72
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
|
||||
+1095
-108
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) ? -1u : info->info[i].mask;
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
if (info->info[i].type == SSNES_STATE_PYTHON)
|
||||
tracker->info[i].py = tracker->py;
|
||||
#endif
|
||||
|
||||
// If we don't have a valid pointer.
|
||||
static const uint8_t empty = 0;
|
||||
|
||||
switch (info->info[i].ram_type)
|
||||
{
|
||||
case SSNES_STATE_WRAM:
|
||||
tracker->info[i].ptr = info->wram ? info->wram : ∅
|
||||
break;
|
||||
case SSNES_STATE_APURAM:
|
||||
tracker->info[i].ptr = info->apuram ? info->apuram : ∅
|
||||
break;
|
||||
case SSNES_STATE_OAM:
|
||||
tracker->info[i].ptr = info->oam ? info->oam : ∅
|
||||
break;
|
||||
case SSNES_STATE_CGRAM:
|
||||
tracker->info[i].ptr = info->cgram ? info->cgram : ∅
|
||||
break;
|
||||
case SSNES_STATE_VRAM:
|
||||
tracker->info[i].ptr = info->vram ? 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 = ∅
|
||||
}
|
||||
}
|
||||
|
||||
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,92 @@
|
||||
/* 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_NONE,
|
||||
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;
|
||||
float 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
|
||||
+802
@@ -0,0 +1,802 @@
|
||||
/* 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;
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
font_renderer_t *font;
|
||||
|
||||
unsigned luma_index[2];
|
||||
unsigned chroma_u_index;
|
||||
unsigned chroma_v_index;
|
||||
|
||||
uint8_t font_y;
|
||||
uint8_t font_u;
|
||||
uint8_t font_v;
|
||||
#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 inline void calculate_yuv(uint8_t *y, uint8_t *u, uint8_t *v, unsigned r, unsigned g, unsigned b)
|
||||
{
|
||||
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);
|
||||
|
||||
*y = y_ < 0 ? 0 : (y_ > 255 ? 255 : y_);
|
||||
*u = y_ < 0 ? 0 : (u_ > 255 ? 255 : u_);
|
||||
*v = v_ < 0 ? 0 : (v_ > 255 ? 255 : v_);
|
||||
}
|
||||
|
||||
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
|
||||
unsigned 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
|
||||
|
||||
calculate_yuv(&xv->ytable[i], &xv->utable[i], &xv->vtable[i], r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (!g_settings.video.font_enable)
|
||||
return;
|
||||
|
||||
const char *path = font_path;
|
||||
if (!*path)
|
||||
path = font_renderer_get_default_font();
|
||||
|
||||
if (path)
|
||||
{
|
||||
xv->font = font_renderer_new(path, font_size);
|
||||
if (xv->font)
|
||||
{
|
||||
int r = g_settings.video.msg_color_r * 255;
|
||||
r = (r < 0 ? 0 : (r > 255 ? 255 : r));
|
||||
int g = g_settings.video.msg_color_g * 255;
|
||||
g = (g < 0 ? 0 : (g > 255 ? 255 : g));
|
||||
int b = g_settings.video.msg_color_b * 255;
|
||||
b = (b < 0 ? 0 : (b > 255 ? 255 : b));
|
||||
|
||||
calculate_yuv(&xv->font_y, &xv->font_u, &xv->font_v,
|
||||
r, g, b);
|
||||
}
|
||||
else
|
||||
SSNES_WARN("Failed to init font.\n");
|
||||
}
|
||||
else
|
||||
SSNES_LOG("Did not find default 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;
|
||||
}
|
||||
}
|
||||
|
||||
struct format_desc
|
||||
{
|
||||
void (*render_16)(xv_t *xv, const void *input,
|
||||
unsigned width, unsigned height, unsigned pitch);
|
||||
void (*render_32)(xv_t *xv, const void *input,
|
||||
unsigned width, unsigned height, unsigned pitch);
|
||||
char components[4];
|
||||
unsigned luma_index[2];
|
||||
unsigned u_index;
|
||||
unsigned v_index;
|
||||
};
|
||||
|
||||
static const struct format_desc formats[] = {
|
||||
{
|
||||
.render_16 = render16_yuy2,
|
||||
.render_32 = render32_yuy2,
|
||||
.components = { 'Y', 'U', 'Y', 'V' },
|
||||
.luma_index = { 0, 2 },
|
||||
.u_index = 1,
|
||||
.v_index = 3,
|
||||
},
|
||||
{
|
||||
.render_16 = render16_uyvy,
|
||||
.render_32 = render32_uyvy,
|
||||
.components = { 'U', 'Y', 'V', 'Y' },
|
||||
.luma_index = { 1, 3 },
|
||||
.u_index = 0,
|
||||
.v_index = 2,
|
||||
},
|
||||
};
|
||||
|
||||
static bool adaptor_set_format(xv_t *xv, Display *dpy, XvPortID port, const video_info_t *video)
|
||||
{
|
||||
int format_count;
|
||||
XvImageFormatValues *format = XvListImageFormats(xv->display, port, &format_count);
|
||||
if (!format)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < format_count; i++)
|
||||
{
|
||||
for (unsigned j = 0; j < sizeof(formats) / sizeof(formats[0]); j++)
|
||||
{
|
||||
if (format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked)
|
||||
{
|
||||
if (format[i].component_order[0] == formats[j].components[0] &&
|
||||
format[i].component_order[1] == formats[j].components[1] &&
|
||||
format[i].component_order[2] == formats[j].components[2] &&
|
||||
format[i].component_order[3] == formats[j].components[3])
|
||||
{
|
||||
xv->fourcc = format[i].id;
|
||||
xv->render_func = video->rgb32 ? formats[j].render_32 : formats[j].render_16;
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
xv->luma_index[0] = formats[j].luma_index[0];
|
||||
xv->luma_index[1] = formats[j].luma_index[1];
|
||||
xv->chroma_u_index = formats[j].u_index;
|
||||
xv->chroma_v_index = formats[j].v_index;
|
||||
#endif
|
||||
XFree(format);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XFree(format);
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
if (!adaptor_set_format(xv, xv->display, adaptor_info[i].base_id, video)) continue;
|
||||
|
||||
xv->port = adaptor_info[i].base_id;
|
||||
xv->depth = adaptor_info[i].formats->depth;
|
||||
xv->visualid = adaptor_info[i].formats->visual_id;
|
||||
|
||||
SSNES_LOG("XVideo: Found suitable XvPort #%u\n", (unsigned)xv->port);
|
||||
break;
|
||||
}
|
||||
XvFreeAdaptorInfo(adaptor_info);
|
||||
|
||||
if (xv->port == 0)
|
||||
{
|
||||
SSNES_ERR("XVideo: Failed to find valid XvPort or format.\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) ? g_extern.system.geom.base_width : video->width) : video->width;
|
||||
unsigned height = video->fullscreen ? ((video->height == 0) ? g_extern.system.geom.base_height : 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);
|
||||
|
||||
xv->width = g_extern.system.geom.max_width;
|
||||
xv->height = g_extern.system.geom.max_height;
|
||||
|
||||
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;
|
||||
|
||||
void *xinput = input_x.init();
|
||||
if (xinput)
|
||||
{
|
||||
*input = &input_x;
|
||||
*input_data = xinput;
|
||||
}
|
||||
else
|
||||
*input = NULL;
|
||||
|
||||
init_yuv_tables(xv);
|
||||
xv_init_font(xv, g_settings.video.font_path, g_settings.video.font_size);
|
||||
|
||||
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? :(
|
||||
// Hacky C code is hacky :D Yay.
|
||||
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_index[2] = { xv->luma_index[0], xv->luma_index[1] };
|
||||
unsigned chroma_u_index = xv->chroma_u_index;
|
||||
unsigned chroma_v_index = xv->chroma_v_index;
|
||||
|
||||
unsigned pitch = width << 1; // YUV formats used are 16 bpp.
|
||||
|
||||
while (head)
|
||||
{
|
||||
int base_x = (_base_x + head->off_x) << 1;
|
||||
base_x &= ~3; // Make sure we always start on the correct boundary so the indices are correct.
|
||||
|
||||
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 * restrict a = head->output + head->pitch * y;
|
||||
uint8_t * restrict out = (uint8_t*)xv->image->data + (base_y - head->height + y) * pitch + base_x;
|
||||
|
||||
for (int x = 0; x < (head->width << 1) && (base_x + x) < pitch; x += 4)
|
||||
{
|
||||
unsigned alpha[2];
|
||||
alpha[0] = a[(x >> 1) + 0];
|
||||
|
||||
if (((x >> 1) + 1) == head->width) // We reached the end, uhoh! Branching like a BOSS! :D
|
||||
alpha[1] = 0;
|
||||
else
|
||||
alpha[1] = a[(x >> 1) + 1];
|
||||
|
||||
unsigned alpha_sub = (alpha[0] + alpha[1]) >> 1; // Blended alpha for the sub-samples U/V channels.
|
||||
|
||||
for (unsigned i = 0; i < 2; i++)
|
||||
{
|
||||
unsigned blended = (xv->font_y * alpha[i] + ((256 - alpha[i]) * out[x + luma_index[i]])) >> 8;
|
||||
out[x + luma_index[i]] = blended;
|
||||
}
|
||||
|
||||
// Blend chroma channels
|
||||
unsigned blended = (xv->font_u * alpha_sub + ((256 - alpha_sub) * out[x + chroma_u_index])) >> 8;
|
||||
out[x + chroma_u_index] = blended;
|
||||
|
||||
blended = (xv->font_v * alpha_sub + ((256 - alpha_sub) * out[x + chroma_v_index])) >> 8;
|
||||
out[x + chroma_v_index] = 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);
|
||||
|
||||
unsigned x, y, owidth, oheight;
|
||||
calc_out_rect(xv->keep_aspect, &x, &y, &owidth, &oheight, target.width, target.height);
|
||||
|
||||
if (msg)
|
||||
xv_render_msg(xv, msg, width << 1, height << 1);
|
||||
|
||||
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
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
/* 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 "ssnes_dinput.h"
|
||||
#include "SDL.h"
|
||||
#include "SDL_syswm.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include "../gfx/sdlwrap.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)
|
||||
{
|
||||
LPDIRECTINPUTDEVICE8 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;
|
||||
SDL_VERSION(&info.version);
|
||||
|
||||
if (!sdlwrap_get_wm_info(&info))
|
||||
{
|
||||
SSNES_ERR("Failed to get SysWM info.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
#if SDL_MODERN
|
||||
di->hWnd = info.info.win.window;
|
||||
#else
|
||||
di->hWnd = info.window;
|
||||
#endif
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
/* 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_KEYSYM_H
|
||||
#define __SSNES_KEYSYM_H
|
||||
|
||||
// Global keysym table for SSNES.
|
||||
// As you may have noticed, it's the same as SDL 1.2 since I'm lazy.
|
||||
|
||||
enum ssnes_key
|
||||
{
|
||||
SK_UNKNOWN = 0,
|
||||
SK_FIRST = 0,
|
||||
SK_BACKSPACE = 8,
|
||||
SK_TAB = 9,
|
||||
SK_CLEAR = 12,
|
||||
SK_RETURN = 13,
|
||||
SK_PAUSE = 19,
|
||||
SK_ESCAPE = 27,
|
||||
SK_SPACE = 32,
|
||||
SK_EXCLAIM = 33,
|
||||
SK_QUOTEDBL = 34,
|
||||
SK_HASH = 35,
|
||||
SK_DOLLAR = 36,
|
||||
SK_AMPERSAND = 38,
|
||||
SK_QUOTE = 39,
|
||||
SK_LEFTPAREN = 40,
|
||||
SK_RIGHTPAREN = 41,
|
||||
SK_ASTERISK = 42,
|
||||
SK_PLUS = 43,
|
||||
SK_COMMA = 44,
|
||||
SK_MINUS = 45,
|
||||
SK_PERIOD = 46,
|
||||
SK_SLASH = 47,
|
||||
SK_0 = 48,
|
||||
SK_1 = 49,
|
||||
SK_2 = 50,
|
||||
SK_3 = 51,
|
||||
SK_4 = 52,
|
||||
SK_5 = 53,
|
||||
SK_6 = 54,
|
||||
SK_7 = 55,
|
||||
SK_8 = 56,
|
||||
SK_9 = 57,
|
||||
SK_COLON = 58,
|
||||
SK_SEMICOLON = 59,
|
||||
SK_LESS = 60,
|
||||
SK_EQUALS = 61,
|
||||
SK_GREATER = 62,
|
||||
SK_QUESTION = 63,
|
||||
SK_AT = 64,
|
||||
SK_LEFTBRACKET = 91,
|
||||
SK_BACKSLASH = 92,
|
||||
SK_RIGHTBRACKET = 93,
|
||||
SK_CARET = 94,
|
||||
SK_UNDERSCORE = 95,
|
||||
SK_BACKQUOTE = 96,
|
||||
SK_a = 97,
|
||||
SK_b = 98,
|
||||
SK_c = 99,
|
||||
SK_d = 100,
|
||||
SK_e = 101,
|
||||
SK_f = 102,
|
||||
SK_g = 103,
|
||||
SK_h = 104,
|
||||
SK_i = 105,
|
||||
SK_j = 106,
|
||||
SK_k = 107,
|
||||
SK_l = 108,
|
||||
SK_m = 109,
|
||||
SK_n = 110,
|
||||
SK_o = 111,
|
||||
SK_p = 112,
|
||||
SK_q = 113,
|
||||
SK_r = 114,
|
||||
SK_s = 115,
|
||||
SK_t = 116,
|
||||
SK_u = 117,
|
||||
SK_v = 118,
|
||||
SK_w = 119,
|
||||
SK_x = 120,
|
||||
SK_y = 121,
|
||||
SK_z = 122,
|
||||
SK_DELETE = 127,
|
||||
|
||||
SK_KP0 = 256,
|
||||
SK_KP1 = 257,
|
||||
SK_KP2 = 258,
|
||||
SK_KP3 = 259,
|
||||
SK_KP4 = 260,
|
||||
SK_KP5 = 261,
|
||||
SK_KP6 = 262,
|
||||
SK_KP7 = 263,
|
||||
SK_KP8 = 264,
|
||||
SK_KP9 = 265,
|
||||
SK_KP_PERIOD = 266,
|
||||
SK_KP_DIVIDE = 267,
|
||||
SK_KP_MULTIPLY = 268,
|
||||
SK_KP_MINUS = 269,
|
||||
SK_KP_PLUS = 270,
|
||||
SK_KP_ENTER = 271,
|
||||
SK_KP_EQUALS = 272,
|
||||
|
||||
SK_UP = 273,
|
||||
SK_DOWN = 274,
|
||||
SK_RIGHT = 275,
|
||||
SK_LEFT = 276,
|
||||
SK_INSERT = 277,
|
||||
SK_HOME = 278,
|
||||
SK_END = 279,
|
||||
SK_PAGEUP = 280,
|
||||
SK_PAGEDOWN = 281,
|
||||
|
||||
SK_F1 = 282,
|
||||
SK_F2 = 283,
|
||||
SK_F3 = 284,
|
||||
SK_F4 = 285,
|
||||
SK_F5 = 286,
|
||||
SK_F6 = 287,
|
||||
SK_F7 = 288,
|
||||
SK_F8 = 289,
|
||||
SK_F9 = 290,
|
||||
SK_F10 = 291,
|
||||
SK_F11 = 292,
|
||||
SK_F12 = 293,
|
||||
SK_F13 = 294,
|
||||
SK_F14 = 295,
|
||||
SK_F15 = 296,
|
||||
|
||||
SK_NUMLOCK = 300,
|
||||
SK_CAPSLOCK = 301,
|
||||
SK_SCROLLOCK = 302,
|
||||
SK_RSHIFT = 303,
|
||||
SK_LSHIFT = 304,
|
||||
SK_RCTRL = 305,
|
||||
SK_LCTRL = 306,
|
||||
SK_RALT = 307,
|
||||
SK_LALT = 308,
|
||||
SK_RMETA = 309,
|
||||
SK_LMETA = 310,
|
||||
SK_LSUPER = 311,
|
||||
SK_RSUPER = 312,
|
||||
SK_MODE = 313,
|
||||
SK_COMPOSE = 314,
|
||||
|
||||
SK_HELP = 315,
|
||||
SK_PRINT = 316,
|
||||
SK_SYSREQ = 317,
|
||||
SK_BREAK = 318,
|
||||
SK_MENU = 319,
|
||||
SK_POWER = 320,
|
||||
SK_EURO = 321,
|
||||
SK_UNDO = 322,
|
||||
|
||||
SK_LAST
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
+163
-74
@@ -17,20 +17,128 @@
|
||||
|
||||
#include "driver.h"
|
||||
|
||||
#include "SDL.h"
|
||||
#include "../gfx/sdlwrap.h"
|
||||
#include <stdbool.h>
|
||||
#include "general.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <libsnes.hpp>
|
||||
#include "ssnes_sdl_input.h"
|
||||
#include "keysym.h"
|
||||
|
||||
static void* sdl_input_init(void)
|
||||
struct key_bind
|
||||
{
|
||||
unsigned sdl;
|
||||
enum ssnes_key sk;
|
||||
};
|
||||
|
||||
static unsigned keysym_lut[SK_LAST];
|
||||
static const struct key_bind lut_binds[] = {
|
||||
{ SDLK_LEFT, SK_LEFT },
|
||||
{ SDLK_RIGHT, SK_RIGHT },
|
||||
{ SDLK_UP, SK_UP },
|
||||
{ SDLK_DOWN, SK_DOWN },
|
||||
{ SDLK_RETURN, SK_RETURN },
|
||||
{ SDLK_TAB, SK_TAB },
|
||||
{ SDLK_INSERT, SK_INSERT },
|
||||
{ SDLK_DELETE, SK_DELETE },
|
||||
{ SDLK_RSHIFT, SK_RSHIFT },
|
||||
{ SDLK_LSHIFT, SK_LSHIFT },
|
||||
{ SDLK_LCTRL, SK_LCTRL },
|
||||
{ SDLK_LALT, SK_LALT },
|
||||
{ SDLK_SPACE, SK_SPACE },
|
||||
{ SDLK_ESCAPE, SK_ESCAPE },
|
||||
{ SDLK_BACKSPACE, SK_BACKSPACE },
|
||||
{ SDLK_KP_ENTER, SK_KP_ENTER },
|
||||
{ SDLK_KP_PLUS, SK_KP_PLUS },
|
||||
{ SDLK_KP_MINUS, SK_KP_MINUS },
|
||||
{ SDLK_KP_MULTIPLY, SK_KP_MULTIPLY },
|
||||
{ SDLK_KP_DIVIDE, SK_KP_DIVIDE },
|
||||
{ SDLK_BACKQUOTE, SK_BACKQUOTE },
|
||||
{ SDLK_PAUSE, SK_PAUSE },
|
||||
{ SDLK_KP0, SK_KP0 },
|
||||
{ SDLK_KP1, SK_KP1 },
|
||||
{ SDLK_KP2, SK_KP2 },
|
||||
{ SDLK_KP3, SK_KP3 },
|
||||
{ SDLK_KP4, SK_KP4 },
|
||||
{ SDLK_KP5, SK_KP5 },
|
||||
{ SDLK_KP6, SK_KP6 },
|
||||
{ SDLK_KP7, SK_KP7 },
|
||||
{ SDLK_KP8, SK_KP8 },
|
||||
{ SDLK_KP9, SK_KP9 },
|
||||
{ SDLK_0, SK_0 },
|
||||
{ SDLK_1, SK_1 },
|
||||
{ SDLK_2, SK_2 },
|
||||
{ SDLK_3, SK_3 },
|
||||
{ SDLK_4, SK_4 },
|
||||
{ SDLK_5, SK_5 },
|
||||
{ SDLK_6, SK_6 },
|
||||
{ SDLK_7, SK_7 },
|
||||
{ SDLK_8, SK_8 },
|
||||
{ SDLK_9, SK_9 },
|
||||
{ SDLK_F1, SK_F1 },
|
||||
{ SDLK_F2, SK_F2 },
|
||||
{ SDLK_F3, SK_F3 },
|
||||
{ SDLK_F4, SK_F4 },
|
||||
{ SDLK_F5, SK_F5 },
|
||||
{ SDLK_F6, SK_F6 },
|
||||
{ SDLK_F7, SK_F7 },
|
||||
{ SDLK_F8, SK_F8 },
|
||||
{ SDLK_F9, SK_F9 },
|
||||
{ SDLK_F10, SK_F10 },
|
||||
{ SDLK_F11, SK_F11 },
|
||||
{ SDLK_F12, SK_F12 },
|
||||
{ SDLK_a, SK_a },
|
||||
{ SDLK_b, SK_b },
|
||||
{ SDLK_c, SK_c },
|
||||
{ SDLK_d, SK_d },
|
||||
{ SDLK_e, SK_e },
|
||||
{ SDLK_f, SK_f },
|
||||
{ SDLK_g, SK_g },
|
||||
{ SDLK_h, SK_h },
|
||||
{ SDLK_i, SK_i },
|
||||
{ SDLK_j, SK_j },
|
||||
{ SDLK_k, SK_k },
|
||||
{ SDLK_l, SK_l },
|
||||
{ SDLK_m, SK_m },
|
||||
{ SDLK_n, SK_n },
|
||||
{ SDLK_o, SK_o },
|
||||
{ SDLK_p, SK_p },
|
||||
{ SDLK_q, SK_q },
|
||||
{ SDLK_r, SK_r },
|
||||
{ SDLK_s, SK_s },
|
||||
{ SDLK_t, SK_t },
|
||||
{ SDLK_u, SK_u },
|
||||
{ SDLK_v, SK_v },
|
||||
{ SDLK_w, SK_w },
|
||||
{ SDLK_x, SK_x },
|
||||
{ SDLK_y, SK_y },
|
||||
{ SDLK_z, SK_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].sk] = lut_binds[i].sdl;
|
||||
}
|
||||
|
||||
static void *sdl_input_init(void)
|
||||
{
|
||||
init_lut();
|
||||
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;
|
||||
|
||||
@@ -59,30 +167,24 @@ static void* sdl_input_init(void)
|
||||
sdl->num_buttons[i] = SDL_JoystickNumButtons(sdl->joysticks[i]);
|
||||
sdl->num_hats[i] = SDL_JoystickNumHats(sdl->joysticks[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
SSNES_WARN("Desired SDL joystick #%u on port %u, but SDL can only detect %u joysticks ...\n",
|
||||
g_settings.input.joypad_map[i], i + 1, sdl->num_joysticks);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
sdl->use_keyboard = true;
|
||||
return sdl;
|
||||
}
|
||||
|
||||
|
||||
static bool sdl_key_pressed(int key)
|
||||
{
|
||||
int num_keys;
|
||||
Uint8 *keymap = SDL_GetKeyState(&num_keys);
|
||||
|
||||
if (key >= num_keys)
|
||||
return false;
|
||||
|
||||
return keymap[key];
|
||||
return sdlwrap_key_pressed(keysym_lut[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))
|
||||
{
|
||||
@@ -123,37 +225,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;
|
||||
}
|
||||
@@ -162,7 +270,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]);
|
||||
@@ -171,23 +279,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:
|
||||
@@ -251,11 +357,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:
|
||||
@@ -276,14 +382,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,37 +412,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;
|
||||
LPDIRECTINPUT8 ctx;
|
||||
LPDIRECTINPUTDEVICE8 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 "keysym.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
|
||||
{
|
||||
unsigned x;
|
||||
enum ssnes_key sk;
|
||||
};
|
||||
|
||||
static unsigned keysym_lut[SK_LAST];
|
||||
static const struct key_bind lut_binds[] = {
|
||||
{ XK_Left, SK_LEFT },
|
||||
{ XK_Right, SK_RIGHT },
|
||||
{ XK_Up, SK_UP },
|
||||
{ XK_Down, SK_DOWN },
|
||||
{ XK_Return, SK_RETURN },
|
||||
{ XK_Tab, SK_TAB },
|
||||
{ XK_Insert, SK_INSERT },
|
||||
{ XK_Delete, SK_DELETE },
|
||||
{ XK_Shift_R, SK_RSHIFT },
|
||||
{ XK_Shift_L, SK_LSHIFT },
|
||||
{ XK_Control_L, SK_LCTRL },
|
||||
{ XK_Alt_L, SK_LALT },
|
||||
{ XK_space, SK_SPACE },
|
||||
{ XK_Escape, SK_ESCAPE },
|
||||
{ XK_BackSpace, SK_BACKSPACE },
|
||||
{ XK_KP_Enter, SK_KP_ENTER },
|
||||
{ XK_KP_Add, SK_KP_PLUS },
|
||||
{ XK_KP_Subtract, SK_KP_MINUS },
|
||||
{ XK_KP_Multiply, SK_KP_MULTIPLY },
|
||||
{ XK_KP_Divide, SK_KP_DIVIDE },
|
||||
{ XK_grave, SK_BACKQUOTE },
|
||||
{ XK_Pause, SK_PAUSE },
|
||||
{ XK_KP_0, SK_KP0 },
|
||||
{ XK_KP_1, SK_KP1 },
|
||||
{ XK_KP_2, SK_KP2 },
|
||||
{ XK_KP_3, SK_KP3 },
|
||||
{ XK_KP_4, SK_KP4 },
|
||||
{ XK_KP_5, SK_KP5 },
|
||||
{ XK_KP_6, SK_KP6 },
|
||||
{ XK_KP_7, SK_KP7 },
|
||||
{ XK_KP_8, SK_KP8 },
|
||||
{ XK_KP_9, SK_KP9 },
|
||||
{ XK_0, SK_0 },
|
||||
{ XK_1, SK_1 },
|
||||
{ XK_2, SK_2 },
|
||||
{ XK_3, SK_3 },
|
||||
{ XK_4, SK_4 },
|
||||
{ XK_5, SK_5 },
|
||||
{ XK_6, SK_6 },
|
||||
{ XK_7, SK_7 },
|
||||
{ XK_8, SK_8 },
|
||||
{ XK_9, SK_9 },
|
||||
{ XK_F1, SK_F1 },
|
||||
{ XK_F2, SK_F2 },
|
||||
{ XK_F3, SK_F3 },
|
||||
{ XK_F4, SK_F4 },
|
||||
{ XK_F5, SK_F5 },
|
||||
{ XK_F6, SK_F6 },
|
||||
{ XK_F7, SK_F7 },
|
||||
{ XK_F8, SK_F8 },
|
||||
{ XK_F9, SK_F9 },
|
||||
{ XK_F10, SK_F10 },
|
||||
{ XK_F11, SK_F11 },
|
||||
{ XK_F12, SK_F12 },
|
||||
{ XK_a, SK_a },
|
||||
{ XK_b, SK_b },
|
||||
{ XK_c, SK_c },
|
||||
{ XK_d, SK_d },
|
||||
{ XK_e, SK_e },
|
||||
{ XK_f, SK_f },
|
||||
{ XK_g, SK_g },
|
||||
{ XK_h, SK_h },
|
||||
{ XK_i, SK_i },
|
||||
{ XK_j, SK_j },
|
||||
{ XK_k, SK_k },
|
||||
{ XK_l, SK_l },
|
||||
{ XK_m, SK_m },
|
||||
{ XK_n, SK_n },
|
||||
{ XK_o, SK_o },
|
||||
{ XK_p, SK_p },
|
||||
{ XK_q, SK_q },
|
||||
{ XK_r, SK_r },
|
||||
{ XK_s, SK_s },
|
||||
{ XK_t, SK_t },
|
||||
{ XK_u, SK_u },
|
||||
{ XK_v, SK_v },
|
||||
{ XK_w, SK_w },
|
||||
{ XK_x, SK_x },
|
||||
{ XK_y, SK_y },
|
||||
{ XK_z, SK_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].sk] = 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
+165
@@ -0,0 +1,165 @@
|
||||
#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
|
||||
|
||||
// SSNES extension. Not required to be implemented for a working implementation.
|
||||
#define SNES_ENVIRONMENT_GET_FULLPATH 0 // const char ** -- Full path of game loaded.
|
||||
#define SNES_ENVIRONMENT_SET_GEOMETRY 1 // const struct snes_geometry * -- Window geometry information for the system/game.
|
||||
#define SNES_ENVIRONMENT_SET_PITCH 2 // const unsigned * -- Pitch of game image.
|
||||
#define SNES_ENVIRONMENT_GET_OVERSCAN 3 // bool * -- Boolean value whether or not the implementation should use overscan.
|
||||
#define SNES_ENVIRONMENT_SET_TIMING 4 // const struct snes_system_timing * -- Set exact timings of the system.
|
||||
// Used primarily for video recording.
|
||||
#define SNES_ENVIRONMENT_GET_CAN_DUPE 5 // bool * -- Boolean value whether or not SSNES supports frame duping,
|
||||
// passing NULL to video frame callback.
|
||||
|
||||
struct snes_geometry
|
||||
{
|
||||
unsigned base_width; // Nominal video width of system.
|
||||
unsigned base_height; // Nominal video height of system.
|
||||
unsigned max_width; // Maximum possible width of system.
|
||||
unsigned max_height; // Maximum possible height of system.
|
||||
};
|
||||
|
||||
struct snes_system_timing
|
||||
{
|
||||
double fps;
|
||||
double sample_rate;
|
||||
};
|
||||
|
||||
typedef bool (*snes_environment_t)(unsigned cmd, void *data);
|
||||
|
||||
// Must be called before calling snes_init().
|
||||
void snes_set_environment(snes_environment_t);
|
||||
////
|
||||
|
||||
typedef void (*snes_video_refresh_t)(const uint16_t *data, unsigned width, unsigned height);
|
||||
typedef void (*snes_audio_sample_t)(uint16_t left, uint16_t right);
|
||||
typedef void (*snes_input_poll_t)(void);
|
||||
typedef int16_t (*snes_input_state_t)(bool port, unsigned device, unsigned index, unsigned id);
|
||||
|
||||
unsigned snes_library_revision_major(void);
|
||||
unsigned snes_library_revision_minor(void);
|
||||
|
||||
const char *snes_library_id(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,317 @@
|
||||
/* 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;
|
||||
|
||||
size_t *frame_pos; // A ring buffer keeping track of positions in the file for each frame.
|
||||
size_t frame_mask;
|
||||
size_t frame_ptr;
|
||||
|
||||
bool playback;
|
||||
size_t min_file_pos;
|
||||
|
||||
bool first_rewind;
|
||||
bool did_rewind;
|
||||
};
|
||||
|
||||
#define BSV_MAGIC 0x42535631
|
||||
|
||||
#define MAGIC_INDEX 0
|
||||
#define SERIALIZER_INDEX 1 // Not current used.
|
||||
#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]);
|
||||
|
||||
// If we're playing back from the start, state_size is 0.
|
||||
if (state_size)
|
||||
{
|
||||
handle->state = malloc(state_size);
|
||||
if (!handle->state)
|
||||
return false;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
handle->min_file_pos = sizeof(header) + 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[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;
|
||||
|
||||
if (state_size > 0)
|
||||
{
|
||||
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;
|
||||
|
||||
// Just pick something really large :D ~1 million frames rewind should do the trick.
|
||||
if (!(handle->frame_pos = calloc((1 << 20), sizeof(size_t))))
|
||||
goto error;
|
||||
|
||||
handle->frame_pos[0] = handle->min_file_pos;
|
||||
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)
|
||||
{
|
||||
handle->frame_pos[handle->frame_ptr] = ftell(handle->file);
|
||||
}
|
||||
|
||||
void bsv_movie_set_frame_end(bsv_movie_t *handle)
|
||||
{
|
||||
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)
|
||||
{
|
||||
handle->did_rewind = true;
|
||||
|
||||
// If we're at the beginning ... :)
|
||||
if ((handle->frame_ptr <= 1) && (handle->frame_pos[0] == handle->min_file_pos))
|
||||
{
|
||||
handle->frame_ptr = 0;
|
||||
fseek(handle->file, handle->min_file_pos, SEEK_SET);
|
||||
}
|
||||
else
|
||||
{
|
||||
// First time rewind is performed, the old frame is simply replayed.
|
||||
// However, playing back that frame caused us to read data, and push data to the ring buffer.
|
||||
// Sucessively rewinding frames, we need to rewind past the read data, plus another.
|
||||
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);
|
||||
}
|
||||
|
||||
// We rewound past the beginning. :O
|
||||
if (ftell(handle->file) <= (long)handle->min_file_pos)
|
||||
{
|
||||
// If recording, we simply reset the starting point. Nice and easy.
|
||||
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);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 129 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user