mirror of
https://github.com/TomHarte/CLK.git
synced 2025-09-12 02:24:31 +00:00
Compare commits
672 Commits
2022-08-22
...
SeparateFe
Author | SHA1 | Date | |
---|---|---|---|
|
e7299c16f6 | ||
|
cdb86022a6 | ||
|
2262725010 | ||
|
e61a4eb5a9 | ||
|
acd7f9f4cd | ||
|
9f1a657cc4 | ||
|
e52d1866ab | ||
|
a02b8222fa | ||
|
3762ee1a63 | ||
|
3ec61e8770 | ||
|
2f7dd0b01a | ||
|
3a02c22072 | ||
|
6ae967de51 | ||
|
5d45aa4a6a | ||
|
0f1468adfd | ||
|
e9347168e6 | ||
|
e2dcb0a8e2 | ||
|
1797bab28f | ||
|
7f48cd6d9d | ||
|
8662f06ae5 | ||
|
3b67d48ebf | ||
|
2ab16867cb | ||
|
3a93b8059a | ||
|
3e09afbb59 | ||
|
9703fed9f8 | ||
|
f30637a773 | ||
|
1d8bc41724 | ||
|
d36a88dd11 | ||
|
de5ee8f0d0 | ||
|
6261ac24b4 | ||
|
b00eac4a34 | ||
|
6e35d84a96 | ||
|
d028555361 | ||
|
1aa953dd4d | ||
|
77c67ab59d | ||
|
05d2e78f80 | ||
|
837d8d29ca | ||
|
8a831b1409 | ||
|
c0547f6e14 | ||
|
81e475f052 | ||
|
4e12d5a70a | ||
|
c630f86f33 | ||
|
1de2631877 | ||
|
dd3fc43bd3 | ||
|
40d5bd4e58 | ||
|
c75efb7dac | ||
|
d117a44069 | ||
|
dc425a03d3 | ||
|
ce8bd011d7 | ||
|
c76048bff9 | ||
|
4cb7abe13d | ||
|
c445295411 | ||
|
5c51bae605 | ||
|
8578dfbf22 | ||
|
f821b60430 | ||
|
8ca0d9e13a | ||
|
3014c957e7 | ||
|
747dc09a80 | ||
|
7f8f1d7e61 | ||
|
a1a7c0e253 | ||
|
9342c6005f | ||
|
14ac4da813 | ||
|
b0e3bd85d6 | ||
|
9b6be2571a | ||
|
4ede538d36 | ||
|
8bf3d85e36 | ||
|
ec9abbe6a7 | ||
|
22ac13d3f2 | ||
|
413ab42b16 | ||
|
876fc6d1e0 | ||
|
b768e438b2 | ||
|
e1d671daf7 | ||
|
4989701de9 | ||
|
fed97b8d26 | ||
|
e7888497b7 | ||
|
0b53c73da8 | ||
|
a6ebfe2ce2 | ||
|
b89076cb72 | ||
|
50343dec43 | ||
|
28c79b2885 | ||
|
60bec3d4c0 | ||
|
56de9c418f | ||
|
5bcb5fb832 | ||
|
abeb361441 | ||
|
f9cc2013a8 | ||
|
e7c40eead9 | ||
|
c29d80006e | ||
|
596661bfbe | ||
|
7e319374b6 | ||
|
7f5d129b13 | ||
|
f6acee18cc | ||
|
3af30b1fec | ||
|
a8cc74f9fe | ||
|
10cd2a36cf | ||
|
ea50d5bda7 | ||
|
809cd7bca9 | ||
|
e56db3c4e5 | ||
|
2b56b7be0d | ||
|
25a245e35c | ||
|
882384b1f3 | ||
|
5cc19f436f | ||
|
fd0eaa5788 | ||
|
992a47c196 | ||
|
8540e7a953 | ||
|
6b19bfeab2 | ||
|
066e42145e | ||
|
efe1425e8e | ||
|
55e0742232 | ||
|
abf92cd09e | ||
|
73a214913a | ||
|
5453840d8c | ||
|
77078e2b7a | ||
|
3f1ae986cb | ||
|
d39d2a88f8 | ||
|
6556865615 | ||
|
44779e68ee | ||
|
54f5bae59e | ||
|
e94b9f695a | ||
|
3797968870 | ||
|
9c079a6c50 | ||
|
561e2b774e | ||
|
0cb4fec504 | ||
|
ec81cdd388 | ||
|
8f0dc9d9a2 | ||
|
4ebc3344cb | ||
|
6552d962ab | ||
|
5f151c07ea | ||
|
1f4d526ea5 | ||
|
19d03dd4fd | ||
|
6c0feeedb4 | ||
|
b5d9586362 | ||
|
8cd38094fc | ||
|
e49e98d309 | ||
|
1b4df01a28 | ||
|
dbddcd109c | ||
|
efa7d659bc | ||
|
5daec050dd | ||
|
f5c8eba843 | ||
|
e5b0e666cc | ||
|
96896f838c | ||
|
f22aa6eb36 | ||
|
6d092e0633 | ||
|
6651a9c323 | ||
|
d40bc58e8b | ||
|
4b53082774 | ||
|
8a5b7e9f47 | ||
|
12bcc2dee7 | ||
|
d587d80616 | ||
|
0070a271f8 | ||
|
d641a9c2b1 | ||
|
ed2d4ebb0c | ||
|
32597b4e95 | ||
|
9f198f6392 | ||
|
107cb18df4 | ||
|
e66c015b43 | ||
|
9d99cc6115 | ||
|
383770515e | ||
|
024b7960cb | ||
|
e0a5d9f31c | ||
|
224c79c492 | ||
|
278e7ba9b0 | ||
|
20c1c6fdcd | ||
|
514022204e | ||
|
564ee1a5cb | ||
|
f3c2c0ffa9 | ||
|
931d2373a4 | ||
|
de3cd9c286 | ||
|
655638656f | ||
|
7d63a50f3e | ||
|
2bf2abf4b2 | ||
|
235d54bb67 | ||
|
e66a92d6cb | ||
|
a6251f436a | ||
|
6d49b2e66b | ||
|
363fd0f781 | ||
|
345e519e6a | ||
|
315e0b4545 | ||
|
4a5b2fd9ba | ||
|
a5a36cb08e | ||
|
aa4582956f | ||
|
c9543d0b36 | ||
|
d36c8df0c9 | ||
|
f26dee16bf | ||
|
131784d007 | ||
|
e703fa9cf8 | ||
|
201a7c17ae | ||
|
e0125e0177 | ||
|
cc04349618 | ||
|
d46f869276 | ||
|
9836a108da | ||
|
da944fde92 | ||
|
c9124f13cd | ||
|
ca7d34ad04 | ||
|
2913368a06 | ||
|
82659e7924 | ||
|
f3a84021ed | ||
|
555d883227 | ||
|
020d9604c6 | ||
|
6845008fd4 | ||
|
cc7b209e1a | ||
|
e8404bdcc0 | ||
|
f8eb2199c2 | ||
|
a13905acf9 | ||
|
5471979f8d | ||
|
5b8a5755f0 | ||
|
caaba836ba | ||
|
8fc043247c | ||
|
e58a488add | ||
|
0ea1da10d6 | ||
|
3381e6b5aa | ||
|
4c1973adc8 | ||
|
318cfab67d | ||
|
1ef34b1f18 | ||
|
12bd71cfe1 | ||
|
c7128f4206 | ||
|
95fd5a52ba | ||
|
bfa167fcdf | ||
|
b948761f30 | ||
|
dd65074bbf | ||
|
9debed25e8 | ||
|
1bdf9a50e6 | ||
|
2592dfd78f | ||
|
ef5d05db53 | ||
|
6a2cda7074 | ||
|
62d381065e | ||
|
091c1b0f45 | ||
|
a28b5bdeea | ||
|
2e45422b03 | ||
|
3606f5befe | ||
|
44ac948bb2 | ||
|
4bac782121 | ||
|
318109b7d5 | ||
|
6990ba9242 | ||
|
54be424159 | ||
|
8d900cf636 | ||
|
6db939f48f | ||
|
5eae11434a | ||
|
7aa8728b39 | ||
|
270c5dfe85 | ||
|
d6e4f12fdc | ||
|
4e3e4806b9 | ||
|
ebc596820e | ||
|
b62e899039 | ||
|
a123ef151c | ||
|
5b31db700b | ||
|
8af0a2313c | ||
|
88eaa4ff02 | ||
|
c140f370fe | ||
|
023da1970a | ||
|
211e145230 | ||
|
bbccc5d6d6 | ||
|
dbfc9a14aa | ||
|
9630a1bc39 | ||
|
3847c9c281 | ||
|
3f2a5929a3 | ||
|
9b71f42375 | ||
|
9c43776392 | ||
|
35a0a1447e | ||
|
bf0ed2813c | ||
|
1edf747f9f | ||
|
f38cf91ea7 | ||
|
5c7367b262 | ||
|
c1457cc5e0 | ||
|
169d7a7418 | ||
|
5143960970 | ||
|
40894964bc | ||
|
927e61484f | ||
|
dce04e7219 | ||
|
f5814b4c75 | ||
|
815a75d9b6 | ||
|
4ad84e5047 | ||
|
8665dca7f0 | ||
|
41d57e03a6 | ||
|
914a9e0c84 | ||
|
8768ee1504 | ||
|
8e6c36bb15 | ||
|
c6401f0444 | ||
|
3c3efe3e22 | ||
|
7028bdd05d | ||
|
7cb51c021b | ||
|
a3df106f92 | ||
|
b538407386 | ||
|
c04d292c8e | ||
|
f9c88fd598 | ||
|
7fcb1b29dd | ||
|
6786e3e78c | ||
|
67755c3811 | ||
|
c6372295c5 | ||
|
a2786e6266 | ||
|
a892523c09 | ||
|
ab595f5e8d | ||
|
38950fe241 | ||
|
46d009f27b | ||
|
34722bae89 | ||
|
d41081c59f | ||
|
ec227ce021 | ||
|
83f6d1cda3 | ||
|
6d315b4660 | ||
|
debdad350d | ||
|
0d4dc214fb | ||
|
5d4c49c913 | ||
|
8f5c7fcabc | ||
|
115acf835e | ||
|
c0fec8db15 | ||
|
f0e70f18fd | ||
|
9d2841bf6a | ||
|
ce6dd188a4 | ||
|
4cc34fd557 | ||
|
3636383b1f | ||
|
1264600bab | ||
|
002d27d9c2 | ||
|
90e8ce3253 | ||
|
a315384e30 | ||
|
c5c722ae56 | ||
|
872b9e5021 | ||
|
492a170b20 | ||
|
30a2b1611f | ||
|
29af5542f8 | ||
|
bc4c54800e | ||
|
4c93d01fe2 | ||
|
8f20cb93e9 | ||
|
73e79b14ea | ||
|
3142f5c21d | ||
|
6d7f189ce7 | ||
|
a91a5b8d07 | ||
|
4cdcd3ac7d | ||
|
0576451102 | ||
|
3f12a28f4f | ||
|
41ba883fb6 | ||
|
1e646eb57b | ||
|
2d6afe1013 | ||
|
d3c446d91b | ||
|
975ead5d01 | ||
|
c6cda7c401 | ||
|
7e69e33ec2 | ||
|
95e00dd958 | ||
|
8a673a697b | ||
|
75ad4cdb67 | ||
|
d42b6df570 | ||
|
81b00c6d97 | ||
|
1e5f751bc0 | ||
|
9a65fffe16 | ||
|
515fa22bfe | ||
|
66ac089cc2 | ||
|
5d5098acb2 | ||
|
1bf8406e7e | ||
|
75acbd2d6c | ||
|
baa6f9b3cd | ||
|
1c6a0ad3f7 | ||
|
fbfa26ad5e | ||
|
b12fd00145 | ||
|
0c8815d6a0 | ||
|
700470915a | ||
|
f8b42d4107 | ||
|
63bd0f918d | ||
|
445b34933a | ||
|
6b85ee9607 | ||
|
bbd8b011ba | ||
|
15fbf4cb7f | ||
|
7293f9dc10 | ||
|
8567c934b1 | ||
|
2744a9b6b0 | ||
|
91047e5b3a | ||
|
c6dd7d4726 | ||
|
b7d80f5ed1 | ||
|
a5765abbad | ||
|
696ec12516 | ||
|
c9734df65c | ||
|
13e490e7d7 | ||
|
cefcc1d443 | ||
|
d1f929e6f7 | ||
|
c9643c4145 | ||
|
e289e6e757 | ||
|
a726c9d97a | ||
|
c77e7c268f | ||
|
9c57bfd58d | ||
|
4efda108c6 | ||
|
191cf4829b | ||
|
9b7a925816 | ||
|
392b0acb58 | ||
|
4b7606894e | ||
|
1fb94d15ab | ||
|
348c42bdea | ||
|
e450e53c4e | ||
|
355ee7fbc7 | ||
|
339086d597 | ||
|
f0b1c34db2 | ||
|
7b25fe5f61 | ||
|
194b5bc36a | ||
|
0951c50e40 | ||
|
9588c9bee2 | ||
|
6f973fc605 | ||
|
eb51ed9ae8 | ||
|
83cf4497dd | ||
|
f6e601daff | ||
|
bb6ceafe0e | ||
|
55e73cb812 | ||
|
f0db676a10 | ||
|
32b29bd63b | ||
|
bfe94eb268 | ||
|
20ec192129 | ||
|
055e9cdf8d | ||
|
a5b9bdc18c | ||
|
eb51ff5cdf | ||
|
1769c24531 | ||
|
1a58ddaa67 | ||
|
183cb519e7 | ||
|
68361913ee | ||
|
ced002125e | ||
|
1e17fc71ab | ||
|
f57c2a961f | ||
|
48a4355592 | ||
|
3bc38d35c9 | ||
|
4d67360702 | ||
|
18def0c97d | ||
|
84cb7df1be | ||
|
97d93ad55c | ||
|
5f85074caa | ||
|
f0a4d1d8ec | ||
|
fb0241cf6e | ||
|
50b5122969 | ||
|
9f450b3ccb | ||
|
4190d25698 | ||
|
befc81743a | ||
|
23ff3fc366 | ||
|
78ce439b9b | ||
|
ce440d52b3 | ||
|
2e7e5ea12b | ||
|
0d8c014099 | ||
|
fee82d3baa | ||
|
76ad465030 | ||
|
483ee8a74f | ||
|
520ae7f2b2 | ||
|
ae5b81c0ab | ||
|
6bd261b222 | ||
|
53bb17c848 | ||
|
6e0f260478 | ||
|
19e333d117 | ||
|
3352feb21b | ||
|
73549eb38c | ||
|
dbff7592f5 | ||
|
4d96122884 | ||
|
9085ba4081 | ||
|
f1f16d1f9a | ||
|
fd14829992 | ||
|
c0fe88a5bb | ||
|
4d9d684618 | ||
|
a0a835cf10 | ||
|
ef67205ce8 | ||
|
794adf470b | ||
|
8cc20844a9 | ||
|
b522d65c50 | ||
|
cb19c2ffb0 | ||
|
5f6ddf8557 | ||
|
72e0bfecc1 | ||
|
cdf547ac82 | ||
|
dd5b4b484a | ||
|
56831e02fc | ||
|
5d2d3944ef | ||
|
f9e21df701 | ||
|
bb436204f6 | ||
|
de45536b5c | ||
|
ebc1264c2c | ||
|
4875148617 | ||
|
7a82b76911 | ||
|
27d37f71ec | ||
|
c4a5a9763e | ||
|
a9f97ac871 | ||
|
475440dc70 | ||
|
dc3f8f5e42 | ||
|
459ef39b08 | ||
|
27812fd0e2 | ||
|
38eb4d36de | ||
|
2bd20a0cf8 | ||
|
da61909ec5 | ||
|
5729ece7bb | ||
|
151f60958e | ||
|
180045ada6 | ||
|
11542e7a7f | ||
|
71598250ea | ||
|
e8aab1fd2a | ||
|
ffb0b2ce0b | ||
|
b7c315058f | ||
|
7d6eac2895 | ||
|
d79aac3081 | ||
|
8d5547dc9e | ||
|
5d89293c92 | ||
|
5ba97da6cd | ||
|
37f07f349e | ||
|
711f7b2d75 | ||
|
dca8c51384 | ||
|
462b7dcbfa | ||
|
2ab4b351ca | ||
|
4eedec50fb | ||
|
ee22a98c17 | ||
|
99ced5476f | ||
|
cdbd821913 | ||
|
8808014a85 | ||
|
6832cbeb31 | ||
|
08b3c42a5c | ||
|
95c526d957 | ||
|
3f3c9f7491 | ||
|
6aa8400996 | ||
|
8ccb803b08 | ||
|
7ce8326c8c | ||
|
28b4f51cb3 | ||
|
2fe6253ca8 | ||
|
e6ae35638b | ||
|
812234f695 | ||
|
b921e893a2 | ||
|
9b235a8f64 | ||
|
c8a82933bc | ||
|
5813e2b6c6 | ||
|
005f38dbff | ||
|
b77fb50e89 | ||
|
ae8f0d339e | ||
|
ccadf69630 | ||
|
bbd2cd47ea | ||
|
63ad2e8263 | ||
|
23e4a47f8b | ||
|
255d2f3486 | ||
|
6ad1d74ddd | ||
|
12ca79e645 | ||
|
85df54ee7d | ||
|
2b220659dd | ||
|
8a8c044976 | ||
|
e79388fc02 | ||
|
f6a72dc2b4 | ||
|
041eb79bf8 | ||
|
7d82b2ad12 | ||
|
c2b8cbfefc | ||
|
53140c016e | ||
|
adbd23eaea | ||
|
3f80df1feb | ||
|
cabf1a052c | ||
|
8ff9f27b91 | ||
|
ae2419e283 | ||
|
c1f0eed0a3 | ||
|
4e5a80e23a | ||
|
46fee9c53a | ||
|
8ddf20b36a | ||
|
7ba6c78d14 | ||
|
fd20323c25 | ||
|
83b9fc3318 | ||
|
1ceabb30b0 | ||
|
f8cb3ca8b5 | ||
|
d8a11eaba7 | ||
|
ab37b00356 | ||
|
b4fcf92a62 | ||
|
38c531fd5a | ||
|
8c670d2105 | ||
|
9a56d053f8 | ||
|
79224d9383 | ||
|
7c328edd4a | ||
|
cb0e259339 | ||
|
9be9e1ab0c | ||
|
149c940a29 | ||
|
ec728ad573 | ||
|
df7f94f362 | ||
|
bc9ddacb8d | ||
|
979bf42541 | ||
|
d09473b66f | ||
|
b31b4a5d10 | ||
|
5560a0ed39 | ||
|
a1ae7c28b2 | ||
|
a364499d17 | ||
|
fb2b7969a2 | ||
|
abb19e6670 | ||
|
555250dbd9 | ||
|
8148397f62 | ||
|
f095bba1ca | ||
|
ee3a3df0b5 | ||
|
aff1caed15 | ||
|
da03cd58c1 | ||
|
ce98ca4bdd | ||
|
cc55f0586d | ||
|
47e8f3c0f1 | ||
|
d5ceb934d2 | ||
|
17c1e51231 | ||
|
fee072b404 | ||
|
0a9c392371 | ||
|
06dbb7167b | ||
|
eff9a09b9f | ||
|
1f19141746 | ||
|
28093196b9 | ||
|
eb206a08d9 | ||
|
b2f005da1b | ||
|
8305a3b46a | ||
|
f3f23f90a3 | ||
|
77bc60bf86 | ||
|
ec5d57fefe | ||
|
58396f0c52 | ||
|
c0377f074f | ||
|
4d3221fc55 | ||
|
59388230a6 | ||
|
1ba4363802 | ||
|
d17fadbe0b | ||
|
9cba56237d | ||
|
ea9411b21c | ||
|
38e85a340a | ||
|
fea8fecf11 | ||
|
c4091a4cdb | ||
|
d826532031 | ||
|
beca7a01c2 | ||
|
2d8e260671 | ||
|
04f5d29ed9 | ||
|
5ed60f9153 | ||
|
2f78a1c7af | ||
|
dc35ec8fa0 | ||
|
7e3dbbbf0a | ||
|
0f017302ce | ||
|
36c3cb1f70 | ||
|
6773a321c1 | ||
|
ffdf44ad4f | ||
|
cb7f1e42ff | ||
|
84a6c89a92 | ||
|
451b730c8e | ||
|
98d3da62b5 | ||
|
45dc99fb9d | ||
|
2edbbfbe37 | ||
|
dad1d7744e | ||
|
de8ce3380c | ||
|
b848b1389a | ||
|
2c44ddfa95 | ||
|
72b6ab4389 | ||
|
1a7509e860 | ||
|
0fe94b2e6d | ||
|
93c1f7fc90 | ||
|
b6da1019bd | ||
|
effe8c102d | ||
|
cee3f78059 | ||
|
68f810883d | ||
|
cbfd8e18e8 | ||
|
8dc1aca67c | ||
|
acc82546c4 | ||
|
df29a50738 | ||
|
d460f40b13 | ||
|
7996fe6dab | ||
|
f50ce7f137 | ||
|
6fa4e379d2 | ||
|
3c954e76ed | ||
|
cd7671e8fa | ||
|
54ca168db5 | ||
|
330d852686 | ||
|
303ea496f1 | ||
|
e7b213604e | ||
|
c3007bffc9 | ||
|
0499dbd4cf | ||
|
20d685ec5c | ||
|
4df2a29a1f | ||
|
722e3a141d | ||
|
91e9248ecc | ||
|
22a3f4de2c | ||
|
d57464a02a | ||
|
1346bf6fff | ||
|
4ff2e7f546 | ||
|
cf356c59aa | ||
|
1555b51d99 | ||
|
64c5b84b8b | ||
|
017f55390a | ||
|
6010c971a1 | ||
|
ea4bf5f31a | ||
|
f4c242d5e9 | ||
|
0595773355 | ||
|
f89ca84902 | ||
|
e9771ce540 | ||
|
246bd5a6ac | ||
|
3c2d01451a | ||
|
4c38fa8ad3 | ||
|
c2c81162a1 | ||
|
3d234147a6 | ||
|
8e7f53751d |
2
.github/workflows/ccpp.yml
vendored
2
.github/workflows/ccpp.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get --allow-releaseinfo-change update && sudo apt-get --fix-missing install libsdl2-dev scons
|
||||
- name: Make
|
||||
|
@@ -9,6 +9,7 @@
|
||||
#ifndef ActivityObserver_h
|
||||
#define ActivityObserver_h
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace Activity {
|
||||
|
@@ -11,8 +11,7 @@
|
||||
|
||||
#include "ConfidenceSource.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a confidence source that calculates its probability by virtual of a history of events.
|
||||
@@ -41,7 +40,6 @@ class ConfidenceCounter: public ConfidenceSource {
|
||||
int misses_ = 1;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ConfidenceCounter_hpp */
|
||||
|
@@ -9,8 +9,7 @@
|
||||
#ifndef ConfidenceSource_hpp
|
||||
#define ConfidenceSource_hpp
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Provides an abstract interface through which objects can declare the probability
|
||||
@@ -22,7 +21,6 @@ struct ConfidenceSource {
|
||||
virtual float get_confidence() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ConfidenceSource_hpp */
|
||||
|
@@ -13,8 +13,7 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Summaries a collection of confidence sources by calculating their weighted sum.
|
||||
@@ -40,7 +39,6 @@ class ConfidenceSummary: public ConfidenceSource {
|
||||
float weight_sum_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ConfidenceSummary_hpp */
|
||||
|
@@ -15,8 +15,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the configurable interface to multiple machines.
|
||||
@@ -36,7 +35,6 @@ class MultiConfigurable: public Configurable::Device {
|
||||
std::vector<Configurable::Device *> devices_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiConfigurable_hpp */
|
||||
|
@@ -14,8 +14,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the joystick machine interface to multiple machines.
|
||||
@@ -34,7 +33,6 @@ class MultiJoystickMachine: public MachineTypes::JoystickMachine {
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiJoystickMachine_hpp */
|
||||
|
@@ -15,8 +15,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the keyboard machine interface to multiple machines.
|
||||
@@ -55,7 +54,6 @@ class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
|
||||
Inputs::Keyboard &get_keyboard() final;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiKeyboardMachine_hpp */
|
||||
|
@@ -15,8 +15,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes the media target interface to multiple machines.
|
||||
@@ -35,7 +34,6 @@ struct MultiMediaTarget: public MachineTypes::MediaTarget {
|
||||
std::vector<MachineTypes::MediaTarget *> targets_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiMediaTarget_hpp */
|
||||
|
@@ -19,8 +19,7 @@
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
template <typename MachineType> class MultiInterface {
|
||||
public:
|
||||
@@ -116,7 +115,5 @@ class MultiAudioProducer: public MultiInterface<MachineTypes::AudioProducer>, pu
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* MultiProducer_hpp */
|
||||
|
@@ -16,8 +16,7 @@
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Provides a class that multiplexes calls to and from Outputs::Speaker::Speaker in order
|
||||
@@ -55,7 +54,6 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
|
||||
bool stereo_output_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiSpeaker_hpp */
|
||||
|
@@ -22,8 +22,7 @@
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Dynamic {
|
||||
namespace Analyser::Dynamic {
|
||||
|
||||
/*!
|
||||
Provides the same interface as to a single machine, while multiplexing all
|
||||
@@ -80,7 +79,6 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiTimedMachine::
|
||||
bool has_picked_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MultiMachine_hpp */
|
||||
|
@@ -12,9 +12,7 @@
|
||||
#include "File.hpp"
|
||||
#include "../../../Storage/Disk/Disk.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
/// Describes a DFS- or ADFS-format catalogue(/directory): the list of files available and the catalogue's boot option.
|
||||
struct Catalogue {
|
||||
@@ -31,8 +29,6 @@ struct Catalogue {
|
||||
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Disk_hpp */
|
||||
|
@@ -13,9 +13,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
struct File {
|
||||
std::string name;
|
||||
@@ -60,8 +58,6 @@ struct File {
|
||||
std::vector<Chunk> chunks;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* File_hpp */
|
||||
|
@@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* AcornAnalyser_hpp */
|
||||
|
@@ -14,14 +14,10 @@
|
||||
#include "File.hpp"
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Tape_hpp */
|
||||
|
@@ -13,9 +13,7 @@
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Acorn {
|
||||
namespace Analyser::Static::Acorn {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
bool has_acorn_adfs = false;
|
||||
@@ -37,8 +35,6 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Acorn_Target_h */
|
||||
|
@@ -13,15 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Amiga {
|
||||
namespace Analyser::Static::Amiga {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_Amiga_StaticAnalyser_hpp */
|
||||
|
@@ -12,9 +12,7 @@
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Amiga {
|
||||
namespace Analyser::Static::Amiga {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(ChipRAM,
|
||||
@@ -41,8 +39,6 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Amiga_Target_h */
|
||||
|
@@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AmstradCPC {
|
||||
namespace Analyser::Static::AmstradCPC {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AmstradCPC_StaticAnalyser_hpp */
|
||||
|
@@ -14,9 +14,7 @@
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AmstradCPC {
|
||||
namespace Analyser::Static::AmstradCPC {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model, CPC464, CPC664, CPC6128);
|
||||
@@ -32,8 +30,5 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_AmstradCPC_Target_h */
|
||||
|
@@ -13,8 +13,17 @@ Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &
|
||||
auto target = std::make_unique<Target>();
|
||||
target->media = media;
|
||||
|
||||
if(!target->media.disks.empty())
|
||||
// If any disks are present, attach a Disk II.
|
||||
if(!target->media.disks.empty()) {
|
||||
target->disk_controller = Target::DiskController::SixteenSector;
|
||||
}
|
||||
|
||||
// The emulated SCSI card requires a IIe, so upgrade to that if
|
||||
// any mass storage is present.
|
||||
if(!target->media.mass_storage_devices.empty()) {
|
||||
target->model = Target::Model::EnhancedIIe;
|
||||
target->scsi_controller = Target::SCSIController::AppleSCSI;
|
||||
}
|
||||
|
||||
TargetList targets;
|
||||
targets.push_back(std::move(target));
|
||||
|
@@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleII {
|
||||
namespace Analyser::Static::AppleII {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AppleII_StaticAnalyser_hpp */
|
||||
|
@@ -13,9 +13,7 @@
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleII {
|
||||
namespace Analyser::Static::AppleII {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model,
|
||||
@@ -29,22 +27,28 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
SixteenSector,
|
||||
ThirteenSector
|
||||
);
|
||||
ReflectableEnum(SCSIController,
|
||||
None,
|
||||
AppleSCSI
|
||||
);
|
||||
|
||||
Model model = Model::IIe;
|
||||
DiskController disk_controller = DiskController::None;
|
||||
SCSIController scsi_controller = SCSIController::None;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AppleII) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
DeclareField(disk_controller);
|
||||
DeclareField(scsi_controller);
|
||||
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(DiskController);
|
||||
AnnounceEnum(SCSIController);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AppleII_Target_h */
|
||||
|
@@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleIIgs {
|
||||
namespace Analyser::Static::AppleIIgs {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AppleIIgs_StaticAnalyser_hpp */
|
||||
|
@@ -13,9 +13,7 @@
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AppleIIgs {
|
||||
namespace Analyser::Static::AppleIIgs {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model,
|
||||
@@ -42,8 +40,6 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AppleIIgs_Target_h */
|
||||
|
@@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Atari2600 {
|
||||
namespace Analyser::Static::Atari2600 {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@@ -11,9 +11,7 @@
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Atari2600 {
|
||||
namespace Analyser::Static::Atari2600 {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target {
|
||||
enum class PagingModel {
|
||||
@@ -38,8 +36,6 @@ struct Target: public ::Analyser::Static::Target {
|
||||
Target() : Analyser::Static::Target(Machine::Atari2600) {}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Atari_Target_h */
|
||||
|
@@ -13,15 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AtariST {
|
||||
namespace Analyser::Static::AtariST {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_AtariST_StaticAnalyser_hpp */
|
||||
|
@@ -12,9 +12,7 @@
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace AtariST {
|
||||
namespace Analyser::Static::AtariST {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(MemorySize,
|
||||
@@ -31,8 +29,6 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_AtariST_Target_h */
|
||||
|
@@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Coleco {
|
||||
namespace Analyser::Static::Coleco {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@@ -14,14 +14,10 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Disk_hpp */
|
||||
|
@@ -9,12 +9,11 @@
|
||||
#ifndef File_hpp
|
||||
#define File_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
struct File {
|
||||
std::wstring name;
|
||||
@@ -35,8 +34,6 @@ struct File {
|
||||
bool is_basic();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* File_hpp */
|
||||
|
@@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CommodoreAnalyser_hpp */
|
||||
|
@@ -12,14 +12,10 @@
|
||||
#include "../../../Storage/Tape/Tape.hpp"
|
||||
#include "File.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Tape_hpp */
|
||||
|
@@ -14,9 +14,7 @@
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Commodore {
|
||||
namespace Analyser::Static::Commodore {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
enum class MemoryModel {
|
||||
@@ -71,8 +69,6 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Commodore_Target_h */
|
||||
|
@@ -16,9 +16,7 @@
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MOS6502 {
|
||||
namespace Analyser::Static::MOS6502 {
|
||||
|
||||
/*!
|
||||
Describes a 6502 instruciton: its address, the operation it performs, its addressing mode
|
||||
@@ -95,7 +93,5 @@ Disassembly Disassemble(
|
||||
std::vector<uint16_t> entry_points);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Disassembler6502_hpp */
|
||||
|
@@ -11,9 +11,7 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Disassembler {
|
||||
namespace Analyser::Static::Disassembler {
|
||||
|
||||
/*!
|
||||
Provides an address mapper that relocates a chunk of memory so that it starts at
|
||||
@@ -25,8 +23,6 @@ template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* AddressMapper_hpp */
|
||||
|
@@ -9,9 +9,7 @@
|
||||
#ifndef Kernel_hpp
|
||||
#define Kernel_hpp
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Disassembly {
|
||||
namespace Analyser::Static::Disassembly {
|
||||
|
||||
template <typename D, typename S> struct PartialDisassembly {
|
||||
D disassembly;
|
||||
@@ -45,8 +43,6 @@ template <typename D, typename S, typename Disassembler> D Disassemble(
|
||||
return partial_disassembly.disassembly;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Kernel_hpp */
|
||||
|
@@ -15,9 +15,7 @@
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Z80 {
|
||||
namespace Analyser::Static::Z80 {
|
||||
|
||||
struct Instruction {
|
||||
/*! The address this instruction starts at. This is a mapped address. */
|
||||
@@ -84,7 +82,5 @@ Disassembly Disassemble(
|
||||
std::vector<uint16_t> entry_points);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_Disassembler_Z80_hpp */
|
||||
|
@@ -13,15 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace DiskII {
|
||||
namespace Analyser::Static::DiskII {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_DiskII_StaticAnalyser_hpp */
|
||||
|
@@ -13,15 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Enterprise {
|
||||
namespace Analyser::Static::Enterprise {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_Enterprise_StaticAnalyser_hpp */
|
||||
|
@@ -15,9 +15,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Enterprise {
|
||||
namespace Analyser::Static::Enterprise {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model, Enterprise64, Enterprise128, Enterprise256);
|
||||
@@ -50,8 +48,6 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Enterprise_Target_h */
|
||||
|
@@ -11,9 +11,7 @@
|
||||
|
||||
#include "../../../Storage/Cartridge/Cartridge.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
namespace Analyser::Static::MSX {
|
||||
|
||||
/*!
|
||||
Extends the base cartridge class by adding a (guess at) the banking scheme.
|
||||
@@ -33,8 +31,6 @@ struct Cartridge: public ::Storage::Cartridge::Cartridge {
|
||||
Storage::Cartridge::Cartridge(segments), type(type) {}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Cartridge_hpp */
|
||||
|
@@ -37,6 +37,11 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
|
||||
auto target = std::make_unique<Analyser::Static::MSX::Target>();
|
||||
target->confidence = confidence;
|
||||
|
||||
// Observation: all ROMs of 48kb or less are from the MSX 1 era.
|
||||
if(segment.data.size() < 48*1024) {
|
||||
target->model = Analyser::Static::MSX::Target::Model::MSX1;
|
||||
}
|
||||
|
||||
if(type == Analyser::Static::MSX::Cartridge::Type::None) {
|
||||
target->media.cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
|
||||
} else {
|
||||
@@ -100,6 +105,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
// TODO: check for a rational init address?
|
||||
|
||||
// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on.
|
||||
// Bonus observation: all such ROMs are from the MSX 1 era.
|
||||
if(data_size <= 0xc000) {
|
||||
targets.emplace_back(CartridgeTarget(segment, start_address, Analyser::Static::MSX::Cartridge::Type::None, 1.0));
|
||||
continue;
|
||||
|
@@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
namespace Analyser::Static::MSX {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_MSX_StaticAnalyser_hpp */
|
||||
|
@@ -14,9 +14,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
namespace Analyser::Static::MSX {
|
||||
|
||||
struct File {
|
||||
std::string name;
|
||||
@@ -37,8 +35,6 @@ struct File {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_MSX_Tape_hpp */
|
||||
|
@@ -14,14 +14,19 @@
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace MSX {
|
||||
namespace Analyser::Static::MSX {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
bool has_disk_drive = false;
|
||||
bool has_msx_music = true;
|
||||
std::string loading_command;
|
||||
|
||||
ReflectableEnum(Model,
|
||||
MSX1,
|
||||
MSX2
|
||||
);
|
||||
Model model = Model::MSX2;
|
||||
|
||||
ReflectableEnum(Region,
|
||||
Japan,
|
||||
USA,
|
||||
@@ -32,14 +37,15 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
Target(): Analyser::Static::Target(Machine::MSX) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(has_disk_drive);
|
||||
DeclareField(has_msx_music);
|
||||
DeclareField(region);
|
||||
AnnounceEnum(Region);
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_MSX_Target_h */
|
||||
|
@@ -13,15 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Macintosh {
|
||||
namespace Analyser::Static::Macintosh {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_Macintosh_StaticAnalyser_hpp */
|
||||
|
@@ -13,9 +13,7 @@
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Macintosh {
|
||||
namespace Analyser::Static::Macintosh {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model, Mac128k, Mac512k, Mac512ke, MacPlus);
|
||||
@@ -30,8 +28,6 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Macintosh_Target_h */
|
||||
|
@@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Oric {
|
||||
namespace Analyser::Static::Oric {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@@ -14,9 +14,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Oric {
|
||||
namespace Analyser::Static::Oric {
|
||||
|
||||
struct File {
|
||||
std::string name;
|
||||
@@ -33,8 +31,6 @@ struct File {
|
||||
|
||||
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Tape_hpp */
|
||||
|
@@ -14,9 +14,7 @@
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Oric {
|
||||
namespace Analyser::Static::Oric {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(ROM,
|
||||
@@ -56,8 +54,6 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Oric_Target_h */
|
||||
|
@@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Sega {
|
||||
namespace Analyser::Static::Sega {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@@ -13,9 +13,7 @@
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Sega {
|
||||
namespace Analyser::Static::Sega {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
enum class Model {
|
||||
@@ -48,10 +46,10 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
#define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem
|
||||
constexpr bool is_master_system(Analyser::Static::Sega::Target::Model model) {
|
||||
return model >= Analyser::Static::Sega::Target::Model::MasterSystem;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Sega_Target_h */
|
||||
|
@@ -59,6 +59,7 @@
|
||||
// Mass Storage Devices (i.e. usually, hard disks)
|
||||
#include "../../Storage/MassStorage/Formats/DAT.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/DSK.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/HDV.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/HFV.hpp"
|
||||
|
||||
// State Snapshots
|
||||
@@ -80,6 +81,8 @@
|
||||
// Target Platform Types
|
||||
#include "../../Storage/TargetPlatforms.hpp"
|
||||
|
||||
template<class> inline constexpr bool always_false_v = false;
|
||||
|
||||
using namespace Analyser::Static;
|
||||
|
||||
namespace {
|
||||
@@ -123,7 +126,21 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
if(extension == "2mg") {
|
||||
// 2MG uses a factory method; defer to it.
|
||||
try {
|
||||
InsertInstance(result.disks, Storage::Disk::Disk2MG::open(file_name), TargetPlatform::DiskII)
|
||||
const auto media = Storage::Disk::Disk2MG::open(file_name);
|
||||
std::visit([&result, &potential_platforms](auto &&arg) {
|
||||
using Type = typename std::decay<decltype(arg)>::type;
|
||||
|
||||
if constexpr (std::is_same<Type, nullptr_t>::value) {
|
||||
// It's valid for no media to be returned.
|
||||
} else if constexpr (std::is_same<Type, Storage::Disk::DiskImageHolderBase *>::value) {
|
||||
InsertInstance(result.disks, arg, TargetPlatform::DiskII);
|
||||
} else if constexpr (std::is_same<Type, Storage::MassStorage::MassStorageDevice *>::value) {
|
||||
// TODO: or is it Apple IIgs?
|
||||
InsertInstance(result.mass_storage_devices, arg, TargetPlatform::AppleII);
|
||||
} else {
|
||||
static_assert(always_false_v<Type>, "Unexpected type encountered.");
|
||||
}
|
||||
}, media);
|
||||
} catch(...) {}
|
||||
}
|
||||
|
||||
@@ -154,6 +171,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::MSX) // DSK (MSX)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
|
||||
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
|
||||
Format("hdv", result.mass_storage_devices, MassStorage::HDV, TargetPlatform::AppleII) // HDV (Apple II, hard disk, single volume image)
|
||||
Format( "hfe",
|
||||
result.disks,
|
||||
Disk::DiskImageHolder<Storage::Disk::HFE>,
|
||||
@@ -167,6 +185,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
Disk::DiskImageHolder<Storage::Disk::IPF>,
|
||||
TargetPlatform::Amiga | TargetPlatform::AtariST | TargetPlatform::AmstradCPC | TargetPlatform::ZXSpectrum) // IPF
|
||||
Format("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST) // MSA
|
||||
Format("mx2", result.cartridges, Cartridge::BinaryDump, TargetPlatform::MSX) // MX2
|
||||
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
|
||||
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
|
||||
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
|
||||
|
@@ -21,8 +21,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Analyser::Static {
|
||||
|
||||
struct State;
|
||||
|
||||
@@ -79,7 +78,6 @@ TargetList GetTargets(const std::string &file_name);
|
||||
*/
|
||||
Media GetMedia(const std::string &file_name);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZX8081 {
|
||||
namespace Analyser::Static::ZX8081 {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@@ -14,9 +14,7 @@
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZX8081 {
|
||||
namespace Analyser::Static::ZX8081 {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(MemoryModel,
|
||||
@@ -40,8 +38,6 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_ZX8081_Target_h */
|
||||
|
@@ -13,14 +13,10 @@
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZXSpectrum {
|
||||
namespace Analyser::Static::ZXSpectrum {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* StaticAnalyser_hpp */
|
||||
|
@@ -13,9 +13,7 @@
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace ZXSpectrum {
|
||||
namespace Analyser::Static::ZXSpectrum {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model,
|
||||
@@ -38,8 +36,6 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Target_h */
|
||||
|
@@ -14,6 +14,8 @@
|
||||
#include "ClockingHintSource.hpp"
|
||||
#include "ForceInline.hpp"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
/*!
|
||||
A JustInTimeActor holds (i) an embedded object with a run_for method; and (ii) an amount
|
||||
of time since run_for was last called.
|
||||
@@ -121,7 +123,13 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
/// If this object provides sequence points, checks for changes to the next
|
||||
/// sequence point upon deletion of the pointer.
|
||||
[[nodiscard]] forceinline auto operator->() {
|
||||
#ifndef NDEBUG
|
||||
assert(!flush_concurrency_check_.test_and_set());
|
||||
#endif
|
||||
flush();
|
||||
#ifndef NDEBUG
|
||||
flush_concurrency_check_.clear();
|
||||
#endif
|
||||
return std::unique_ptr<T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(this));
|
||||
}
|
||||
|
||||
@@ -130,7 +138,13 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
/// Despite being const, this will flush the object and, if relevant, update the next sequence point.
|
||||
[[nodiscard]] forceinline auto operator -> () const {
|
||||
auto non_const_this = const_cast<JustInTimeActor<T, LocalTimeScale, multiplier, divider> *>(this);
|
||||
#ifndef NDEBUG
|
||||
assert(!non_const_this->flush_concurrency_check_.test_and_set());
|
||||
#endif
|
||||
non_const_this->flush();
|
||||
#ifndef NDEBUG
|
||||
non_const_this->flush_concurrency_check_.clear();
|
||||
#endif
|
||||
return std::unique_ptr<const T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(non_const_this));
|
||||
}
|
||||
|
||||
@@ -264,6 +278,10 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {
|
||||
clocking_preference_ = clocking;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
std::atomic_flag flush_concurrency_check_{};
|
||||
#endif
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@@ -8,8 +8,18 @@
|
||||
|
||||
#include "ncr5380.hpp"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define NDEBUG
|
||||
#endif
|
||||
#define LOG_PREFIX "[5380] "
|
||||
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
// TODO:
|
||||
//
|
||||
// end_of_dma_ should be set if: /EOP && /DACK && (/RD || /WR); for at least 100ns.
|
||||
|
||||
|
||||
using namespace NCR::NCR5380;
|
||||
using SCSI::Line;
|
||||
|
||||
@@ -28,20 +38,16 @@ NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) :
|
||||
void NCR5380::write(int address, uint8_t value, bool) {
|
||||
switch(address & 7) {
|
||||
case 0:
|
||||
// LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value));
|
||||
data_bus_ = value;
|
||||
LOG("[0] Set current SCSI bus state to " << PADHEX(2) << int(value));
|
||||
|
||||
data_bus_ = value;
|
||||
if(dma_request_ && dma_operation_ == DMAOperation::Send) {
|
||||
// printf("w %02x\n", value);
|
||||
dma_acknowledge_ = true;
|
||||
dma_request_ = false;
|
||||
update_control_output();
|
||||
bus_.set_device_output(device_id_, bus_output_);
|
||||
dma_acknowledge(value);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: {
|
||||
// LOG("[SCSI 1] Initiator command register set: " << PADHEX(2) << int(value));
|
||||
LOG("[1] Initiator command register set: " << PADHEX(2) << int(value));
|
||||
initiator_command_ = value;
|
||||
|
||||
bus_output_ &= ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention);
|
||||
@@ -57,7 +63,7 @@ void NCR5380::write(int address, uint8_t value, bool) {
|
||||
} break;
|
||||
|
||||
case 2:
|
||||
// LOG("[SCSI 2] Set mode: " << PADHEX(2) << int(value));
|
||||
LOG("[2] Set mode: " << PADHEX(2) << int(value));
|
||||
mode_ = value;
|
||||
|
||||
// bit 7: 1 = use block mode DMA mode (if DMA mode is also enabled)
|
||||
@@ -69,6 +75,7 @@ void NCR5380::write(int address, uint8_t value, bool) {
|
||||
// bit 1: 1 = use DMA mode
|
||||
// bit 0: 1 = begin arbitration mode (device ID should be in register 0)
|
||||
arbitration_in_progress_ = false;
|
||||
phase_mismatch_ = false;
|
||||
switch(mode_ & 0x3) {
|
||||
case 0x0:
|
||||
bus_output_ &= ~SCSI::Line::Busy;
|
||||
@@ -88,31 +95,36 @@ void NCR5380::write(int address, uint8_t value, bool) {
|
||||
bus_.update_observers();
|
||||
break;
|
||||
}
|
||||
|
||||
// "[The End of DMA Transfer] bit is reset when the DMA MODE bit
|
||||
// is reset (0) in the Mode Register".
|
||||
end_of_dma_ &= bool(value & 0x2);
|
||||
|
||||
update_control_output();
|
||||
break;
|
||||
|
||||
case 3: {
|
||||
// LOG("[SCSI 3] Set target command: " << PADHEX(2) << int(value));
|
||||
LOG("[3] Set target command: " << PADHEX(2) << int(value));
|
||||
target_command_ = value;
|
||||
update_control_output();
|
||||
} break;
|
||||
|
||||
case 4:
|
||||
// LOG("[SCSI 4] Set select enabled: " << PADHEX(2) << int(value));
|
||||
LOG("[4] Set select enabled: " << PADHEX(2) << int(value));
|
||||
break;
|
||||
|
||||
case 5:
|
||||
// LOG("[SCSI 5] Start DMA send: " << PADHEX(2) << int(value));
|
||||
LOG("[5] Start DMA send: " << PADHEX(2) << int(value));
|
||||
dma_operation_ = DMAOperation::Send;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
// LOG("[SCSI 6] Start DMA target receive: " << PADHEX(2) << int(value));
|
||||
LOG("[6] Start DMA target receive: " << PADHEX(2) << int(value));
|
||||
dma_operation_ = DMAOperation::TargetReceive;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
// LOG("[SCSI 7] Start DMA initiator receive: " << PADHEX(2) << int(value));
|
||||
LOG("[7] Start DMA initiator receive: " << PADHEX(2) << int(value));
|
||||
dma_operation_ = DMAOperation::InitiatorReceive;
|
||||
break;
|
||||
}
|
||||
@@ -136,18 +148,15 @@ void NCR5380::write(int address, uint8_t value, bool) {
|
||||
uint8_t NCR5380::read(int address, bool) {
|
||||
switch(address & 7) {
|
||||
case 0:
|
||||
// LOG("[SCSI 0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
|
||||
LOG("[0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
|
||||
|
||||
if(dma_request_ && dma_operation_ == DMAOperation::InitiatorReceive) {
|
||||
dma_acknowledge_ = true;
|
||||
dma_request_ = false;
|
||||
update_control_output();
|
||||
bus_.set_device_output(device_id_, bus_output_);
|
||||
return dma_acknowledge();
|
||||
}
|
||||
return uint8_t(bus_.get_state());
|
||||
|
||||
case 1:
|
||||
// LOG("[SCSI 1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') << (lost_arbitration_ ? 'l' : '-'));
|
||||
LOG("[1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') << (lost_arbitration_ ? 'l' : '-'));
|
||||
return
|
||||
// Bits repeated as they were set.
|
||||
(initiator_command_ & ~0x60) |
|
||||
@@ -159,11 +168,11 @@ uint8_t NCR5380::read(int address, bool) {
|
||||
(lost_arbitration_ ? 0x20 : 0x00);
|
||||
|
||||
case 2:
|
||||
// LOG("[SCSI 2] Get mode");
|
||||
LOG("[2] Get mode");
|
||||
return mode_;
|
||||
|
||||
case 3:
|
||||
// LOG("[SCSI 3] Get target command");
|
||||
LOG("[3] Get target command");
|
||||
return target_command_;
|
||||
|
||||
case 4: {
|
||||
@@ -177,41 +186,38 @@ uint8_t NCR5380::read(int address, bool) {
|
||||
((bus_state & Line::Input) ? 0x04 : 0x00) |
|
||||
((bus_state & Line::SelectTarget) ? 0x02 : 0x00) |
|
||||
((bus_state & Line::Parity) ? 0x01 : 0x00);
|
||||
// LOG("[SCSI 4] Get current bus state: " << PADHEX(2) << int(result));
|
||||
LOG("[4] Get current bus state: " << PADHEX(2) << int(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
case 5: {
|
||||
const auto bus_state = bus_.get_state();
|
||||
const bool phase_matches =
|
||||
(target_output() & (Line::Message | Line::Control | Line::Input)) ==
|
||||
(bus_state & (Line::Message | Line::Control | Line::Input));
|
||||
|
||||
const uint8_t result =
|
||||
/* b7 = end of DMA */
|
||||
(end_of_dma_ ? 0x80 : 0x00) |
|
||||
((dma_request_ && state_ == ExecutionState::PerformingDMA) ? 0x40 : 0x00) |
|
||||
/* b5 = parity error */
|
||||
/* b4 = IRQ active */
|
||||
(phase_matches ? 0x08 : 0x00) |
|
||||
(irq_ ? 0x10 : 0x00) |
|
||||
(phase_matches() ? 0x08 : 0x00) |
|
||||
/* b2 = busy error */
|
||||
((bus_state & Line::Attention) ? 0x02 : 0x00) |
|
||||
((bus_state & Line::Acknowledge) ? 0x01 : 0x00);
|
||||
// LOG("[SCSI 5] Get bus and status: " << PADHEX(2) << int(result));
|
||||
LOG("[5] Get bus and status: " << PADHEX(2) << int(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
case 6:
|
||||
// LOG("[SCSI 6] Get input data");
|
||||
LOG("[6] Get input data");
|
||||
return 0xff;
|
||||
|
||||
case 7:
|
||||
// LOG("[SCSI 7] Reset parity/interrupt");
|
||||
LOG("[7] Reset parity/interrupt");
|
||||
irq_ = false;
|
||||
return 0xff;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
SCSI::BusState NCR5380::target_output() {
|
||||
SCSI::BusState NCR5380::target_output() const {
|
||||
SCSI::BusState output = SCSI::DefaultBusState;
|
||||
if(target_command_ & 0x08) output |= Line::Request;
|
||||
if(target_command_ & 0x04) output |= Line::Message;
|
||||
@@ -236,6 +242,17 @@ void NCR5380::update_control_output() {
|
||||
}
|
||||
|
||||
void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) {
|
||||
/*
|
||||
When connected as an Initiator with DMA Mode True,
|
||||
if the phase lines I//O, C//D, and /MSG do not match the
|
||||
phase bits in the Target Command Register, a phase mismatch
|
||||
interrupt is generated when /REQ goes active.
|
||||
*/
|
||||
if((mode_ & 0x42) == 0x02 && new_state & SCSI::Line::Request && !phase_matches()) {
|
||||
irq_ = true;
|
||||
phase_mismatch_ = true;
|
||||
}
|
||||
|
||||
switch(state_) {
|
||||
default: break;
|
||||
|
||||
@@ -296,7 +313,13 @@ void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double
|
||||
dma_request_ = false;
|
||||
break;
|
||||
case SCSI::Line::Request:
|
||||
dma_request_ = true;
|
||||
// Don't issue a new DMA request if a phase mismatch has
|
||||
// been detected and this is an intiator receiving.
|
||||
// This is a bit of reading between the lines.
|
||||
// (i.e. guesswork, partly)
|
||||
dma_request_ =
|
||||
!phase_mismatch_ ||
|
||||
(dma_operation_ != DMAOperation::InitiatorReceive);
|
||||
break;
|
||||
case SCSI::Line::Request | SCSI::Line::Acknowledge:
|
||||
dma_request_ = false;
|
||||
@@ -316,3 +339,38 @@ void NCR5380::set_execution_state(ExecutionState state) {
|
||||
state_ = state;
|
||||
if(state != ExecutionState::PerformingDMA) dma_operation_ = DMAOperation::Ready;
|
||||
}
|
||||
|
||||
size_t NCR5380::scsi_id() {
|
||||
return device_id_;
|
||||
}
|
||||
|
||||
bool NCR5380::dma_request() {
|
||||
return dma_request_;
|
||||
}
|
||||
|
||||
uint8_t NCR5380::dma_acknowledge() {
|
||||
const uint8_t bus_state = uint8_t(bus_.get_state());
|
||||
|
||||
dma_acknowledge_ = true;
|
||||
dma_request_ = false;
|
||||
update_control_output();
|
||||
bus_.set_device_output(device_id_, bus_output_);
|
||||
|
||||
return bus_state;
|
||||
}
|
||||
|
||||
void NCR5380::dma_acknowledge(uint8_t value) {
|
||||
data_bus_ = value;
|
||||
|
||||
dma_acknowledge_ = true;
|
||||
dma_request_ = false;
|
||||
update_control_output();
|
||||
bus_.set_device_output(device_id_, bus_output_);
|
||||
}
|
||||
|
||||
bool NCR5380::phase_matches() const {
|
||||
const auto bus_state = bus_.get_state();
|
||||
return
|
||||
(target_output() & (Line::Message | Line::Control | Line::Input)) ==
|
||||
(bus_state & (Line::Message | Line::Control | Line::Input));
|
||||
}
|
||||
|
@@ -14,8 +14,7 @@
|
||||
#include "../../Storage/MassStorage/SCSI/SCSI.hpp"
|
||||
|
||||
|
||||
namespace NCR {
|
||||
namespace NCR5380 {
|
||||
namespace NCR::NCR5380 {
|
||||
|
||||
/*!
|
||||
Models the NCR 5380, a SCSI interface chip.
|
||||
@@ -30,6 +29,18 @@ class NCR5380 final: public SCSI::Bus::Observer {
|
||||
/*! Reads from @c address. */
|
||||
uint8_t read(int address, bool dma_acknowledge = false);
|
||||
|
||||
/*! @returns The SCSI ID assigned to this device. */
|
||||
size_t scsi_id();
|
||||
|
||||
/*! @return @c true if DMA request is active; @c false otherwise. */
|
||||
bool dma_request();
|
||||
|
||||
/*! Signals DMA acknowledge with a simultaneous read. */
|
||||
uint8_t dma_acknowledge();
|
||||
|
||||
/*! Signals DMA acknowledge with a simultaneous write. */
|
||||
void dma_acknowledge(uint8_t);
|
||||
|
||||
private:
|
||||
SCSI::Bus &bus_;
|
||||
|
||||
@@ -46,6 +57,10 @@ class NCR5380 final: public SCSI::Bus::Observer {
|
||||
bool assert_data_bus_ = false;
|
||||
bool dma_request_ = false;
|
||||
bool dma_acknowledge_ = false;
|
||||
bool end_of_dma_ = false;
|
||||
|
||||
bool irq_ = false;
|
||||
bool phase_mismatch_ = false;
|
||||
|
||||
enum class ExecutionState {
|
||||
None,
|
||||
@@ -63,13 +78,13 @@ class NCR5380 final: public SCSI::Bus::Observer {
|
||||
|
||||
void set_execution_state(ExecutionState state);
|
||||
|
||||
SCSI::BusState target_output();
|
||||
SCSI::BusState target_output() const;
|
||||
void update_control_output();
|
||||
|
||||
void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) final;
|
||||
bool phase_matches() const;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ncr5380_hpp */
|
||||
|
@@ -15,8 +15,7 @@
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6522 {
|
||||
namespace MOS::MOS6522 {
|
||||
|
||||
enum Port {
|
||||
A = 0,
|
||||
@@ -138,7 +137,6 @@ template <class BusHandlerT> class MOS6522: public MOS6522Storage {
|
||||
void evaluate_port_b_output();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#include "Implementation/6522Implementation.hpp"
|
||||
|
@@ -12,8 +12,7 @@
|
||||
//
|
||||
// PB6 count-down mode for timer 2.
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6522 {
|
||||
namespace MOS::MOS6522 {
|
||||
|
||||
template <typename T> void MOS6522<T>::access(int address) {
|
||||
switch(address) {
|
||||
@@ -494,4 +493,3 @@ template <typename T> void MOS6522<T>::shift_out() {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -11,8 +11,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6522 {
|
||||
namespace MOS::MOS6522 {
|
||||
|
||||
class MOS6522Storage {
|
||||
protected:
|
||||
@@ -107,7 +106,6 @@ class MOS6522Storage {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _522Storage_hpp */
|
||||
|
@@ -14,8 +14,7 @@
|
||||
#include "Implementation/6526Storage.hpp"
|
||||
#include "../Serial/Line.hpp"
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6526 {
|
||||
namespace MOS::MOS6526 {
|
||||
|
||||
enum Port {
|
||||
A = 0,
|
||||
@@ -86,7 +85,6 @@ template <typename PortHandlerT, Personality personality> class MOS6526:
|
||||
bool serial_line_did_produce_bit(Serial::Line<true> *line, int bit) final;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#include "Implementation/6526Implementation.hpp"
|
||||
|
@@ -12,8 +12,7 @@
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6526 {
|
||||
namespace MOS::MOS6526 {
|
||||
|
||||
enum Interrupts: uint8_t {
|
||||
TimerA = 1 << 0,
|
||||
@@ -238,7 +237,6 @@ bool MOS6526<BusHandlerT, personality>::serial_line_did_produce_bit(Serial::Line
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _526Implementation_h */
|
||||
|
@@ -13,8 +13,7 @@
|
||||
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6526 {
|
||||
namespace MOS::MOS6526 {
|
||||
|
||||
class TODBase {
|
||||
public:
|
||||
@@ -333,7 +332,6 @@ struct MOS6526Storage {
|
||||
int pending_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _526Storage_h */
|
||||
|
@@ -15,8 +15,7 @@
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6560 {
|
||||
namespace MOS::MOS6560 {
|
||||
|
||||
// audio state
|
||||
class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
@@ -520,7 +519,6 @@ template <class BusHandler> class MOS6560 {
|
||||
OutputMode output_mode_ = OutputMode::NTSC;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _560_hpp */
|
||||
|
@@ -14,8 +14,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
namespace Motorola {
|
||||
namespace CRTC {
|
||||
namespace Motorola::CRTC {
|
||||
|
||||
struct BusState {
|
||||
bool display_enable = false;
|
||||
@@ -269,7 +268,6 @@ template <class T> class CRTC6845 {
|
||||
unsigned int character_is_visible_shifter_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CRTC6845_hpp */
|
||||
|
@@ -15,8 +15,7 @@
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
#include "../Serial/Line.hpp"
|
||||
|
||||
namespace Motorola {
|
||||
namespace ACIA {
|
||||
namespace Motorola::ACIA {
|
||||
|
||||
class ACIA: public ClockingHint::Source, private Serial::Line<false>::ReadDelegate {
|
||||
public:
|
||||
@@ -126,7 +125,6 @@ class ACIA: public ClockingHint::Source, private Serial::Line<false>::ReadDelega
|
||||
uint8_t get_status();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Motorola_ACIA_6850_hpp */
|
||||
|
@@ -9,12 +9,12 @@
|
||||
#ifndef MFP68901_hpp
|
||||
#define MFP68901_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
namespace Motorola {
|
||||
namespace MFP68901 {
|
||||
#include <cstdint>
|
||||
|
||||
namespace Motorola::MFP68901 {
|
||||
|
||||
class PortHandler {
|
||||
public:
|
||||
@@ -181,7 +181,6 @@ class MFP68901: public ClockingHint::Source {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MFP68901_hpp */
|
||||
|
@@ -11,8 +11,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Intel {
|
||||
namespace i8255 {
|
||||
namespace Intel::i8255 {
|
||||
|
||||
class PortHandler {
|
||||
public:
|
||||
@@ -88,7 +87,6 @@ template <class T> class i8255 {
|
||||
T &port_handler_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* i8255_hpp */
|
||||
|
@@ -15,8 +15,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Intel {
|
||||
namespace i8272 {
|
||||
namespace Intel::i8272 {
|
||||
|
||||
class BusHandler {
|
||||
public:
|
||||
@@ -130,7 +129,6 @@ class i8272 : public Storage::Disk::MFMController {
|
||||
bool is_sleeping_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* i8272_hpp */
|
||||
|
@@ -11,8 +11,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Zilog {
|
||||
namespace SCC {
|
||||
namespace Zilog::SCC {
|
||||
|
||||
/*!
|
||||
Models the Zilog 8530 SCC, a serial adaptor.
|
||||
@@ -110,7 +109,5 @@ class z8530 {
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* z8530_hpp */
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -12,12 +12,36 @@
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
#include "Implementation/9918Base.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace TI {
|
||||
namespace TMS {
|
||||
namespace TI::TMS {
|
||||
|
||||
enum Personality {
|
||||
TMS9918A, // includes the 9928 and 9929; set TV standard and output device as desired.
|
||||
|
||||
// Yamaha extensions.
|
||||
V9938,
|
||||
V9958,
|
||||
|
||||
// Sega extensions.
|
||||
SMSVDP,
|
||||
SMS2VDP,
|
||||
GGVDP,
|
||||
MDVDP,
|
||||
};
|
||||
|
||||
enum class TVStandard {
|
||||
/*! i.e. 50Hz output at around 312.5 lines/field */
|
||||
PAL,
|
||||
/*! i.e. 60Hz output at around 262.5 lines/field */
|
||||
NTSC
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#include "Implementation/9918Base.hpp"
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
/*!
|
||||
Provides emulation of the TMS9918a, TMS9928 and TMS9929. Likely in the future to be the
|
||||
@@ -30,13 +54,10 @@ namespace TMS {
|
||||
These chips have only one non-on-demand interaction with the outside world: an interrupt line.
|
||||
See get_time_until_interrupt and get_interrupt_line for asynchronous operation options.
|
||||
*/
|
||||
class TMS9918: public Base {
|
||||
template <Personality personality> class TMS9918: private Base<personality> {
|
||||
public:
|
||||
/*!
|
||||
Constructs an instance of the drive controller that behaves according to personality @c p.
|
||||
@param p The type of controller to emulate.
|
||||
*/
|
||||
TMS9918(Personality p);
|
||||
/*! Constructs an instance of the VDP that behaves according to the templated personality. */
|
||||
TMS9918();
|
||||
|
||||
/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */
|
||||
void set_tv_standard(TVStandard standard);
|
||||
@@ -44,18 +65,24 @@ class TMS9918: public Base {
|
||||
/*! Sets the scan target this TMS will post content to. */
|
||||
void set_scan_target(Outputs::Display::ScanTarget *);
|
||||
|
||||
/// Gets the current scan status.
|
||||
/*! Gets the current scan status. */
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
/*! Sets the type of display the CRT will request. */
|
||||
/*! Sets the type of CRT display. */
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
|
||||
/*! Gets the type of display the CRT will request. */
|
||||
/*! Gets the type of CRT display. */
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
|
||||
/*!
|
||||
Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
|
||||
that the input clock rate is 3579545 Hz, the NTSC colour clock rate.
|
||||
Runs the VDP for the number of cycles indicate; the input clock rate is implicitly assumed.
|
||||
|
||||
For everything except the Mega Drive VDP:
|
||||
* the input clock rate should be 3579545 Hz, the NTSC colour clock rate.
|
||||
|
||||
For the Mega Drive:
|
||||
* the input clock rate should be around 7.6MHz; 15/7ths of the NTSC colour
|
||||
clock rate for NTSC output and 12/7ths of the PAL colour clock rate for PAL output.
|
||||
*/
|
||||
void run_for(const HalfCycles cycles);
|
||||
|
||||
@@ -65,11 +92,11 @@ class TMS9918: public Base {
|
||||
/*! Gets a register value. */
|
||||
uint8_t read(int address);
|
||||
|
||||
/*! Gets the current scan line; provided by the Master System only. */
|
||||
uint8_t get_current_line();
|
||||
/*! Gets the current scan line; provided by the Sega VDPs only. */
|
||||
uint8_t get_current_line() const;
|
||||
|
||||
/*! Gets the current latched horizontal counter; provided by the Master System only. */
|
||||
uint8_t get_latched_horizontal_counter();
|
||||
/*! Gets the current latched horizontal counter; provided by the Sega VDPs only. */
|
||||
uint8_t get_latched_horizontal_counter() const;
|
||||
|
||||
/*! Latches the current horizontal counter. */
|
||||
void latch_horizontal_counter();
|
||||
@@ -81,7 +108,7 @@ class TMS9918: public Base {
|
||||
If get_interrupt_line is true now of if get_interrupt_line would
|
||||
never return true, returns HalfCycles::max().
|
||||
*/
|
||||
HalfCycles get_next_sequence_point();
|
||||
HalfCycles get_next_sequence_point() const;
|
||||
|
||||
/*!
|
||||
Returns the amount of time until the nominated line interrupt position is
|
||||
@@ -96,10 +123,9 @@ class TMS9918: public Base {
|
||||
/*!
|
||||
@returns @c true if the interrupt line is currently active; @c false otherwise.
|
||||
*/
|
||||
bool get_interrupt_line();
|
||||
bool get_interrupt_line() const;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* TMS9918_hpp */
|
||||
|
1372
Components/9918/Implementation/9918.cpp
Normal file
1372
Components/9918/Implementation/9918.cpp
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
115
Components/9918/Implementation/AccessEnums.hpp
Normal file
115
Components/9918/Implementation/AccessEnums.hpp
Normal file
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// AccessEnums.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 26/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef AccessEnums_hpp
|
||||
#define AccessEnums_hpp
|
||||
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
// The screen mode is a necessary predecessor to picking the line mode,
|
||||
// which is the thing latched per line.
|
||||
enum class ScreenMode {
|
||||
// Original TMS modes.
|
||||
Blank,
|
||||
Text,
|
||||
MultiColour,
|
||||
ColouredText,
|
||||
Graphics,
|
||||
|
||||
// 8-bit Sega modes.
|
||||
SMSMode4,
|
||||
|
||||
// New Yamaha V9938 modes.
|
||||
YamahaText80,
|
||||
YamahaGraphics3,
|
||||
YamahaGraphics4,
|
||||
YamahaGraphics5,
|
||||
YamahaGraphics6,
|
||||
YamahaGraphics7,
|
||||
|
||||
// Rebranded Yamaha V9938 modes.
|
||||
YamahaGraphics1 = ColouredText,
|
||||
YamahaGraphics2 = Graphics,
|
||||
};
|
||||
|
||||
constexpr int pixels_per_byte(ScreenMode mode) {
|
||||
switch(mode) {
|
||||
default:
|
||||
case ScreenMode::Blank: return 1;
|
||||
case ScreenMode::Text: return 6;
|
||||
case ScreenMode::MultiColour: return 2;
|
||||
case ScreenMode::ColouredText: return 8;
|
||||
case ScreenMode::Graphics: return 8;
|
||||
case ScreenMode::SMSMode4: return 2;
|
||||
case ScreenMode::YamahaText80: return 6;
|
||||
case ScreenMode::YamahaGraphics3: return 8;
|
||||
case ScreenMode::YamahaGraphics4: return 2;
|
||||
case ScreenMode::YamahaGraphics5: return 4;
|
||||
case ScreenMode::YamahaGraphics6: return 2;
|
||||
case ScreenMode::YamahaGraphics7: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int width(ScreenMode mode) {
|
||||
switch(mode) {
|
||||
default:
|
||||
case ScreenMode::Blank: return 0;
|
||||
case ScreenMode::Text: return 240;
|
||||
case ScreenMode::MultiColour: return 256;
|
||||
case ScreenMode::ColouredText: return 256;
|
||||
case ScreenMode::Graphics: return 256;
|
||||
case ScreenMode::SMSMode4: return 256;
|
||||
case ScreenMode::YamahaText80: return 480;
|
||||
case ScreenMode::YamahaGraphics3: return 256;
|
||||
case ScreenMode::YamahaGraphics4: return 256;
|
||||
case ScreenMode::YamahaGraphics5: return 512;
|
||||
case ScreenMode::YamahaGraphics6: return 512;
|
||||
case ScreenMode::YamahaGraphics7: return 256;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool interleaves_banks(ScreenMode mode) {
|
||||
return mode == ScreenMode::YamahaGraphics6 || mode == ScreenMode::YamahaGraphics7;
|
||||
}
|
||||
|
||||
constexpr bool is_text(ScreenMode mode) {
|
||||
return mode == ScreenMode::Text || mode == ScreenMode::YamahaText80;
|
||||
}
|
||||
|
||||
enum class FetchMode {
|
||||
Text,
|
||||
Character,
|
||||
Refresh,
|
||||
SMS,
|
||||
Yamaha,
|
||||
};
|
||||
|
||||
enum class MemoryAccess {
|
||||
Read, Write, None
|
||||
};
|
||||
|
||||
enum class VerticalState {
|
||||
/// Describes any line on which pixels do not appear and no fetching occurs, including
|
||||
/// the border, blanking and sync.
|
||||
Blank,
|
||||
/// A line on which pixels do not appear but fetching occurs.
|
||||
Prefetch,
|
||||
/// A line on which pixels appear and fetching occurs.
|
||||
Pixels,
|
||||
};
|
||||
|
||||
enum class SpriteMode {
|
||||
Mode1,
|
||||
Mode2,
|
||||
MasterSystem,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* AccessEnums_hpp */
|
187
Components/9918/Implementation/ClockConverter.hpp
Normal file
187
Components/9918/Implementation/ClockConverter.hpp
Normal file
@@ -0,0 +1,187 @@
|
||||
//
|
||||
// ClockConverter.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ClockConverter_hpp
|
||||
#define ClockConverter_hpp
|
||||
|
||||
#include "../9918.hpp"
|
||||
#include "PersonalityTraits.hpp"
|
||||
#include "LineLayout.hpp"
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
enum class Clock {
|
||||
/// Whatever rate this VDP runs at, with location 0 being "the start" of the line per internal preference.
|
||||
Internal,
|
||||
/// A 342-cycle/line clock with the same start position as ::Internal.
|
||||
TMSPixel,
|
||||
/// A 171-cycle/line clock that begins at the memory window which starts straight after ::Internal = 0.
|
||||
TMSMemoryWindow,
|
||||
/// A fixed 1368-cycle/line clock that is used to count output to the CRT.
|
||||
CRT,
|
||||
};
|
||||
|
||||
enum class Origin {
|
||||
///
|
||||
ModeLatch,
|
||||
|
||||
/// Provides the same clock rate as ::Internal but is relocated so that 0 is the start of horizontal sync — very not coincidentally,
|
||||
/// where Grauw puts 0 on his detailed TMS and Yamaha timing diagrams.
|
||||
StartOfSync,
|
||||
};
|
||||
|
||||
template <Personality personality, Clock clk> constexpr int clock_rate() {
|
||||
static_assert(
|
||||
is_classic_vdp(personality) ||
|
||||
is_yamaha_vdp(personality) ||
|
||||
(personality == Personality::MDVDP)
|
||||
);
|
||||
|
||||
switch(clk) {
|
||||
case Clock::TMSPixel: return 342;
|
||||
case Clock::TMSMemoryWindow: return 171;
|
||||
case Clock::CRT: return 1368;
|
||||
case Clock::Internal:
|
||||
if constexpr (is_classic_vdp(personality)) {
|
||||
return 342;
|
||||
} else if constexpr (is_yamaha_vdp(personality)) {
|
||||
return 1368;
|
||||
} else if constexpr (personality == Personality::MDVDP) {
|
||||
return 3420;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Scales @c length from @c clock to the internal clock rate.
|
||||
template <Personality personality, Clock clock> constexpr int to_internal(int length) {
|
||||
return length * clock_rate<personality, Clock::Internal>() / clock_rate<personality, clock>();
|
||||
}
|
||||
|
||||
/// Moves @c position that is relative to @c Origin::StartOfSync so that it is relative to @c origin ;
|
||||
/// i.e. can be thought of as "to [internal with origin as specified]".
|
||||
template <Personality personality, Origin origin> constexpr int to_internal(int position) {
|
||||
if constexpr (origin == Origin::ModeLatch) {
|
||||
return (
|
||||
position + LineLayout<personality>::CyclesPerLine - LineLayout<personality>::ModeLatchCycle
|
||||
) % LineLayout<personality>::CyclesPerLine;
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
/// Converts @c position from one that is measured at the rate implied by @c clock and relative to @c Origin::StartOfSync
|
||||
/// to one that is at the internal clock rate and relative to @c origin.
|
||||
template <Personality personality, Origin origin, Clock clock> constexpr int to_internal(int position) {
|
||||
position = to_internal<personality, clock>(position);
|
||||
return to_internal<personality, origin>(position);
|
||||
}
|
||||
|
||||
/// Scales @c length from the internal clock rate to @c clock.
|
||||
template <Personality personality, Clock clock> constexpr int from_internal(int length) {
|
||||
return length * clock_rate<personality, clock>() / clock_rate<personality, Clock::Internal>();
|
||||
}
|
||||
|
||||
/// Moves @c position that is relative to @c origin so that it is relative to @c Origin::StartOfSync ;
|
||||
/// i.e. can be thought of as "from [internal with origin as specified]".
|
||||
template <Personality personality, Origin origin> constexpr int from_internal(int length) {
|
||||
if constexpr (origin == Origin::ModeLatch) {
|
||||
return (
|
||||
length + LineLayout<personality>::ModeLatchCycle
|
||||
) % LineLayout<personality>::CyclesPerLine;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
/// Converts @c position from one that is measured at the internal clock rate and relative to @c origin
|
||||
/// to one that is at the rate implied by @c clock and relative to @c Origin::StartOfSync
|
||||
template <Personality personality, Origin origin, Clock clock> constexpr int from_internal(int position) {
|
||||
position = from_internal<personality, origin>(position);
|
||||
return from_internal<personality, clock>(position);
|
||||
}
|
||||
|
||||
/*!
|
||||
Provides a [potentially-]stateful conversion between the external and internal clocks.
|
||||
Unlike the other clock conversions, this may be non-integral, requiring that
|
||||
an error term be tracked.
|
||||
*/
|
||||
template <Personality personality> class ClockConverter {
|
||||
public:
|
||||
/*!
|
||||
Given that another @c source external **half-cycles** has occurred,
|
||||
indicates how many complete internal **cycles** have additionally elapsed
|
||||
since the last call to @c to_internal.
|
||||
|
||||
E.g. for the TMS, @c source will count 456 ticks per line, and the internal clock
|
||||
runs at 342 ticks per line, so the proper conversion is to multiply by 3/4.
|
||||
*/
|
||||
int to_internal(int source) {
|
||||
switch(personality) {
|
||||
// Default behaviour is to apply a multiplication by 3/4;
|
||||
// this is correct for the TMS and Sega VDPs other than the Mega Drive.
|
||||
default: {
|
||||
const int result = source * 3 + cycles_error_;
|
||||
cycles_error_ = result & 3;
|
||||
return result >> 2;
|
||||
}
|
||||
|
||||
// The two Yamaha chips have an internal clock that is four times
|
||||
// as fast as the TMS, therefore a stateless translation is possible.
|
||||
case Personality::V9938:
|
||||
case Personality::V9958:
|
||||
return source * 3;
|
||||
|
||||
// The Mega Drive runs at 3420 master clocks per line, which is then
|
||||
// divided by 4 or 5 depending on other state. That's 7 times the
|
||||
// rate provided to the CPU; given that the input is in half-cycles
|
||||
// the proper multiplier is therefore 3.5.
|
||||
case Personality::MDVDP: {
|
||||
const int result = source * 7 + cycles_error_;
|
||||
cycles_error_ = result & 1;
|
||||
return result >> 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Provides the number of external cycles that need to begin from now in order to
|
||||
get at least @c internal_cycles into the future.
|
||||
*/
|
||||
HalfCycles half_cycles_before_internal_cycles(int internal_cycles) const {
|
||||
// Logic here correlates with multipliers as per @c to_internal.
|
||||
switch(personality) {
|
||||
default:
|
||||
// Relative to the external clock multiplied by 3, it will definitely take this
|
||||
// many cycles to complete a further (internal_cycles - 1) after the current one.
|
||||
internal_cycles = (internal_cycles - 1) << 2;
|
||||
|
||||
// It will also be necessary to complete the current one.
|
||||
internal_cycles += 4 - cycles_error_;
|
||||
|
||||
// Round up to get the first external cycle after
|
||||
// the number of internal_cycles has elapsed.
|
||||
return HalfCycles((internal_cycles + 2) / 3);
|
||||
|
||||
case Personality::V9938:
|
||||
case Personality::V9958:
|
||||
return HalfCycles((internal_cycles + 2) / 3);
|
||||
|
||||
case Personality::MDVDP:
|
||||
internal_cycles = (internal_cycles - 1) << 1;
|
||||
internal_cycles += 2 - cycles_error_;
|
||||
return HalfCycles((internal_cycles + 6) / 7);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Holds current residue in conversion from the external to
|
||||
// internal clock.
|
||||
int cycles_error_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* ClockConverter_hpp */
|
572
Components/9918/Implementation/Draw.hpp
Normal file
572
Components/9918/Implementation/Draw.hpp
Normal file
@@ -0,0 +1,572 @@
|
||||
//
|
||||
// Draw.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Draw_hpp
|
||||
#define Draw_hpp
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
// MARK: - Sprites, as generalised.
|
||||
|
||||
template <Personality personality>
|
||||
template <SpriteMode mode, bool double_width>
|
||||
void Base<personality>::draw_sprites([[maybe_unused]] uint8_t y, int start, int end, const std::array<uint32_t, 16> &palette, int *colour_buffer) {
|
||||
if(!draw_line_buffer_->sprites) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &buffer = *draw_line_buffer_->sprites;
|
||||
if(!buffer.active_sprite_slot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int shift_advance = sprites_magnified_ ? 1 : 2;
|
||||
|
||||
// If this is the start of the line clip any part of any sprites that is off to the left.
|
||||
if(!start) {
|
||||
for(int index = 0; index < buffer.active_sprite_slot; ++index) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
if(sprite.x < 0) sprite.shift_position -= shift_advance * sprite.x;
|
||||
}
|
||||
}
|
||||
|
||||
int sprite_buffer[256];
|
||||
int sprite_collision = 0;
|
||||
memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0]));
|
||||
|
||||
if constexpr (mode == SpriteMode::MasterSystem) {
|
||||
// Draw all sprites into the sprite buffer.
|
||||
for(int index = buffer.active_sprite_slot - 1; index >= 0; --index) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
if(sprite.shift_position >= 16) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int pixel_start = std::max(start, sprite.x);
|
||||
|
||||
// TODO: it feels like the work below should be simplifiable;
|
||||
// the double shift in particular, and hopefully the variable shift.
|
||||
for(int c = pixel_start; c < end && sprite.shift_position < 16; ++c) {
|
||||
const int shift = (sprite.shift_position >> 1);
|
||||
const int sprite_colour =
|
||||
(((sprite.image[3] << shift) & 0x80) >> 4) |
|
||||
(((sprite.image[2] << shift) & 0x80) >> 5) |
|
||||
(((sprite.image[1] << shift) & 0x80) >> 6) |
|
||||
(((sprite.image[0] << shift) & 0x80) >> 7);
|
||||
|
||||
if(sprite_colour) {
|
||||
sprite_collision |= sprite_buffer[c];
|
||||
sprite_buffer[c] = sprite_colour | 0x10;
|
||||
}
|
||||
|
||||
sprite.shift_position += shift_advance;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the sprite buffer onto the colour buffer, wherever the tile map doesn't have
|
||||
// priority (or is transparent).
|
||||
for(int c = start; c < end; ++c) {
|
||||
if(
|
||||
sprite_buffer[c] &&
|
||||
(!(colour_buffer[c]&0x20) || !(colour_buffer[c]&0xf))
|
||||
) colour_buffer[c] = sprite_buffer[c];
|
||||
}
|
||||
|
||||
if(sprite_collision) {
|
||||
status_ |= StatusSpriteCollision;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if constexpr (SpriteBuffer::test_is_filling) {
|
||||
assert(!buffer.is_filling);
|
||||
}
|
||||
|
||||
constexpr uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
|
||||
constexpr int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||
const int sprite_width = sprites_16x16_ ? 16 : 8;
|
||||
const int shifter_target = sprite_width << 1;
|
||||
const int pixel_width = sprites_magnified_ ? sprite_width << 1 : sprite_width;
|
||||
int min_sprite = 0;
|
||||
|
||||
//
|
||||
// Approach taken for Mode 2 sprites:
|
||||
//
|
||||
// (1) precompute full sprite images, at up to 32 pixels wide;
|
||||
// (2) for each sprite that is marked as CC, walk backwards until the
|
||||
// first sprite that is not marked CC, ORing it into the precomputed
|
||||
// image at each step;
|
||||
// (3) subsequently, just draw each sprite image independently.
|
||||
//
|
||||
if constexpr (mode == SpriteMode::Mode2) {
|
||||
// Determine the lowest visible sprite; exit early if that leaves no sprites visible.
|
||||
for(; min_sprite < buffer.active_sprite_slot; min_sprite++) {
|
||||
auto &sprite = buffer.active_sprites[min_sprite];
|
||||
if(sprite.opaque()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(min_sprite == buffer.active_sprite_slot) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!start) {
|
||||
// Pre-rasterise the sprites one-by-one.
|
||||
if(sprites_magnified_) {
|
||||
for(int index = min_sprite; index < buffer.active_sprite_slot; index++) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
for(int c = 0; c < 32; c+= 2) {
|
||||
const int shift = (c >> 1) ^ 7;
|
||||
const int bit = 1 & (sprite.image[shift >> 3] >> (shift & 7));
|
||||
|
||||
Storage<personality>::sprite_cache_[index][c] =
|
||||
Storage<personality>::sprite_cache_[index][c + 1] =
|
||||
(sprite.image[2] & 0xf & sprite_colour_selection_masks[bit]) |
|
||||
uint8_t((bit << StatusSpriteCollisionShift) & sprite.collision_bit());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(int index = min_sprite; index < buffer.active_sprite_slot; index++) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
for(int c = 0; c < 16; c++) {
|
||||
const int shift = c ^ 7;
|
||||
const int bit = 1 & (sprite.image[shift >> 3] >> (shift & 7));
|
||||
|
||||
Storage<personality>::sprite_cache_[index][c] =
|
||||
(sprite.image[2] & 0xf & sprite_colour_selection_masks[bit]) |
|
||||
uint8_t((bit << StatusSpriteCollisionShift) & sprite.collision_bit());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go backwards compositing any sprites that are set as OR masks onto their parents.
|
||||
for(int index = buffer.active_sprite_slot - 1; index >= min_sprite + 1; --index) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
if(sprite.opaque()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sprite may affect all previous up to and cindlugin the next one that is opaque.
|
||||
for(int previous_index = index - 1; previous_index >= min_sprite; --previous_index) {
|
||||
// Determine region of overlap (if any).
|
||||
auto &previous = buffer.active_sprites[previous_index];
|
||||
const int origin = sprite.x - previous.x;
|
||||
const int x1 = std::max(0, -origin);
|
||||
const int x2 = std::min(pixel_width - origin, pixel_width);
|
||||
|
||||
// Composite sprites.
|
||||
for(int x = x1; x < x2; x++) {
|
||||
Storage<personality>::sprite_cache_[previous_index][x + origin]
|
||||
|= Storage<personality>::sprite_cache_[index][x];
|
||||
}
|
||||
|
||||
// If a previous opaque sprite has been found, stop.
|
||||
if(previous.opaque()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw.
|
||||
for(int index = buffer.active_sprite_slot - 1; index >= min_sprite; --index) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
const int x1 = std::max(0, start - sprite.x);
|
||||
const int x2 = std::min(end - sprite.x, pixel_width);
|
||||
|
||||
for(int x = x1; x < x2; x++) {
|
||||
const uint8_t colour = Storage<personality>::sprite_cache_[index][x];
|
||||
|
||||
// Plot colour, if visible.
|
||||
if(colour) {
|
||||
pixel_origin_[sprite.x + x] = palette[colour & 0xf];
|
||||
}
|
||||
|
||||
// TODO: is collision location recorded in mode 1?
|
||||
|
||||
// Check for a new collision.
|
||||
if(!(status_ & StatusSpriteCollision)) {
|
||||
sprite_collision |= sprite_buffer[sprite.x + x];
|
||||
sprite_buffer[sprite.x + x] |= colour;
|
||||
status_ |= sprite_collision & StatusSpriteCollision;
|
||||
|
||||
if(status_ & StatusSpriteCollision) {
|
||||
Storage<personality>::collision_location_[0] = uint16_t(x);
|
||||
Storage<personality>::collision_location_[1] = uint16_t(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if constexpr (mode == SpriteMode::Mode1) {
|
||||
for(int index = buffer.active_sprite_slot - 1; index >= min_sprite; --index) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
if(sprite.shift_position >= shifter_target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int pixel_start = std::max(start, sprite.x);
|
||||
for(int c = pixel_start; c < end && sprite.shift_position < shifter_target; ++c) {
|
||||
const int shift = (sprite.shift_position >> 1) ^ 7;
|
||||
int sprite_colour = (sprite.image[shift >> 3] >> (shift & 7)) & 1;
|
||||
|
||||
// A colision is detected regardless of sprite colour ...
|
||||
sprite_collision |= sprite_buffer[c] & sprite_colour;
|
||||
sprite_buffer[c] |= sprite_colour;
|
||||
|
||||
// ... but a sprite with the transparent colour won't actually be visible.
|
||||
sprite_colour &= colour_masks[sprite.image[2] & 0xf];
|
||||
|
||||
pixel_origin_[c] =
|
||||
(pixel_origin_[c] & sprite_colour_selection_masks[sprite_colour^1]) |
|
||||
(palette[sprite.image[2] & 0xf] & sprite_colour_selection_masks[sprite_colour]);
|
||||
|
||||
sprite.shift_position += shift_advance;
|
||||
}
|
||||
}
|
||||
|
||||
status_ |= sprite_collision << StatusSpriteCollisionShift;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Mode 2 logic, as I currently understand it, as a note for my future self:
|
||||
//
|
||||
// If a sprite is marked as 'CC' then it doesn't collide, but its colour value is
|
||||
// ORd with those of all lower-numbered sprites down to the next one that is visible on
|
||||
// that line and not marked CC.
|
||||
//
|
||||
// If no previous sprite meets that criteria, no pixels are displayed. But if one does
|
||||
// then pixels are displayed even where they don't overlap with the earlier sprites.
|
||||
//
|
||||
// ... so in terms of my loop above, I guess I need temporary storage to accumulate
|
||||
// an OR mask up until I hit a non-CC sprite, at which point I composite everything out?
|
||||
// I'm not immediately sure whether I can appropriately reuse sprite_buffer, but possibly?
|
||||
|
||||
// MARK: - TMS9918
|
||||
|
||||
template <Personality personality>
|
||||
template <SpriteMode sprite_mode>
|
||||
void Base<personality>::draw_tms_character(int start, int end) {
|
||||
auto &line_buffer = *draw_line_buffer_;
|
||||
|
||||
// Paint the background tiles.
|
||||
const int pixels_left = end - start;
|
||||
if(this->screen_mode_ == ScreenMode::MultiColour) {
|
||||
for(int c = start; c < end; ++c) {
|
||||
pixel_target_[c] = palette()[
|
||||
(line_buffer.tiles.patterns[c >> 3][0] >> (((c & 4)^4))) & 15
|
||||
];
|
||||
}
|
||||
} else {
|
||||
const int shift = start & 7;
|
||||
int byte_column = start >> 3;
|
||||
|
||||
int length = std::min(pixels_left, 8 - shift);
|
||||
|
||||
int pattern = Numeric::bit_reverse(line_buffer.tiles.patterns[byte_column][0]) >> shift;
|
||||
uint8_t colour = line_buffer.tiles.patterns[byte_column][1];
|
||||
uint32_t colours[2] = {
|
||||
palette()[(colour & 15) ? (colour & 15) : background_colour_],
|
||||
palette()[(colour >> 4) ? (colour >> 4) : background_colour_]
|
||||
};
|
||||
|
||||
int background_pixels_left = pixels_left;
|
||||
while(true) {
|
||||
background_pixels_left -= length;
|
||||
for(int c = 0; c < length; ++c) {
|
||||
pixel_target_[c] = colours[pattern&0x01];
|
||||
pattern >>= 1;
|
||||
}
|
||||
pixel_target_ += length;
|
||||
|
||||
if(!background_pixels_left) break;
|
||||
length = std::min(8, background_pixels_left);
|
||||
byte_column++;
|
||||
|
||||
pattern = Numeric::bit_reverse(line_buffer.tiles.patterns[byte_column][0]);
|
||||
colour = line_buffer.tiles.patterns[byte_column][1];
|
||||
colours[0] = palette()[(colour & 15) ? (colour & 15) : background_colour_];
|
||||
colours[1] = palette()[(colour >> 4) ? (colour >> 4) : background_colour_];
|
||||
}
|
||||
}
|
||||
|
||||
draw_sprites<sprite_mode, false>(0, start, end, palette()); // TODO: propagate a real 'y' into here.
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
template <bool apply_blink>
|
||||
void Base<personality>::draw_tms_text(int start, int end) {
|
||||
auto &line_buffer = *draw_line_buffer_;
|
||||
uint32_t colours[2][2] = {
|
||||
{palette()[background_colour_], palette()[text_colour_]},
|
||||
{0, 0}
|
||||
};
|
||||
if constexpr (apply_blink) {
|
||||
colours[1][0] = palette()[Storage<personality>::blink_background_colour_];
|
||||
colours[1][1] = palette()[Storage<personality>::blink_text_colour_];
|
||||
}
|
||||
|
||||
const int shift = start % 6;
|
||||
int byte_column = start / 6;
|
||||
int pattern = Numeric::bit_reverse(line_buffer.characters.shapes[byte_column]) >> shift;
|
||||
int pixels_left = end - start;
|
||||
int length = std::min(pixels_left, 6 - shift);
|
||||
int flag = 0;
|
||||
if constexpr (apply_blink) {
|
||||
flag = (line_buffer.characters.flags[byte_column >> 3] >> ((byte_column & 7) ^ 7)) & Storage<personality>::in_blink_;
|
||||
}
|
||||
while(true) {
|
||||
pixels_left -= length;
|
||||
for(int c = 0; c < length; ++c) {
|
||||
pixel_target_[c] = colours[flag][(pattern&0x01)];
|
||||
pattern >>= 1;
|
||||
}
|
||||
pixel_target_ += length;
|
||||
|
||||
if(!pixels_left) break;
|
||||
length = std::min(6, pixels_left);
|
||||
byte_column++;
|
||||
pattern = Numeric::bit_reverse(line_buffer.characters.shapes[byte_column]);
|
||||
if constexpr (apply_blink) {
|
||||
flag = (line_buffer.characters.flags[byte_column >> 3] >> ((byte_column & 7) ^ 7)) & Storage<personality>::in_blink_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Master System
|
||||
|
||||
template <Personality personality>
|
||||
void Base<personality>::draw_sms([[maybe_unused]] int start, [[maybe_unused]] int end, [[maybe_unused]] uint32_t cram_dot) {
|
||||
if constexpr (is_sega_vdp(personality)) {
|
||||
int colour_buffer[256];
|
||||
auto &line_buffer = *draw_line_buffer_;
|
||||
|
||||
/*
|
||||
Add extra border for any pixels that fall before the fine scroll.
|
||||
*/
|
||||
int tile_start = start, tile_end = end;
|
||||
int tile_offset = start;
|
||||
if(output_pointer_.row >= 16 || !Storage<personality>::horizontal_scroll_lock_) {
|
||||
for(int c = start; c < (line_buffer.latched_horizontal_scroll & 7); ++c) {
|
||||
colour_buffer[c] = 16 + background_colour_;
|
||||
++tile_offset;
|
||||
}
|
||||
|
||||
// Remove the border area from that to which tiles will be drawn.
|
||||
tile_start = std::max(start - (line_buffer.latched_horizontal_scroll & 7), 0);
|
||||
tile_end = std::max(end - (line_buffer.latched_horizontal_scroll & 7), 0);
|
||||
}
|
||||
|
||||
|
||||
uint32_t pattern;
|
||||
uint8_t *const pattern_index = reinterpret_cast<uint8_t *>(&pattern);
|
||||
|
||||
/*
|
||||
Add background tiles; these will fill the colour_buffer with values in which
|
||||
the low five bits are a palette index, and bit six is set if this tile has
|
||||
priority over sprites.
|
||||
*/
|
||||
if(tile_start < end) {
|
||||
const int shift = tile_start & 7;
|
||||
int byte_column = tile_start >> 3;
|
||||
int pixels_left = tile_end - tile_start;
|
||||
int length = std::min(pixels_left, 8 - shift);
|
||||
|
||||
pattern = *reinterpret_cast<const uint32_t *>(line_buffer.tiles.patterns[byte_column]);
|
||||
if(line_buffer.tiles.flags[byte_column]&2)
|
||||
pattern >>= shift;
|
||||
else
|
||||
pattern <<= shift;
|
||||
|
||||
while(true) {
|
||||
const int palette_offset = (line_buffer.tiles.flags[byte_column]&0x18) << 1;
|
||||
if(line_buffer.tiles.flags[byte_column]&2) {
|
||||
for(int c = 0; c < length; ++c) {
|
||||
colour_buffer[tile_offset] =
|
||||
((pattern_index[3] & 0x01) << 3) |
|
||||
((pattern_index[2] & 0x01) << 2) |
|
||||
((pattern_index[1] & 0x01) << 1) |
|
||||
((pattern_index[0] & 0x01) << 0) |
|
||||
palette_offset;
|
||||
++tile_offset;
|
||||
pattern >>= 1;
|
||||
}
|
||||
} else {
|
||||
for(int c = 0; c < length; ++c) {
|
||||
colour_buffer[tile_offset] =
|
||||
((pattern_index[3] & 0x80) >> 4) |
|
||||
((pattern_index[2] & 0x80) >> 5) |
|
||||
((pattern_index[1] & 0x80) >> 6) |
|
||||
((pattern_index[0] & 0x80) >> 7) |
|
||||
palette_offset;
|
||||
++tile_offset;
|
||||
pattern <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pixels_left -= length;
|
||||
if(!pixels_left) break;
|
||||
|
||||
length = std::min(8, pixels_left);
|
||||
byte_column++;
|
||||
pattern = *reinterpret_cast<const uint32_t *>(line_buffer.tiles.patterns[byte_column]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Apply sprites (if any).
|
||||
*/
|
||||
draw_sprites<SpriteMode::MasterSystem, false>(0, start, end, palette(), colour_buffer); // TODO provide good y, as per elsewhere.
|
||||
|
||||
// Map from the 32-colour buffer to real output pixels, applying the specific CRAM dot if any.
|
||||
pixel_target_[start] = Storage<personality>::colour_ram_[colour_buffer[start] & 0x1f] | cram_dot;
|
||||
for(int c = start+1; c < end; ++c) {
|
||||
pixel_target_[c] = Storage<personality>::colour_ram_[colour_buffer[c] & 0x1f];
|
||||
}
|
||||
|
||||
// If the VDP is set to hide the left column and this is the final call that'll come
|
||||
// this line, hide it.
|
||||
if(end == 256) {
|
||||
if(Storage<personality>::hide_left_column_) {
|
||||
pixel_origin_[0] = pixel_origin_[1] = pixel_origin_[2] = pixel_origin_[3] =
|
||||
pixel_origin_[4] = pixel_origin_[5] = pixel_origin_[6] = pixel_origin_[7] =
|
||||
Storage<personality>::colour_ram_[16 + background_colour_];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Yamaha
|
||||
|
||||
template <Personality personality>
|
||||
template <ScreenMode mode>
|
||||
void Base<personality>::draw_yamaha(uint8_t y, int start, int end) {
|
||||
[[maybe_unused]] const auto active_palette = palette();
|
||||
const int sprite_start = start >> 2;
|
||||
const int sprite_end = end >> 2;
|
||||
auto &line_buffer = *draw_line_buffer_;
|
||||
|
||||
// Observation justifying Duff's device below: it's acceptable to paint too many pixels — to paint
|
||||
// beyond `end` — provided that the overpainting is within normal bitmap bounds, because any
|
||||
// mispainted pixels will be replaced before becoming visible to the user.
|
||||
|
||||
if constexpr (mode == ScreenMode::YamahaGraphics4 || mode == ScreenMode::YamahaGraphics6) {
|
||||
start >>= (mode == ScreenMode::YamahaGraphics4) ? 2 : 1;
|
||||
end >>= (mode == ScreenMode::YamahaGraphics4) ? 2 : 1;
|
||||
|
||||
int column = start & ~1;
|
||||
const int offset = start & 1;
|
||||
start >>= 1;
|
||||
end = (end + 1) >> 1;
|
||||
|
||||
switch(offset) {
|
||||
case 0:
|
||||
do {
|
||||
pixel_target_[column+0] = active_palette[line_buffer.bitmap[start] >> 4]; [[fallthrough]];
|
||||
case 1: pixel_target_[column+1] = active_palette[line_buffer.bitmap[start] & 0xf];
|
||||
++start;
|
||||
column += 2;
|
||||
} while(start < end);
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (mode == ScreenMode::YamahaGraphics5) {
|
||||
start >>= 1;
|
||||
end >>= 1;
|
||||
|
||||
int column = start & ~3;
|
||||
const int offset = start & 3;
|
||||
start >>= 2;
|
||||
end = (end + 3) >> 2;
|
||||
|
||||
switch(offset) {
|
||||
case 0:
|
||||
do {
|
||||
pixel_target_[column+0] = active_palette[line_buffer.bitmap[start] >> 6]; [[fallthrough]];
|
||||
case 1: pixel_target_[column+1] = active_palette[(line_buffer.bitmap[start] >> 4) & 3]; [[fallthrough]];
|
||||
case 2: pixel_target_[column+2] = active_palette[(line_buffer.bitmap[start] >> 2) & 3]; [[fallthrough]];
|
||||
case 3: pixel_target_[column+3] = active_palette[line_buffer.bitmap[start] & 3];
|
||||
++start;
|
||||
column += 4;
|
||||
} while(start < end);
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (mode == ScreenMode::YamahaGraphics7) {
|
||||
start >>= 2;
|
||||
end >>= 2;
|
||||
|
||||
while(start < end) {
|
||||
pixel_target_[start] =
|
||||
palette_pack(
|
||||
uint8_t((line_buffer.bitmap[start] & 0x1c) + ((line_buffer.bitmap[start] & 0x1c) << 3) + ((line_buffer.bitmap[start] & 0x1c) >> 3)),
|
||||
uint8_t((line_buffer.bitmap[start] & 0xe0) + ((line_buffer.bitmap[start] & 0xe0) >> 3) + ((line_buffer.bitmap[start] & 0xe0) >> 6)),
|
||||
uint8_t((line_buffer.bitmap[start] & 0x03) + ((line_buffer.bitmap[start] & 0x03) << 2) + ((line_buffer.bitmap[start] & 0x03) << 4) + ((line_buffer.bitmap[start] & 0x03) << 6))
|
||||
);
|
||||
++start;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::array<uint32_t, 16> graphics7_sprite_palette = {
|
||||
palette_pack(0b00000000, 0b00000000, 0b00000000), palette_pack(0b00000000, 0b00000000, 0b01001001),
|
||||
palette_pack(0b00000000, 0b01101101, 0b00000000), palette_pack(0b00000000, 0b01101101, 0b01001001),
|
||||
palette_pack(0b01101101, 0b00000000, 0b00000000), palette_pack(0b01101101, 0b00000000, 0b01001001),
|
||||
palette_pack(0b01101101, 0b01101101, 0b00000000), palette_pack(0b01101101, 0b01101101, 0b01001001),
|
||||
|
||||
palette_pack(0b10010010, 0b11111111, 0b01001001), palette_pack(0b00000000, 0b00000000, 0b11111111),
|
||||
palette_pack(0b00000000, 0b11111111, 0b00000000), palette_pack(0b00000000, 0b11111111, 0b11111111),
|
||||
palette_pack(0b11111111, 0b00000000, 0b00000000), palette_pack(0b11111111, 0b00000000, 0b11111111),
|
||||
palette_pack(0b11111111, 0b11111111, 0b00000000), palette_pack(0b11111111, 0b11111111, 0b11111111),
|
||||
};
|
||||
|
||||
// Possibly TODO: is the data-sheet trying to allege some sort of colour mixing for sprites in Mode 6?
|
||||
draw_sprites<
|
||||
SpriteMode::Mode2,
|
||||
mode == ScreenMode::YamahaGraphics5 || mode == ScreenMode::YamahaGraphics6
|
||||
>(y, sprite_start, sprite_end, mode == ScreenMode::YamahaGraphics7 ? graphics7_sprite_palette : palette());
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
void Base<personality>::draw_yamaha(uint8_t y, int start, int end) {
|
||||
if constexpr (is_yamaha_vdp(personality)) {
|
||||
switch(draw_line_buffer_->screen_mode) {
|
||||
// Modes that are the same (or close enough) to those on the TMS.
|
||||
case ScreenMode::Text: draw_tms_text<false>(start >> 2, end >> 2); break;
|
||||
case ScreenMode::YamahaText80: draw_tms_text<true>(start >> 1, end >> 1); break;
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::ColouredText:
|
||||
case ScreenMode::Graphics: draw_tms_character(start >> 2, end >> 2); break;
|
||||
|
||||
case ScreenMode::YamahaGraphics3:
|
||||
draw_tms_character<SpriteMode::Mode2>(start >> 2, end >> 2);
|
||||
break;
|
||||
|
||||
#define Dispatch(x) case ScreenMode::x: draw_yamaha<ScreenMode::x>(y, start, end); break;
|
||||
Dispatch(YamahaGraphics4);
|
||||
Dispatch(YamahaGraphics5);
|
||||
Dispatch(YamahaGraphics6);
|
||||
Dispatch(YamahaGraphics7);
|
||||
#undef Dispatch
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mega Drive
|
||||
|
||||
// TODO.
|
||||
|
||||
}
|
||||
|
||||
#endif /* Draw_hpp */
|
796
Components/9918/Implementation/Fetch.hpp
Normal file
796
Components/9918/Implementation/Fetch.hpp
Normal file
@@ -0,0 +1,796 @@
|
||||
//
|
||||
// Fetch.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Fetch_hpp
|
||||
#define Fetch_hpp
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
/*
|
||||
Fetching routines follow below; they obey the following rules:
|
||||
|
||||
1) input is a start position and an end position; they should perform the proper
|
||||
operations for the period: start <= time < end.
|
||||
2) times are measured relative to the an appropriate clock — they directly
|
||||
count access windows on the TMS and Master System, and cycles on a Yamaha.
|
||||
3) within each sequencer, cycle are numbered as per Grauw's timing diagrams. The difference
|
||||
between those and internal timing, if there is one, is handled by the dispatcher.
|
||||
4) all of these functions are templated with a `use_end` parameter. That will be true if
|
||||
end is < [cycles per line], false otherwise. So functions can use it to eliminate
|
||||
should-exit-now checks (which is likely to be the more usual path of execution).
|
||||
|
||||
Provided for the benefit of the methods below:
|
||||
|
||||
* the function external_slot(), which will perform any pending VRAM read/write.
|
||||
|
||||
All functions should just spool data to intermediary storage. Fetching and drawing are decoupled.
|
||||
*/
|
||||
|
||||
// MARK: - Address mask helpers.
|
||||
|
||||
/// @returns An instance of @c AddressT with all top bits set down to and including
|
||||
/// bit @c end and all others clear.
|
||||
///
|
||||
/// So e.g. if @c AddressT is @c uint16_t and this VDP has a 15-bit address space then
|
||||
/// @c top_bits<10> will be the address with bits 15 to 10 (inclusive) set and the rest clear.
|
||||
template <typename AddressT, int end> constexpr AddressT top_bits() {
|
||||
return AddressT(~0) - AddressT((1 << end) - 1);
|
||||
}
|
||||
|
||||
/// Modifies and returns @c source so that all bits above position @c n are set; the others are unmodified.
|
||||
template <int n, typename AddressT> constexpr AddressT bits(AddressT source = 0) {
|
||||
return AddressT(source | top_bits<AddressT, n>());
|
||||
}
|
||||
|
||||
// MARK: - 171-window Dispatcher.
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end, typename SequencerT> void Base<personality>::dispatch(SequencerT &fetcher, int start, int end) {
|
||||
#define index(n) \
|
||||
if(use_end && end == n) return; \
|
||||
[[fallthrough]]; \
|
||||
case n: fetcher.template fetch<from_internal<personality, Origin::StartOfSync>(n)>();
|
||||
|
||||
switch(start) {
|
||||
default: assert(false);
|
||||
index(0); index(1); index(2); index(3); index(4); index(5); index(6); index(7); index(8); index(9);
|
||||
index(10); index(11); index(12); index(13); index(14); index(15); index(16); index(17); index(18); index(19);
|
||||
index(20); index(21); index(22); index(23); index(24); index(25); index(26); index(27); index(28); index(29);
|
||||
index(30); index(31); index(32); index(33); index(34); index(35); index(36); index(37); index(38); index(39);
|
||||
index(40); index(41); index(42); index(43); index(44); index(45); index(46); index(47); index(48); index(49);
|
||||
index(50); index(51); index(52); index(53); index(54); index(55); index(56); index(57); index(58); index(59);
|
||||
index(60); index(61); index(62); index(63); index(64); index(65); index(66); index(67); index(68); index(69);
|
||||
index(70); index(71); index(72); index(73); index(74); index(75); index(76); index(77); index(78); index(79);
|
||||
index(80); index(81); index(82); index(83); index(84); index(85); index(86); index(87); index(88); index(89);
|
||||
index(90); index(91); index(92); index(93); index(94); index(95); index(96); index(97); index(98); index(99);
|
||||
index(100); index(101); index(102); index(103); index(104); index(105); index(106); index(107); index(108); index(109);
|
||||
index(110); index(111); index(112); index(113); index(114); index(115); index(116); index(117); index(118); index(119);
|
||||
index(120); index(121); index(122); index(123); index(124); index(125); index(126); index(127); index(128); index(129);
|
||||
index(130); index(131); index(132); index(133); index(134); index(135); index(136); index(137); index(138); index(139);
|
||||
index(140); index(141); index(142); index(143); index(144); index(145); index(146); index(147); index(148); index(149);
|
||||
index(150); index(151); index(152); index(153); index(154); index(155); index(156); index(157); index(158); index(159);
|
||||
index(160); index(161); index(162); index(163); index(164); index(165); index(166); index(167); index(168); index(169);
|
||||
index(170);
|
||||
}
|
||||
|
||||
#undef index
|
||||
}
|
||||
|
||||
// MARK: - Fetchers.
|
||||
|
||||
template <Personality personality>
|
||||
struct TextFetcher {
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
|
||||
TextFetcher(Base<personality> *base, uint8_t y) :
|
||||
base(base),
|
||||
row_base(base->pattern_name_address_ & bits<10>(AddressT((y >> 3) * 40))),
|
||||
row_offset(base->pattern_generator_table_address_ & bits<11>(AddressT(y & 7))) {}
|
||||
|
||||
void fetch_name(AddressT column, int slot = 0) {
|
||||
base->name_[slot] = base->ram_[row_base + column];
|
||||
}
|
||||
|
||||
void fetch_pattern(AddressT column, int slot = 0) {
|
||||
base->fetch_line_buffer_->characters.shapes[column] = base->ram_[row_offset + size_t(base->name_[slot] << 3)];
|
||||
}
|
||||
|
||||
Base<personality> *const base;
|
||||
const AddressT row_base;
|
||||
const AddressT row_offset;
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
struct CharacterFetcher {
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
|
||||
CharacterFetcher(Base<personality> *base, uint8_t y) :
|
||||
base(base),
|
||||
y(y),
|
||||
row_base(base->pattern_name_address_ & bits<10>(AddressT((y << 2)&~31)))
|
||||
{
|
||||
pattern_base = base->pattern_generator_table_address_;
|
||||
colour_base = base->colour_table_address_;
|
||||
colour_name_shift = 6;
|
||||
|
||||
const ScreenMode mode = base->fetch_line_buffer_->screen_mode;
|
||||
if(mode == ScreenMode::Graphics || mode == ScreenMode::YamahaGraphics3) {
|
||||
// If this is high resolution mode, allow the row number to affect the pattern and colour addresses.
|
||||
pattern_base &= bits<13>(AddressT(((y & 0xc0) << 5)));
|
||||
colour_base &= bits<13>(AddressT(((y & 0xc0) << 5)));
|
||||
|
||||
colour_base += AddressT(y & 7);
|
||||
colour_name_shift = 0;
|
||||
} else {
|
||||
colour_base &= bits<6, AddressT>();
|
||||
pattern_base &= bits<11, AddressT>();
|
||||
}
|
||||
|
||||
if(mode == ScreenMode::MultiColour) {
|
||||
pattern_base += AddressT((y >> 2) & 7);
|
||||
} else {
|
||||
pattern_base += AddressT(y & 7);
|
||||
}
|
||||
}
|
||||
|
||||
void fetch_name(int column) {
|
||||
base->tile_offset_ = base->ram_[row_base + AddressT(column)];
|
||||
}
|
||||
|
||||
void fetch_pattern(int column) {
|
||||
base->fetch_line_buffer_->tiles.patterns[column][0] = base->ram_[pattern_base + AddressT(base->tile_offset_ << 3)];
|
||||
}
|
||||
|
||||
void fetch_colour(int column) {
|
||||
base->fetch_line_buffer_->tiles.patterns[column][1] = base->ram_[colour_base + AddressT((base->tile_offset_ << 3) >> colour_name_shift)];
|
||||
}
|
||||
|
||||
Base<personality> *const base;
|
||||
const uint8_t y;
|
||||
const AddressT row_base;
|
||||
AddressT pattern_base;
|
||||
AddressT colour_base;
|
||||
int colour_name_shift;
|
||||
};
|
||||
|
||||
constexpr SpriteMode sprite_mode(ScreenMode screen_mode) {
|
||||
switch(screen_mode) {
|
||||
default:
|
||||
return SpriteMode::Mode2;
|
||||
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::ColouredText:
|
||||
case ScreenMode::Graphics:
|
||||
return SpriteMode::Mode1;
|
||||
|
||||
case ScreenMode::SMSMode4:
|
||||
return SpriteMode::MasterSystem;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: should this be extended to include Master System sprites?
|
||||
template <Personality personality, SpriteMode mode>
|
||||
class SpriteFetcher {
|
||||
public:
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
|
||||
// The Yamaha VDP adds an additional table when in Sprite Mode 2, the sprite colour
|
||||
// table, which is intended to fill the 512 bytes before the programmer-located sprite
|
||||
// attribute table.
|
||||
//
|
||||
// It partially enforces this proximity by forcing bits 7 and 8 to 0 in the address of
|
||||
// the attribute table, and forcing them to 1 but masking out bit 9 for the colour table.
|
||||
//
|
||||
// AttributeAddressMask is used to enable or disable that behaviour.
|
||||
static constexpr AddressT AttributeAddressMask = (mode == SpriteMode::Mode2) ? AddressT(~0x180) : AddressT(~0x000);
|
||||
|
||||
SpriteFetcher(Base<personality> *base, uint8_t y) :
|
||||
base(base),
|
||||
y(y) {}
|
||||
|
||||
void fetch_location(int slot) {
|
||||
fetch_xy(slot);
|
||||
|
||||
if constexpr (mode == SpriteMode::Mode2) {
|
||||
fetch_xy(slot + 1);
|
||||
|
||||
base->name_[0] = name(slot);
|
||||
base->name_[1] = name(slot + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void fetch_pattern(int slot) {
|
||||
switch(mode) {
|
||||
case SpriteMode::Mode1:
|
||||
fetch_image(slot, name(slot));
|
||||
break;
|
||||
|
||||
case SpriteMode::Mode2:
|
||||
fetch_image(slot, base->name_[0]);
|
||||
fetch_image(slot + 1, base->name_[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void fetch_y(int sprite) {
|
||||
const AddressT address = base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT(sprite << 2));
|
||||
const uint8_t sprite_y = base->ram_[address];
|
||||
base->posit_sprite(sprite, sprite_y, y);
|
||||
}
|
||||
|
||||
private:
|
||||
void fetch_xy(int slot) {
|
||||
auto &buffer = *base->fetch_sprite_buffer_;
|
||||
buffer.active_sprites[slot].x =
|
||||
base->ram_[
|
||||
base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 1))
|
||||
];
|
||||
}
|
||||
|
||||
uint8_t name(int slot) {
|
||||
auto &buffer = *base->fetch_sprite_buffer_;
|
||||
const AddressT address =
|
||||
base->sprite_attribute_table_address_ &
|
||||
AttributeAddressMask &
|
||||
bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 2));
|
||||
const uint8_t name = base->ram_[address] & (base->sprites_16x16_ ? ~3 : ~0);
|
||||
return name;
|
||||
}
|
||||
|
||||
void fetch_image(int slot, uint8_t name) {
|
||||
uint8_t colour = 0;
|
||||
auto &sprite = base->fetch_sprite_buffer_->active_sprites[slot];
|
||||
switch(mode) {
|
||||
case SpriteMode::Mode1:
|
||||
// Fetch colour from the attribute table, per this sprite's slot.
|
||||
colour = base->ram_[
|
||||
base->sprite_attribute_table_address_ & bits<7>(AddressT((sprite.index << 2) | 3))
|
||||
];
|
||||
break;
|
||||
|
||||
case SpriteMode::Mode2: {
|
||||
// Fetch colour from the colour table, per this sprite's slot and row.
|
||||
const AddressT colour_table_address = (base->sprite_attribute_table_address_ | ~AttributeAddressMask) & AddressT(~0x200);
|
||||
colour = base->ram_[
|
||||
colour_table_address &
|
||||
bits<9>(
|
||||
AddressT(sprite.index << 4) |
|
||||
AddressT(sprite.row)
|
||||
)
|
||||
];
|
||||
} break;
|
||||
}
|
||||
sprite.image[2] = colour;
|
||||
sprite.x -= sprite.early_clock();
|
||||
|
||||
const AddressT graphic_location = base->sprite_generator_table_address_ & bits<11>(AddressT((name << 3) | sprite.row));
|
||||
sprite.image[0] = base->ram_[graphic_location];
|
||||
sprite.image[1] = base->ram_[graphic_location+16];
|
||||
|
||||
if constexpr (SpriteBuffer::test_is_filling) {
|
||||
if(slot == ((mode == SpriteMode::Mode2) ? 7 : 3)) {
|
||||
base->fetch_sprite_buffer_->is_filling = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Base<personality> *const base;
|
||||
const uint8_t y;
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
struct SMSFetcher {
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
struct RowInfo {
|
||||
AddressT pattern_address_base;
|
||||
AddressT sub_row[2];
|
||||
};
|
||||
|
||||
SMSFetcher(Base<personality> *base, uint8_t y) :
|
||||
base(base),
|
||||
storage(static_cast<Storage<personality> *>(base)),
|
||||
y(y),
|
||||
horizontal_offset((y >= 16 || !storage->horizontal_scroll_lock_) ? (base->fetch_line_buffer_->latched_horizontal_scroll >> 3) : 0)
|
||||
{
|
||||
// Limit address bits in use if this is a SMS2 mode.
|
||||
const bool is_tall_mode = base->mode_timing_.pixel_lines != 192;
|
||||
const AddressT pattern_name_address = storage->pattern_name_address_ | (is_tall_mode ? 0x800 : 0);
|
||||
const AddressT pattern_name_offset = is_tall_mode ? 0x100 : 0;
|
||||
|
||||
// Determine row info for the screen both (i) if vertical scrolling is applied; and (ii) if it isn't.
|
||||
// The programmer can opt out of applying vertical scrolling to the right-hand portion of the display.
|
||||
const int scrolled_row = (y + storage->latched_vertical_scroll_) % (is_tall_mode ? 256 : 224);
|
||||
scrolled_row_info.pattern_address_base = (pattern_name_address & bits<11>(AddressT((scrolled_row & ~7) << 3))) - pattern_name_offset;
|
||||
scrolled_row_info.sub_row[0] = AddressT((scrolled_row & 7) << 2);
|
||||
scrolled_row_info.sub_row[1] = AddressT(28 ^ ((scrolled_row & 7) << 2));
|
||||
if(storage->vertical_scroll_lock_) {
|
||||
static_row_info.pattern_address_base = bits<11>(AddressT(pattern_name_address & ((y & ~7) << 3))) - pattern_name_offset;
|
||||
static_row_info.sub_row[0] = AddressT((y & 7) << 2);
|
||||
static_row_info.sub_row[1] = 28 ^ AddressT((y & 7) << 2);
|
||||
} else static_row_info = scrolled_row_info;
|
||||
}
|
||||
|
||||
void fetch_sprite(int sprite) {
|
||||
auto &sprite_buffer = *base->fetch_sprite_buffer_;
|
||||
sprite_buffer.active_sprites[sprite].x =
|
||||
base->ram_[
|
||||
storage->sprite_attribute_table_address_ & bits<7>((sprite_buffer.active_sprites[sprite].index << 1) | 0)
|
||||
] - (storage->shift_sprites_8px_left_ ? 8 : 0);
|
||||
const uint8_t name = base->ram_[
|
||||
storage->sprite_attribute_table_address_ & bits<7>((sprite_buffer.active_sprites[sprite].index << 1) | 1)
|
||||
] & (base->sprites_16x16_ ? ~1 : ~0);
|
||||
|
||||
const AddressT graphic_location =
|
||||
storage->sprite_generator_table_address_ &
|
||||
bits<13>(AddressT((name << 5) | (sprite_buffer.active_sprites[sprite].row << 2)));
|
||||
sprite_buffer.active_sprites[sprite].image[0] = base->ram_[graphic_location];
|
||||
sprite_buffer.active_sprites[sprite].image[1] = base->ram_[graphic_location+1];
|
||||
sprite_buffer.active_sprites[sprite].image[2] = base->ram_[graphic_location+2];
|
||||
sprite_buffer.active_sprites[sprite].image[3] = base->ram_[graphic_location+3];
|
||||
}
|
||||
|
||||
void fetch_tile_name(int column) {
|
||||
const RowInfo &row_info = column < 24 ? scrolled_row_info : static_row_info;
|
||||
const size_t scrolled_column = (column - horizontal_offset) & 0x1f;
|
||||
const size_t address = row_info.pattern_address_base + (scrolled_column << 1);
|
||||
auto &line_buffer = *base->fetch_line_buffer_;
|
||||
|
||||
line_buffer.tiles.flags[column] = base->ram_[address+1];
|
||||
base->tile_offset_ = AddressT(
|
||||
(((line_buffer.tiles.flags[column]&1) << 8) | base->ram_[address]) << 5
|
||||
) + row_info.sub_row[(line_buffer.tiles.flags[column]&4) >> 2];
|
||||
}
|
||||
|
||||
void fetch_tile_pattern(int column) {
|
||||
auto &line_buffer = *base->fetch_line_buffer_;
|
||||
line_buffer.tiles.patterns[column][0] = base->ram_[base->tile_offset_];
|
||||
line_buffer.tiles.patterns[column][1] = base->ram_[base->tile_offset_+1];
|
||||
line_buffer.tiles.patterns[column][2] = base->ram_[base->tile_offset_+2];
|
||||
line_buffer.tiles.patterns[column][3] = base->ram_[base->tile_offset_+3];
|
||||
}
|
||||
|
||||
void posit_sprite(int sprite) {
|
||||
base->posit_sprite(sprite, base->ram_[storage->sprite_attribute_table_address_ & bits<8>(AddressT(sprite))], y);
|
||||
}
|
||||
|
||||
Base<personality> *const base;
|
||||
const Storage<personality> *const storage;
|
||||
const uint8_t y;
|
||||
const int horizontal_offset;
|
||||
RowInfo scrolled_row_info, static_row_info;
|
||||
};
|
||||
|
||||
// MARK: - TMS Sequencers.
|
||||
|
||||
template <Personality personality>
|
||||
struct RefreshSequencer {
|
||||
RefreshSequencer(Base<personality> *base) : base(base) {}
|
||||
|
||||
template <int cycle> void fetch() {
|
||||
if(cycle < 26 || (cycle & 1) || cycle >= 154) {
|
||||
base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||
}
|
||||
}
|
||||
|
||||
Base<personality> *const base;
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
struct TextSequencer {
|
||||
template <typename... Args> TextSequencer(Args&&... args) : fetcher(std::forward<Args>(args)...) {}
|
||||
|
||||
template <int cycle> void fetch() {
|
||||
// The first 30 and the final 4 slots are external.
|
||||
if constexpr (cycle < 30 || cycle >= 150) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||
return;
|
||||
} else {
|
||||
// For the 120 slots in between follow a three-step pattern of:
|
||||
constexpr int offset = cycle - 30;
|
||||
constexpr auto column = AddressT(offset / 3);
|
||||
switch(offset % 3) {
|
||||
case 0: // (1) fetch tile name.
|
||||
fetcher.fetch_name(column);
|
||||
break;
|
||||
case 1: // (2) external slot.
|
||||
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||
break;
|
||||
case 2: // (3) fetch tile pattern.
|
||||
fetcher.fetch_pattern(column);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
TextFetcher<personality> fetcher;
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
struct CharacterSequencer {
|
||||
template <typename... Args> CharacterSequencer(Args&&... args) :
|
||||
character_fetcher(std::forward<Args>(args)...),
|
||||
sprite_fetcher(std::forward<Args>(args)...) {}
|
||||
|
||||
template <int cycle> void fetch() {
|
||||
if(cycle < 5) {
|
||||
character_fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||
}
|
||||
|
||||
if(cycle == 5) {
|
||||
// Fetch: n1, c2, pat2a, pat2b, y3, x3, n3, c3, pat3a, pat3b.
|
||||
sprite_fetcher.fetch_pattern(2);
|
||||
sprite_fetcher.fetch_location(3);
|
||||
sprite_fetcher.fetch_pattern(3);
|
||||
}
|
||||
|
||||
if(cycle > 14 && cycle < 19) {
|
||||
character_fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||
}
|
||||
|
||||
// Fetch 8 new sprite Y coordinates, to begin selecting sprites for next line.
|
||||
if(cycle == 19) {
|
||||
sprite_fetcher.fetch_y(0); sprite_fetcher.fetch_y(1); sprite_fetcher.fetch_y(2); sprite_fetcher.fetch_y(3);
|
||||
sprite_fetcher.fetch_y(4); sprite_fetcher.fetch_y(5); sprite_fetcher.fetch_y(6); sprite_fetcher.fetch_y(7);
|
||||
}
|
||||
|
||||
// Body of line: tiles themselves, plus some additional potential sprites.
|
||||
if(cycle >= 27 && cycle < 155) {
|
||||
constexpr int offset = cycle - 27;
|
||||
constexpr int block = offset >> 2;
|
||||
constexpr int sub_block = offset & 3;
|
||||
switch(sub_block) {
|
||||
case 0: character_fetcher.fetch_name(block); break;
|
||||
case 1:
|
||||
if(!(block & 3)) {
|
||||
character_fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||
} else {
|
||||
constexpr int sprite = 8 + ((block >> 2) * 3) + ((block & 3) - 1);
|
||||
sprite_fetcher.fetch_y(sprite);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
character_fetcher.fetch_pattern(block);
|
||||
character_fetcher.fetch_colour(block);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if(cycle >= 155 && cycle < 157) {
|
||||
character_fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||
}
|
||||
|
||||
if(cycle == 157) {
|
||||
// Fetch: y0, x0, n0, c0, pat0a, pat0b, y1, x1, n1, c1, pat1a, pat1b, y2, x2.
|
||||
sprite_fetcher.fetch_location(0);
|
||||
sprite_fetcher.fetch_pattern(0);
|
||||
sprite_fetcher.fetch_location(1);
|
||||
sprite_fetcher.fetch_pattern(1);
|
||||
sprite_fetcher.fetch_location(2);
|
||||
}
|
||||
}
|
||||
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
CharacterFetcher<personality> character_fetcher;
|
||||
SpriteFetcher<personality, SpriteMode::Mode1> sprite_fetcher;
|
||||
};
|
||||
|
||||
// MARK: - TMS fetch routines.
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end> void Base<personality>::fetch_tms_refresh(uint8_t, int start, int end) {
|
||||
RefreshSequencer sequencer(this);
|
||||
dispatch<use_end>(sequencer, start, end);
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end> void Base<personality>::fetch_tms_text(uint8_t y, int start, int end) {
|
||||
TextSequencer<personality> sequencer(this, y);
|
||||
dispatch<use_end>(sequencer, start, end);
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end> void Base<personality>::fetch_tms_character(uint8_t y, int start, int end) {
|
||||
CharacterSequencer<personality> sequencer(this, y);
|
||||
dispatch<use_end>(sequencer, start, end);
|
||||
}
|
||||
|
||||
// MARK: - Master System
|
||||
|
||||
template <Personality personality>
|
||||
struct SMSSequencer {
|
||||
template <typename... Args> SMSSequencer(Args&&... args) : fetcher(std::forward<Args>(args)...) {}
|
||||
|
||||
// Cf. https://www.smspower.org/forums/16485-GenesisMode4VRAMTiming with this implementation pegging
|
||||
// window 0 to HSYNC low.
|
||||
template <int cycle> void fetch() {
|
||||
if(cycle < 3) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||
}
|
||||
|
||||
if(cycle == 3) {
|
||||
fetcher.fetch_sprite(4);
|
||||
fetcher.fetch_sprite(5);
|
||||
fetcher.fetch_sprite(6);
|
||||
fetcher.fetch_sprite(7);
|
||||
}
|
||||
|
||||
if(cycle == 15 || cycle == 16) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||
}
|
||||
|
||||
if(cycle == 17) {
|
||||
fetcher.posit_sprite(0); fetcher.posit_sprite(1); fetcher.posit_sprite(2); fetcher.posit_sprite(3);
|
||||
fetcher.posit_sprite(4); fetcher.posit_sprite(5); fetcher.posit_sprite(6); fetcher.posit_sprite(7);
|
||||
fetcher.posit_sprite(8); fetcher.posit_sprite(9); fetcher.posit_sprite(10); fetcher.posit_sprite(11);
|
||||
fetcher.posit_sprite(12); fetcher.posit_sprite(13); fetcher.posit_sprite(14); fetcher.posit_sprite(15);
|
||||
}
|
||||
|
||||
if(cycle >= 25 && cycle < 153) {
|
||||
constexpr int offset = cycle - 25;
|
||||
constexpr int block = offset >> 2;
|
||||
constexpr int sub_block = offset & 3;
|
||||
|
||||
switch(sub_block) {
|
||||
default: break;
|
||||
|
||||
case 0: fetcher.fetch_tile_name(block); break;
|
||||
case 1:
|
||||
if(!(block & 3)) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||
} else {
|
||||
constexpr int sprite = (8 + ((block >> 2) * 3) + ((block & 3) - 1)) << 1;
|
||||
fetcher.posit_sprite(sprite);
|
||||
fetcher.posit_sprite(sprite+1);
|
||||
}
|
||||
break;
|
||||
case 2: fetcher.fetch_tile_pattern(block); break;
|
||||
}
|
||||
}
|
||||
|
||||
if(cycle >= 153 && cycle < 157) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||
}
|
||||
|
||||
if(cycle == 157) {
|
||||
fetcher.fetch_sprite(0);
|
||||
fetcher.fetch_sprite(1);
|
||||
fetcher.fetch_sprite(2);
|
||||
fetcher.fetch_sprite(3);
|
||||
}
|
||||
|
||||
if(cycle >= 169) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Origin::ModeLatch, Clock::TMSMemoryWindow>(cycle));
|
||||
}
|
||||
}
|
||||
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
SMSFetcher<personality> fetcher;
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end> void Base<personality>::fetch_sms([[maybe_unused]] uint8_t y, [[maybe_unused]] int start, [[maybe_unused]] int end) {
|
||||
if constexpr (is_sega_vdp(personality)) {
|
||||
SMSSequencer<personality> sequencer(this, y);
|
||||
dispatch<use_end>(sequencer, start, end);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Yamaha
|
||||
|
||||
template <Personality personality>
|
||||
template<ScreenMode mode> void Base<personality>::fetch_yamaha(uint8_t y, int end) {
|
||||
CharacterFetcher character_fetcher(this, y);
|
||||
TextFetcher text_fetcher(this, y);
|
||||
SpriteFetcher<personality, sprite_mode(mode)> sprite_fetcher(this, y);
|
||||
|
||||
using Type = typename Storage<personality>::Event::Type;
|
||||
while(Storage<personality>::next_event_->offset < end) {
|
||||
switch(Storage<personality>::next_event_->type) {
|
||||
case Type::External:
|
||||
do_external_slot(Storage<personality>::next_event_->offset);
|
||||
break;
|
||||
|
||||
case Type::Name:
|
||||
switch(mode) {
|
||||
case ScreenMode::Text: {
|
||||
const auto column = AddressT(Storage<personality>::next_event_->id << 1);
|
||||
|
||||
text_fetcher.fetch_name(column, 0);
|
||||
text_fetcher.fetch_name(column + 1, 1);
|
||||
} break;
|
||||
|
||||
case ScreenMode::YamahaText80: {
|
||||
const auto column = AddressT(Storage<personality>::next_event_->id << 2);
|
||||
const auto start = pattern_name_address_ & bits<12>(AddressT((y >> 3) * 80));
|
||||
|
||||
name_[0] = ram_[start + column + 0];
|
||||
name_[1] = ram_[start + column + 1];
|
||||
name_[2] = ram_[start + column + 2];
|
||||
name_[3] = ram_[start + column + 3];
|
||||
} break;
|
||||
|
||||
case ScreenMode::Graphics:
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::ColouredText:
|
||||
character_fetcher.fetch_name(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::Colour:
|
||||
switch(mode) {
|
||||
case ScreenMode::YamahaText80: {
|
||||
const auto column = AddressT(Storage<personality>::next_event_->id);
|
||||
const auto address = colour_table_address_ & bits<9>(AddressT((y >> 3) * 10));
|
||||
auto &line_buffer = *fetch_line_buffer_;
|
||||
line_buffer.characters.flags[column] = ram_[address + column];
|
||||
} break;
|
||||
|
||||
case ScreenMode::Graphics:
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::ColouredText:
|
||||
character_fetcher.fetch_colour(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::Pattern:
|
||||
switch(mode) {
|
||||
case ScreenMode::Text: {
|
||||
const auto column = AddressT(Storage<personality>::next_event_->id << 1);
|
||||
|
||||
text_fetcher.fetch_pattern(column, 0);
|
||||
text_fetcher.fetch_pattern(column + 1, 1);
|
||||
} break;
|
||||
|
||||
case ScreenMode::YamahaText80: {
|
||||
const auto column = Storage<personality>::next_event_->id << 2;
|
||||
const auto start = pattern_generator_table_address_ & bits<11>(AddressT(y & 7));
|
||||
auto &line_buffer = *fetch_line_buffer_;
|
||||
|
||||
line_buffer.characters.shapes[column + 0] = ram_[start + AddressT(name_[0] << 3)];
|
||||
line_buffer.characters.shapes[column + 1] = ram_[start + AddressT(name_[1] << 3)];
|
||||
line_buffer.characters.shapes[column + 2] = ram_[start + AddressT(name_[2] << 3)];
|
||||
line_buffer.characters.shapes[column + 3] = ram_[start + AddressT(name_[3] << 3)];
|
||||
} break;
|
||||
|
||||
case ScreenMode::Graphics:
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::ColouredText:
|
||||
character_fetcher.fetch_pattern(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
|
||||
case ScreenMode::YamahaGraphics3:
|
||||
// As per comment elsewhere; my _guess_ is that G3 is slotted as if it were
|
||||
// a bitmap mode, with the three bytes that describe each column fitting into
|
||||
// the relevant windows.
|
||||
character_fetcher.fetch_name(Storage<personality>::next_event_->id);
|
||||
character_fetcher.fetch_colour(Storage<personality>::next_event_->id);
|
||||
character_fetcher.fetch_pattern(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
|
||||
case ScreenMode::YamahaGraphics4:
|
||||
case ScreenMode::YamahaGraphics5: {
|
||||
const int column = Storage<personality>::next_event_->id << 2;
|
||||
const auto start = bits<15>((y << 7) | column);
|
||||
auto &line_buffer = *fetch_line_buffer_;
|
||||
|
||||
line_buffer.bitmap[column + 0] = ram_[pattern_name_address_ & AddressT(start + 0)];
|
||||
line_buffer.bitmap[column + 1] = ram_[pattern_name_address_ & AddressT(start + 1)];
|
||||
line_buffer.bitmap[column + 2] = ram_[pattern_name_address_ & AddressT(start + 2)];
|
||||
line_buffer.bitmap[column + 3] = ram_[pattern_name_address_ & AddressT(start + 3)];
|
||||
} break;
|
||||
|
||||
case ScreenMode::YamahaGraphics6:
|
||||
case ScreenMode::YamahaGraphics7: {
|
||||
const uint8_t *const ram2 = &ram_[65536];
|
||||
const int column = Storage<personality>::next_event_->id << 3;
|
||||
const auto start = bits<15>((y << 7) | (column >> 1));
|
||||
auto &line_buffer = *fetch_line_buffer_;
|
||||
|
||||
// Fetch from alternate banks.
|
||||
line_buffer.bitmap[column + 0] = ram_[pattern_name_address_ & AddressT(start + 0) & 0xffff];
|
||||
line_buffer.bitmap[column + 1] = ram2[pattern_name_address_ & AddressT(start + 0) & 0xffff];
|
||||
line_buffer.bitmap[column + 2] = ram_[pattern_name_address_ & AddressT(start + 1) & 0xffff];
|
||||
line_buffer.bitmap[column + 3] = ram2[pattern_name_address_ & AddressT(start + 1) & 0xffff];
|
||||
line_buffer.bitmap[column + 4] = ram_[pattern_name_address_ & AddressT(start + 2) & 0xffff];
|
||||
line_buffer.bitmap[column + 5] = ram2[pattern_name_address_ & AddressT(start + 2) & 0xffff];
|
||||
line_buffer.bitmap[column + 6] = ram_[pattern_name_address_ & AddressT(start + 3) & 0xffff];
|
||||
line_buffer.bitmap[column + 7] = ram2[pattern_name_address_ & AddressT(start + 3) & 0xffff];
|
||||
} break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::SpriteY:
|
||||
switch(mode) {
|
||||
case ScreenMode::Blank:
|
||||
case ScreenMode::Text:
|
||||
case ScreenMode::YamahaText80:
|
||||
// Ensure the compiler can discard character_fetcher in these modes.
|
||||
break;
|
||||
|
||||
default:
|
||||
sprite_fetcher.fetch_y(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::SpriteLocation:
|
||||
switch(mode) {
|
||||
case ScreenMode::Blank:
|
||||
case ScreenMode::Text:
|
||||
case ScreenMode::YamahaText80:
|
||||
// Ensure the compiler can discard character_fetcher in these modes.
|
||||
break;
|
||||
|
||||
default:
|
||||
sprite_fetcher.fetch_location(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::SpritePattern:
|
||||
switch(mode) {
|
||||
case ScreenMode::Blank:
|
||||
case ScreenMode::Text:
|
||||
case ScreenMode::YamahaText80:
|
||||
// Ensure the compiler can discard character_fetcher in these modes.
|
||||
break;
|
||||
|
||||
default:
|
||||
sprite_fetcher.fetch_pattern(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
++Storage<personality>::next_event_;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end> void Base<personality>::fetch_yamaha(uint8_t y, int, int end) {
|
||||
if constexpr (is_yamaha_vdp(personality)) {
|
||||
// Dispatch according to [supported] screen mode.
|
||||
#define Dispatch(mode) case mode: fetch_yamaha<mode>(y, end); break;
|
||||
switch(fetch_line_buffer_->screen_mode) {
|
||||
default: break;
|
||||
Dispatch(ScreenMode::Blank);
|
||||
Dispatch(ScreenMode::Text);
|
||||
Dispatch(ScreenMode::MultiColour);
|
||||
Dispatch(ScreenMode::ColouredText);
|
||||
Dispatch(ScreenMode::Graphics);
|
||||
Dispatch(ScreenMode::YamahaText80);
|
||||
Dispatch(ScreenMode::YamahaGraphics3);
|
||||
Dispatch(ScreenMode::YamahaGraphics4);
|
||||
Dispatch(ScreenMode::YamahaGraphics5);
|
||||
Dispatch(ScreenMode::YamahaGraphics6);
|
||||
Dispatch(ScreenMode::YamahaGraphics7);
|
||||
}
|
||||
#undef Dispatch
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mega Drive
|
||||
|
||||
// TODO.
|
||||
|
||||
}
|
||||
|
||||
#endif /* Fetch_hpp */
|
132
Components/9918/Implementation/LineBuffer.hpp
Normal file
132
Components/9918/Implementation/LineBuffer.hpp
Normal file
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// LineBuffer.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/02/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef LineBuffer_hpp
|
||||
#define LineBuffer_hpp
|
||||
|
||||
#include "AccessEnums.hpp"
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
// Temporary buffers collect a representation of each line prior to pixel serialisation.
|
||||
|
||||
struct SpriteBuffer {
|
||||
// An active sprite is one that has been selected for composition onto
|
||||
// _this_ line.
|
||||
struct ActiveSprite {
|
||||
int index = 0; // The original in-table index of this sprite.
|
||||
int row = 0; // The row of the sprite that should be drawn.
|
||||
int x = 0; // The sprite's x position on screen.
|
||||
|
||||
uint8_t image[4]; // Up to four bytes of image information.
|
||||
//
|
||||
// In practice:
|
||||
//
|
||||
// Master System mode: the four bytes of this 8x8 sprite;
|
||||
// TMS and Yamaha: [0] = the left half of this sprite; [1] = the right side (if 16x16 sprites enabled); [2] = colour, early-clock bit, etc.
|
||||
int shift_position = 0; // An offset representing how much of the image information has already been drawn.
|
||||
|
||||
// Yamaha helpers.
|
||||
bool opaque() const {
|
||||
return !(image[2] & 0x40);
|
||||
}
|
||||
|
||||
/// @returns @c 0x20 if this sprite should generate collisions; @c 0x00 otherwise.
|
||||
int collision_bit() const {
|
||||
return ((image[2] & 0x20) | ((image[2] & 0x40) >> 1)) ^ 0x20;
|
||||
}
|
||||
|
||||
// Yamaha and TMS helpers.
|
||||
int early_clock() const {
|
||||
return (image[2] & 0x80) >> 2;
|
||||
}
|
||||
} active_sprites[8];
|
||||
|
||||
int active_sprite_slot = 0; // A pointer to the slot into which a new active sprite will be deposited, if required.
|
||||
bool sprites_stopped = false; // A special TMS feature is that a sentinel value can be used to prevent any further sprites
|
||||
// being evaluated for display. This flag determines whether the sentinel has yet been reached.
|
||||
uint8_t sprite_terminator = 0;
|
||||
|
||||
#ifndef NDEBUG
|
||||
static constexpr bool test_is_filling = true;
|
||||
#else
|
||||
static constexpr bool test_is_filling = false;
|
||||
#endif
|
||||
bool is_filling = false;
|
||||
|
||||
void reset_sprite_collection();
|
||||
};
|
||||
|
||||
struct LineBuffer {
|
||||
LineBuffer() {}
|
||||
|
||||
// The fetch mode describes the proper timing diagram for this line;
|
||||
// screen mode captures proper output mode.
|
||||
FetchMode fetch_mode = FetchMode::Text;
|
||||
ScreenMode screen_mode = ScreenMode::Text;
|
||||
VerticalState vertical_state = VerticalState::Blank;
|
||||
SpriteBuffer *sprites = nullptr;
|
||||
|
||||
// Holds the horizontal scroll position to apply to this line;
|
||||
// of those VDPs currently implemented, affects the Master System only.
|
||||
uint8_t latched_horizontal_scroll = 0;
|
||||
|
||||
// The names array holds pattern names, as an offset into memory, and
|
||||
// potentially flags also.
|
||||
union {
|
||||
// This struct captures maximal potential detail across the TMS9918
|
||||
// and Sega VDP for tiled modes (plus multicolour).
|
||||
struct {
|
||||
uint8_t flags[32]{};
|
||||
|
||||
// The patterns array holds tile patterns, corresponding 1:1 with names.
|
||||
// Four bytes per pattern is the maximum required by any
|
||||
// currently-implemented VDP.
|
||||
uint8_t patterns[32][4]{};
|
||||
} tiles;
|
||||
|
||||
// The Yamaha and TMS both have text modes, with the former going up to
|
||||
// 80 columns plus 10 bytes of colour-esque flags.
|
||||
struct {
|
||||
uint8_t shapes[80];
|
||||
uint8_t flags[10];
|
||||
} characters;
|
||||
|
||||
// The Yamaha VDP also has a variety of bitmap modes,
|
||||
// the widest of which is 512px @ 4bpp.
|
||||
uint8_t bitmap[256];
|
||||
};
|
||||
|
||||
/*
|
||||
Horizontal layout (on a 342-cycle clock):
|
||||
|
||||
15 cycles right border
|
||||
58 cycles blanking & sync
|
||||
13 cycles left border
|
||||
|
||||
... i.e. to cycle 86, then:
|
||||
|
||||
border up to first_pixel_output_column;
|
||||
pixels up to next_border_column;
|
||||
border up to the end.
|
||||
|
||||
e.g. standard 256-pixel modes will want to set
|
||||
first_pixel_output_column = 86, next_border_column = 342.
|
||||
*/
|
||||
int first_pixel_output_column = 94;
|
||||
int next_border_column = 334;
|
||||
int pixel_count = 256;
|
||||
};
|
||||
|
||||
struct LineBufferPointer {
|
||||
int row = 0, column = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* LineBuffer_hpp */
|
110
Components/9918/Implementation/LineLayout.hpp
Normal file
110
Components/9918/Implementation/LineLayout.hpp
Normal file
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// LineLayout.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/05/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef LineLayout_h
|
||||
#define LineLayout_h
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
template <Personality personality, typename Enable = void> struct LineLayout;
|
||||
|
||||
// Line layout is:
|
||||
//
|
||||
// [0, EndOfSync] sync
|
||||
// (EndOfSync, StartOfColourBurst] blank
|
||||
// (StartOfColourBurst, EndOfColourBurst] colour burst
|
||||
// (EndOfColourBurst, EndOfLeftErase] blank
|
||||
// (EndOfLeftErase, EndOfLeftBorder] border colour
|
||||
// (EndOfLeftBorder, EndOfPixels] pixel content
|
||||
// (EndOfPixels, EndOfRightBorder] border colour
|
||||
// [EndOfRightBorder, <end of line>] blank
|
||||
//
|
||||
// ... with minor caveats:
|
||||
// * horizontal adjust on the Yamaha VDPs is applied to EndOfLeftBorder and EndOfPixels;
|
||||
// * the Sega VDPs may programatically extend the left border; and
|
||||
// * text mode on all VDPs adjusts border width.
|
||||
//
|
||||
// ModeLaytchCycle is the cycle at which the video mode, blank disable/enable and
|
||||
// sprite enable/disable are latched for the line.
|
||||
|
||||
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_classic_vdp(personality) && !is_sega_vdp(personality)>> {
|
||||
constexpr static int StartOfSync = 0;
|
||||
constexpr static int EndOfSync = 26;
|
||||
constexpr static int StartOfColourBurst = 29;
|
||||
constexpr static int EndOfColourBurst = 43;
|
||||
constexpr static int EndOfLeftErase = 50;
|
||||
constexpr static int EndOfLeftBorder = 63;
|
||||
constexpr static int EndOfPixels = 319;
|
||||
constexpr static int EndOfRightBorder = 334;
|
||||
|
||||
constexpr static int CyclesPerLine = 342;
|
||||
|
||||
constexpr static int TextModeEndOfLeftBorder = 69;
|
||||
constexpr static int TextModeEndOfPixels = 309;
|
||||
|
||||
constexpr static int ModeLatchCycle = 36; // Just a guess; correlates with the known 144 for the Yamaha VDPs,
|
||||
// and falls into the collection gap between the final sprite
|
||||
// graphics and the initial tiles or pixels.
|
||||
|
||||
constexpr static bool HasDynamicLineInterrupt = false;
|
||||
constexpr static bool HasFixedLineInterrupt = false;
|
||||
constexpr static int EndOfFrameInterrupt = 313;
|
||||
|
||||
/// The number of internal cycles that must elapse between a request to read or write and
|
||||
/// it becoming a candidate for action.
|
||||
constexpr static int VRAMAccessDelay = 6;
|
||||
};
|
||||
|
||||
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_sega_vdp(personality)>> :
|
||||
public LineLayout<Personality::TMS9918A> {
|
||||
|
||||
// Cf. https://www.smspower.org/forums/8161-SMSDisplayTiming
|
||||
|
||||
// "For a line interrupt, /INT is pulled low 608 mclks into the appropriate scanline relative to pixel 0.
|
||||
// This is 3 mclks before the rising edge of /HSYNC which starts the next scanline."
|
||||
//
|
||||
// i.e. it's 304 internal clocks after the end of the left border.
|
||||
constexpr static bool HasFixedLineInterrupt = false;
|
||||
constexpr static int FixedLineInterrupt = (EndOfLeftBorder + 304) % CyclesPerLine;
|
||||
|
||||
// For a frame interrupt, /INT is pulled low 607 mclks into scanline 192 (of scanlines 0 through 261) relative to pixel 0.
|
||||
// This is 4 mclks before the rising edge of /HSYNC which starts the next scanline.
|
||||
//
|
||||
// i.e. it's 1/2 cycle before the line interrupt position, which I have rounded. Ugh.
|
||||
constexpr static int EndOfFrameInterrupt = 313;
|
||||
};
|
||||
|
||||
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
|
||||
constexpr static int StartOfSync = 0;
|
||||
constexpr static int EndOfSync = 100;
|
||||
constexpr static int StartOfColourBurst = 113;
|
||||
constexpr static int EndOfColourBurst = 167;
|
||||
constexpr static int EndOfLeftErase = 202;
|
||||
constexpr static int EndOfLeftBorder = 258;
|
||||
constexpr static int EndOfPixels = 1282;
|
||||
constexpr static int EndOfRightBorder = 1341;
|
||||
|
||||
constexpr static int CyclesPerLine = 1368;
|
||||
|
||||
constexpr static int TextModeEndOfLeftBorder = 294;
|
||||
constexpr static int TextModeEndOfPixels = 1254;
|
||||
|
||||
constexpr static int ModeLatchCycle = 144;
|
||||
|
||||
constexpr static bool HasDynamicLineInterrupt = true;
|
||||
constexpr static bool HasFixedLineInterrupt = false;
|
||||
constexpr static int EndOfFrameInterrupt = 313;
|
||||
|
||||
/// The number of internal cycles that must elapse between a request to read or write and
|
||||
/// it becoming a candidate for action.
|
||||
constexpr static int VRAMAccessDelay = 16;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* LineLayout_h */
|
50
Components/9918/Implementation/PersonalityTraits.hpp
Normal file
50
Components/9918/Implementation/PersonalityTraits.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// PersonalityTraits.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 06/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef PersonalityTraits_hpp
|
||||
#define PersonalityTraits_hpp
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
// Genus determinants for the various personalityes.
|
||||
constexpr bool is_sega_vdp(Personality p) {
|
||||
return p >= Personality::SMSVDP;
|
||||
}
|
||||
|
||||
constexpr bool is_yamaha_vdp(Personality p) {
|
||||
return p == Personality::V9938 || p == Personality::V9958;
|
||||
}
|
||||
|
||||
// i.e. one with the original internal timings.
|
||||
constexpr bool is_classic_vdp(Personality p) {
|
||||
return
|
||||
p == Personality::TMS9918A ||
|
||||
p == Personality::SMSVDP ||
|
||||
p == Personality::SMS2VDP ||
|
||||
p == Personality::GGVDP;
|
||||
}
|
||||
|
||||
constexpr size_t memory_size(Personality p) {
|
||||
switch(p) {
|
||||
case TI::TMS::TMS9918A:
|
||||
case TI::TMS::SMSVDP:
|
||||
case TI::TMS::SMS2VDP:
|
||||
case TI::TMS::GGVDP: return 16 * 1024;
|
||||
case TI::TMS::MDVDP: return 64 * 1024;
|
||||
case TI::TMS::V9938: return 128 * 1024;
|
||||
case TI::TMS::V9958: return 192 * 1024;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr size_t memory_mask(Personality p) {
|
||||
return memory_size(p) - 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif /* PersonalityTraits_hpp */
|
491
Components/9918/Implementation/Storage.hpp
Normal file
491
Components/9918/Implementation/Storage.hpp
Normal file
@@ -0,0 +1,491 @@
|
||||
//
|
||||
//
|
||||
// Storage.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/02/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Storage_h
|
||||
#define Storage_h
|
||||
|
||||
#include "LineBuffer.hpp"
|
||||
#include "YamahaCommands.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
/// A container for personality-specific storage; see specific instances below.
|
||||
template <Personality personality, typename Enable = void> struct Storage {
|
||||
};
|
||||
|
||||
template <> struct Storage<Personality::TMS9918A> {
|
||||
using AddressT = uint16_t;
|
||||
|
||||
void begin_line(ScreenMode, bool) {}
|
||||
};
|
||||
|
||||
struct YamahaFetcher {
|
||||
public:
|
||||
/// Describes an _observable_ memory access event. i.e. anything that it is safe
|
||||
/// (and convenient) to treat as atomic in between external slots.
|
||||
struct Event {
|
||||
/// Offset of the _beginning_ of the event. Not completely arbitrarily: this is when
|
||||
/// external data must be ready by in order to take part in those slots.
|
||||
uint16_t offset = 1368;
|
||||
enum class Type: uint8_t {
|
||||
/// A slot for reading or writing data on behalf of the CPU or the command engine.
|
||||
External,
|
||||
|
||||
//
|
||||
// Sprites.
|
||||
//
|
||||
SpriteY,
|
||||
SpriteLocation,
|
||||
SpritePattern,
|
||||
|
||||
//
|
||||
// Backgrounds.
|
||||
//
|
||||
Name,
|
||||
Colour,
|
||||
Pattern,
|
||||
} type = Type::External;
|
||||
uint8_t id = 0;
|
||||
|
||||
constexpr Event(Type type, uint8_t id = 0) noexcept :
|
||||
type(type),
|
||||
id(id) {}
|
||||
|
||||
constexpr Event() noexcept {}
|
||||
};
|
||||
|
||||
// State that tracks fetching position within a line.
|
||||
const Event *next_event_ = nullptr;
|
||||
|
||||
// Sprite collection state.
|
||||
bool sprites_enabled_ = true;
|
||||
|
||||
protected:
|
||||
/// @return 1 + the number of times within a line that @c GeneratorT produces an event.
|
||||
template <typename GeneratorT> static constexpr size_t events_size() {
|
||||
size_t size = 0;
|
||||
for(int c = 0; c < 1368; c++) {
|
||||
const auto event_type = GeneratorT::event(c);
|
||||
size += event_type.has_value();
|
||||
}
|
||||
return size + 1;
|
||||
}
|
||||
|
||||
/// @return An array of all events generated by @c GeneratorT in line order.
|
||||
template <typename GeneratorT, size_t size = events_size<GeneratorT>()>
|
||||
static constexpr std::array<Event, size> events() {
|
||||
std::array<Event, size> result{};
|
||||
size_t index = 0;
|
||||
for(int c = 0; c < 1368; c++) {
|
||||
// Specific personality doesn't matter here; both Yamahas use the same internal timing.
|
||||
const int mapped_location = from_internal<Personality::V9938, Origin::StartOfSync>(c);
|
||||
const auto event = GeneratorT::event(mapped_location);
|
||||
if(!event) {
|
||||
continue;
|
||||
}
|
||||
result[index] = *event;
|
||||
result[index].offset = uint16_t(c);
|
||||
++index;
|
||||
}
|
||||
result[index] = Event();
|
||||
return result;
|
||||
}
|
||||
|
||||
struct StandardGenerators {
|
||||
static constexpr std::optional<Event> external_every_eight(int index) {
|
||||
if(index & 7) return std::nullopt;
|
||||
return Event::Type::External;
|
||||
}
|
||||
};
|
||||
|
||||
struct RefreshGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
// From 0 to 126: CPU/CMD slots at every cycle divisible by 8.
|
||||
if(grauw_index < 126) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 0);
|
||||
}
|
||||
|
||||
// From 164 to 1234: eight-cycle windows, the first 15 of each 16 being
|
||||
// CPU/CMD and the final being refresh.
|
||||
if(grauw_index >= 164 && grauw_index < 1234) {
|
||||
const int offset = grauw_index - 164;
|
||||
if(offset & 7) return std::nullopt;
|
||||
if(((offset >> 3) & 15) == 15) return std::nullopt;
|
||||
return Event::Type::External;
|
||||
}
|
||||
|
||||
// From 1268 to 1330: CPU/CMD slots at every cycle divisible by 8.
|
||||
if(grauw_index >= 1268 && grauw_index < 1330) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1268);
|
||||
}
|
||||
|
||||
// A CPU/CMD at 1334.
|
||||
if(grauw_index == 1334) {
|
||||
return Event::Type::External;
|
||||
}
|
||||
|
||||
// From 1344 to 1366: CPU/CMD slots every cycle divisible by 8.
|
||||
if(grauw_index >= 1344 && grauw_index < 1366) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1344);
|
||||
}
|
||||
|
||||
// Otherwise: nothing.
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
template <bool include_sprites> struct BitmapGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
if(!include_sprites) {
|
||||
// Various standard zones of one-every-eight external slots.
|
||||
if(grauw_index < 124) {
|
||||
return StandardGenerators::external_every_eight(grauw_index + 2);
|
||||
}
|
||||
if(grauw_index > 1266) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1266);
|
||||
}
|
||||
} else {
|
||||
// This records collection points for all data for selected sprites.
|
||||
// There's only four of them (each site covering two sprites),
|
||||
// so it's clearer just to be explicit.
|
||||
//
|
||||
// There's also a corresponding number of extra external slots to spell out.
|
||||
switch(grauw_index) {
|
||||
default: break;
|
||||
case 1238: return Event(Event::Type::SpriteLocation, 0);
|
||||
case 1302: return Event(Event::Type::SpriteLocation, 2);
|
||||
case 2: return Event(Event::Type::SpriteLocation, 4);
|
||||
case 66: return Event(Event::Type::SpriteLocation, 6);
|
||||
case 1270: return Event(Event::Type::SpritePattern, 0);
|
||||
case 1338: return Event(Event::Type::SpritePattern, 2);
|
||||
case 34: return Event(Event::Type::SpritePattern, 4);
|
||||
case 98: return Event(Event::Type::SpritePattern, 6);
|
||||
case 1264: case 1330: case 28: case 92:
|
||||
return Event::Type::External;
|
||||
}
|
||||
}
|
||||
|
||||
if(grauw_index >= 162 && grauw_index < 176) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 162);
|
||||
}
|
||||
|
||||
// Everywhere else the pattern is:
|
||||
//
|
||||
// external or sprite y, external, data block
|
||||
//
|
||||
// Subject to caveats:
|
||||
//
|
||||
// 1) the first data block is just a dummy fetch with no side effects,
|
||||
// so this emulator declines to record it; and
|
||||
// 2) every fourth block, the second external is actually a refresh.
|
||||
//
|
||||
if(grauw_index >= 182 && grauw_index < 1238) {
|
||||
const int offset = grauw_index - 182;
|
||||
const int block = offset / 32;
|
||||
const int sub_block = offset & 31;
|
||||
|
||||
switch(sub_block) {
|
||||
default: return std::nullopt;
|
||||
case 0:
|
||||
if(include_sprites) {
|
||||
// Don't include the sprite post-amble (i.e. a spurious read with no side effects).
|
||||
if(block < 32) {
|
||||
return Event(Event::Type::SpriteY, uint8_t(block));
|
||||
}
|
||||
} else {
|
||||
return Event::Type::External;
|
||||
}
|
||||
case 6:
|
||||
if((block & 3) != 3) {
|
||||
return Event::Type::External;
|
||||
}
|
||||
break;
|
||||
case 12:
|
||||
if(block) {
|
||||
return Event(Event::Type::Pattern, uint8_t(block - 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
struct TextGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
// Capture various one-in-eight zones.
|
||||
if(grauw_index < 72) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 2);
|
||||
}
|
||||
if(grauw_index >= 166 && grauw_index < 228) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 166);
|
||||
}
|
||||
if(grauw_index >= 1206 && grauw_index < 1332) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1206);
|
||||
}
|
||||
if(grauw_index == 1336) {
|
||||
return Event::Type::External;
|
||||
}
|
||||
if(grauw_index >= 1346) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1346);
|
||||
}
|
||||
|
||||
// Elsewhere...
|
||||
if(grauw_index >= 246) {
|
||||
const int offset = grauw_index - 246;
|
||||
const int block = offset / 48;
|
||||
const int sub_block = offset % 48;
|
||||
switch(sub_block) {
|
||||
default: break;
|
||||
case 0: return Event(Event::Type::Name, uint8_t(block));
|
||||
case 18: return (block & 1) ? Event::Type::External : Event(Event::Type::Colour, uint8_t(block >> 1));
|
||||
case 24: return Event(Event::Type::Pattern, uint8_t(block));
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
struct CharacterGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
// Grab sprite events.
|
||||
switch(grauw_index) {
|
||||
default: break;
|
||||
case 1242: return Event(Event::Type::SpriteLocation, 0);
|
||||
case 1306: return Event(Event::Type::SpriteLocation, 1);
|
||||
case 6: return Event(Event::Type::SpriteLocation, 2);
|
||||
case 70: return Event(Event::Type::SpriteLocation, 3);
|
||||
case 1274: return Event(Event::Type::SpritePattern, 0);
|
||||
case 1342: return Event(Event::Type::SpritePattern, 1);
|
||||
case 38: return Event(Event::Type::SpritePattern, 2);
|
||||
case 102: return Event(Event::Type::SpritePattern, 3);
|
||||
case 1268: case 1334: case 32: case 96: return Event::Type::External;
|
||||
}
|
||||
|
||||
if(grauw_index >= 166 && grauw_index < 180) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 166);
|
||||
}
|
||||
|
||||
if(grauw_index >= 182 && grauw_index < 1238) {
|
||||
const int offset = grauw_index - 182;
|
||||
const int block = offset / 32;
|
||||
const int sub_block = offset & 31;
|
||||
switch(sub_block) {
|
||||
case 0: if(block > 0) return Event(Event::Type::Name, uint8_t(block - 1));
|
||||
case 6: if((sub_block & 3) != 3) return Event::Type::External;
|
||||
case 12: if(block < 32) return Event(Event::Type::SpriteY, uint8_t(block));
|
||||
case 18: if(block > 0) return Event(Event::Type::Pattern, uint8_t(block - 1));
|
||||
case 24: if(block > 0) return Event(Event::Type::Colour, uint8_t(block - 1));
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
struct YamahaCommandState {
|
||||
CommandContext command_context_;
|
||||
ModeDescription mode_description_;
|
||||
std::unique_ptr<Command> command_ = nullptr;
|
||||
|
||||
enum class CommandStep {
|
||||
None,
|
||||
|
||||
CopySourcePixelToStatus,
|
||||
|
||||
ReadSourcePixel,
|
||||
ReadDestinationPixel,
|
||||
WritePixel,
|
||||
|
||||
ReadSourceByte,
|
||||
WriteByte,
|
||||
};
|
||||
CommandStep next_command_step_ = CommandStep::None;
|
||||
int minimum_command_column_ = 0;
|
||||
uint8_t command_latch_ = 0;
|
||||
|
||||
void update_command_step(int current_column) {
|
||||
if(!command_) {
|
||||
next_command_step_ = CommandStep::None;
|
||||
return;
|
||||
}
|
||||
if(command_->done()) {
|
||||
command_ = nullptr;
|
||||
next_command_step_ = CommandStep::None;
|
||||
return;
|
||||
}
|
||||
|
||||
minimum_command_column_ = current_column + command_->cycles;
|
||||
switch(command_->access) {
|
||||
case Command::AccessType::ReadPoint:
|
||||
next_command_step_ = CommandStep::CopySourcePixelToStatus;
|
||||
break;
|
||||
|
||||
case Command::AccessType::CopyPoint:
|
||||
next_command_step_ = CommandStep::ReadSourcePixel;
|
||||
break;
|
||||
case Command::AccessType::PlotPoint:
|
||||
next_command_step_ = CommandStep::ReadDestinationPixel;
|
||||
break;
|
||||
|
||||
case Command::AccessType::WaitForColourReceipt:
|
||||
// i.e. nothing to do until a colour is received.
|
||||
next_command_step_ = CommandStep::None;
|
||||
break;
|
||||
|
||||
case Command::AccessType::CopyByte:
|
||||
next_command_step_ = CommandStep::ReadSourceByte;
|
||||
break;
|
||||
case Command::AccessType::WriteByte:
|
||||
next_command_step_ = CommandStep::WriteByte;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Yamaha-specific storage.
|
||||
template <Personality personality> struct Storage<personality, std::enable_if_t<is_yamaha_vdp(personality)>>: public YamahaFetcher, public YamahaCommandState {
|
||||
using AddressT = uint32_t;
|
||||
|
||||
// The Yamaha's (optional in real hardware) additional 64kb of expansion RAM.
|
||||
// This is a valid target and source for the command engine, but can't be used as a source for current video data.
|
||||
std::array<uint8_t, 65536> expansion_ram_;
|
||||
|
||||
// Register indirections.
|
||||
int selected_status_ = 0;
|
||||
int indirect_register_ = 0;
|
||||
bool increment_indirect_register_ = false;
|
||||
|
||||
// Output horizontal and vertical adjustment, plus the selected vertical offset (i.e. hardware scroll).
|
||||
int adjustment_[2]{};
|
||||
uint8_t vertical_offset_ = 0;
|
||||
|
||||
// The palette, plus a shadow copy in which colour 0 is not the current palette colour 0,
|
||||
// but is rather the current global background colour. This simplifies flow when colour 0
|
||||
// is set as transparent.
|
||||
std::array<uint32_t, 16> palette_{};
|
||||
std::array<uint32_t, 16> background_palette_{};
|
||||
bool solid_background_ = true;
|
||||
|
||||
// Transient state for palette setting.
|
||||
uint8_t new_colour_ = 0;
|
||||
uint8_t palette_entry_ = 0;
|
||||
bool palette_write_phase_ = false;
|
||||
|
||||
// Recepticle for all five bits of the current screen mode.
|
||||
uint8_t mode_ = 0;
|
||||
|
||||
// Used ephemerally during drawing to compound sprites with the 'CC'
|
||||
// (compound colour?) bit set.
|
||||
uint8_t sprite_cache_[8][32]{};
|
||||
|
||||
// Text blink colours.
|
||||
uint8_t blink_text_colour_ = 0;
|
||||
uint8_t blink_background_colour_ = 0;
|
||||
|
||||
// Blink state (which is also affects even/odd page display in applicable modes).
|
||||
int in_blink_ = 1;
|
||||
uint8_t blink_periods_ = 0;
|
||||
uint8_t blink_counter_ = 0;
|
||||
|
||||
// Additional things exposed by status registers.
|
||||
uint8_t colour_status_ = 0;
|
||||
uint16_t colour_location_ = 0;
|
||||
uint16_t collision_location_[2]{};
|
||||
bool line_matches_ = false;
|
||||
|
||||
Storage() noexcept {
|
||||
// Seed to something valid.
|
||||
next_event_ = refresh_events.data();
|
||||
}
|
||||
|
||||
/// Resets line-ephemeral state for a new line.
|
||||
void begin_line(ScreenMode mode, bool is_refresh) {
|
||||
if(is_refresh) {
|
||||
next_event_ = refresh_events.data();
|
||||
return;
|
||||
}
|
||||
|
||||
switch(mode) {
|
||||
case ScreenMode::YamahaText80:
|
||||
case ScreenMode::Text:
|
||||
next_event_ = text_events.data();
|
||||
break;
|
||||
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::YamahaGraphics1:
|
||||
case ScreenMode::YamahaGraphics2:
|
||||
next_event_ = character_events.data();
|
||||
break;
|
||||
|
||||
case ScreenMode::YamahaGraphics3: // TODO: verify; my guess is that G3 is timed like a bitmap mode
|
||||
// in order to fit the pattern for sprite mode 2. Just a guess.
|
||||
default:
|
||||
next_event_ = sprites_enabled_ ? sprites_events.data() : no_sprites_events.data();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr auto refresh_events = events<RefreshGenerator>();
|
||||
static constexpr auto no_sprites_events = events<BitmapGenerator<false>>();
|
||||
static constexpr auto sprites_events = events<BitmapGenerator<true>>();
|
||||
static constexpr auto text_events = events<TextGenerator>();
|
||||
static constexpr auto character_events = events<CharacterGenerator>();
|
||||
};
|
||||
|
||||
// Master System-specific storage.
|
||||
template <Personality personality> struct Storage<personality, std::enable_if_t<is_sega_vdp(personality)>> {
|
||||
using AddressT = uint16_t;
|
||||
|
||||
// The SMS VDP has a programmer-set colour palette, with a dedicated patch of RAM. But the RAM is only exactly
|
||||
// fast enough for the pixel clock. So when the programmer writes to it, that causes a one-pixel glitch; there
|
||||
// isn't the bandwidth for the read both write to occur simultaneously. The following buffer therefore keeps
|
||||
// track of pending collisions, for visual reproduction.
|
||||
struct CRAMDot {
|
||||
LineBufferPointer location;
|
||||
uint32_t value;
|
||||
};
|
||||
std::vector<CRAMDot> upcoming_cram_dots_;
|
||||
|
||||
// The Master System's additional colour RAM.
|
||||
uint32_t colour_ram_[32];
|
||||
bool cram_is_selected_ = false;
|
||||
|
||||
// Programmer-set flags.
|
||||
bool vertical_scroll_lock_ = false;
|
||||
bool horizontal_scroll_lock_ = false;
|
||||
bool hide_left_column_ = false;
|
||||
bool shift_sprites_8px_left_ = false;
|
||||
bool mode4_enable_ = false;
|
||||
uint8_t horizontal_scroll_ = 0;
|
||||
uint8_t vertical_scroll_ = 0;
|
||||
|
||||
// Holds the vertical scroll position for this frame; this is latched
|
||||
// once and cannot dynamically be changed until the next frame.
|
||||
uint8_t latched_vertical_scroll_ = 0;
|
||||
|
||||
// Various resource addresses with VDP-version-specific modifications
|
||||
// built in.
|
||||
AddressT pattern_name_address_;
|
||||
AddressT sprite_attribute_table_address_;
|
||||
AddressT sprite_generator_table_address_;
|
||||
|
||||
void begin_line(ScreenMode, bool) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Storage_h */
|
384
Components/9918/Implementation/YamahaCommands.hpp
Normal file
384
Components/9918/Implementation/YamahaCommands.hpp
Normal file
@@ -0,0 +1,384 @@
|
||||
//
|
||||
// YamahaCommands.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 26/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef YamahaCommands_hpp
|
||||
#define YamahaCommands_hpp
|
||||
|
||||
#include "AccessEnums.hpp"
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
// MARK: - Generics.
|
||||
|
||||
struct Vector {
|
||||
int v[2]{};
|
||||
|
||||
template <int offset, bool high> void set(uint8_t value) {
|
||||
constexpr uint8_t mask = high ? (offset ? 0x3 : 0x1) : 0xff;
|
||||
constexpr int shift = high ? 8 : 0;
|
||||
v[offset] = (v[offset] & ~(mask << shift)) | ((value & mask) << shift);
|
||||
}
|
||||
|
||||
template <int offset> void add(int amount) {
|
||||
v[offset] += amount;
|
||||
|
||||
if constexpr (offset == 1) {
|
||||
v[offset] &= 0x3ff;
|
||||
} else {
|
||||
v[offset] &= 0x1ff;
|
||||
}
|
||||
}
|
||||
|
||||
Vector & operator += (const Vector &rhs) {
|
||||
add<0>(rhs.v[0]);
|
||||
add<1>(rhs.v[1]);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
struct Colour {
|
||||
void set(uint8_t value) {
|
||||
colour = value;
|
||||
colour4bpp = uint8_t((value & 0xf) | (value << 4));
|
||||
colour2bpp = uint8_t((colour4bpp & 0x33) | ((colour4bpp & 0x33) << 2));
|
||||
}
|
||||
|
||||
void reset() {
|
||||
colour = 0x00;
|
||||
colour4bpp = 0xff;
|
||||
}
|
||||
|
||||
bool has_value() const {
|
||||
return (colour & 0xf) == (colour4bpp & 0xf);
|
||||
}
|
||||
|
||||
/// Colour as written by the CPU.
|
||||
uint8_t colour = 0x00;
|
||||
/// The low four bits of the CPU-written colour, repeated twice.
|
||||
uint8_t colour4bpp = 0xff;
|
||||
/// The low two bits of the CPU-written colour, repeated four times.
|
||||
uint8_t colour2bpp = 0xff;
|
||||
};
|
||||
|
||||
struct CommandContext {
|
||||
Vector source;
|
||||
Vector destination;
|
||||
Vector size;
|
||||
|
||||
uint8_t arguments = 0;
|
||||
Colour colour;
|
||||
Colour latched_colour;
|
||||
|
||||
enum class LogicalOperation {
|
||||
Copy = 0b0000,
|
||||
And = 0b0001,
|
||||
Or = 0b0010,
|
||||
Xor = 0b0011,
|
||||
Not = 0b0100,
|
||||
};
|
||||
LogicalOperation pixel_operation;
|
||||
bool test_source;
|
||||
};
|
||||
|
||||
struct ModeDescription {
|
||||
int width = 256;
|
||||
int pixels_per_byte = 4;
|
||||
bool rotate_address = false;
|
||||
int start_cycle = 0;
|
||||
int end_cycle = 0;
|
||||
};
|
||||
|
||||
struct Command {
|
||||
// In net:
|
||||
//
|
||||
// This command is blocked until @c access has been performed, reading
|
||||
// from or writing to @c value. It should not be performed until at least
|
||||
// @c cycles have passed.
|
||||
enum class AccessType {
|
||||
/// Plots a single pixel of the current contextual colour at @c destination,
|
||||
/// which occurs as a read, then a 24-cycle gap, then a write.
|
||||
PlotPoint,
|
||||
|
||||
/// Blocks until the next CPU write to the colour register.
|
||||
WaitForColourReceipt,
|
||||
|
||||
/// Writes an entire byte to the address containing the current @c destination.
|
||||
WriteByte,
|
||||
|
||||
/// Copies a single pixel from @c source location to @c destination,
|
||||
/// being a read, a 32-cycle gap, then a PlotPoint.
|
||||
CopyPoint,
|
||||
|
||||
/// Copies a complete byte from @c source location to @c destination,
|
||||
/// being a read, a 24-cycle gap, then a write.
|
||||
CopyByte,
|
||||
|
||||
/// Copies a single pixel from @c source to the colour status register.
|
||||
ReadPoint,
|
||||
|
||||
// ReadByte,
|
||||
// WaitForColourSend,
|
||||
};
|
||||
AccessType access = AccessType::PlotPoint;
|
||||
int cycles = 0;
|
||||
bool is_cpu_transfer = false;
|
||||
bool y_only = false;
|
||||
|
||||
/// Current command parameters.
|
||||
CommandContext &context;
|
||||
ModeDescription &mode_description;
|
||||
Command(CommandContext &context, ModeDescription &mode_description) : context(context), mode_description(mode_description) {}
|
||||
virtual ~Command() {}
|
||||
|
||||
/// @returns @c true if all output from this command is done; @c false otherwise.
|
||||
virtual bool done() = 0;
|
||||
|
||||
/// Repopulates the fields above with the next action to take, being provided with the
|
||||
/// number of pixels per byte in the current screen mode.
|
||||
virtual void advance() = 0;
|
||||
|
||||
protected:
|
||||
template <int axis, bool include_source> void advance_axis(int offset = 1) {
|
||||
context.destination.add<axis>(context.arguments & (0x4 << axis) ? -offset : offset);
|
||||
if constexpr (include_source) {
|
||||
context.source.add<axis>(context.arguments & (0x4 << axis) ? -offset : offset);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace Commands {
|
||||
|
||||
// MARK: - Line drawing.
|
||||
|
||||
/// Implements the LINE command, which is plain-old Bresenham.
|
||||
///
|
||||
/// Per Grauw timing is:
|
||||
///
|
||||
/// * 88 cycles between every pixel plot;
|
||||
/// * plus an additional 32 cycles if a step along the minor axis is taken.
|
||||
struct Line: public Command {
|
||||
public:
|
||||
Line(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
|
||||
// context.destination = start position;
|
||||
// context.size.v[0] = long side dots;
|
||||
// context.size.v[1] = short side dots;
|
||||
// context.arguments => direction
|
||||
|
||||
position_ = context.size.v[1];
|
||||
numerator_ = position_ << 1;
|
||||
denominator_ = context.size.v[0] << 1;
|
||||
|
||||
cycles = 32;
|
||||
access = AccessType::PlotPoint;
|
||||
}
|
||||
|
||||
bool done() final {
|
||||
return !context.size.v[0];
|
||||
}
|
||||
|
||||
void advance() final {
|
||||
--context.size.v[0];
|
||||
cycles = 88;
|
||||
|
||||
// b0: 1 => long direction is y;
|
||||
// 0 => long direction is x.
|
||||
//
|
||||
// b2: 1 => x direction is left;
|
||||
// 0 => x direction is right.
|
||||
//
|
||||
// b3: 1 => y direction is up;
|
||||
// 0 => y direction is down.
|
||||
if(context.arguments & 0x1) {
|
||||
advance_axis<1, false>();
|
||||
} else {
|
||||
advance_axis<0, false>();
|
||||
}
|
||||
|
||||
position_ -= numerator_;
|
||||
if(position_ < 0) {
|
||||
position_ += denominator_;
|
||||
cycles += 32;
|
||||
|
||||
if(context.arguments & 0x1) {
|
||||
advance_axis<0, false>();
|
||||
} else {
|
||||
advance_axis<1, false>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int position_, numerator_, denominator_, duration_;
|
||||
};
|
||||
|
||||
// MARK: - Single pixel manipulation.
|
||||
|
||||
/// Implements the PSET command, which plots a single pixel and POINT, which reads one.
|
||||
///
|
||||
/// No timings are documented, so this'll output or input as quickly as possible.
|
||||
template <bool is_read> struct Point: public Command {
|
||||
public:
|
||||
Point(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
|
||||
cycles = 0; // TODO.
|
||||
access = is_read ? AccessType::ReadPoint : AccessType::PlotPoint;
|
||||
}
|
||||
|
||||
bool done() final {
|
||||
return done_;
|
||||
}
|
||||
|
||||
void advance() final {
|
||||
done_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool done_ = false;
|
||||
};
|
||||
|
||||
// MARK: - Rectangular base.
|
||||
|
||||
/// Useful base class for anything that does logical work in a rectangle.
|
||||
template <bool logical, bool include_source> struct Rectangle: public Command {
|
||||
public:
|
||||
Rectangle(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
|
||||
if constexpr (include_source) {
|
||||
start_x_[0] = context.source.v[0];
|
||||
}
|
||||
start_x_[1] = context.destination.v[0];
|
||||
width_ = context.size.v[0];
|
||||
|
||||
if(!width_) {
|
||||
// Width = 0 => maximal width for this mode.
|
||||
// (aside: it's still unclear to me whether commands are
|
||||
// automatically clipped to the display; I think so but
|
||||
// don't want to spend any time on it until I'm certain)
|
||||
// context.size.v[0] = width_ = mode_description.width;
|
||||
}
|
||||
}
|
||||
|
||||
/// Advances the current destination and, if @c include_source is @c true also the source;
|
||||
/// @returns @c true if a new row was started; @c false otherwise.
|
||||
bool advance_pixel() {
|
||||
if constexpr (logical) {
|
||||
advance_axis<0, include_source>();
|
||||
--context.size.v[0];
|
||||
|
||||
if(context.size.v[0]) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
advance_axis<0, include_source>(mode_description.pixels_per_byte);
|
||||
context.size.v[0] -= mode_description.pixels_per_byte;
|
||||
|
||||
if(context.size.v[0] & ~(mode_description.pixels_per_byte - 1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
context.size.v[0] = width_;
|
||||
if constexpr (include_source) {
|
||||
context.source.v[0] = start_x_[0];
|
||||
}
|
||||
context.destination.v[0] = start_x_[1];
|
||||
|
||||
advance_axis<1, include_source>();
|
||||
--context.size.v[1];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool done() final {
|
||||
return !context.size.v[1] || !width_;
|
||||
}
|
||||
|
||||
private:
|
||||
int start_x_[2]{}, width_ = 0;
|
||||
};
|
||||
|
||||
// MARK: - Rectangular moves to/from CPU.
|
||||
|
||||
template <bool logical> struct MoveFromCPU: public Rectangle<logical, false> {
|
||||
MoveFromCPU(CommandContext &context, ModeDescription &mode_description) : Rectangle<logical, false>(context, mode_description) {
|
||||
Command::is_cpu_transfer = true;
|
||||
|
||||
// This command is started with the first colour ready to transfer.
|
||||
Command::cycles = 32;
|
||||
Command::access = logical ? Command::AccessType::PlotPoint : Command::AccessType::WriteByte;
|
||||
}
|
||||
|
||||
void advance() final {
|
||||
switch(Command::access) {
|
||||
default: break;
|
||||
|
||||
case Command::AccessType::WaitForColourReceipt:
|
||||
Command::cycles = 32;
|
||||
Command::access = logical ? Command::AccessType::PlotPoint : Command::AccessType::WriteByte;
|
||||
break;
|
||||
|
||||
case Command::AccessType::WriteByte:
|
||||
case Command::AccessType::PlotPoint:
|
||||
Command::cycles = 0;
|
||||
Command::access = Command::AccessType::WaitForColourReceipt;
|
||||
if(Rectangle<logical, false>::advance_pixel()) {
|
||||
Command::cycles = 64;
|
||||
// TODO: I'm not sure this will be honoured per the outer wrapping.
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// MARK: - Rectangular moves within VRAM.
|
||||
|
||||
enum class MoveType {
|
||||
Logical,
|
||||
HighSpeed,
|
||||
YOnly,
|
||||
};
|
||||
|
||||
template <MoveType type> struct Move: public Rectangle<type == MoveType::Logical, true> {
|
||||
static constexpr bool is_logical = type == MoveType::Logical;
|
||||
static constexpr bool is_y_only = type == MoveType::YOnly;
|
||||
using RectangleBase = Rectangle<is_logical, true>;
|
||||
|
||||
Move(CommandContext &context, ModeDescription &mode_description) : RectangleBase(context, mode_description) {
|
||||
Command::access = is_logical ? Command::AccessType::CopyPoint : Command::AccessType::CopyByte;
|
||||
Command::cycles = is_y_only ? 0 : 64;
|
||||
Command::y_only = is_y_only;
|
||||
}
|
||||
|
||||
void advance() final {
|
||||
Command::cycles = is_y_only ? 40 : 64;
|
||||
if(RectangleBase::advance_pixel()) {
|
||||
Command::cycles += is_y_only ? 0 : 64;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// MARK: - Rectangular fills.
|
||||
|
||||
template <bool logical> struct Fill: public Rectangle<logical, false> {
|
||||
using RectangleBase = Rectangle<logical, false>;
|
||||
|
||||
Fill(CommandContext &context, ModeDescription &mode_description) : RectangleBase(context, mode_description) {
|
||||
Command::cycles = logical ? 64 : 56;
|
||||
Command::access = logical ? Command::AccessType::PlotPoint : Command::AccessType::WriteByte;
|
||||
}
|
||||
|
||||
void advance() final {
|
||||
Command::cycles = logical ? 72 : 48;
|
||||
if(RectangleBase::advance_pixel()) {
|
||||
Command::cycles += logical ? 64 : 56;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* YamahaCommands_hpp */
|
@@ -14,8 +14,7 @@
|
||||
|
||||
#include "../../Reflection/Struct.hpp"
|
||||
|
||||
namespace GI {
|
||||
namespace AY38910 {
|
||||
namespace GI::AY38910 {
|
||||
|
||||
/*!
|
||||
A port handler provides all input for an AY's two 8-bit ports, and may optionally receive
|
||||
@@ -219,7 +218,6 @@ struct State: public Reflection::StructImpl<State> {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* AY_3_8910_hpp */
|
||||
|
@@ -11,8 +11,7 @@
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Apple {
|
||||
namespace Clock {
|
||||
namespace Apple::Clock {
|
||||
|
||||
/*!
|
||||
Models Apple's real-time clocks, as contained in the Macintosh and IIgs.
|
||||
@@ -293,7 +292,6 @@ class ParallelClock: public ClockStorage {
|
||||
uint8_t control_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Apple_RealTimeClock_hpp */
|
||||
|
@@ -11,8 +11,7 @@
|
||||
|
||||
#include "IWM.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Disk {
|
||||
namespace Apple::Disk {
|
||||
|
||||
class DiskIIDrive: public IWMDrive {
|
||||
public:
|
||||
@@ -27,7 +26,6 @@ class DiskIIDrive: public IWMDrive {
|
||||
int stepper_position_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DiskIIDrive_hpp */
|
||||
|
@@ -11,8 +11,7 @@
|
||||
|
||||
#include "IWM.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
namespace Apple::Macintosh {
|
||||
|
||||
class DoubleDensityDrive: public IWMDrive {
|
||||
public:
|
||||
@@ -47,7 +46,6 @@ class DoubleDensityDrive: public IWMDrive {
|
||||
int step_direction_ = 1;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MacintoshDoubleDensityDrive_hpp */
|
||||
|
@@ -13,8 +13,7 @@
|
||||
#include <functional>
|
||||
#include "LowFrequencyOscillator.hpp"
|
||||
|
||||
namespace Yamaha {
|
||||
namespace OPL {
|
||||
namespace Yamaha::OPL {
|
||||
|
||||
/*!
|
||||
Models an OPL-style envelope generator.
|
||||
@@ -258,7 +257,6 @@ template <int envelope_precision, int period_precision> class EnvelopeGenerator
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* EnvelopeGenerator_h */
|
||||
|
@@ -9,8 +9,7 @@
|
||||
#ifndef KeyLevelScaler_h
|
||||
#define KeyLevelScaler_h
|
||||
|
||||
namespace Yamaha {
|
||||
namespace OPL {
|
||||
namespace Yamaha::OPL {
|
||||
|
||||
template <int frequency_precision> class KeyLevelScaler {
|
||||
public:
|
||||
@@ -51,8 +50,6 @@ template <int frequency_precision> class KeyLevelScaler {
|
||||
int shift_ = 0;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* KeyLevelScaler_h */
|
||||
|
@@ -11,8 +11,7 @@
|
||||
|
||||
#include "../../../Numeric/LFSR.hpp"
|
||||
|
||||
namespace Yamaha {
|
||||
namespace OPL {
|
||||
namespace Yamaha::OPL {
|
||||
|
||||
/*!
|
||||
Models the output of the OPL low-frequency oscillator, which provides a couple of optional fixed-frequency
|
||||
@@ -62,7 +61,6 @@ class LowFrequencyOscillator {
|
||||
Numeric::LFSR<int, 0x800302> noise_source_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* LowFrequencyOscillator_hpp */
|
||||
|
@@ -12,8 +12,7 @@
|
||||
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
namespace Yamaha {
|
||||
namespace OPL {
|
||||
namespace Yamaha::OPL {
|
||||
|
||||
template <typename Child> class OPLBase: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
@@ -34,7 +33,6 @@ template <typename Child> class OPLBase: public ::Outputs::Speaker::SampleSource
|
||||
uint8_t selected_register_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OPLBase_h */
|
||||
|
@@ -13,8 +13,7 @@
|
||||
#include "LowFrequencyOscillator.hpp"
|
||||
#include "Tables.hpp"
|
||||
|
||||
namespace Yamaha {
|
||||
namespace OPL {
|
||||
namespace Yamaha::OPL {
|
||||
|
||||
/*!
|
||||
Models an OPL-style phase generator of templated precision; having been told its period ('f-num'), octave ('block') and
|
||||
@@ -119,7 +118,6 @@ template <int precision> class PhaseGenerator {
|
||||
int enable_vibrato_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* PhaseGenerator_h */
|
||||
|
@@ -9,8 +9,7 @@
|
||||
#ifndef Tables_hpp
|
||||
#define Tables_hpp
|
||||
|
||||
namespace Yamaha {
|
||||
namespace OPL {
|
||||
namespace Yamaha::OPL {
|
||||
|
||||
/*
|
||||
These are the OPL's built-in log-sin and exponentiation tables, as recovered by
|
||||
@@ -221,7 +220,6 @@ inline int LogSign::level(int fractional) const {
|
||||
return power_two(*this, fractional);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Tables_hpp */
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user