├── .github
└── workflows
│ └── build-test.yml
├── .gitignore
├── .gitmodules
├── CMakeLists.txt
├── CMakePresets.json
├── LICENSE
├── README.md
├── cmake
└── CodeCoverage.cmake
├── link.T
├── src
├── CMakeLists.txt
├── ay38910.cpp
├── ay38910.h
├── cartridge.cpp
├── cartridge.h
├── debugfont.cpp
├── gfxutil.h
├── libretro
│ ├── libretro.cpp
│ └── libretro.h
├── m6809.cpp
├── m6809.h
├── m6809_disassemble.cpp
├── m6809_disassemble.h
├── sysrom.h
├── updatetimer.h
├── veclib.h
├── vectorizer.cpp
├── vectorizer.h
├── vectrexia.cpp
├── vectrexia.h
├── via6522.cpp
├── via6522.h
└── win32.h
├── tests
├── CMakeLists.txt
├── cartridge_test.cpp
├── gfxutil_test.cpp
├── m6809_test.cpp
├── m6809opcode_test.cpp
└── vectrex_test.cpp
├── vcpkg-configuration.json
├── vcpkg.json
└── vectgif
├── CMakeLists.txt
├── gif.h
└── main.cpp
/.github/workflows/build-test.yml:
--------------------------------------------------------------------------------
1 | name: 'build-test'
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | build_and_test:
13 | name: '${{ matrix.os }}: build and unit tests'
14 | runs-on: ${{ matrix.os }}
15 | strategy:
16 | fail-fast: false
17 | matrix:
18 | os: [ubuntu-latest, macos-latest, windows-latest]
19 |
20 | steps:
21 | - uses: actions/checkout@v4
22 | with:
23 | submodules: true
24 |
25 | - name: switch to gcc-12 on linux
26 | if: matrix.os == 'ubuntu-latest'
27 | run: |
28 | sudo apt install gcc-12 g++-12
29 | sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 100 --slave /usr/bin/g++ g++ /usr/bin/g++-12 --slave /usr/bin/gcov gcov /usr/bin/gcov-12
30 | sudo update-alternatives --set gcc /usr/bin/gcc-12
31 |
32 | - uses: lukka/get-cmake@latest
33 |
34 | - name: Setup anew (or from cache) vcpkg
35 | uses: lukka/run-vcpkg@v11
36 |
37 | - name: Run CMake consuming CMakePreset.json and run vcpkg to build packages
38 | uses: lukka/run-cmake@v10
39 | with:
40 | configurePreset: ${{ matrix.os == 'ubuntu-latest' && 'ninja-debug' || matrix.os == 'macos-latest' && 'ninja-debug' || matrix.os == 'windows-latest' && 'msvc-x64-debug' }}
41 | buildPreset: ${{ matrix.os == 'ubuntu-latest' && 'ninja-debug' || matrix.os == 'macos-latest' && 'ninja-debug' || matrix.os == 'windows-latest' && 'msvc-x64-debug' }}
42 | testPreset: ${{ matrix.os == 'ubuntu-latest' && 'ninja-test' || matrix.os == 'macos-latest' && 'ninja-test' || matrix.os == 'windows-latest' && 'msvc-x64-test' }}
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Object files
2 | *.o
3 | *.ko
4 | *.obj
5 | *.elf
6 |
7 | # Precompiled Headers
8 | *.gch
9 | *.pch
10 |
11 | # Libraries
12 | *.lib
13 | *.a
14 | *.la
15 | *.lo
16 |
17 | # Shared objects (inc. Windows DLLs)
18 | *.dll
19 | *.so
20 | *.so.*
21 | *.dylib
22 |
23 | # Executables
24 | *.exe
25 | *.out
26 | *.app
27 | *.i*86
28 | *.x86_64
29 | *.hex
30 |
31 | # Debug files
32 | *.dSYM/
33 | build/
34 |
35 | # Vectrex files
36 | *.bin
37 | *.vec
38 |
39 | # Vagrant folder
40 | .vagrant/
41 |
42 | # IDE
43 | .idea
44 | cmake-build-*/
45 | .vs
46 | coverage.info
47 | CMakeSettings.json
48 | CMakeUserPresets.json
49 | vcpkg_installed/
50 | .vscode/
51 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "vcpkg"]
2 | path = vcpkg
3 | url = git@github.com:microsoft/vcpkg.git
4 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.9)
2 | project(vectrexia VERSION 0.1.0)
3 | enable_language(CXX)
4 |
5 | include(TestBigEndian)
6 | include(CTest)
7 |
8 | set(CMAKE_CXX_STANDARD 23)
9 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
10 | set(CMAKE_CXX_EXTENSIONS OFF)
11 |
12 |
13 | if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
14 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
15 | endif()
16 |
17 | TEST_BIG_ENDIAN(IS_BIG_ENDIAN)
18 |
19 | if (IS_BIG_ENDIAN)
20 | message(STATUS "System is big endian")
21 | add_definitions(-D__MSB_FIRST)
22 | else()
23 | message(STATUS "System is little endian")
24 | endif()
25 |
26 | enable_testing()
27 | add_subdirectory(src)
28 | add_subdirectory(tests)
29 | add_subdirectory(vectgif)
30 |
--------------------------------------------------------------------------------
/CMakePresets.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "configurePresets": [
4 | {
5 | "name": "vcpkg-base",
6 | "hidden": true,
7 | "binaryDir": "${sourceDir}/build/${presetName}",
8 | "cacheVariables": {
9 | "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
10 | "VCPKG_FEATURE_FLAGS": "manifests"
11 | }
12 | },
13 | {
14 | "name": "base-debug",
15 | "hidden": true,
16 | "inherits": "vcpkg-base",
17 | "cacheVariables": {
18 | "CMAKE_BUILD_TYPE": "Debug",
19 | "CMAKE_CXX_STANDARD": "23"
20 | }
21 | },
22 | {
23 | "name": "base-release",
24 | "hidden": true,
25 | "inherits": "vcpkg-base",
26 | "cacheVariables": {
27 | "CMAKE_BUILD_TYPE": "Release",
28 | "CMAKE_CXX_STANDARD": "23"
29 | }
30 | },
31 | {
32 | "name": "visualstudio-vcpkg",
33 | "hidden": true,
34 | "generator": "Visual Studio 17 2022",
35 | "inherits": "vcpkg-base"
36 | },
37 | {
38 | "name": "ninja-vcpkg",
39 | "hidden": true,
40 | "generator": "Ninja",
41 | "inherits": "vcpkg-base"
42 | },
43 | {
44 | "name": "msvc-x64-debug",
45 | "inherits": [
46 | "visualstudio-vcpkg",
47 | "base-debug"
48 | ],
49 | "binaryDir": "${sourceDir}/build/x64-Debug",
50 | "cacheVariables": {
51 | "CMAKE_GENERATOR_PLATFORM": "x64",
52 | "CMAKE_CXX_FLAGS": "/DDEBUG /W3 /MDd /EHsc",
53 | "CMAKE_C_FLAGS": "/DDEBUG /W3 /MDd /EHsc"
54 | }
55 | },
56 | {
57 | "name": "msvc-x64-release",
58 | "inherits": [
59 | "visualstudio-vcpkg",
60 | "base-release"
61 | ],
62 | "binaryDir": "${sourceDir}/build/x64-Release",
63 | "cacheVariables": {
64 | "CMAKE_GENERATOR_PLATFORM": "x64",
65 | "CMAKE_CXX_FLAGS": "/O2 /MD /EHsc",
66 | "CMAKE_C_FLAGS": "/O2 /MD /EHsc"
67 | }
68 | },
69 | {
70 | "name": "ninja-debug",
71 | "inherits": [
72 | "ninja-vcpkg",
73 | "base-debug"
74 | ],
75 | "binaryDir": "${sourceDir}/build/ninja-debug"
76 | },
77 | {
78 | "name": "ninja-release",
79 | "inherits": [
80 | "ninja-vcpkg",
81 | "base-release"
82 | ],
83 | "binaryDir": "${sourceDir}/build/ninja-release"
84 | }
85 | ],
86 | "buildPresets": [
87 | {
88 | "name": "msvc-x64-debug",
89 | "configurePreset": "msvc-x64-debug",
90 | "description": "Build for Windows x64 Debug",
91 | "verbose": true
92 | },
93 | {
94 | "name": "msvc-x64-release",
95 | "configurePreset": "msvc-x64-release",
96 | "description": "Build for Windows x64 Release",
97 | "verbose": true
98 | },
99 | {
100 | "name": "ninja-debug",
101 | "configurePreset": "ninja-debug",
102 | "description": "Build for Ninja debug",
103 | "verbose": true
104 | },
105 | {
106 | "name": "ninja-release",
107 | "configurePreset": "ninja-release",
108 | "description": "Build for Ninja Release",
109 | "verbose": true
110 | }
111 | ],
112 | "testPresets": [
113 | {
114 | "name": "msvc-x64-test",
115 | "configurePreset": "msvc-x64-debug",
116 | "description": "Run tests for Windows x64 Debug",
117 | "displayName": "Tests (MSVC)"
118 | },
119 | {
120 | "name": "ninja-test",
121 | "configurePreset": "ninja-debug",
122 | "description": "Run tests for Ninja Debug",
123 | "displayName": "Tests (Ninja)"
124 | }
125 | ]
126 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
341 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vectrexia
2 | 
3 |
4 | Vectrexia is a work in progress Vectrex emulator written as a `libretro` core. It is being developed as part of a series of blog posts about writing a [Vectrex Emulator](https://beardypig.github.io/2016/01/15/emulator-build-along-1/).
5 |
6 |
7 | ## Compilation
8 |
9 | To compile this `libretro` core use `cmake`. Your C++ compiler must support the C++23 standard, eg. `clang >= 17` or `gcc >= 12` or `MSVC >= 17.5 `.
10 |
11 | ``` shell
12 | $ cmake --preset ninja-debug
13 | $ cmake --build --preset ninja-debug
14 | ```
15 |
16 | The tests can also be run with CTest
17 | ```shell
18 | $ ctest --preset ninja-test
19 | ```
20 |
21 | ## Authors
22 | - @beardypig
23 | - @pelorat
24 |
--------------------------------------------------------------------------------
/cmake/CodeCoverage.cmake:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2012 - 2017, Lars Bilke
2 | # All rights reserved.
3 | #
4 | # Redistribution and use in source and binary forms, with or without modification,
5 | # are permitted provided that the following conditions are met:
6 | #
7 | # 1. Redistributions of source code must retain the above copyright notice, this
8 | # list of conditions and the following disclaimer.
9 | #
10 | # 2. Redistributions in binary form must reproduce the above copyright notice,
11 | # this list of conditions and the following disclaimer in the documentation
12 | # and/or other materials provided with the distribution.
13 | #
14 | # 3. Neither the name of the copyright holder nor the names of its contributors
15 | # may be used to endorse or promote products derived from this software without
16 | # specific prior written permission.
17 | #
18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | #
29 | # CHANGES:
30 | #
31 | # 2012-01-31, Lars Bilke
32 | # - Enable Code Coverage
33 | #
34 | # 2013-09-17, Joakim Söderberg
35 | # - Added support for Clang.
36 | # - Some additional usage instructions.
37 | #
38 | # 2016-02-03, Lars Bilke
39 | # - Refactored functions to use named parameters
40 | #
41 | # 2017-06-02, Lars Bilke
42 | # - Merged with modified version from github.com/ufz/ogs
43 | #
44 | #
45 | # USAGE:
46 | #
47 | # 1. Copy this file into your cmake modules path.
48 | #
49 | # 2. Add the following line to your CMakeLists.txt:
50 | # include(CodeCoverage)
51 | #
52 | # 3. Append necessary compiler flags:
53 | # APPEND_COVERAGE_COMPILER_FLAGS()
54 | #
55 | # 4. If you need to exclude additional directories from the report, specify them
56 | # using the COVERAGE_LCOV_EXCLUDES variable before calling SETUP_TARGET_FOR_COVERAGE_LCOV.
57 | # Example:
58 | # set(COVERAGE_LCOV_EXCLUDES 'dir1/*' 'dir2/*')
59 | #
60 | # 5. Use the functions described below to create a custom make target which
61 | # runs your test executable and produces a code coverage report.
62 | #
63 | # 6. Build a Debug build:
64 | # cmake -DCMAKE_BUILD_TYPE=Debug ..
65 | # make
66 | # make my_coverage_target
67 | #
68 |
69 | include(CMakeParseArguments)
70 |
71 | # Check prereqs
72 | find_program( GCOV_PATH gcov )
73 | find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl)
74 | find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat )
75 | find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test)
76 | find_program( SIMPLE_PYTHON_EXECUTABLE python )
77 |
78 | if(NOT GCOV_PATH)
79 | message(FATAL_ERROR "gcov not found! Aborting...")
80 | endif() # NOT GCOV_PATH
81 |
82 | if("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
83 | if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3)
84 | message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...")
85 | endif()
86 | elseif(NOT CMAKE_COMPILER_IS_GNUCXX)
87 | message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...")
88 | endif()
89 |
90 | set(COVERAGE_COMPILER_FLAGS "-g -O0 --coverage -fprofile-arcs -ftest-coverage"
91 | CACHE INTERNAL "")
92 |
93 | set(CMAKE_CXX_FLAGS_COVERAGE
94 | ${COVERAGE_COMPILER_FLAGS}
95 | CACHE STRING "Flags used by the C++ compiler during coverage builds."
96 | FORCE )
97 | set(CMAKE_C_FLAGS_COVERAGE
98 | ${COVERAGE_COMPILER_FLAGS}
99 | CACHE STRING "Flags used by the C compiler during coverage builds."
100 | FORCE )
101 | set(CMAKE_EXE_LINKER_FLAGS_COVERAGE
102 | ""
103 | CACHE STRING "Flags used for linking binaries during coverage builds."
104 | FORCE )
105 | set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
106 | ""
107 | CACHE STRING "Flags used by the shared libraries linker during coverage builds."
108 | FORCE )
109 | mark_as_advanced(
110 | CMAKE_CXX_FLAGS_COVERAGE
111 | CMAKE_C_FLAGS_COVERAGE
112 | CMAKE_EXE_LINKER_FLAGS_COVERAGE
113 | CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
114 |
115 | if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
116 | message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading")
117 | endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug"
118 |
119 | if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
120 | link_libraries(gcov)
121 | else()
122 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
123 | endif()
124 |
125 | # Defines a target for running and collection code coverage information
126 | # Builds dependencies, runs the given executable and outputs reports.
127 | # NOTE! The executable should always have a ZERO as exit code otherwise
128 | # the coverage generation will not complete.
129 | #
130 | # SETUP_TARGET_FOR_COVERAGE_LCOV(
131 | # NAME testrunner_coverage # New target name
132 | # EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
133 | # DEPENDENCIES testrunner # Dependencies to build first
134 | # )
135 | function(SETUP_TARGET_FOR_COVERAGE_LCOV)
136 |
137 | set(options NONE)
138 | set(oneValueArgs NAME)
139 | set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
140 | cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
141 |
142 | if(NOT LCOV_PATH)
143 | message(FATAL_ERROR "lcov not found! Aborting...")
144 | endif() # NOT LCOV_PATH
145 |
146 | if(NOT GENHTML_PATH)
147 | message(FATAL_ERROR "genhtml not found! Aborting...")
148 | endif() # NOT GENHTML_PATH
149 |
150 | # Setup target
151 | add_custom_target(${Coverage_NAME}
152 |
153 | # Cleanup lcov
154 | COMMAND ${LCOV_PATH} --directory . --zerocounters
155 | # Create baseline to make sure untouched files show up in the report
156 | COMMAND ${LCOV_PATH} -c -i -d . -o ${Coverage_NAME}.base
157 |
158 | # Run tests
159 | COMMAND ${Coverage_EXECUTABLE}
160 |
161 | # Capturing lcov counters and generating report
162 | COMMAND ${LCOV_PATH} --directory . --capture --output-file ${Coverage_NAME}.info
163 | # add baseline counters
164 | COMMAND ${LCOV_PATH} -a ${Coverage_NAME}.base -a ${Coverage_NAME}.info --output-file ${Coverage_NAME}.total
165 | COMMAND ${LCOV_PATH} --remove ${Coverage_NAME}.total ${COVERAGE_LCOV_EXCLUDES} --output-file ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned
166 | COMMAND ${GENHTML_PATH} -o ${Coverage_NAME} ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned
167 | COMMAND ${CMAKE_COMMAND} -E remove ${Coverage_NAME}.base ${Coverage_NAME}.total ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned
168 |
169 | WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
170 | DEPENDS ${Coverage_DEPENDENCIES}
171 | COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report."
172 | )
173 |
174 | # Show where to find the lcov info report
175 | add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
176 | COMMAND ;
177 | COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info."
178 | )
179 |
180 | # Show info where to find the report
181 | add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
182 | COMMAND ;
183 | COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report."
184 | )
185 |
186 | endfunction() # SETUP_TARGET_FOR_COVERAGE_LCOV
187 |
188 | # Defines a target for running and collection code coverage information
189 | # Builds dependencies, runs the given executable and outputs reports.
190 | # NOTE! The executable should always have a ZERO as exit code otherwise
191 | # the coverage generation will not complete.
192 | #
193 | # SETUP_TARGET_FOR_COVERAGE_GCOVR_XML(
194 | # NAME ctest_coverage # New target name
195 | # EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
196 | # DEPENDENCIES executable_target # Dependencies to build first
197 | # )
198 | function(SETUP_TARGET_FOR_COVERAGE_GCOVR_XML)
199 |
200 | set(options NONE)
201 | set(oneValueArgs NAME)
202 | set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
203 | cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
204 |
205 | if(NOT SIMPLE_PYTHON_EXECUTABLE)
206 | message(FATAL_ERROR "python not found! Aborting...")
207 | endif() # NOT SIMPLE_PYTHON_EXECUTABLE
208 |
209 | if(NOT GCOVR_PATH)
210 | message(FATAL_ERROR "gcovr not found! Aborting...")
211 | endif() # NOT GCOVR_PATH
212 |
213 | # Combine excludes to several -e arguments
214 | set(GCOVR_EXCLUDES "")
215 | foreach(EXCLUDE ${COVERAGE_GCOVR_EXCLUDES})
216 | list(APPEND GCOVR_EXCLUDES "-e")
217 | list(APPEND GCOVR_EXCLUDES "${EXCLUDE}")
218 | endforeach()
219 |
220 | add_custom_target(${Coverage_NAME}
221 | # Run tests
222 | ${Coverage_EXECUTABLE}
223 |
224 | # Running gcovr
225 | COMMAND ${GCOVR_PATH} --xml
226 | -r ${CMAKE_SOURCE_DIR} ${GCOVR_EXCLUDES}
227 | --object-directory=${PROJECT_BINARY_DIR}
228 | -o ${Coverage_NAME}.xml
229 | WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
230 | DEPENDS ${Coverage_DEPENDENCIES}
231 | COMMENT "Running gcovr to produce Cobertura code coverage report."
232 | )
233 |
234 | # Show info where to find the report
235 | add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
236 | COMMAND ;
237 | COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml."
238 | )
239 |
240 | endfunction() # SETUP_TARGET_FOR_COVERAGE_GCOVR_XML
241 |
242 | # Defines a target for running and collection code coverage information
243 | # Builds dependencies, runs the given executable and outputs reports.
244 | # NOTE! The executable should always have a ZERO as exit code otherwise
245 | # the coverage generation will not complete.
246 | #
247 | # SETUP_TARGET_FOR_COVERAGE_GCOVR_HTML(
248 | # NAME ctest_coverage # New target name
249 | # EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
250 | # DEPENDENCIES executable_target # Dependencies to build first
251 | # )
252 | function(SETUP_TARGET_FOR_COVERAGE_GCOVR_HTML)
253 |
254 | set(options NONE)
255 | set(oneValueArgs NAME)
256 | set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
257 | cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
258 |
259 | if(NOT SIMPLE_PYTHON_EXECUTABLE)
260 | message(FATAL_ERROR "python not found! Aborting...")
261 | endif() # NOT SIMPLE_PYTHON_EXECUTABLE
262 |
263 | if(NOT GCOVR_PATH)
264 | message(FATAL_ERROR "gcovr not found! Aborting...")
265 | endif() # NOT GCOVR_PATH
266 |
267 | # Combine excludes to several -e arguments
268 | set(GCOVR_EXCLUDES "")
269 | foreach(EXCLUDE ${COVERAGE_GCOVR_EXCLUDES})
270 | list(APPEND GCOVR_EXCLUDES "-e")
271 | list(APPEND GCOVR_EXCLUDES "${EXCLUDE}")
272 | endforeach()
273 |
274 | add_custom_target(${Coverage_NAME}
275 | # Run tests
276 | ${Coverage_EXECUTABLE}
277 |
278 | # Create folder
279 | COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME}
280 |
281 | # Running gcovr
282 | COMMAND ${GCOVR_PATH} --html --html-details
283 | -r ${CMAKE_SOURCE_DIR} ${GCOVR_EXCLUDES}
284 | --object-directory=${PROJECT_BINARY_DIR}
285 | -o ${Coverage_NAME}/index.html
286 | WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
287 | DEPENDS ${Coverage_DEPENDENCIES}
288 | COMMENT "Running gcovr to produce HTML code coverage report."
289 | )
290 |
291 | # Show info where to find the report
292 | add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
293 | COMMAND ;
294 | COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report."
295 | )
296 |
297 | endfunction() # SETUP_TARGET_FOR_COVERAGE_GCOVR_HTML
298 |
299 | function(APPEND_COVERAGE_COMPILER_FLAGS)
300 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
301 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
302 | message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}")
303 | endfunction() # APPEND_COVERAGE_COMPILER_FLAGS
304 |
--------------------------------------------------------------------------------
/link.T:
--------------------------------------------------------------------------------
1 | {
2 | global: retro_*;
3 | local: *;
4 | };
5 |
--------------------------------------------------------------------------------
/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | include_directories(.)
2 | set(LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/link.T")
3 |
4 | #
5 | # Vectrexia target source files
6 | #
7 | set(VECTREXIA_SOURCE
8 | libretro/libretro.cpp
9 | vectrexia.cpp
10 | cartridge.cpp
11 | m6809_disassemble.cpp
12 | m6809.cpp
13 | via6522.cpp
14 | ay38910.cpp
15 | vectorizer.cpp gfxutil.h
16 | debugfont.cpp)
17 |
18 | # vectrexia_libretro
19 | # Main target; a shared library (.so / .dll)
20 | #
21 | add_library(vectrexia_libretro SHARED ${VECTREXIA_SOURCE})
22 |
23 | # vectrexia_libretro_static
24 | # Extra MSVC target; static library needed for compiling the tests
25 | # on windows using Visual Studio.
26 | #
27 | if(MSVC)
28 | add_library(vectrexia_libretro_static STATIC ${VECTREXIA_SOURCE})
29 | endif()
30 |
31 | set(CMAKE_EXE_LINKER_FLAGS "-T ${LINKER_SCRIPT}")
32 | set_target_properties(vectrexia_libretro PROPERTIES PREFIX "")
33 | set_source_files_properties(libretro/libretro.cpp
34 | PROPERTIES OBJECT_DEPENDS ${LINKER_SCRIPT})
35 |
36 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DVECTREXIA_DEBUG")
37 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DVECTREXIA_DEBUG")
38 |
39 | if (VECTORIZER_DEBUG)
40 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DVECTORIZER_DEBUG")
41 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DVECTORIZER_DEBUG")
42 | endif()
--------------------------------------------------------------------------------
/src/ay38910.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 beardypig
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include "ay38910.h"
24 |
25 | void AY38910::Step(uint8_t bus, uint8_t bc1, uint8_t bc2, uint8_t bdir)
26 | {
27 | switch(bdir << 2 | bc2 << 1 | bc1)
28 | {
29 | case PSG_NACT: // disabled
30 | case PSG_IAB:
31 | case PSG_DW:
32 | break;
33 | case PSG_LATCH_ADDR: // latch address
34 | case PSG_LATCH_BAR:
35 | case PSG_LATCH_INTAK:
36 | // NOTE: address are octal in the documentation
37 | addr = (uint8_t)(bus & 0xf);
38 | break;
39 | case PSG_DWS:
40 | Write(addr, bus);
41 | break;
42 | case PSG_DTS:
43 | // read callback
44 | if (store_reg_func) {
45 | if (addr == PSG_REG_PORTA) { // read from IO
46 | if (read_io_func)
47 | store_reg_func(store_reg_ref, read_io_func(read_io_ref));
48 | }
49 | else { // read from regular register
50 | store_reg_func(store_reg_ref, regs[addr]);
51 | }
52 | }
53 | break;
54 | default:
55 | break;
56 | }
57 | }
58 |
59 | void AY38910::Write(uint8_t reg, uint8_t value)
60 | {
61 | regs[reg] = value;
62 |
63 | switch(reg)
64 | {
65 | // from the datasheet (256CT10 + FT10)
66 | // the tone period is made up of the lower 4 bits of the coarse tone register and the fine register
67 | // the maximum value for period is 4095 and the minimum value is 1
68 | case PSG_REG_A_FINE:
69 | case PSG_REG_A_COARSE:
70 | channel_a.setPeriod((uint8_t) (regs[PSG_REG_A_COARSE] & 0xf), regs[PSG_REG_A_FINE]);
71 | break;
72 |
73 | case PSG_REG_B_FINE: // same as period A
74 | case PSG_REG_B_COARSE:
75 | channel_b.setPeriod((uint8_t) (regs[PSG_REG_B_COARSE] & 0xf), regs[PSG_REG_B_FINE]);
76 | break;
77 |
78 | case PSG_REG_C_FINE: // same as period A/B
79 | case PSG_REG_C_COARSE:
80 | channel_c.setPeriod((uint8_t) (regs[PSG_REG_C_COARSE] & 0xf), regs[PSG_REG_C_FINE]);
81 | break;
82 |
83 | case PSG_REG_NOISE:
84 | channel_noise.setPeriod((uint8_t) (regs[PSG_REG_NOISE] & 0x1f));
85 | break;
86 |
87 | case PSG_REG_MIXER_CTRL:
88 | // disable and enable the channels and io
89 | channel_a.enabled = !(value & 1);
90 | channel_a.noise_enabled = !(value & (1 << 3));
91 | channel_b.enabled = !(value & 2);
92 | channel_b.noise_enabled = !(value & (2 << 3));
93 | channel_c.enabled = !(value & 4);
94 | channel_c.noise_enabled = !(value & (4 << 3));
95 | break;
96 |
97 | case PSG_REG_A_AMPL:
98 | channel_a.amplitude_mode = (uint8_t)((value >> 4) & 1);
99 | channel_a.amplitude_fixed = (uint8_t) (value & 0xf);
100 | break;
101 | case PSG_REG_B_AMPL:
102 | channel_b.amplitude_mode = (uint8_t)((value >> 4) & 1);
103 | channel_b.amplitude_fixed = (uint8_t) (value & 0xf);
104 | break;
105 | case PSG_REG_C_AMPL:
106 | channel_c.amplitude_mode = (uint8_t)((value >> 4) & 1);
107 | channel_c.amplitude_fixed = (uint8_t) (value & 0xf);
108 | break;
109 |
110 | case PSG_REG_ENV_FINE:
111 | case PSG_REG_ENV_COARSE:
112 | envelope.setPeriod(regs[PSG_REG_ENV_COARSE], regs[PSG_REG_ENV_FINE]);
113 | break;
114 | case PSG_REG_ENV_CTRL:
115 | // control the shape of the envelope
116 | envelope.setControl((uint8_t) (value & 0xf));
117 | break;
118 |
119 | default:break;
120 | }
121 | }
122 |
123 | void AY38910::SetIOReadCallback(AY38910::read_io_callback func, intptr_t ref)
124 | {
125 | read_io_func = func;
126 | read_io_ref = ref;
127 | }
128 |
129 | void AY38910::SetRegStoreCallback(AY38910::store_reg_callback func, intptr_t ref)
130 | {
131 | store_reg_func = func;
132 | store_reg_ref = ref;
133 | }
134 |
135 | void AY38910::FillBuffer(uint8_t *buffer, size_t length)
136 | {
137 | int16_t ampl_a = 0, ampl_b = 0, ampl_c = 0;
138 | memset(buffer, 0, length);
139 |
140 | for (int i = 0; i < length; i++)
141 | {
142 | auto noise = channel_noise.step();
143 | auto envelope_counter = envelope.step();
144 |
145 | ampl_a = channel_a.step(noise, envelope_counter);
146 | ampl_b = channel_b.step(noise, envelope_counter);
147 | ampl_c = channel_c.step(noise, envelope_counter);
148 |
149 | *(buffer++) = (uint8_t)((uint16_t)(((channel_a_on ? ampl_a : 0) +
150 | (channel_b_on ? ampl_b : 0) +
151 | (channel_c_on ? ampl_c : 0)) / 3.0) >> 8);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/ay38910.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 beardypig
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 | #ifndef VECTREXIA_AY38910_H
20 | #define VECTREXIA_AY38910_H
21 |
22 | #include
23 | #include
24 | #include
25 |
26 | const double pi = std::acos(-1);
27 |
28 | enum {
29 | PSG_NACT,
30 | PSG_LATCH_ADDR,
31 | PSG_IAB,
32 | PSG_DTS, // read
33 | PSG_LATCH_BAR,
34 | PSG_DW,
35 | PSG_DWS, // write
36 | PSG_LATCH_INTAK
37 | };
38 |
39 | enum {
40 | PSG_REG_A_FINE = 000,
41 | PSG_REG_A_COARSE = 001,
42 | PSG_REG_B_FINE = 002,
43 | PSG_REG_B_COARSE = 003,
44 | PSG_REG_C_FINE = 004,
45 | PSG_REG_C_COARSE = 005,
46 | PSG_REG_NOISE = 006,
47 | PSG_REG_MIXER_CTRL = 007,
48 | PSG_REG_A_AMPL = 010,
49 | PSG_REG_B_AMPL = 011,
50 | PSG_REG_C_AMPL = 012,
51 | PSG_REG_ENV_FINE = 013,
52 | PSG_REG_ENV_COARSE = 014,
53 | PSG_REG_ENV_CTRL = 015,
54 | PSG_REG_PORTA = 016,
55 | PSG_REG_PORTB = 017
56 | };
57 |
58 | class AY38910
59 | {
60 | using read_io_callback = uint8_t (*)(intptr_t);
61 | using store_reg_callback = void (*)(intptr_t, uint8_t);
62 |
63 | template
64 | struct periodic_t
65 | {
66 | uint16_t period_ = 1;
67 | double frequency_;
68 | double velocity_ = 0.0;
69 |
70 | const int16_t amplitude_table[16] = { 0x0000, 0x0055, 0x0079, 0x00AB, 0x00F1, 0x0155, 0x01E3, 0x02AA,
71 | 0x03C5, 0x0555, 0x078B, 0x0AAB, 0x0F16, 0x1555, 0x1E2B, 0x2AAA };
72 |
73 | double setPeriod(uint8_t coarse, uint8_t fine)
74 | {
75 | period_ = std::max((uint16_t) ((coarse << 8) | fine), 1);
76 | frequency_ = 1.5e6/(period_ * 16);
77 | velocity_ = (frequency_ / (double)sample_rate) * 4.0;
78 | return frequency_;
79 | }
80 | double setPeriod(uint8_t fine)
81 | {
82 | return setPeriod(0, fine);
83 | }
84 |
85 | };
86 |
87 | template
88 | struct channel_t : periodic_t
89 | {
90 | uint8_t amplitude_mode = 0; // fixed or envelope variable
91 | uint8_t amplitude_fixed;
92 | bool enabled, noise_enabled;
93 | double radian_;
94 |
95 | int16_t step(int16_t noise, uint8_t envelope)
96 | {
97 | int16_t out;
98 |
99 | if (noise_enabled)
100 | {
101 | out = noise;
102 | }
103 | else if (!enabled) // redundant - && !noise_enabled)
104 | {
105 | // if the channel is disable and the noise is disabled then the sound can be modulated by the volume
106 | out = 1;
107 | }
108 | else // enabled channel
109 | {
110 | out = (int16_t) ((std::sin(radian_) > 0.0 ? 1.0 : -1.0));
111 | }
112 |
113 | radian_ += periodic_t::velocity_;
114 | return out * amplitude(envelope);
115 |
116 | }
117 |
118 | inline int16_t amplitude(uint8_t envelope_amplitude) const
119 | {
120 | //return periodic_t::amplitude_table[amplitude_fixed];
121 | if (!amplitude_mode)
122 | return periodic_t::amplitude_table[amplitude_fixed];
123 | else
124 | return periodic_t::amplitude_table[envelope_amplitude];
125 | }
126 | };
127 |
128 | template
129 | struct noise_t : periodic_t
130 | {
131 | uint32_t rng = 1;
132 | double tick_count_ = 0.0;
133 | // returns one sample, the time period covered by this method is 1/sample_rate
134 | // the rng is ticking a long at frequency = 1.5e6/(period * 16);
135 | int16_t step()
136 | {
137 | while (tick_count_ > 1.0)
138 | {
139 | rng ^= (((rng & 1) ^ ((rng >> 3) & 1)) << 17);
140 | rng >>= 1;
141 | tick_count_ -= 1.0;
142 | }
143 |
144 | tick_count_ += periodic_t::velocity_;
145 | return (int16_t) (rng & 1);
146 | }
147 | };
148 |
149 | template
150 | struct envelope_t : periodic_t
151 | {
152 | uint8_t counter = 0, envelope_cycle = 0;
153 | uint8_t hold, attack, alternate, cont, direction;
154 | bool holding = false;
155 | double tick_count_ = 0.0;
156 |
157 | void setControl(uint8_t value)
158 | {
159 | hold = (uint8_t) (value & 1);
160 | attack = (uint8_t) ((value >> 1) & 1);
161 | alternate = (uint8_t) ((value >> 2) & 1);
162 | cont = (uint8_t) ((value >> 3) & 1);
163 | direction = attack;
164 | holding = false;
165 | //printf("Envelope control changed: continue=%d, attack=%d, alternate=%d, hold=%d\n",
166 | // cont, attack, alternate, hold);
167 | }
168 |
169 | inline void step_cycle()
170 | {
171 | // the divider is really 256 but we need the counter to go at 16 * that
172 | // ie. a cycle last 16 ticks (16*16 = 265)
173 | if ((envelope_cycle++ & 0xf) == 0xf)
174 | {
175 | envelope_cycle = 0;
176 |
177 | holding = hold;
178 | // toggle the direction of the counter
179 | if (alternate)
180 | {
181 | direction ^= 1;
182 | }
183 |
184 | if (cont)
185 | {
186 | counter = 0;
187 | }
188 | }
189 | }
190 |
191 | uint8_t step()
192 | {
193 | while (tick_count_ > 1.0)
194 | {
195 | //if ((!direction && counter > 0) || (direction && counter < 0xf))
196 | // printf("ticking envelop sub-cycle: counter=%d going %s (holding: %d)\n", counter, (direction) ? "up" : "down", holding);
197 |
198 | step_cycle();
199 | if (!holding)
200 | {
201 | // direction can be toggled by the alternate flag
202 | // count down to 0 when direction/attack is 0
203 | if (!direction && counter > 0)
204 | {
205 | counter -= 1;
206 | }
207 | // count up to 0xf when direction/attack is 1
208 | else if (direction && counter < 0xf)
209 | {
210 | counter += 1;
211 | }
212 | }
213 |
214 | tick_count_ -= 1.0;
215 | }
216 |
217 | tick_count_ += periodic_t::velocity_;
218 | return (uint8_t) (counter & 0xf);
219 | }
220 | };
221 |
222 | uint8_t regs[0xf];
223 | uint8_t addr;
224 |
225 |
226 | // port a/b read callbacks
227 | store_reg_callback store_reg_func = nullptr;
228 |
229 | intptr_t store_reg_ref = 0;
230 | read_io_callback read_io_func = nullptr;
231 | intptr_t read_io_ref = 0;
232 | public:
233 |
234 | bool channel_a_on = true, channel_b_on = true, channel_c_on = true;
235 |
236 | void Step(uint8_t bus, uint8_t bc1, uint8_t bc2, uint8_t bdir);
237 | void SetIOReadCallback(read_io_callback func, intptr_t ref);
238 | void SetRegStoreCallback(store_reg_callback func, intptr_t ref);
239 | void Write(uint8_t reg, uint8_t value);
240 | void FillBuffer(uint8_t * const buffer, size_t length);
241 |
242 | channel_t<44100> channel_a, channel_b, channel_c;
243 | noise_t<44100> channel_noise;
244 | envelope_t<44100> envelope;
245 | };
246 |
247 |
248 | #endif //VECTREXIA_AY38910_H
249 |
--------------------------------------------------------------------------------
/src/cartridge.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 beardypig
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 | #include "cartridge.h"
20 | #include
21 | #include
22 |
23 | void Cartridge::Load(const uint8_t *data, size_t size)
24 | {
25 | if (size <= MAX_ROM_SIZE) {
26 | // if the ROM is larger than 32K and smaller than or equal to 64K then it uses PB7 for bank switching
27 | memcpy(rom_.data(), data, size);
28 |
29 | if (size <= REGULAR_ROM_SIZE)
30 | {
31 | memcpy(rom_.data()+REGULAR_ROM_SIZE, data, size);
32 | }
33 | else {
34 | printf("[CART]: Loading a bank switched ROM\n");
35 | }
36 | is_loaded_flag_ = true;
37 | }
38 | else
39 | {
40 | Unload();
41 | }
42 |
43 | }
44 |
45 | void Cartridge::Unload()
46 | {
47 | rom_.fill(0);
48 | is_loaded_flag_ = false;
49 | }
50 |
51 | bool Cartridge::is_loaded()
52 | {
53 | return is_loaded_flag_;
54 | }
55 |
56 | uint8_t Cartridge::Read(uint16_t addr, uint8_t pb6)
57 | {
58 | // bank switch with pb6 by setting the MSB to pb6
59 | return rom_[(addr & ~0x8000) | ((pb6 ^ 1) << 15)];
60 | }
61 |
62 | void Cartridge::Write(uint16_t addr, uint8_t data, uint8_t pb6)
63 | {
64 | (void)addr;
65 | (void)data;
66 | (void)pb6;
67 | }
68 |
--------------------------------------------------------------------------------
/src/cartridge.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 beardypig
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 | #ifndef VECTREXIA_CARTRIDGE_H
20 | #define VECTREXIA_CARTRIDGE_H
21 |
22 | #include
23 | #include
24 | #include
25 |
26 | class Cartridge
27 | {
28 | const int REGULAR_ROM_SIZE = 32768;
29 | const int MAX_ROM_SIZE = 65536;
30 | // 64K of cartridge for bank switched ROMs
31 | std::array rom_ = {};
32 | bool is_loaded_flag_ = false;
33 |
34 | public:
35 | Cartridge() = default;
36 |
37 | void Load(const uint8_t* data, size_t size);
38 | void Unload();
39 | bool is_loaded();
40 |
41 | uint8_t Read(uint16_t addr, uint8_t pb6=1);
42 | void Write(uint16_t addr, uint8_t data, uint8_t pb6=1);
43 | };
44 |
45 |
46 | #endif //VECTREXIA_CARTRIDGE_H
47 |
--------------------------------------------------------------------------------
/src/debugfont.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 beardypig
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 | #include
20 |
21 | constexpr int FONT_CHARACTERS = 128;
22 | constexpr int FONT_SIZE = 8;
23 |
24 | uint8_t font8x8_basic[FONT_CHARACTERS][FONT_SIZE] = {
25 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0000 (nul)
26 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0001
27 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0002
28 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0003
29 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0004
30 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0005
31 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0006
32 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0007
33 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0008
34 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0009
35 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000A
36 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000B
37 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000C
38 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000D
39 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000E
40 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000F
41 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0010
42 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0011
43 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0012
44 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0013
45 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0014
46 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0015
47 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0016
48 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0017
49 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0018
50 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0019
51 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001A
52 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001B
53 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001C
54 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001D
55 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001E
56 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001F
57 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (space)
58 | { 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // U+0021 (!)
59 | { 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0022 (")
60 | { 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // U+0023 (#)
61 | { 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // U+0024 ($)
62 | { 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // U+0025 (%)
63 | { 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // U+0026 (&)
64 | { 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (')
65 | { 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // U+0028 (()
66 | { 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // U+0029 ())
67 | { 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // U+002A (*)
68 | { 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // U+002B (+)
69 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+002C (,)
70 | { 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // U+002D (-)
71 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+002E (.)
72 | { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/)
73 | { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // U+0030 (0)
74 | { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // U+0031 (1)
75 | { 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // U+0032 (2)
76 | { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // U+0033 (3)
77 | { 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // U+0034 (4)
78 | { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // U+0035 (5)
79 | { 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // U+0036 (6)
80 | { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // U+0037 (7)
81 | { 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+0038 (8)
82 | { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // U+0039 (9)
83 | { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+003A (:)
84 | { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+003B (//)
85 | { 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // U+003C (<)
86 | { 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // U+003D (=)
87 | { 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // U+003E (>)
88 | { 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // U+003F (?)
89 | { 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // U+0040 (@)
90 | { 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // U+0041 (A)
91 | { 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // U+0042 (B)
92 | { 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // U+0043 (C)
93 | { 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // U+0044 (D)
94 | { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // U+0045 (E)
95 | { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // U+0046 (F)
96 | { 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // U+0047 (G)
97 | { 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // U+0048 (H)
98 | { 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0049 (I)
99 | { 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // U+004A (J)
100 | { 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // U+004B (K)
101 | { 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // U+004C (L)
102 | { 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // U+004D (M)
103 | { 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // U+004E (N)
104 | { 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // U+004F (O)
105 | { 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // U+0050 (P)
106 | { 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // U+0051 (Q)
107 | { 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // U+0052 (R)
108 | { 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // U+0053 (S)
109 | { 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0054 (T)
110 | { 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U+0055 (U)
111 | { 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0056 (V)
112 | { 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // U+0057 (W)
113 | { 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // U+0058 (X)
114 | { 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // U+0059 (Y)
115 | { 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // U+005A (Z)
116 | { 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // U+005B ([)
117 | { 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // U+005C (\)
118 | { 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // U+005D (])
119 | { 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // U+005E (^)
120 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_)
121 | { 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`)
122 | { 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // U+0061 (a)
123 | { 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // U+0062 (b)
124 | { 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // U+0063 (c)
125 | { 0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // U+0064 (d)
126 | { 0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // U+0065 (e)
127 | { 0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // U+0066 (f)
128 | { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0067 (g)
129 | { 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // U+0068 (h)
130 | { 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0069 (i)
131 | { 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // U+006A (j)
132 | { 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // U+006B (k)
133 | { 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+006C (l)
134 | { 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // U+006D (m)
135 | { 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // U+006E (n)
136 | { 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+006F (o)
137 | { 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // U+0070 (p)
138 | { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // U+0071 (q)
139 | { 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // U+0072 (r)
140 | { 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // U+0073 (s)
141 | { 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // U+0074 (t)
142 | { 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // U+0075 (u)
143 | { 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0076 (v)
144 | { 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // U+0077 (w)
145 | { 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // U+0078 (x)
146 | { 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0079 (y)
147 | { 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // U+007A (z)
148 | { 0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // U+007B ({)
149 | { 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // U+007C (|)
150 | { 0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // U+007D (})
151 | { 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007E (~)
152 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // U+007F
153 | };
154 |
--------------------------------------------------------------------------------
/src/gfxutil.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 beardypig, pelorat
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 |
20 | #ifndef VECTREXIA_GFXUTIL_H
21 | #define VECTREXIA_GFXUTIL_H
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include "veclib.h"
32 |
33 | extern uint8_t font8x8_basic[128][8];
34 |
35 | namespace vxgfx
36 | {
37 |
38 | /*
39 | * color channel blending function
40 | */
41 | inline float blend_channel(const float a, const float b, const float t) {
42 | return ::sqrt((1.0f - t) * ::pow(a, 2.0f) + t * ::pow(b, 2.0f));
43 | }
44 |
45 | /*
46 | * alpha channel blending function
47 | */
48 | inline float blend_alpha(const float a, const float b, const float t) {
49 | return (1.0f - t) * a + t * b;
50 | }
51 |
52 | /*
53 | * ARGB pixel format
54 | */
55 | struct pf_argb_t {
56 |
57 | using value_type = uint32_t;
58 | value_type value = static_cast(0xff) << 24u;
59 |
60 | inline pf_argb_t() = default;
61 | inline ~pf_argb_t() = default;
62 | inline explicit pf_argb_t(value_type v) noexcept : value(v) {}
63 | inline pf_argb_t(const pf_argb_t&) = default;
64 | inline pf_argb_t(pf_argb_t&&) = default;
65 | inline pf_argb_t &operator=(const pf_argb_t&) = default;
66 | inline pf_argb_t &operator=(pf_argb_t &&) = default;
67 |
68 | constexpr pf_argb_t(uint8_t r, uint8_t g, uint8_t b) noexcept {
69 | value = static_cast(0xff) << 24u
70 | | static_cast(r) << 16u
71 | | static_cast(g) << 8u
72 | | static_cast(b);
73 | }
74 |
75 | constexpr pf_argb_t(uint8_t a, uint8_t r, uint8_t g, uint8_t b) noexcept {
76 | value = static_cast(a) << 24u
77 | | static_cast(r) << 16u
78 | | static_cast(g) << 8u
79 | | static_cast(b);
80 | }
81 |
82 | constexpr static uint8_t to_c8(const float &v) {
83 | return static_cast(v * 255.0f);
84 | }
85 |
86 | constexpr static value_type comp_a(const pf_argb_t &c) {
87 | return (c.value >> 24u) & 0xffu;
88 | }
89 |
90 | constexpr static value_type comp_r(const pf_argb_t &c) {
91 | return (c.value >> 16u) & 0xffu;
92 | }
93 |
94 | constexpr static value_type comp_g(const pf_argb_t &c) {
95 | return (c.value >> 8u) & 0xffu;
96 | }
97 |
98 | constexpr static value_type comp_b(const pf_argb_t &c) {
99 | return c.value & 0xffu;
100 | }
101 |
102 | constexpr float a() const {
103 | return static_cast(comp_a(*this)) / 255.0f;
104 | }
105 |
106 | constexpr float r() const {
107 | return static_cast(comp_r(*this)) / 255.0f;
108 | }
109 |
110 | constexpr float g() const {
111 | return static_cast(comp_g(*this)) / 255.0f;
112 | }
113 |
114 | constexpr float b() const {
115 | return static_cast(comp_b(*this)) / 255.0f;
116 | }
117 |
118 | constexpr void a(uint8_t v) {
119 | value |= static_cast(v) << 24u;
120 | }
121 |
122 | constexpr void r(uint8_t v) {
123 | value |= static_cast(v) << 16u;
124 | }
125 |
126 | constexpr void g(uint8_t v) {
127 | value |= static_cast(v) << 8u;
128 | }
129 |
130 | constexpr void b(uint8_t v) {
131 | value |= static_cast(v);
132 | }
133 |
134 | constexpr void operator+=(const float v) {
135 | *this = brightness(v);
136 | }
137 |
138 | inline pf_argb_t blend(const pf_argb_t &rhs, const float blend_point) const {
139 | return {
140 | static_cast(blend_alpha(a(), rhs.a(), blend_point) * 255.0f),
141 | static_cast(blend_channel(r(), rhs.r(), blend_point) * 255.0f),
142 | static_cast(blend_channel(g(), rhs.g(), blend_point) * 255.0f),
143 | static_cast(blend_channel(b(), rhs.b(), blend_point) * 255.0f),
144 | };
145 | }
146 |
147 | constexpr pf_argb_t brightness(const float v) const {
148 | auto r_ = to_c8(vxl::clamp(r() + v, 0.0f, 1.0f));
149 | auto g_ = to_c8(vxl::clamp(g() + v, 0.0f, 1.0f));
150 | auto b_ = to_c8(vxl::clamp(b() + v, 0.0f, 1.0f));
151 | return { r_, g_, b_ };
152 | }
153 | };
154 |
155 | struct pf_rgb565_t {
156 | using value_type = uint16_t;
157 | value_type value = 0;
158 |
159 | constexpr pf_rgb565_t() = default;
160 |
161 | constexpr static value_type comp_r(const pf_rgb565_t &c) {
162 | return static_cast(c.value >> 11u & 0x1fu);
163 | }
164 |
165 | constexpr static value_type comp_g(const pf_rgb565_t &c) {
166 | return static_cast(c.value >> 5u & 0x3fu);
167 | }
168 |
169 | constexpr static value_type comp_b(const pf_rgb565_t &c) {
170 | return static_cast(c.value & 0x1fu);
171 | }
172 |
173 | constexpr float r() const {
174 | return comp_r(*this) / 31.0f;
175 | }
176 |
177 | constexpr float g() const {
178 | return comp_g(*this) / 63.0f;
179 | }
180 |
181 | constexpr float b() const {
182 | return comp_b(*this) / 31.0f;
183 | }
184 |
185 | constexpr static uint8_t to_c8(const float &v) {
186 | return static_cast(v * 255.0f);
187 | }
188 |
189 | inline pf_rgb565_t brightness(const float v) const {
190 | auto r_ = to_c8(vxl::clamp(r() + v, 0.0f, 1.0f));
191 | auto g_ = to_c8(vxl::clamp(g() + v, 0.0f, 1.0f));
192 | auto b_ = to_c8(vxl::clamp(b() + v, 0.0f, 1.0f));
193 | return { r_, g_, b_ };
194 | }
195 |
196 | inline explicit pf_rgb565_t(const pf_argb_t v)
197 | : pf_rgb565_t(
198 | static_cast(v.r() * v.a()),
199 | static_cast(v.g() * v.a()),
200 | static_cast(v.b() * v.a()))
201 | { /* ... */ }
202 |
203 | constexpr pf_rgb565_t(uint8_t r, uint8_t g, uint8_t b) {
204 | value = static_cast(
205 | (r >> 3u & 0x1fu) << 11u |
206 | (g >> 2u & 0x3fu) << 5u |
207 | (b >> 3u & 0x1fu));
208 | }
209 | };
210 |
211 | /*
212 | * Monochrome luminosity based pixel format
213 | */
214 | struct pf_mono_t {
215 | using value_type = float;
216 | value_type value = 0.0f;
217 |
218 | pf_mono_t() = default;
219 | ~pf_mono_t() = default;
220 | inline pf_mono_t(const pf_mono_t&) = default;
221 | inline pf_mono_t(pf_mono_t&&) = default;
222 | inline pf_mono_t &operator=(const pf_mono_t&) = default;
223 | inline pf_mono_t &operator=(pf_mono_t &&) = default;
224 |
225 | constexpr explicit pf_mono_t(value_type v) noexcept : value(v) {}
226 |
227 | //
228 | // This constructor performs color to grayscale conversion. The alpha value will
229 | // darken or brighten the image since this pixel format has no alpha support.
230 | constexpr explicit pf_mono_t(pf_argb_t argb) noexcept {
231 | value = (1.0f / 0xff) * argb.a() * (0.2627f * argb.r() + 0.6780f * argb.g() + 0.0593f * argb.b());
232 | }
233 |
234 | //
235 | // This constructor performs color to grayscale conversion of the three RGB arguments
236 | constexpr pf_mono_t(uint8_t r, uint8_t g, uint8_t b) noexcept {
237 | value = 0.2627f * r + 0.6780f * g + 0.0593f * b;
238 | }
239 |
240 | constexpr float a() const {
241 | return value;
242 | }
243 |
244 | constexpr float r() const {
245 | return value;
246 | }
247 |
248 | constexpr float g() const {
249 | return value;
250 | }
251 |
252 | constexpr float b() const {
253 | return value;
254 | }
255 |
256 | constexpr void operator+= (const float &v) {
257 | value += v;
258 | }
259 |
260 | constexpr void operator+= (const pf_mono_t &v) {
261 | value += v.value;
262 | }
263 |
264 | inline pf_mono_t blend(const pf_mono_t &rhs, const float blend_point) const {
265 | return pf_mono_t{ (value * blend_point) + rhs.value * (1.0f - blend_point) };
266 | }
267 |
268 | constexpr void blend(const pf_mono_t &rhs, const float blend_point) {
269 | value = (value * blend_point) + rhs.value * (1.0f - blend_point);
270 | }
271 |
272 | inline pf_mono_t brightness(const pf_mono_t &v) const {
273 | return pf_mono_t{ value + v.value };
274 | }
275 |
276 | inline pf_mono_t operator* (float v) const {
277 | return pf_mono_t{ value * v };
278 | }
279 | };
280 |
281 | /*
282 | * Line drawing mode: direct (overwrite)
283 | */
284 | struct m_direct {
285 | template
286 | constexpr void operator()(Fb &fb, size_t pos, const Pf &color) const {
287 | fb.data()[pos] = color;
288 | }
289 | };
290 |
291 | /*
292 | * Line drawing mode: brightness (additive)
293 | */
294 | struct m_brightness {
295 | template
296 | constexpr void operator()(Fb &fb, size_t pos, const Pf &color) const {
297 | fb.data()[pos] += color;
298 | }
299 | };
300 |
301 | /*
302 | * Line drawing mode: colour blending
303 | */
304 | template
305 | struct m_blend {
306 | template
307 | constexpr void operator()(Fb &fb, size_t pos, const Pf &color) const {
308 | fb.data()[pos].blend(color, (Bp / 100.0f));
309 | }
310 | };
311 |
312 | struct point_t {
313 | constexpr point_t(int x_, int y_) : x(x_), y(y_) {}
314 | int x;
315 | int y;
316 | };
317 |
318 | struct rect_t
319 | {
320 | int left = 0;
321 | int top = 0;
322 | int right = 0;
323 | int bottom = 0;
324 |
325 | constexpr rect_t() = default;
326 |
327 | constexpr rect_t(int x, int y, int w, int h)
328 | : left(x), top(y), right(x + w), bottom(y + h)
329 | { /* ... */
330 | }
331 |
332 | constexpr rect_t(int w, int h)
333 | : right(w), bottom(h)
334 | { /* ... */
335 | }
336 |
337 | constexpr rect_t(const point_t tl, const point_t br)
338 | : left(tl.x), top(tl.y), right(br.x), bottom(br.y)
339 | { /* ... */
340 | }
341 |
342 | constexpr rect_t(const point_t *tl, const point_t *br)
343 | : left(tl->x), top(tl->y), right(br->x), bottom(br->y)
344 | { /* ... */
345 | }
346 |
347 | constexpr int area() const {
348 | return width() * height();
349 | }
350 |
351 | constexpr operator bool() const {
352 | return area() > 0;
353 | }
354 |
355 | constexpr int width() const {
356 | return right - left;
357 | }
358 |
359 | constexpr int height() const {
360 | return bottom - top;
361 | }
362 |
363 | constexpr void offset(const int x, const int y) {
364 | left += x;
365 | right += x;
366 | top += y;
367 | bottom += y;
368 | }
369 |
370 | constexpr void move(const point_t &p) {
371 | return move(p.x, p.y);
372 | }
373 |
374 | constexpr void move(const int x, const int y) {
375 | offset(x - left, y - top);
376 | }
377 |
378 | constexpr void normalize() {
379 | if (left > right) {
380 | std::swap(left, right);
381 | }
382 | if (top > bottom) {
383 | std::swap(top, bottom);
384 | }
385 | }
386 | };
387 |
388 | /*
389 | * Framebuffer class, thin wrapper for an array in a unique_ptr
390 | *
391 | * Usage:
392 | * vxgfx::framebuffer buffer;
393 | *
394 | */
395 | template
396 | class framebuffer
397 | {
398 | private:
399 | using data_type = std::array;
400 | public:
401 |
402 | //
403 | // define some types that can referenced by others
404 |
405 | using value_type = Pf;
406 | using pointer = value_type * ;
407 | using reference = value_type & ;
408 | using iterator = typename data_type::iterator;
409 | using const_iterator = typename data_type::const_iterator;
410 |
411 | const int width = W;
412 | const int height = H;
413 |
414 | //
415 | // STL compatible iterator pass-throughs
416 |
417 | constexpr auto begin()->iterator {
418 | return buffer->begin();
419 | }
420 |
421 | constexpr auto begin() const ->const_iterator {
422 | return buffer->cbegin();
423 | }
424 |
425 | constexpr auto cbegin() const ->const_iterator {
426 | return buffer->cbegin();
427 | }
428 |
429 | constexpr auto end()->iterator {
430 | return buffer->end();
431 | }
432 |
433 | constexpr auto end() const ->const_iterator {
434 | return buffer->end();
435 | }
436 |
437 | constexpr auto cend() const ->const_iterator {
438 | return buffer->cend();
439 | }
440 |
441 | constexpr framebuffer() {
442 | buffer = std::make_unique();
443 | }
444 |
445 |
446 | // Clears the internal array<> using the pixel format default (Pf)
447 | constexpr void clear() {
448 | buffer->fill(Pf{});
449 | }
450 |
451 | // Fill buffer with colour
452 | constexpr void fill(Pf c) {
453 | buffer->fill(std::move(c));
454 | }
455 |
456 | // Returns the array size
457 | constexpr size_t size() const {
458 | return buffer->size();
459 | }
460 |
461 | // Returns a pointer to the internal array<>
462 | constexpr pointer data() const {
463 | return buffer.get()->data();
464 | }
465 |
466 | const rect_t rect() const {
467 | return rect_t(W, H);
468 | }
469 |
470 | //
471 | // copy / constructors / operators
472 |
473 | constexpr explicit framebuffer(Pf c)
474 | : framebuffer() {
475 | fill(std::move(c));
476 | }
477 |
478 | constexpr framebuffer(const framebuffer &rhs)
479 | : framebuffer() {
480 | *this = rhs;
481 | }
482 |
483 | constexpr framebuffer &operator=(const framebuffer &rhs) {
484 | *buffer = *rhs.buffer;
485 | return *this;
486 | }
487 |
488 | constexpr framebuffer(framebuffer &&rhs) {
489 | *this = std::move(rhs);
490 | }
491 |
492 | constexpr framebuffer &operator=(framebuffer &&rhs) {
493 | buffer = std::move(rhs.buffer);
494 | return *this;
495 | }
496 |
497 | template
498 | constexpr void plot_pixel(const int x, const int y, DrawMode mode, Pf color) {
499 | if (x < width && x >= 0 && y < height && y >= 0) {
500 | mode(*this, (y * width) + x, color);
501 | }
502 | }
503 |
504 | const Pf get_pixel(const int x, const int y) const {
505 | return (x < width && x >= 0 && y < height && y >= 0)
506 | ? (*buffer.get())[(y * width) + x] : Pf();
507 | }
508 |
509 | ~framebuffer() = default;
510 |
511 | private:
512 | std::unique_ptr buffer{};
513 | };
514 |
515 | /*
516 | * vectrex viewport voltage span
517 | */
518 | struct viewport {
519 | float l = -2.5f;
520 | float r = +2.5f;
521 | float t = -5.0f;
522 | float b = +5.0f;
523 | using pointer = viewport * ;
524 | viewport() = default;
525 | viewport(float width, float height) :
526 | l(-width / 2), t(-height / 2),
527 | r(width / 2), b(height / 2) {}
528 | void offset(float x, float y) {
529 | l += x; r += x;
530 | t += y; b += y;
531 | }
532 | auto translate(float x, float y, int w, int h)
533 | ->std::pair {
534 | return std::make_pair(
535 | static_cast((x - l) / (r - l) * w),
536 | static_cast((y - t) / (b - t) * h));
537 | }
538 | };
539 |
540 | /*
541 | * Basic line drawing function
542 | */
543 | template
544 | void draw_line(T &fb, int x0, int y0, int x1, int y1, const Pf &c)
545 | {
546 | int dx = abs(x1 - x0);
547 | int dy = abs(y1 - y0);
548 | int sx = x0 < x1 ? 1 : -1;
549 | int sy = y0 < y1 ? 1 : -1;
550 | int err = dx - dy;
551 |
552 | while (1)
553 | {
554 | fb.plot_pixel(x0, y0, DrawMode(), c);
555 |
556 | if (x0 == x1 && y0 == y1)
557 | break;
558 |
559 | int e2 = 2 * err;
560 | if (e2 > -dy)
561 | {
562 | err = err - dy;
563 | x0 = x0 + sx;
564 | }
565 |
566 | if (e2 < dx)
567 | {
568 | err = err + dx;
569 | y0 = y0 + sy;
570 | }
571 | }
572 | }
573 |
574 | /*
575 | * Antialiased line drawing. No endpoint calculations due to int coordinates.
576 | * https://en.wikipedia.org/wiki/Xiaolin_Wu's_line_algorithm
577 | *
578 | * TODO: Optimization for horizontal and vertical lines. Currently, no matter
579 | * the drawing direction, it will always call plot_pixel() twice even when not
580 | * needed, for instance when the brightness for one of them is 0.0f which is
581 | * the case for vertical and horizontal lines.
582 | */
583 | template
584 | void draw_aline(T &fb, int x0, int y0, int x1, int y1, const Pf &c)
585 | {
586 | auto itrunc = [](float v) ->int { return static_cast(v); };
587 | auto ftrunc = [](float v) -> float { return std::floor(v); };
588 | auto rftrunc = [](float v) -> float { return 1.0f - std::floor(v); };
589 |
590 | auto steep = std::abs(y1 - y0) > std::abs(x1 - x0);
591 | if (steep) {
592 | std::swap(x0, y0);
593 | std::swap(x1, y1);
594 | }
595 | if (x0 > x1) {
596 | std::swap(x0, x1);
597 | std::swap(y0, y1);
598 | }
599 |
600 | auto iy = static_cast(y0);
601 | auto dx = static_cast(x1) - x0;
602 | auto dy = static_cast(y1) - y0;
603 | auto gradient = (dx == 0.0f) ? 1.0f : dy / dx;
604 |
605 | if (steep)
606 | {
607 | for (int x = x0; x <= x1; ++x)
608 | {
609 | Pf p1 = c * rftrunc(iy);
610 | Pf p2 = c * ftrunc(iy);
611 | fb.plot_pixel(itrunc(iy), x, DrawMode(), p1);
612 | fb.plot_pixel(itrunc(iy) - 1, x, DrawMode(), p2);
613 | iy += gradient;
614 | }
615 | }
616 | else
617 | {
618 | for (int x = x0; x <= x1; ++x)
619 | {
620 | Pf p1 = c * rftrunc(iy);
621 | Pf p2 = c * ftrunc(iy);
622 | fb.plot_pixel(x, itrunc(iy), DrawMode(), p1);
623 | fb.plot_pixel(x, itrunc(iy) - 1, DrawMode(), p2);
624 | iy += gradient;
625 | }
626 | }
627 | }
628 |
629 | /*
630 | * Voltage based line drawing
631 | */
632 | template
633 | void draw_line(T &fb, viewport &vp, float x0, float y0, float x1, float y1, const Pf &c) {
634 | auto p1 = vp.translate(x0, y0, fb.width, fb.height);
635 | auto p2 = vp.translate(x1, y1, fb.width, fb.height);
636 | draw_line(fb, p1.first, p1.second, p2.first, p2.second, c);
637 | }
638 |
639 | /*
640 | * Text drawing
641 | */
642 | constexpr unsigned int PIXEL_WIDTH = 8;
643 | constexpr unsigned int PIXEL_HEIGHT = 8;
644 | constexpr unsigned int PIXEL_SPACING = 1;
645 |
646 | template
647 | void draw_text(T &fb, int x, int y, const Pf &c, std::string message) {
648 | for (auto &m : message) {
649 | // each character is 8 x 8 pixels
650 | for (auto y_pixel = 0; y_pixel < PIXEL_HEIGHT; y_pixel++) {
651 | for (uint8_t x_pixel = 0; x_pixel < PIXEL_WIDTH; x_pixel++) {
652 | auto fchar = font8x8_basic[m & 0x7fu][y_pixel];
653 | if (fchar & (1u << x_pixel)) { // draw the pixel or not
654 | fb.plot_pixel(x + x_pixel, y + y_pixel, DrawMode(), c);
655 | }
656 | }
657 | }
658 | x += PIXEL_WIDTH + PIXEL_SPACING;
659 | }
660 | }
661 |
662 | inline rect_t intersect(const rect_t *a, const rect_t *b) {
663 |
664 | const point_t p0{
665 | std::max(a->left, b->left),
666 | std::max(a->top, b->top)
667 | };
668 |
669 | const point_t p1{
670 | std::min(a->right, b->right),
671 | std::min(a->bottom, b->bottom)
672 | };
673 |
674 | return (p0.x >= p1.x || p0.y >= p1.y)
675 | ? rect_t{} : rect_t{ p0, p1 };
676 | }
677 |
678 | inline rect_t intersect(const rect_t &a, const rect_t &b) {
679 | return intersect(&a, &b);
680 | }
681 |
682 | struct transform {
683 | rect_t src;
684 | rect_t dst;
685 | float wr; // ratio width
686 | float hr; // ratio height
687 |
688 | transform(rect_t s, rect_t d)
689 | : src(std::move(s)), dst(std::move(d)) {
690 | wr = static_cast(s.width()) / d.width();
691 | hr = static_cast(s.height()) / d.height();
692 | }
693 |
694 | point_t translate(const int dx, const int dy) const {
695 | return point_t(
696 | static_cast(dx * wr),
697 | static_cast(dy * hr)
698 | );
699 | }
700 |
701 | point_t translate(const point_t p) {
702 | return translate(p.x, p.y);
703 | }
704 | };
705 |
706 | template
707 | void draw(PfDst &pfDst, point_t offset, PfSrc &pfSrc, const rect_t &passepartout, Fn draw) {
708 |
709 | // Initialized in all branches so not needed here
710 | int px;
711 | int py;
712 | int pw;
713 | int ph;
714 |
715 | // Get framebuffer rects
716 | const auto srcRect = pfSrc.rect();
717 | auto dstRect = pfDst.rect();
718 |
719 | if (srcRect == dstRect && offset.x == 0 && offset.y == 0)
720 | {
721 | // This is a 1:1 overlay, so take a shortcut
722 | px = 0; // cutout x
723 | py = 0; // cutout y
724 | pw = pfSrc.width; // cutout w
725 | ph = pfSrc.height; // cutout h
726 | }
727 | else
728 | {
729 | // Calcuate the source cutout rectangle, and if it's empty
730 | // we do nothing, just exit the function.
731 | auto ppRect = (passepartout == srcRect)
732 | ? srcRect : intersect(srcRect, passepartout);
733 |
734 | if (!ppRect)
735 | return;
736 |
737 | // Create a rect for calculating the intersection with the
738 | // destination framebuffer. This is the cutout adjusted to
739 | // the offset. We also create a copy so we can record how
740 | // the rectangle changes (i.e. which edges are moved).
741 | rect_t dstIntersect(offset.x, offset.y, ppRect.width(), ppRect.height());
742 | rect_t dstIntersectCopy = dstIntersect;
743 |
744 | // calculate the intersection and exit if empty
745 | dstRect = intersect(dstRect, dstIntersect);
746 | if (!dstRect)
747 | return;
748 |
749 | // Calculate how the intersection changed relative to the copy
750 | // this is how we will need to adjust the original cutout, then
751 | // adjust the original cutout rect. This is the absolute source
752 | // rect we need to copy to the destination buffer. If the math
753 | // has worked out it's entierly contained in the bounds of srcRect.
754 | ppRect.left += (dstRect.left - dstIntersectCopy.left);
755 | ppRect.top += (dstRect.top - dstIntersectCopy.top);
756 | ppRect.right += (dstRect.right - dstIntersectCopy.right);
757 | ppRect.bottom += (dstRect.bottom - dstIntersectCopy.bottom);
758 |
759 | px = ppRect.left;
760 | py = ppRect.top;
761 | pw = ppRect.width();
762 | ph = ppRect.height();
763 | }
764 |
765 | auto rawDst = pfDst.data();
766 | auto rawSrc = pfSrc.data();
767 |
768 | // Copy loop.
769 | for (int y = 0; y < ph; y++) {
770 | for (int x = 0; x < pw; x++) {
771 | auto srcPos = ((y + py) * pfSrc.width) + (x + px);
772 | auto dstPos = ((y + offset.y) * pfDst.width) + (x + offset.x);
773 | draw(rawDst[dstPos], rawSrc[srcPos]);
774 | }
775 | }
776 | }
777 |
778 | } // namespace vxgfx
779 |
780 | #endif //VECTREXIA_GFXUTIL_H
781 |
--------------------------------------------------------------------------------
/src/libretro/libretro.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 beardypig
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 | #include
20 | #include
21 | #include
22 | #include
23 |
24 | #if _MSC_VER >= 1910 && !__INTEL_COMPILER
25 | #include "win32.h"
26 | #endif
27 |
28 | #include "libretro.h"
29 | #include "vectrexia.h"
30 |
31 | constexpr int CYCLES_PER_FRAME = 30000;
32 | unsigned long cycles_per_frame = CYCLES_PER_FRAME;
33 | std::unique_ptr vectrex = std::make_unique();
34 | vxgfx::framebuffer out_buffer{};
35 |
36 | // Callbacks
37 | static retro_log_printf_t log_cb;
38 | static retro_video_refresh_t video_cb;
39 | static retro_input_poll_t input_poll_cb;
40 | static retro_input_state_t input_state_cb;
41 | static retro_environment_t environ_cb;
42 | static retro_audio_sample_t audio_cb;
43 | static retro_audio_sample_batch_t audio_batch_cb;
44 |
45 | unsigned retro_api_version(void) { return RETRO_API_VERSION; }
46 |
47 | static void update_variables(void);
48 |
49 | // Cheats
50 | void retro_cheat_reset(void) {}
51 | void retro_cheat_set(unsigned index, bool enabled, const char *code) {}
52 |
53 | // Load a cartridge
54 | bool retro_load_game(const struct retro_game_info *info)
55 | {
56 | // Load custom core settings
57 | update_variables();
58 |
59 | // Set the controller descriptor
60 | struct retro_input_descriptor desc[] = {
61 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" },
62 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" },
63 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" },
64 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" },
65 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "1" },
66 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "2" },
67 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "3" },
68 | { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "4" },
69 | { 0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Analog X" },
70 | { 0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Analog Y" },
71 |
72 | { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" },
73 | { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" },
74 | { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" },
75 | { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" },
76 | { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "1" },
77 | { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "2" },
78 | { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "3" },
79 | { 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "4" },
80 | { 1, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Analog X" },
81 | { 1, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Analog Y" },
82 | { 1, RETRO_DEVICE_NONE, 0, 0, nullptr },
83 | };
84 |
85 | environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc);
86 |
87 | // Reset the Vectrex, clears the cart ROM and loads the System ROM
88 | vectrex->Reset();
89 |
90 | if (info && info->data) { // ensure there is ROM data
91 | return vectrex->LoadCartridge((const uint8_t*)info->data, info->size);
92 | }
93 |
94 | return true;
95 | }
96 |
97 | bool retro_load_game_special(unsigned game_type, const struct retro_game_info *info, size_t num_info) { return false; }
98 |
99 | // Unload the cartridge
100 | void retro_unload_game(void) { vectrex->UnloadCartridge(); }
101 |
102 | unsigned retro_get_region(void) { return RETRO_REGION_PAL; }
103 |
104 | // libretro unused api functions
105 | void retro_set_controller_port_device(unsigned port, unsigned device) {}
106 |
107 |
108 | void *retro_get_memory_data(unsigned id) { return nullptr; }
109 | size_t retro_get_memory_size(unsigned id){ return 0; }
110 |
111 | // Serialisation methods
112 | size_t retro_serialize_size(void) { return 0; }
113 | bool retro_serialize(void *data, size_t size) { return false; }
114 | bool retro_unserialize(const void *data, size_t size) { return false; }
115 |
116 | // End of retrolib
117 | void retro_deinit(void) { }
118 |
119 | // libretro global setters
120 | void retro_set_environment(retro_environment_t cb) {
121 | environ_cb = cb;
122 |
123 | struct retro_variable variables[] = {
124 | #ifdef VECTREXIA_DEBUG
125 | { "vectrexia_internal_slowdown", "Internal Slowdown; 1x|2x|5x|10x|20x|50x|100x|1000x|10000x|30000x" },
126 | #endif
127 | { NULL, NULL },
128 | };
129 |
130 | bool no_rom = true;
131 | cb(RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME, &no_rom);
132 | cb(RETRO_ENVIRONMENT_SET_VARIABLES, variables);
133 | }
134 |
135 | void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) {}
136 | void retro_set_video_refresh(retro_video_refresh_t cb) { video_cb = cb; }
137 | void retro_set_audio_sample(retro_audio_sample_t cb) { audio_cb = cb; }
138 | void retro_set_input_poll(retro_input_poll_t cb) { input_poll_cb = cb; }
139 | void retro_set_input_state(retro_input_state_t cb) { input_state_cb = cb; }
140 |
141 | void retro_init(void)
142 | {
143 | /* set up some logging */
144 | struct retro_log_callback log;
145 | unsigned level = 4;
146 |
147 | if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log))
148 | log_cb = log.log;
149 | else
150 | log_cb = nullptr;
151 |
152 | // the performance level is guide to frontend to give an idea of how intensive this core is to run
153 | environ_cb(RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL, &level);
154 |
155 | vectrex->Reset();
156 | }
157 |
158 |
159 | /*
160 | * Tell libretro about this core, it's name, version and which rom files it supports.
161 | */
162 | void retro_get_system_info(struct retro_system_info *info)
163 | {
164 | memset(info, 0, sizeof(retro_system_info));
165 | info->library_name = vectrex->GetName();
166 | info->library_version = vectrex->GetVersion();
167 | info->need_fullpath = false;
168 | info->valid_extensions = "bin|vec";
169 | }
170 |
171 | /*
172 | * Tell libretro about the AV system; the fps, sound sample rate and the
173 | * resolution of the display.
174 | */
175 | void retro_get_system_av_info(struct retro_system_av_info *info) {
176 |
177 | int pixel_format = RETRO_PIXEL_FORMAT_RGB565;
178 |
179 | memset(info, 0, sizeof(retro_system_av_info));
180 | info->timing.fps = 50.0;
181 | info->timing.sample_rate = 44100.0;
182 | info->geometry.base_width = FRAME_WIDTH;
183 | info->geometry.base_height = FRAME_HEIGHT;
184 | info->geometry.max_width = FRAME_WIDTH;
185 | info->geometry.max_height = FRAME_HEIGHT;
186 | //info->geometry.aspect_ratio = 330.0f / 410.0f;
187 |
188 | // the performance level is guide to frontend to give an idea of how intensive this core is to run
189 | environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &pixel_format);
190 | }
191 |
192 | // Reset the Vectrex
193 | void retro_reset(void)
194 | {
195 | vectrex->Reset();
196 | }
197 |
198 | // Test the user input and return the state of the joysticks and buttons
199 | void get_joystick_state(unsigned port, uint8_t &x, uint8_t &y, uint8_t &b1, uint8_t &b2, uint8_t &b3, uint8_t &b4)
200 | {
201 | if (input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT))
202 | x = 0x00;
203 | else if (input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT))
204 | x = 0xff;
205 | else
206 | x = (uint8_t) (
207 | (input_state_cb(port, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X) / 256) + 128);
208 |
209 | if (input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP))
210 | y = 0xff;
211 | else if (input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN ))
212 | y = 0x00;
213 | else
214 | {
215 | // retroarch y axis is inverted wrt to the vectrex
216 | auto y_value = input_state_cb(port, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y);
217 | y = (uint8_t) (~((y_value/256) + 128) + 1);
218 | }
219 |
220 | b1 = (unsigned char) (input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A ) ? 1 : 0);
221 | b2 = (unsigned char) (input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B ) ? 1 : 0);
222 | b3 = (unsigned char) (input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X ) ? 1 : 0);
223 | b4 = (unsigned char) (input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y ) ? 1 : 0);
224 | }
225 |
226 | static const auto green = vxgfx::pf_argb_t(255, 255, 0, 128 );
227 |
228 | // Run a single frames with out Vectrex emulation.
229 | void retro_run(void)
230 | {
231 | bool updated = false;
232 | if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated)
233 | update_variables();
234 |
235 | // User input
236 | input_poll_cb();
237 |
238 | uint8_t p1_x, p1_y, p2_x, p2_y;
239 | uint8_t p1_b1, p1_b2, p1_b3, p1_b4, p2_b1, p2_b2, p2_b3, p2_b4;
240 |
241 | // updates the p1_* variables
242 | get_joystick_state(0, p1_x, p1_y, p1_b1, p1_b2, p1_b3, p1_b4);
243 | get_joystick_state(1, p2_x, p2_y, p2_b1, p2_b2, p2_b3, p2_b4);
244 |
245 | vectrex->SetPlayerOne(p1_x, p1_y, p1_b1, p1_b2, p1_b3, p1_b4);
246 | vectrex->SetPlayerTwo(p2_x, p2_y, p2_b1, p2_b2, p2_b3, p2_b4);
247 |
248 | vectrex->psg_->channel_a_on = !input_state_cb(0, RETRO_DEVICE_KEYBOARD, 0, RETROK_1);
249 | vectrex->psg_->channel_b_on = !input_state_cb(0, RETRO_DEVICE_KEYBOARD, 0, RETROK_2);
250 | vectrex->psg_->channel_c_on = !input_state_cb(0, RETRO_DEVICE_KEYBOARD, 0, RETROK_3);
251 |
252 | // Vectrex CPU is 1.5MHz (1500000) and at 50 fps, a frame lasts 20ms, therefore in every frame 30,000 cycles happen.
253 | auto cycles_run = vectrex->Run(cycles_per_frame);
254 |
255 | // Get buffers
256 | auto fb = vectrex->getFramebuffer();
257 | auto db = vectrex->getDebugbuffer();
258 |
259 | // Print sound debugging text
260 | vxgfx::draw_text(*db, 2, 10, green, vxl::format("@ %.fHz", (double)(cycles_run * 50)));
261 | vxgfx::draw_text(*db, 2, 20, green, vxl::format("Channel A: %3.0fHz (noise: %d)", vectrex->psg_->channel_a.frequency_, vectrex->psg_->channel_a.noise_enabled));
262 | vxgfx::draw_text(*db, 2, 30, green, vxl::format("Channel B: %3.0fHz (noise: %d)", vectrex->psg_->channel_b.frequency_, vectrex->psg_->channel_b.noise_enabled));
263 | vxgfx::draw_text(*db, 2, 40, green, vxl::format("Channel C: %3.0fHz (noise: %d)", vectrex->psg_->channel_c.frequency_, vectrex->psg_->channel_c.noise_enabled));
264 |
265 |
266 | // Define the pf_mono_t => pf_rgb565_t transform
267 | auto mono_to_rgb565 = [](const vxgfx::pf_mono_t &p) {
268 | return vxgfx::pf_rgb565_t(static_cast(0xff * p.value),
269 | static_cast(0xff * p.value),
270 | static_cast(0xff * p.value));
271 | };
272 |
273 | // fb => out_buffer transform
274 | std::transform(fb->begin(), fb->end(), out_buffer.begin(), mono_to_rgb565);
275 |
276 | // TODO
277 | // some blending of db on top of out_buffer
278 |
279 | // 882 audio samples per frame (44.1kHz @ 50 fps)
280 | uint8_t buffer[882];
281 | vectrex->psg_->FillBuffer(buffer, sizeof(buffer));
282 |
283 | for (unsigned char i : buffer) {
284 | auto convs = static_cast((i << 8u) - 0x7ffu);
285 | // mono sound, same data for both channels
286 | audio_cb(convs, convs);
287 | }
288 |
289 | video_cb(reinterpret_cast(out_buffer.data()),
290 | FRAME_WIDTH, FRAME_HEIGHT, sizeof(unsigned short) * FRAME_WIDTH);
291 | }
292 |
293 |
294 | static void update_variables(void) {
295 | #ifdef VECTREXIA_DEBUG
296 | struct retro_variable var = {
297 | .key = "vectrexia_internal_slowdown",
298 | };
299 |
300 | if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
301 | char str[100];
302 | snprintf(str, sizeof(str), "%s", var.value);
303 |
304 | auto pch = strtok(str, "x");
305 | if (pch) {
306 | auto factor = strtoul(pch, nullptr, 0);
307 | cycles_per_frame = CYCLES_PER_FRAME / factor;
308 | }
309 |
310 | log_cb(RETRO_LOG_DEBUG, "[vectrexia]: Running at %lu cycles per frame.\n", cycles_per_frame);
311 | }
312 | #endif
313 | }
--------------------------------------------------------------------------------
/src/m6809_disassemble.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 beardypig
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 | #ifndef VECTREXIA_M6809_DISASSEMBLE_H
20 | #define VECTREXIA_M6809_DISASSEMBLE_H
21 |
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include "veclib.h"
31 |
32 | class M6809Disassemble
33 | {
34 | using read_callback_t = uint8_t (*)(intptr_t, uint16_t);
35 | using disasm_handler_t = std::string (*)(M6809Disassemble &, uint16_t &);
36 |
37 | // read callback
38 | read_callback_t read_callback_func;
39 | intptr_t read_callback_ref;
40 |
41 | std::array disasm_handlers;
42 | std::array disasm_handlers_page1;
43 | std::array disasm_handlers_page2;
44 | const char index_mode_register_table[4] = {'x', 'y', 'u', 's'};
45 | const char *exg_register_table[0xc] = {"d", "x", "y", "u", "s", "pc", "INVALID_REG", "s", "a", "b", "cc", "dp"};
46 |
47 | inline uint8_t Read8(const uint16_t &addr)
48 | {
49 | if (read_callback_func)
50 | return read_callback_func(read_callback_ref, addr);
51 | return 0;
52 | }
53 |
54 | inline uint16_t Read16(const uint16_t &addr)
55 | {
56 | return (uint16_t) Read8((uint16_t) (addr)) << 8 | (uint16_t) Read8((uint16_t) (addr + 1));
57 | }
58 |
59 | struct op_abx { std::string operator()() { return "abx"; } };
60 | struct op_adca { std::string operator()() { return "adca"; } };
61 | struct op_adcb { std::string operator()() { return "adcb"; } };
62 | struct op_adda { std::string operator()() { return "adda"; } };
63 | struct op_addb { std::string operator()() { return "addb"; } };
64 | struct op_addd { std::string operator()() { return "addd"; } };
65 | struct op_anda { std::string operator()() { return "anda"; } };
66 | struct op_andb { std::string operator()() { return "andb"; } };
67 | struct op_andcc { std::string operator()() { return "andcc"; } };
68 | struct op_asr { std::string operator()() { return "asr"; } };
69 | struct op_asra { std::string operator()() { return "asra"; } };
70 | struct op_asrb { std::string operator()() { return "asrb"; } };
71 | struct op_bcc { std::string operator()() { return "bcc"; } };
72 | struct op_bcs { std::string operator()() { return "bcs"; } };
73 | struct op_beq { std::string operator()() { return "beq"; } };
74 | struct op_bge { std::string operator()() { return "bge"; } };
75 | struct op_bgt { std::string operator()() { return "bgt"; } };
76 | struct op_bhi { std::string operator()() { return "bhi"; } };
77 | struct op_bita { std::string operator()() { return "bita"; } };
78 | struct op_bitb { std::string operator()() { return "bitb"; } };
79 | struct op_ble { std::string operator()() { return "ble"; } };
80 | struct op_bls { std::string operator()() { return "bls"; } };
81 | struct op_blt { std::string operator()() { return "blt"; } };
82 | struct op_bmi { std::string operator()() { return "bmi"; } };
83 | struct op_bne { std::string operator()() { return "bne"; } };
84 | struct op_bpl { std::string operator()() { return "bpl"; } };
85 | struct op_bra { std::string operator()() { return "bra"; } };
86 | struct op_brn { std::string operator()() { return "brn"; } };
87 | struct op_bvc { std::string operator()() { return "bvc"; } };
88 | struct op_bvs { std::string operator()() { return "bvs"; } };
89 | struct op_clr { std::string operator()() { return "clr"; } };
90 | struct op_clra { std::string operator()() { return "clra"; } };
91 | struct op_clrb { std::string operator()() { return "clrb"; } };
92 | struct op_cmpa { std::string operator()() { return "cmpa"; } };
93 | struct op_cmpb { std::string operator()() { return "cmpb"; } };
94 | struct op_cmpd { std::string operator()() { return "cmpd"; } };
95 | struct op_cmps { std::string operator()() { return "cmps"; } };
96 | struct op_cmpu { std::string operator()() { return "cmpu"; } };
97 | struct op_cmpx { std::string operator()() { return "cmpx"; } };
98 | struct op_cmpy { std::string operator()() { return "cmpy"; } };
99 | struct op_com { std::string operator()() { return "com"; } };
100 | struct op_coma { std::string operator()() { return "coma"; } };
101 | struct op_comb { std::string operator()() { return "comb"; } };
102 | struct op_cwai { std::string operator()() { return "cwai"; } };
103 | struct op_daa { std::string operator()() { return "daa"; } };
104 | struct op_dec { std::string operator()() { return "dec"; } };
105 | struct op_deca { std::string operator()() { return "deca"; } };
106 | struct op_decb { std::string operator()() { return "decb"; } };
107 | struct op_eora { std::string operator()() { return "eora"; } };
108 | struct op_eorb { std::string operator()() { return "eorb"; } };
109 | struct op_exg { std::string operator()() { return "exg"; } };
110 | struct op_inc { std::string operator()() { return "inc"; } };
111 | struct op_inca { std::string operator()() { return "inca"; } };
112 | struct op_incb { std::string operator()() { return "incb"; } };
113 | struct op_jmp { std::string operator()() { return "jmp"; } };
114 | struct op_jsr { std::string operator()() { return "jsr"; } };
115 | struct op_lbcc { std::string operator()() { return "lbcc"; } };
116 | struct op_lbcs { std::string operator()() { return "lbcs"; } };
117 | struct op_lbeq { std::string operator()() { return "lbeq"; } };
118 | struct op_lbge { std::string operator()() { return "lbge"; } };
119 | struct op_lbgt { std::string operator()() { return "lbgt"; } };
120 | struct op_lbhi { std::string operator()() { return "lbhi"; } };
121 | struct op_lble { std::string operator()() { return "lble"; } };
122 | struct op_lbls { std::string operator()() { return "lbls"; } };
123 | struct op_lblt { std::string operator()() { return "lblt"; } };
124 | struct op_lbmi { std::string operator()() { return "lbmi"; } };
125 | struct op_lbne { std::string operator()() { return "lbne"; } };
126 | struct op_lbpl { std::string operator()() { return "lbpl"; } };
127 | struct op_lbra { std::string operator()() { return "lbra"; } };
128 | struct op_lbrn { std::string operator()() { return "lbrn"; } };
129 | struct op_lbvc { std::string operator()() { return "lbvc"; } };
130 | struct op_lbvs { std::string operator()() { return "lbvs"; } };
131 | struct op_lda { std::string operator()() { return "lda"; } };
132 | struct op_ldb { std::string operator()() { return "ldb"; } };
133 | struct op_ldd { std::string operator()() { return "ldd"; } };
134 | struct op_lds { std::string operator()() { return "lds"; } };
135 | struct op_ldu { std::string operator()() { return "ldu"; } };
136 | struct op_ldx { std::string operator()() { return "ldx"; } };
137 | struct op_ldy { std::string operator()() { return "ldy"; } };
138 | struct op_leas { std::string operator()() { return "leas"; } };
139 | struct op_leau { std::string operator()() { return "leau"; } };
140 | struct op_leax { std::string operator()() { return "leax"; } };
141 | struct op_leay { std::string operator()() { return "leay"; } };
142 | struct op_lsl { std::string operator()() { return "lsl"; } };
143 | struct op_lsla { std::string operator()() { return "lsla"; } };
144 | struct op_lslb { std::string operator()() { return "lslb"; } };
145 | struct op_lsr { std::string operator()() { return "lsr"; } };
146 | struct op_lsra { std::string operator()() { return "lsra"; } };
147 | struct op_lsrb { std::string operator()() { return "lsrb"; } };
148 | struct op_mul { std::string operator()() { return "mul"; } };
149 | struct op_neg { std::string operator()() { return "neg"; } };
150 | struct op_nega { std::string operator()() { return "nega"; } };
151 | struct op_negb { std::string operator()() { return "negb"; } };
152 | struct op_nop { std::string operator()() { return "nop"; } };
153 | struct op_ora { std::string operator()() { return "ora"; } };
154 | struct op_orb { std::string operator()() { return "orb"; } };
155 | struct op_orcc { std::string operator()() { return "orcc"; } };
156 | struct op_pshs { std::string operator()() { return "pshs"; } };
157 | struct op_pshu { std::string operator()() { return "pshu"; } };
158 | struct op_puls { std::string operator()() { return "puls"; } };
159 | struct op_pulu { std::string operator()() { return "pulu"; } };
160 | struct op_rol { std::string operator()() { return "rol"; } };
161 | struct op_rola { std::string operator()() { return "rola"; } };
162 | struct op_rolb { std::string operator()() { return "rolb"; } };
163 | struct op_ror { std::string operator()() { return "ror"; } };
164 | struct op_rora { std::string operator()() { return "rora"; } };
165 | struct op_rorb { std::string operator()() { return "rorb"; } };
166 | struct op_rti { std::string operator()() { return "rti"; } };
167 | struct op_rts { std::string operator()() { return "rts"; } };
168 | struct op_sbca { std::string operator()() { return "sbca"; } };
169 | struct op_sbcb { std::string operator()() { return "sbcb"; } };
170 | struct op_sex { std::string operator()() { return "sex"; } };
171 | struct op_sta { std::string operator()() { return "sta"; } };
172 | struct op_stb { std::string operator()() { return "stb"; } };
173 | struct op_std { std::string operator()() { return "std"; } };
174 | struct op_sts { std::string operator()() { return "sts"; } };
175 | struct op_stu { std::string operator()() { return "stu"; } };
176 | struct op_stx { std::string operator()() { return "stx"; } };
177 | struct op_sty { std::string operator()() { return "sty"; } };
178 | struct op_suba { std::string operator()() { return "suba"; } };
179 | struct op_subb { std::string operator()() { return "subb"; } };
180 | struct op_subd { std::string operator()() { return "subd"; } };
181 | struct op_swi1 { std::string operator()() { return "swi1"; } };
182 | struct op_swi2 { std::string operator()() { return "swi2"; } };
183 | struct op_swi3 { std::string operator()() { return "swi3"; } };
184 | struct op_sync { std::string operator()() { return "sync"; } };
185 | struct op_tfr { std::string operator()() { return "tfr"; } };
186 | struct op_tst { std::string operator()() { return "tst"; } };
187 | struct op_tsta { std::string operator()() { return "tsta"; } };
188 | struct op_tstb { std::string operator()() { return "tstb"; } };
189 | struct op_bsr { std::string operator()() { return "bsr"; } };
190 | struct op_lbsr { std::string operator()() { return "lbsr"; } };
191 |
192 | struct DirectAddressing {
193 | std::string operator()(M6809Disassemble& dis, uint16_t &addr)
194 | {
195 | return vxl::format("<$%02X", dis.Read8(addr++));
196 | }
197 | };
198 |
199 | struct InherentAddressing { std::string operator()(M6809Disassemble& dis, uint16_t &addr) { return ""; } };
200 | template
201 | struct RelativeAddressing {
202 | std::string operator()(M6809Disassemble& dis, uint16_t &addr)
203 | {
204 | std::string r;
205 | if (sizeof(T) == 1)
206 | {
207 | auto a = dis.Read8(addr++);
208 | r = vxl::format("$%04X", addr + static_cast(a));
209 | }
210 | else
211 | {
212 | auto a = dis.Read16(addr);
213 | r = vxl::format("$%04X", addr + static_cast(a));
214 | addr += 2;
215 | }
216 | r += vxl::format(" # $%04X", addr);
217 | return r;
218 | }
219 | };
220 | using RelativeAddressingShort = RelativeAddressing;
221 | using RelativeAddressingLong = RelativeAddressing;
222 |
223 | template
224 | struct ImmediateAddressing {
225 | std::string operator()(M6809Disassemble& dis, uint16_t &addr)
226 | {
227 | if (sizeof(T) == 1)
228 | {
229 | return vxl::format("#$%02X", dis.Read8(addr++));
230 | }
231 | else
232 | {
233 | auto r = vxl::format("#$%04X", dis.Read16(addr));
234 | addr += 2;
235 | return r;
236 | }
237 |
238 | }
239 | };
240 |
241 | using ImmediateAddressing8 = ImmediateAddressing;
242 | using ImmediateAddressing16 = ImmediateAddressing;
243 |
244 | struct ExtendedAddressing {
245 | std::string operator()(M6809Disassemble& dis, uint16_t &addr)
246 | {
247 | auto r = vxl::format("$%04x", dis.Read16(addr));
248 | addr += 2;
249 | return r;
250 | }
251 | };
252 |
253 | struct IndexedAddressing {
254 | std::string operator()(M6809Disassemble& dis, uint16_t &addr)
255 | {
256 | uint8_t post_byte = dis.Read8(addr++);
257 | std::string mode;
258 | const char reg = dis.index_mode_register_table[(post_byte >> 5) & 0x03]; // bits 5+6
259 |
260 | if (!(post_byte >> 7))
261 | {
262 | // (+/- 4 bit offset),R
263 | return vxl::format("%02d,%c", (int8_t)((post_byte & 0xf) - (post_byte & 0x10)), reg);
264 | }
265 | else
266 | {
267 | uint8_t b8;
268 | uint16_t b16;
269 | switch (post_byte & 0x0f)
270 | {
271 | case 0:
272 | // ,R+
273 | mode = vxl::format(",%c+", reg);
274 | break;
275 | case 1:
276 | // ,R++
277 | // register is incremented by 1 or 2
278 | mode = vxl::format(",%c++", reg);
279 | break;
280 | case 2:
281 | // ,-R
282 | mode = vxl::format(",-%c", reg);
283 | break;
284 | case 3:
285 | // ,--R
286 | mode = vxl::format(",--%c", reg);
287 | break;
288 | case 4:
289 | // ,R
290 | mode = vxl::format(",%c", reg);
291 | break;
292 | case 5:
293 | // (+/- B), R
294 | mode = vxl::format("b, %c", reg);
295 | break;
296 | case 6:
297 | // (+/- A), R
298 | mode = vxl::format("a, %c", reg);
299 | break;
300 | case 8:
301 | // (+/- 7 bit offset), R
302 | b8 = dis.Read8(addr++);
303 | mode = vxl::format("%d,%c", (int8_t)b8, reg);
304 | break;
305 | case 9:
306 | // (+/- 15 bit offset), R
307 | b16 = dis.Read16(addr);
308 | mode = vxl::format("%d,%c", (int16_t)b16, reg);
309 | addr += 2;
310 | break;
311 | case 0xb:
312 | // (+/- D), R
313 | mode = vxl::format("d, %c", reg);
314 | break;
315 | case 0xc:
316 | // (+/- 7 bit offset), PC
317 | b8 = dis.Read8(addr++);
318 | mode = vxl::format("%d,PC", (int8_t)b8);
319 | break;
320 | case 0xd:
321 | // (+/- 15 bit offset), PC
322 | b16 = dis.Read16(addr);
323 | mode = vxl::format("%d,PC", (int16_t)b16);
324 | addr += 2;
325 | break;
326 | case 0xf:
327 | mode = vxl::format("$%04x", dis.Read16(addr));
328 | addr += 2;
329 | break;
330 | default:
331 | // Illegal
332 | return ", ILLEGAL";
333 | }
334 | // indirect mode
335 | if ((post_byte >> 4) & 1)
336 | {
337 | return "[" + mode + "]";
338 | }
339 | else
340 | {
341 | return mode;
342 | }
343 | }
344 |
345 | }
346 | };
347 |
348 | template
349 | struct opcode
350 | {
351 | std::string operator()(M6809Disassemble& dis, uint16_t &addr)
352 | {
353 | return mnemonic()() + " " + Addressing()(dis, addr);
354 | }
355 | };
356 |
357 | template
358 | static std::string disasm_exg_tbl(M6809Disassemble& dis, uint16_t &addr)
359 | {
360 | auto post_byte = dis.Read8(addr++);
361 | auto reg_a = dis.exg_register_table[(post_byte >> 4) & 0xf];
362 | auto reg_b = dis.exg_register_table[post_byte & 0x0f];
363 | return mnemonic()() + vxl::format(" %s, %s", reg_a, reg_b);
364 | }
365 |
366 | template
367 | static std::string opcodewrap(M6809Disassemble& dis, uint16_t &addr)
368 | {
369 | return Op()(dis, addr);
370 | }
371 |
372 | static std::string disasm_page1(M6809Disassemble& dis, uint16_t &addr);
373 | static std::string disasm_page2(M6809Disassemble& dis, uint16_t &addr);
374 |
375 | public:
376 | M6809Disassemble();
377 | std::string disasm(uint16_t &addr);
378 | void SetReadCallback(read_callback_t func, intptr_t ref);
379 |
380 | };
381 |
382 |
383 | #endif //VECTREXIA_M6809_DISASSEMBLE_H
384 |
--------------------------------------------------------------------------------
/src/updatetimer.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 beardypig
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 | #ifndef VECTREXIA_UPDATETIMER_H
20 | #define VECTREXIA_UPDATETIMER_H
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | using update_callback_t = std::function;
28 |
29 | class TimerUtil
30 | {
31 | public:
32 | static inline uint64_t cycles_to_nanos(uint64_t cycles)
33 | {
34 | return (uint64_t) (cycles * (1 / 1.5e-3));
35 | }
36 |
37 | static inline uint64_t nanos_to_cycles(uint64_t nanos)
38 | {
39 | return (uint64_t) (nanos / (1 / 1.5e-3));
40 | }
41 | };
42 |
43 | template
44 | class UpdateTimer
45 | {
46 | struct data
47 | {
48 | uint64_t cycles;
49 | T *ptr;
50 | T value;
51 |
52 | bool operator== (const uint64_t &count)
53 | {
54 | if (cycles <= count)
55 | {
56 | *ptr = value;
57 | return true;
58 | }
59 | return false;
60 | }
61 | };
62 |
63 | std::vector items;
64 | public:
65 | // enqueue and item to be updated at a later time
66 | void enqueue(uint64_t cycles, T* ptr, T value)
67 | {
68 | items.push_back({cycles, ptr, value });
69 | }
70 | void tick(uint64_t cycles)
71 | {
72 | // https://en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom
73 | items.erase(std::remove(items.begin(), items.end(), cycles), items.end());
74 | }
75 | void clear()
76 | {
77 | items.clear();
78 | }
79 | };
80 |
81 | class CallbackTimer
82 | {
83 | struct data
84 | {
85 | uint64_t cycles, remaining_nanos;
86 | update_callback_t callback;
87 |
88 | bool operator== (const uint64_t &count)
89 | {
90 | if (cycles <= count)
91 | {
92 | callback(remaining_nanos);
93 | return true;
94 | }
95 | return false;
96 | }
97 | };
98 |
99 | std::vector items;
100 | public:
101 | // enqueue and item to be updated at a later time
102 | void enqueue(uint64_t current_cycle, uint64_t nanosecond, update_callback_t callback)
103 | {
104 | // eg. 7800e-9 / (1/1.5e6) == 7800e-3 / (1/1.5) == 7800 / (1/1.5e-3)
105 | uint64_t cycles = TimerUtil::nanos_to_cycles(nanosecond);
106 | uint64_t remainder = nanosecond - TimerUtil::cycles_to_nanos(cycles);
107 | //printf("A delay of %lldns causes a delay of %lld cycles, with an extra delay of %lldns\n",
108 | // nanosecond, cycles, remainder);
109 | items.push_back({ current_cycle + cycles, remainder, callback });
110 |
111 | //printf("Callback queue length: %d\n", items.size());
112 | }
113 | void tick(uint64_t cycles)
114 | {
115 | // https://en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom
116 | items.erase(std::remove(items.begin(), items.end(), cycles), items.end());
117 | }
118 | void clear()
119 | {
120 | items.clear();
121 | }
122 |
123 | };
124 |
125 |
126 | #endif //VECTREXIA_UPDATETIMER_H
127 |
--------------------------------------------------------------------------------
/src/veclib.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 beardypig, pelorat
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 |
20 | #ifndef VECTREXIA_VECLIB_H
21 | #define VECTREXIA_VECLIB_H
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 |
28 | /*
29 | * Name space for vectrexia utility functions
30 | */
31 | namespace vxl
32 | {
33 |
34 | /*
35 | * Implementation of std::clamp
36 | * See https://en.cppreference.com/w/cpp/algorithm/clamp
37 | * std::clamp is new in C++17
38 | */
39 | template
40 | constexpr const T& clamp(const T& v, const T& lo, const T& hi, Compare comp) {
41 | return assert(!comp(hi, lo)),
42 | comp(v, lo) ? lo : comp(hi, v) ? hi : v;
43 | }
44 |
45 | template
46 | constexpr const T& clamp(const T& v, const T& lo, const T& hi) {
47 | return vxl::clamp(v, lo, hi, std::less<>());
48 | }
49 |
50 | inline std::string format(const char *fmt, ...)
51 | {
52 | va_list ap, ap2;
53 | va_start(ap, fmt);
54 | va_copy(ap2, ap);
55 | std::string out;
56 |
57 | int size = vsnprintf(nullptr, 0, fmt, ap);
58 | va_end(ap);
59 | if (size > 0) {
60 | out = std::string(size, 0);
61 | vsnprintf(&out[0], out.size()+1, fmt, ap2);
62 | }
63 |
64 | va_end(ap2);
65 | return out;
66 | }
67 |
68 | }
69 |
70 |
71 | #endif // VECTREXIA_VECLIB_H
--------------------------------------------------------------------------------
/src/vectorizer.cpp:
--------------------------------------------------------------------------------
1 | #include "gfxutil.h"
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "vectorizer.h"
7 |
8 | void Vectorizer::Step(uint8_t porta, uint8_t portb, uint8_t zero_, uint8_t blank_)
9 | {
10 | // porta is connected to the databus of the sound chip and DAC
11 |
12 | // PB0 - SWITCH
13 | // PB1 - SEL 0
14 | // PB2 - SEL 1
15 | // PB5 - COMPARE (input)
16 | // PB6 - CART N/C? (input)
17 | // PB7 - RAMP
18 |
19 | uint8_t switch_ = (uint8_t)(portb & 0x1);
20 | uint8_t select = (uint8_t)((portb >> 1) & 0x3);
21 |
22 | blank = blank_;
23 |
24 | signal_queue.tick(cycles);
25 |
26 | // sample x is always set
27 | float sample_v = dac(porta);
28 | float ref_0 = 0.0f;
29 |
30 | // DAC sample is between -2.5, +2.5
31 | sample_x = sample_v;
32 |
33 | if (!switch_)
34 | {
35 | switch (select)
36 | {
37 | case 0: // Y Axis Sample and Hold between [-5, +5]
38 | sample_y = sample_v * 2;
39 | break;
40 | case 1: // Zero reference
41 | ref_0 = sample_v * 2;
42 | break;
43 | case 2: // Z Axis (brightness) Sample and Hold
44 | sample_z = std::max(0.0f, -sample_v * 2); // clamp to [0, 5]
45 | break;
46 | default:
47 | break;
48 | }
49 | }
50 |
51 | auto new_integrator_x = ref_0 - sample_x;
52 | auto new_integrator_y = sample_y - ref_0;
53 |
54 | uint8_t ramp_ = (uint8_t)portb >> 7;
55 | // update RAMP and integrators in 7800ns
56 | signal_queue.enqueue(cycles,
57 | signal_delay,
58 | [this, ramp_, zero_, new_integrator_x, new_integrator_y](uint64_t n){
59 | UpdateSignals(ramp_, zero_, {new_integrator_x, new_integrator_y}, n);
60 | });
61 |
62 | #ifdef VECTORIZER_DEBUG
63 | min_x = std::min(axes.x, min_x);
64 | max_x = std::max(axes.x, max_x);
65 | min_y = std::min(axes.y, min_y);
66 | max_y = std::max(axes.y, max_y);
67 | #endif
68 |
69 | cycles++;
70 | }
71 |
72 | void Vectorizer::UpdateSignals(uint8_t ramp_, uint8_t zero_, const integrators_t &integrators_, uint64_t remaining_nanos)
73 | {
74 | float ramp_time_old = 0.0f;
75 | float ramp_time_new = 0.0f;
76 |
77 | if (!ramp_ && ramp) // ramp turning on
78 | {
79 | // the change is delayed, so the ramp time will only be what time is left from
80 | // the current cycle ie. the remainder time
81 | ramp_time_new = (float)(time_per_clock - (remaining_nanos / 1.0e9));
82 | }
83 | else if (ramp_ && !ramp) // ramp turning off
84 | {
85 | // when turning off, there is still a partial cycles amount of time to run for...
86 | ramp_time_old = (float)(remaining_nanos / 1.0e9);
87 | }
88 | else if (!ramp_ && !ramp) // still active
89 | {
90 | // if the integrators have changed, then you need one vector for the first part of the cycles
91 | // and another for the second part of the cycle
92 | ramp_time_old = (float) (remaining_nanos / 1.0e9);
93 | ramp_time_new = time_per_clock - ramp_time_old;
94 | }
95 |
96 | if (!zero_)
97 | {
98 | axes.zero();
99 | }
100 |
101 | // draw vectors using the OLD integrator value
102 | axes.integrate(ramp_time_old, integrators);
103 |
104 | vectors_.push_back({axes, blank, ramp, sample_z / 5.0f, cycles});
105 |
106 | // draw vectors using the NEW integrator values
107 | axes.integrate(ramp_time_new, integrators_);
108 |
109 | vectors_.push_back({axes, blank, ramp_, sample_z / 5.0f, cycles});
110 |
111 | zero = zero_;
112 | ramp = ramp_;
113 | integrators = integrators_;
114 | }
115 |
116 | //
117 |
118 | VectorBuffer *Vectorizer::getVectorBuffer()
119 | {
120 | struct line_vector_t
121 | {
122 | float x0, y0, x1, y1;
123 | float intensity0, intensity1;
124 | uint64_t cycles0, cycles1;
125 | line_vector_t(axes_t pos, float intensity_, uint64_t cycles_)
126 | {
127 | x0 = x1 = pos.x;
128 | y0 = y1 = pos.y;
129 | intensity0 = intensity1 = intensity_;
130 | cycles0 = cycles1 = cycles_;
131 | };
132 | line_vector_t(axes_t pos, uint64_t cycles_)
133 | {
134 | x0 = x1 = pos.x;
135 | y0 = y1 = pos.y;
136 | intensity0 = intensity1 = 0.0f;
137 | cycles0 = cycles1 = cycles_;
138 | }
139 | void set_end(axes_t pos)
140 | {
141 | x1 = pos.x;
142 | y1 = pos.y;
143 | }
144 | };
145 |
146 | // start with black
147 | vector_buffer.clear();
148 |
149 | std::vector to_draw;
150 | std::vector debug_to_draw;
151 | bool beam = false;
152 |
153 | for (auto vect = vectors_.begin(); vect != vectors_.end(); vect++)
154 | {
155 | if (beam)
156 | {
157 | if (!vect->blank)
158 | {
159 | #ifdef VECTORIZER_DEBUG
160 | line_vector_t debug_vect(vect->pos, vect->end_cycle);
161 | debug_to_draw.push_back(debug_vect);
162 | #endif
163 | beam = false;
164 | }
165 | // Update line when the beam is on or if it's just turned off (that's the end of the line)
166 | line_vector_t &new_vect = to_draw.back();
167 | new_vect.set_end(vect->pos);
168 | }
169 | else
170 | {
171 | if (vect->blank) // beam has just turned on
172 | {
173 | line_vector_t new_vect(vect->pos, vect->intensity, vect->end_cycle);
174 | to_draw.push_back(new_vect);
175 | beam = true;
176 | }
177 | #ifdef VECTORIZER_DEBUG
178 | if (!debug_to_draw.empty()) // is off, or just ending
179 | {
180 | // extend the debug vector
181 | line_vector_t &debug_vect = debug_to_draw.back();
182 | // beam may only be on for 1 cycle
183 | debug_vect.set_end(vect->pos);
184 |
185 | line_vector_t new_debug_vect(vect->pos, vect->intensity, vect->end_cycle);
186 | debug_to_draw.push_back(new_debug_vect);
187 | }
188 | #endif
189 | }
190 |
191 | // fade the vector based on how long ago it was drawn
192 | vect->intensity -= ((cycles - vect->end_cycle) * (1.0f / decay_cycles));
193 | vect->end_cycle = cycles;
194 | }
195 |
196 | // remove all the vectors that have 0 intensity or less
197 | vectors_.erase(std::remove_if(vectors_.begin(), vectors_.end(),
198 | [](const Vector &v) { return v.intensity <= 0.0f; }), vectors_.end());
199 |
200 | for (const auto &vect: to_draw)
201 | {
202 | if (vect.intensity0 > 0.0f)
203 | {
204 | vxgfx::draw_line(vector_buffer, vp,
205 | vect.x0 * scale_factor, vect.y0 * scale_factor,
206 | vect.x1 * scale_factor, vect.y1 * scale_factor,
207 | vxgfx::pf_mono_t{ vect.intensity0 });
208 | }
209 | }
210 |
211 | #ifdef VECTORIZER_DEBUG
212 | for (const auto &debug_vect: debug_to_draw)
213 | {
214 | debug_framebuffer.draw_line(debug_vect.x0 * scale_factor, debug_vect.y0 * scale_factor,
215 | debug_vect.x1 * scale_factor, debug_vect.y1 * scale_factor,
216 | color_t{1.0f, 0.0f, 0.0f, DEBUG_LINE_INTENSITY});
217 | }
218 |
219 | debug_framebuffer.draw_debug_grid({1.0f, 1.0f, 0.0f, 0.2f}, 0.5f / scale_factor, 1.0f / scale_factor);
220 |
221 | uint64_t dcycles = TimerUtil::nanos_to_cycles(signal_delay);
222 | uint64_t dremainder = signal_delay - TimerUtil::cycles_to_nanos(dcycles);
223 |
224 | debug_framebuffer.draw_debug_text(2, FRAME_HEIGHT-10, {0.0, 1.0, 0.0, 0.5f}, "delay: %" PRId64 "~ + %" PRId64 "ns decay: %d", dcycles, dremainder, decay_cycles);
225 | debug_framebuffer.draw_debug_text(2, FRAME_HEIGHT-20, {0.0, 1.0, 0.0, 0.5f}, "y: [%.2f, %.2f]", min_y, max_y);
226 | debug_framebuffer.draw_debug_text(2, FRAME_HEIGHT-30, {0.0, 1.0, 0.0, 0.5f}, "x: [%.2f, %.2f]", min_x, max_x);
227 |
228 | auto grid_size_len = snprintf(NULL, 0, "%.2f%%", scale_factor * 100);
229 | debug_framebuffer.draw_debug_text(FRAME_WIDTH - (grid_size_len * 9), 2, {0.0, 1.0, 0.0, 0.5f}, "%.2f%%", scale_factor * 100);
230 |
231 | debug_framebuffer.draw_debug_text(2, 2, {0.0, 1.0, 0.0, 0.5f}, "%'" PRId64, cycles);
232 |
233 | min_x = 10.0f;
234 | min_y = 10.0f;
235 | max_x = -10.0f;
236 | max_y = -10.0f;
237 |
238 | auto ret = debug_framebuffer + framebuffer;
239 | debug_framebuffer.fill(); // clear at the end, so that libretro can write debug message here if it wants
240 |
241 | return ret;
242 |
243 | #endif
244 |
245 | return &vector_buffer;
246 | }
247 | DebugBuffer * Vectorizer::getDebugBuffer()
248 | {
249 | return &debug_buffer;
250 | }
251 | //
252 |
--------------------------------------------------------------------------------
/src/vectorizer.h:
--------------------------------------------------------------------------------
1 | #ifndef VECTREXIA_VECTORIZER2_H
2 | #define VECTREXIA_VECTORIZER2_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include "gfxutil.h"
10 | #include "updatetimer.h"
11 |
12 |
13 | static const float VECTOR_MAX_V = 5.0f;
14 | static const float VECTOR_MIN_V = -5.0f;
15 | static const float time_per_clock = (float) (1.0f / 1.5e6);
16 | static const float DEBUG_LINE_INTENSITY = 0.03f;
17 | static const int FRAME_WIDTH = 330;
18 | static const int FRAME_HEIGHT = 410;
19 |
20 | struct integrators_t
21 | {
22 | float x = 0.0f,
23 | y = 0.0f;
24 | };
25 |
26 | struct axes_t
27 | {
28 | float x = 0.0f,
29 | y = 0.0f;
30 | inline void zero() {
31 | x = 0.0f;
32 | y = 0.0f;
33 | }
34 | inline void integrate(float ramp_time, const integrators_t &integrators)
35 | {
36 | x += 10000 * ramp_time * integrators.x;
37 | y += 10000 * ramp_time * integrators.y;
38 | }
39 | };
40 |
41 | using VectorBuffer = vxgfx::framebuffer;
42 | using DebugBuffer = vxgfx::framebuffer;
43 |
44 | class Vectorizer
45 | {
46 | struct Vector
47 | {
48 | axes_t pos;
49 | uint8_t blank, ramp;
50 | float intensity;
51 | uint64_t end_cycle;
52 | };
53 |
54 | // Sample and hold voltages (-5v - 5v) for Y axis and Z axis
55 | float sample_y = 0.0f;
56 | float sample_z = 0.0f;
57 | // not really a sample and hold, the value of X is whatever is out of the DAC ie. PORTA
58 | float sample_x = 0.0f;
59 |
60 | // DAC voltage for the X/Y axes
61 | axes_t axes;
62 |
63 | // voltages of the integrators
64 | integrators_t integrators;
65 |
66 | // The DAC is connected to PORTA, the MSB of the input is inverted
67 | // the output from the DAC will range from -2.5v to +2.5v
68 | inline float dac(uint8_t value)
69 | {
70 | return 2.5f - ((value ^ 0x80) * (5.0f / 256.0f));
71 | }
72 |
73 | template
74 | auto clamp(T input, T min, T max)
75 | {
76 | return std::min(std::max(input, min), max);
77 | };
78 |
79 | // signals that control switches/beam
80 | uint8_t blank = 1;
81 |
82 | // signals that control switches/beam
83 | // RAMP and ZERO both control analog switches (IC305, MC54/74HC4066)
84 | // IC305 has VCC connected to +5v and GND connected to -5v - according to the datasheet this will cause a delay
85 | // of ~400ns.
86 | // The transistor Q301 adds further delay of ~260ns.
87 | uint8_t zero = 1;
88 | uint8_t ramp = 1;
89 |
90 | // The DAC could add a delay of up to ~150ns.
91 | // Total delay:
92 | CallbackTimer signal_queue;
93 |
94 | uint64_t cycles = 0;
95 |
96 | vxgfx::viewport vp;
97 |
98 | std::vector vectors_;
99 | VectorBuffer vector_buffer{};
100 | DebugBuffer debug_buffer{};
101 |
102 | float min_x, max_x, min_y, max_y;
103 |
104 | public:
105 | void Step(uint8_t porta, uint8_t portb, uint8_t zero, uint8_t blank);
106 |
107 | // Returns a vxgfx::framebuffer
108 | VectorBuffer *getVectorBuffer();
109 |
110 | // Returns a vxgfx::framebuffer
111 | DebugBuffer *getDebugBuffer();
112 |
113 | uint64_t signal_delay = 7800;
114 | int decay_cycles = 40000; // a beam lasts for 40k cycles
115 | float scale_factor = 1.0f;
116 | float pan_offset_x = 0.0f;
117 | float pan_offset_y = 0.0f;
118 |
119 | void UpdateSignals(uint8_t ramp, uint8_t zero, const integrators_t &integrators, uint64_t remaining_nanos);
120 | };
121 |
122 |
123 | #endif //VECTREXIA_VECTORIZER2_H
124 |
--------------------------------------------------------------------------------
/src/vectrexia.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 beardypig
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include "vectrexia.h"
24 | #include "cartridge.h"
25 |
26 | const char *Vectrex::GetName()
27 | {
28 | return kName_;
29 | }
30 |
31 | const char *Vectrex::GetVersion()
32 | {
33 | return kVersion_;
34 | }
35 |
36 | void Vectrex::Reset()
37 | {
38 | cpu_->Reset();
39 | }
40 |
41 | uint64_t Vectrex::Run(uint64_t cycles)
42 | {
43 | uint64_t cycles_run = 0;
44 | while (cycles_run < cycles)
45 | {
46 | uint64_t cpu_cycles = 0;
47 | // run one instruction on the CPU
48 | // The VIA 6522 interrupt line is connected to the M6809 IRQ line
49 | m6809_error_t rcode = cpu_->Execute(cpu_cycles, (via_->GetIRQ()) ? IRQ : NONE);
50 | if (rcode != E_SUCCESS)
51 | {
52 | auto registers = cpu_->getRegisters();
53 | if (rcode == E_UNKNOWN_OPCODE)
54 | message("Unknown opcode at $%04x [$%02x]", registers.PC - 1, Read((uint16_t) (registers.PC - 1)));
55 | else if (rcode == E_UNKNOWN_OPCODE_PAGE1)
56 | message("Unknown page 1 opcode at $%04x [$%02x]", registers.PC - 1,
57 | Read((uint16_t) (registers.PC - 1)));
58 | else if (rcode == E_UNKNOWN_OPCODE_PAGE2)
59 | message("Unknown page 2 opcode at $%04x [$%02x]", registers.PC - 1),
60 | Read((uint16_t) (registers.PC - 1));
61 | }
62 |
63 | // run the VIA for the same number of cycles
64 | for (int via_cycles = 0; via_cycles < cpu_cycles; via_cycles++)
65 | {
66 | via_->Step();
67 | vector_buffer_.Step(via_->getPortAState(), via_->getPortBState(),
68 | via_->getCA2State(), via_->getCB2State());
69 | UpdateJoystick(via_->getPortAState(), via_->getPortBState());
70 | psg_->Step(via_->getPortAState(), (uint8_t) ((via_->getPortBState() >> 3) & 1),
71 | 1, (uint8_t) ((via_->getPortBState() >> 4) & 1));
72 | this->cycles++;
73 | }
74 |
75 | cycles_run += cpu_cycles;
76 | }
77 | return cycles_run;
78 | }
79 |
80 | bool Vectrex::LoadCartridge(const uint8_t *data, size_t size)
81 | {
82 | cartridge_ = std::make_unique();
83 | cartridge_->Load(data, size);
84 | return cartridge_->is_loaded();
85 | }
86 |
87 | void Vectrex::UnloadCartridge()
88 | {
89 | // Can only unload a cartridge, if one has been loaded
90 | if (cartridge_ && cartridge_->is_loaded())
91 | cartridge_->Unload();
92 | }
93 |
94 |
95 | static uint8_t read_mem(intptr_t ref, uint16_t addr)
96 | {
97 | return reinterpret_cast(ref)->Read(addr);
98 | }
99 |
100 | static void write_mem(intptr_t ref, uint16_t addr, uint8_t data)
101 | {
102 | reinterpret_cast(ref)->Write(addr, data);
103 | }
104 |
105 | static uint8_t read_psg_io(intptr_t ref)
106 | {
107 | return reinterpret_cast(ref)->ReadPSGIO();
108 | }
109 |
110 | static void store_psg_reg(intptr_t ref, uint8_t data)
111 | {
112 | reinterpret_cast(ref)->StorePSGReg(data);
113 | }
114 |
115 | static uint8_t read_via_porta(intptr_t ref)
116 | {
117 | return reinterpret_cast(ref)->ReadPortA();
118 | }
119 |
120 | static uint8_t read_via_portb(intptr_t ref)
121 | {
122 | return reinterpret_cast(ref)->ReadPortB();
123 | }
124 |
125 | Vectrex::Vectrex() noexcept
126 | {
127 | cpu_ = std::make_unique();
128 | via_ = std::make_unique();
129 | psg_ = std::make_unique();
130 |
131 | // CPU Callbacks
132 | cpu_->SetReadCallback(read_mem, reinterpret_cast(this));
133 | cpu_->SetWriteCallback(write_mem, reinterpret_cast(this));
134 |
135 | // VIA Callback
136 | via_->SetPortAReadCallback(read_via_porta, reinterpret_cast(this));
137 | via_->SetPortBReadCallback(read_via_portb, reinterpret_cast(this));
138 |
139 | // PSG callbacks
140 | psg_->SetIOReadCallback(read_psg_io, reinterpret_cast(this));
141 | psg_->SetRegStoreCallback(store_psg_reg, reinterpret_cast(this));
142 | }
143 |
144 | uint8_t Vectrex::Read(uint16_t addr)
145 | {
146 | // 0000-7FFF: cartridge
147 | if (addr < 0x8000 && cartridge_) {
148 | return cartridge_->Read(addr, (uint8_t) (via_->getPortBState() >> 6 & 1));
149 | }
150 | // E000-FFFF: system ROM
151 | else if (addr >= 0xe000) {
152 | // offset the addr relative to E000
153 | return sysrom_[addr & ~0xe000];
154 | }
155 | // 8000-C7FF: Unused
156 | // C800-CFFF: RAM
157 | // D000-D7FF: 6522VIA
158 | // D800-DFFF: don't use for reads
159 | else if ((addr >= 0xc800)) {
160 | // C800-CF77: RAM
161 | if (addr < 0xD000) {
162 | return ram_[addr & 0x3ff];
163 | }
164 | else if (addr < 0xD800) {
165 | // D000-D7FF: 6522VIA I/O
166 | return via_->Read((uint8_t) (addr & 0xf));
167 | }
168 | }
169 | return 0x00;
170 | }
171 |
172 | void Vectrex::Write(uint16_t addr, uint8_t data)
173 | {
174 | // 0000-7FFF: cartridge
175 | if (addr < 0x8000 && cartridge_) {
176 | cartridge_->Write(addr, (uint8_t) (via_->getPortBState() >> 6 & 1));
177 | }
178 | // E000-FFFF: system ROM
179 | else if (addr >= 0xe000) {
180 | // system rom - cannot write
181 | }
182 | // 8000-C7FF: Unused
183 | // C800-CFFF: RAM
184 | // D000-D7FF: 6522VIA
185 | // D800-DFFF: For writes can write to _both_ RAM and 6522
186 | else if (addr >= 0xc800) {
187 | // C800-CF77: RAM
188 | if (addr & 0x800) {
189 | ram_[addr & 0x3ff] = data;
190 | }
191 | if (addr & 0x1000) {
192 | // D000-D7FF: 6522VIA I/O
193 | via_->Write((uint8_t) (addr & 0xf), data);
194 | }
195 | }
196 | }
197 |
198 | void Vectrex::message(const char *fmt, ...)
199 | {
200 | va_list args;
201 | va_start(args, fmt);
202 | fprintf(stderr, "[vectrexia]: ");
203 | vfprintf(stderr, fmt, args);
204 | fprintf(stderr, "\n");
205 | va_end(args);
206 | }
207 |
208 |
209 | // function pointers for when PORTA/B are read
210 | uint8_t Vectrex::ReadPortA() {
211 | return psg_port;
212 | }
213 |
214 | uint8_t Vectrex::ReadPortB() {
215 | return joystick_compare;
216 | }
217 |
218 | void Vectrex::UpdateJoystick(uint8_t porta, uint8_t portb) {
219 | // porta is connected to the databus of the sound chip and DAC
220 | // portb 3+4 and ca1 for sound chip stuff
221 |
222 | // PB0 - S/H
223 | // PB1 - SEL 0
224 | // PB2 - SEL 1
225 | // PB5 - COMPARE (input)
226 | // PB6 - CART N/C?
227 | // PB7 - RAMP
228 | uint8_t select, pot;
229 | select = (uint8_t) ((portb >> 1) & 0x3);
230 |
231 | // S/H enables the demultiplexor
232 | // select selects the channel for the comparator
233 | switch (select) {
234 | case 0:
235 | pot = p1_joystick.pot_x;
236 | break;
237 | case 1:
238 | pot = p1_joystick.pot_y;
239 | break;
240 | case 2:
241 | pot = p2_joystick.pot_x;
242 | break;
243 | case 3:
244 | pot = p2_joystick.pot_y;
245 | break;
246 | default:
247 | pot = 0x7f;
248 | }
249 |
250 | // compare the value on porta to the selected joystick pot value
251 | joystick_compare = (uint8_t) ((pot > (porta ^ 0x80)) ? 0x20 : 0);
252 | }
253 |
254 | void Vectrex::SetPlayerOne(uint8_t x, uint8_t y, uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4)
255 | {
256 | // the sticks are analog and range from 0x00 -> 0xff (left -> right/down -> up)
257 | p1_joystick.pot_x = x;
258 | p1_joystick.pot_y = y;
259 |
260 | // the buttons are active low
261 | p1_joystick.btn_1 = (uint8_t) (b1 ^ 1);
262 | p1_joystick.btn_2 = (uint8_t) (b2 ^ 1);
263 | p1_joystick.btn_3 = (uint8_t) (b3 ^ 1);
264 | p1_joystick.btn_4 = (uint8_t) (b4 ^ 1);
265 | }
266 |
267 | void Vectrex::SetPlayerTwo(uint8_t x, uint8_t y, uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4)
268 | {
269 | p2_joystick.pot_x = x;
270 | p2_joystick.pot_y = y;
271 |
272 | p2_joystick.btn_1 = (uint8_t) (b1 ^ 1);
273 | p2_joystick.btn_2 = (uint8_t) (b2 ^ 1);
274 | p2_joystick.btn_3 = (uint8_t) (b3 ^ 1);
275 | p2_joystick.btn_4 = (uint8_t) (b4 ^ 1);
276 | }
277 |
278 | uint8_t Vectrex::ReadPSGIO()
279 | {
280 | return (p2_joystick.btn_4 << 7) | \
281 | (p2_joystick.btn_3 << 6) | \
282 | (p2_joystick.btn_2 << 5) | \
283 | (p2_joystick.btn_1 << 4) | \
284 | (p1_joystick.btn_4 << 3) | \
285 | (p1_joystick.btn_3 << 2) | \
286 | (p1_joystick.btn_2 << 1) | \
287 | (p1_joystick.btn_1);
288 | }
289 |
290 | void Vectrex::StorePSGReg(uint8_t reg)
291 | {
292 | psg_port = reg;
293 | }
294 |
295 | VectorBuffer *Vectrex::getFramebuffer()
296 | {
297 | return vector_buffer_.getVectorBuffer();
298 | }
299 |
300 | DebugBuffer *Vectrex::getDebugbuffer()
301 | {
302 | return vector_buffer_.getDebugBuffer();
303 | }
304 |
305 | M6809 &Vectrex::GetM6809()
306 | {
307 | return *cpu_;
308 | }
--------------------------------------------------------------------------------
/src/vectrexia.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 beardypig
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 | #ifndef VECTREXIA_VECTREXIA_H
20 | #define VECTREXIA_VECTREXIA_H
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 |
29 | #if _MSC_VER >= 1910 && !__INTEL_COMPILER
30 | #include "win32.h"
31 | #endif
32 |
33 | #include "cartridge.h"
34 | #include "sysrom.h"
35 | #include "m6809.h"
36 | #include "via6522.h"
37 | #include "ay38910.h"
38 | #include "vectorizer.h"
39 |
40 | class Vectrex
41 | {
42 | const char *kName_ = "Vectrexia";
43 | const char *kVersion_ = "0.2.0";
44 |
45 | // memory areas
46 | // 8K of system ROM
47 | // 1K of system RAM
48 | const std::array sysrom_ = system_bios;
49 | std::array ram_{};
50 |
51 | // This structure represents the values of the potentiometers and the buttons a vectrex controller
52 | struct
53 | {
54 | uint8_t pot_x, pot_y;
55 | uint8_t btn_1, btn_2, btn_3, btn_4;
56 | } p1_joystick, p2_joystick;
57 | uint8_t joystick_compare;
58 | uint8_t psg_port;
59 |
60 | public:
61 | std::unique_ptr cartridge_{};
62 | std::unique_ptr cpu_{};
63 | std::unique_ptr via_{};
64 | std::unique_ptr psg_{};
65 | Vectorizer vector_buffer_;
66 | uint64_t cycles;
67 |
68 | Vectrex() noexcept;
69 | Vectrex(const Vectrex&) = delete;
70 | Vectrex(Vectrex&&) = delete;
71 | Vectrex &operator=(const Vectrex&) = delete;
72 | Vectrex &operator=(Vectrex&&) = delete;
73 | ~Vectrex() = default;
74 |
75 | void Reset();
76 | uint64_t Run(uint64_t cycles);
77 |
78 | bool LoadCartridge(const uint8_t *data, size_t size);
79 | void UnloadCartridge();
80 |
81 | const char *GetName();
82 | const char *GetVersion();
83 |
84 | uint8_t Read(uint16_t addr);
85 | void Write(uint16_t addr, uint8_t data);
86 |
87 | void message(const char *fmt, ...);
88 |
89 | VectorBuffer *getFramebuffer();
90 | DebugBuffer *getDebugbuffer();
91 |
92 | uint8_t ReadPortA();
93 | uint8_t ReadPortB();
94 | void UpdateJoystick(uint8_t porta, uint8_t portb);
95 | void SetPlayerOne(uint8_t x, uint8_t y, uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4);
96 | void SetPlayerTwo(uint8_t x, uint8_t y, uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4);
97 | uint8_t ReadPSGIO();
98 | void StorePSGReg(uint8_t reg);
99 | M6809 &GetM6809();
100 | };
101 |
102 | #endif //VECTREXIA_VECTREXIA_H
103 |
--------------------------------------------------------------------------------
/src/via6522.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 beardypig
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include "via6522.h"
25 |
26 | uint8_t VIA6522::Read(uint8_t reg)
27 | {
28 | uint8_t data = 0;
29 |
30 | switch (reg & 0xf) {
31 | case REG_ORB:
32 | data = read_portb();
33 | break;
34 |
35 | case REG_ORA:
36 | if ((registers.PCR & CA2_MASK) == CA2_OUTPUT) {
37 | // CA2 goes low to signal "data taken", in pulse mode that will be restored to 1 at the end of the
38 | // VIA emulation
39 | ca2_state = 0;
40 | }
41 | case REG_ORA_NO_HANDSHAKE:
42 | data = read_porta();
43 | break;
44 |
45 | // Timer 1
46 | case REG_T1CL: // timer 1 low-order counter
47 | // stop timer 1
48 | timer1.enabled = false;
49 |
50 | // Set PB7 if Timer 1 has control of PB7
51 | if (registers.ACR & T1_PB7_CONTROL)
52 | registers.PB7 = 0x80;
53 |
54 | // clear the timer 1 interrupt
55 | set_ifr(TIMER1_INT, 0);
56 |
57 | data = (uint8_t)(timer1.counter & 0xff);
58 | break;
59 | case REG_T1CH: // timer 1 high-order counter
60 | data = (uint8_t)(timer1.counter >> 8);
61 | break;
62 | case REG_T1LL: // timer 1 low-order latch
63 | data = registers.T1LL;
64 | break;
65 | case REG_T1LH: // timer 2 high-order latch
66 | data = registers.T1LH;
67 | break;
68 |
69 | // Timer 2
70 | case REG_T2CL: // timer 2 low-order counter
71 | // stop timer 2
72 | timer2.enabled = false;
73 |
74 | // clear the timer 2 interrupt
75 | set_ifr(TIMER2_INT, 0);
76 |
77 | data = (uint8_t)(timer2.counter & 0xff);
78 | break;
79 | case REG_T2CH: // timer 2 high-order counter
80 | data = (uint8_t)(timer2.counter >> 8);
81 | break;
82 |
83 | // Shift Register
84 | case REG_SR:
85 | // clear the SR interrupt
86 | set_ifr(SR_INT, 0);
87 | // reset shifted bits counter
88 | sr.shifted = 0;
89 | sr.enabled = true;
90 | data = registers.SR;
91 | break;
92 |
93 | // Interrupt Registers
94 | case REG_IER:
95 | // interrupt enable register, the MSB is always set when reading.
96 | data = registers.IER | IRQ_MASK;
97 | break;
98 |
99 | // Basic Reads
100 | case REG_DDRB:
101 | // DDR register 0 for output, 1 for input
102 | data = registers.DDRB;
103 | break;
104 | case REG_DDRA:
105 | data = registers.DDRA;
106 | break;
107 | case REG_ACR:
108 | data = registers.ACR;
109 | break;
110 | case REG_PCR:
111 | data = registers.PCR;
112 | break;
113 | case REG_IFR:
114 | data = registers.IFR;
115 | break;
116 | default:
117 | break;
118 | }
119 | // invalid address
120 | return data;
121 | }
122 |
123 | void VIA6522::Write(uint8_t reg, uint8_t data)
124 | {
125 | switch (reg & 0xf) {
126 |
127 | case REG_ORB:
128 | // Port B lines (CB1, CB2) handshake on a write operation only.
129 | if ((registers.PCR & CB2_MASK) == CB2_OUTPUT) {
130 | cb2_state = 0;
131 | }
132 |
133 | registers.ORB = data;
134 | break;
135 | case REG_ORA:
136 | // If CA2 is output
137 | if ((registers.PCR & CA2_MASK) == CA2_OUTPUT) {
138 | ca2_state = 1;
139 | }
140 | case REG_ORA_NO_HANDSHAKE:
141 | registers.ORA = data;
142 | break;
143 |
144 | // Timer 1
145 | case REG_T1CL: // timer 1 low-order counter
146 | registers.T1LL = data;
147 | break;
148 | case REG_T1CH: // timer 1 high-order counter
149 | registers.T1LH = data;
150 | timer1.counter = (registers.T1LH << 8) | registers.T1LL;
151 |
152 | // start timer 1
153 | timer1.enabled = true;
154 | timer1.one_shot = false;
155 | // Does Timer 1 control PB7
156 | if ((registers.ACR & T1_MASK) == T1_PB7_CONTROL)
157 | // set timer 1's registers.PB7 state to low
158 | registers.PB7 = 0;
159 |
160 | // clear the timer 1 interrupt
161 | set_ifr(TIMER1_INT, 0);
162 | break;
163 | case REG_T1LL: // timer 1 low-order latch
164 | registers.T1LL = data;
165 | break;
166 | case REG_T1LH: // timer 2 high-order latch
167 | registers.T1LH = data;
168 | break;
169 |
170 | // Timer 2
171 | case REG_T2CL: // timer 2 low-order counter
172 | registers.T2CL = data;
173 | break;
174 | case REG_T2CH: // timer 2 high-order counter
175 | registers.T2CH = data;
176 | timer2.counter = (registers.T2CH << 8) | registers.T1CL;
177 |
178 | // start timer 2
179 | timer2.enabled = true;
180 | timer2.one_shot = false;
181 |
182 | // clear the timer 2 interrupt
183 | set_ifr(TIMER2_INT, 0);
184 | break;
185 |
186 | // Shift Register
187 | case REG_SR:
188 | // clear the SR interrupt
189 | set_ifr(SR_INT, 0);
190 | // reset shifted bits counter
191 | sr.shifted = 0;
192 | registers.SR = data;
193 | sr.enabled = true;
194 | break;
195 |
196 | // Interrupt Registers
197 | case REG_IFR:
198 | // interrupt flag register
199 | set_ifr(data, 0);
200 | break;
201 |
202 | case REG_IER:
203 | // interrupt enable register
204 | // enable or disable interrupts based on the MSB and using the rest of the data as a mask
205 | set_ier(data & ~IRQ_MASK, (uint8_t) (data & 0x80 ? 1 : 0));
206 | break;
207 |
208 | case REG_PCR:
209 | registers.PCR = data;
210 | // if CA/B2 is in OUT LOW mode, set to low, otherwise high
211 | ca2_state = (uint8_t) (((registers.PCR & CA2_MASK) == CA2_OUT_LOW) ? 0 : 1);
212 | cb2_state = (uint8_t) (((registers.PCR & CB2_MASK) == CB2_OUT_LOW) ? 0 : 1);
213 | break;
214 |
215 | // Basic Writes
216 | case REG_DDRB:
217 | registers.DDRB = data;
218 | break;
219 | case REG_DDRA:
220 | registers.DDRA = data;
221 | break;
222 | case REG_ACR:
223 | registers.ACR = data;
224 | break;
225 | default:
226 | break;
227 | }
228 | }
229 |
230 | void VIA6522::Reset()
231 | {
232 | // registers
233 | registers.ORB = 0;
234 | registers.ORA = 0;
235 | registers.DDRB = 0;
236 | registers.DDRA = 0;
237 | registers.T1CL = 0;
238 | registers.T1CH = 0;
239 | registers.T1LL = 0;
240 | registers.T1LH = 0;
241 | registers.T2CL = 0;
242 | registers.T2CH = 0;
243 | registers.SR = 0;
244 | registers.ACR = 0;
245 | registers.PCR = 0;
246 | registers.IFR = 0;
247 | registers.IER = 0;
248 | registers.IRA = 0;
249 | registers.IRB = 0;
250 | registers.IRA_latch = 0;
251 | registers.IRB_latch = 0;
252 |
253 | ca1_state = 0;
254 | ca2_state = 1;
255 | cb1_state = 0;
256 | cb1_state_sr = 0;
257 | cb2_state = 1;
258 | cb2_state_sr = 0;
259 |
260 | // timer data
261 | timer1.counter = 0;
262 | timer1.enabled = false;
263 | timer1.one_shot = false;
264 | timer2.counter = 0;
265 | timer2.enabled = false;
266 | timer2.one_shot = false;
267 | registers.PB7 = 0x80;
268 |
269 | // shift register
270 | sr.enabled = false;
271 | sr.shifted = 0;
272 | sr.counter = 0;
273 |
274 | clk = 0;
275 | }
276 |
277 | void VIA6522::Step()
278 | {
279 | // Update any delayed signals
280 | delayed_signals.tick(clk++);
281 |
282 | // Timers
283 | if (timer1.enabled) {
284 | // decrement the counter and test if it has rolled over
285 | if (--timer1.counter == 0xffff) {
286 | // is the continuous interrupt bit set
287 | if (registers.ACR & T1_CONTINUOUS) {
288 | // set the timer 1 interrupt
289 | set_ifr(TIMER1_INT, 1);
290 |
291 | // toggle PB7 (ACR & 0x80)
292 | if (registers.ACR & T1_PB7_CONTROL) {
293 | registers.PB7 ^= 0x80;
294 | }
295 | //via_debug_timer("\r\n");
296 |
297 | // reload counter from latches
298 | timer1.counter = (registers.T1LH << 8) | registers.T1LL;
299 | } else { // one-shot mode
300 | if (!timer1.one_shot) {
301 | // set timer 1 interrupt
302 | set_ifr(TIMER1_INT, 1);
303 |
304 | if (registers.ACR & T1_PB7_CONTROL) {
305 | //via_debug_timer2(", PB7 set");
306 | // restore PB7 to 1, it was set to 0 by writing to T1C-H
307 | registers.PB7 = 0x80;
308 | }
309 | //via_debug_timer2("\r\n");
310 | timer1.one_shot = true;
311 | }
312 | }
313 | }
314 | }
315 |
316 | if (timer2.enabled) {
317 | // pulsed mode is not used
318 | if ((registers.ACR & T2_MASK) == T2_TIMED) { // timed, one-shot mode
319 | // In one-shot mode the timer keeps going, but the interrupt is only triggered once
320 | if (--timer2.counter == 0xffff && !timer2.one_shot) {
321 | // set the Timer 2 interrupt
322 | set_ifr(TIMER2_INT, 1);
323 | timer2.one_shot = true;
324 | }
325 | }
326 | }
327 |
328 | switch (registers.ACR & SR_MASK) {
329 | case SR_DISABLED:
330 | case SR_OUT_EXT:
331 | case SR_IN_EXT:
332 | // This mode is controlled by CB1 positive edge, however in the Vectrex CB1 is not connected.
333 | // For these modes CB1 is an input
334 | break;
335 | case SR_IN_T2:
336 | case SR_OUT_T2:
337 | case SR_OUT_T2_FREE:
338 | // CB1 becomes an output
339 | // when counter T2 counter rolls
340 | if (sr.counter == 0x00) {
341 | // Toggle CB1 on the clock time out
342 | sr.update(*this, (uint8_t) (cb1_state_sr ^ 1));
343 | }
344 | break;
345 | case SR_IN_O2:
346 | case SR_OUT_O2:
347 | // CB1 is an output
348 | // Toggle CB1 on the phase 2 clock
349 | sr.update(*this, (uint8_t) (cb1_state_sr ^ 1));
350 | default:break;
351 | }
352 |
353 | if (--sr.counter == 0xff) {
354 | // reset the Shift Reigster T2 counter to T2 low-order byte
355 | sr.counter = registers.T2CL;
356 | }
357 |
358 | // End of pulse mode handshake
359 | // If PORTA is using pulse mode handshaking, restore CA2 to 1 at the beginning of the next cycle
360 | if ((registers.PCR & CA2_MASK) == CA2_OUT_PULSE)
361 | delayed_signals.enqueue(clk+1, &ca2_state, 1);
362 |
363 | // Same for PORTB
364 | if ((registers.PCR & CB2_MASK) == CB2_OUT_PULSE)
365 | delayed_signals.enqueue(clk+1, &ca2_state, 1);
366 |
367 |
368 | }
369 |
370 | void VIA6522::SetPortAReadCallback(VIA6522::port_callback_t func, intptr_t ref)
371 | {
372 | porta_callback_func = func;
373 | porta_callback_ref = ref;
374 | }
375 |
376 | void VIA6522::SetPortBReadCallback(VIA6522::port_callback_t func, intptr_t ref)
377 | {
378 | portb_callback_func = func;
379 | portb_callback_ref = ref;
380 | }
381 |
382 | uint8_t VIA6522::GetIRQ()
383 | {
384 | return registers.IFR & IRQ_MASK;
385 | }
386 |
387 | // returns the port b data bus state
388 | uint8_t VIA6522::getPortBState()
389 | {
390 | uint8_t portb;
391 | // if Timer 1 has control of PB7
392 | if (registers.ACR & T1_PB7_CONTROL) {
393 | // mask PB7 from ORB and use the Timer 1 controlled PB7
394 | portb = (uint8_t) ((registers.ORB & ~0x80) | registers.PB7);
395 | } else {
396 | portb = registers.ORB;
397 | }
398 | return portb;
399 | }
400 |
401 | // returns the port a data bus state
402 | uint8_t VIA6522::getPortAState()
403 | {
404 | return registers.ORA;
405 | }
406 |
--------------------------------------------------------------------------------
/src/via6522.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 beardypig
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 | #ifndef VECTREXIA_VIA6522_H
20 | #define VECTREXIA_VIA6522_H
21 |
22 | #include
23 | #include "updatetimer.h"
24 |
25 | // Registers
26 | enum {
27 | REG_ORB = 0,
28 | REG_ORA,
29 | REG_DDRB,
30 | REG_DDRA,
31 | REG_T1CL,
32 | REG_T1CH,
33 | REG_T1LL,
34 | REG_T1LH,
35 | REG_T2CL,
36 | REG_T2CH,
37 | REG_SR,
38 | REG_ACR,
39 | REG_PCR,
40 | REG_IFR,
41 | REG_IER,
42 | REG_ORA_NO_HANDSHAKE
43 | };
44 |
45 | // Interrupts
46 | enum {
47 | CA2_INT = 0x01,
48 | CA1_INT = 0x02,
49 | SR_INT = 0x04,
50 | CB2_INT = 0x08,
51 | CB1_INT = 0x10,
52 | TIMER2_INT = 0x20,
53 | TIMER1_INT = 0x40,
54 | IRQ_MASK = 0x80
55 | };
56 |
57 | // ACR flags
58 | enum {
59 | // Timer 1
60 | T1_TIMED = 0x00,
61 | T1_CONTINUOUS = 0x40,
62 | T1_TIMED_PB7 = 0x80,
63 | T1_CONTINUOUS_PB7 = 0xc0,
64 | T1_PB7_CONTROL = T1_TIMED_PB7 & T1_CONTINUOUS_PB7,
65 | T1_MASK = T1_CONTINUOUS_PB7,
66 | // Timer 2
67 | T2_TIMED = 0x00,
68 | T2_PULSE_PB6 = 0x20,
69 | T2_MASK = T2_PULSE_PB6,
70 | // Shift Register
71 | SR_DISABLED = 0x00,
72 | SR_IN_T2 = 0x04,
73 | SR_IN_O2 = 0x08,
74 | SR_IN_EXT = 0x0C,
75 | SR_OUT_T2_FREE = 0x10,
76 | SR_OUT_T2 = 0x14,
77 | SR_OUT_O2 = 0x18,
78 | SR_OUT_EXT = 0x1c,
79 | SR_MASK = SR_OUT_EXT,
80 | SR_EXT = (SR_IN_EXT & SR_OUT_EXT),
81 | SR_IN_OUT = SR_OUT_T2_FREE, // the direction of the shift (0 in, 1 out)
82 | // PA/PB Latching
83 | PA_LATCH_OFF = 0x00,
84 | PA_LATCH_ON = 0x01,
85 | PA_LATCH_MASK = PA_LATCH_ON,
86 | PB_LATCH_OFF = 0x00,
87 | PB_LATCH_ON = 0x02,
88 | PB_LATCH_MASK = PB_LATCH_ON
89 | };
90 |
91 | // PCR Flags
92 |
93 | enum {
94 | // CB2 Flags
95 | CB2_INPUT = 0x00,
96 | CB2_INPUT_NEG = 0x00,
97 | CB2_INPUT_INT_NEG = 0x20,
98 | CB2_INPUT_POS = 0x40,
99 | CB2_INPUT_INT_POS = 0x60,
100 | CB2_OUTPUT = 0x80,
101 | CB2_OUT_HANDSHAKE = 0x80,
102 | CB2_OUT_PULSE = 0xa0,
103 | CB2_OUT_LOW = 0xc0,
104 | CB2_OUT_HIGH = 0xe0,
105 | CB2_IN_OUT = CB2_OUT_HANDSHAKE,
106 | CB2_MASK = CB2_OUT_HIGH,
107 |
108 | // CB1 Flags
109 | CB1_INT_NEG = 0x00,
110 | CB1_INT_POS = 0x10,
111 | CB1_MASK = CB1_INT_POS,
112 |
113 | // CA2 Flags
114 | CA2_INPUT = 0x00,
115 | CA2_INPUT_NEG = 0x00,
116 | CA2_INPUT_INT_NEG = 0x02,
117 | CA2_INPUT_POS = 0x04,
118 | CA2_INPUT_INT_POS = 0x06,
119 | CA2_OUTPUT = 0x08,
120 | CA2_OUT_HANDSHAKE = 0x08,
121 | CA2_OUT_PULSE = 0x0a,
122 | CA2_OUT_LOW = 0x0c,
123 | CA2_OUT_HIGH = 0x0e,
124 | CA2_IN_OUT = CA2_OUT_HANDSHAKE,
125 | CA2_MASK = CA2_OUT_HIGH,
126 |
127 | // CA1 Flags
128 | CA1_INT_NEG = 0x00,
129 | CA1_INT_POS = 0x01,
130 | CA1_MASK = CA1_INT_POS
131 | };
132 |
133 | class VIA6522
134 | {
135 | using port_callback_t = uint8_t (*)(intptr_t);
136 | using update_callback_t = void (*)(intptr_t, uint8_t, uint8_t, bool, bool, bool, bool);
137 |
138 | struct Timer
139 | {
140 | uint16_t counter;
141 | bool enabled;
142 | bool one_shot; // if the timer in one shot mode has been trigger yet
143 | };
144 |
145 |
146 | struct ShiftRegister
147 | {
148 | uint8_t shifted; // number of bits shifts, set the SR interrupt on 8
149 | uint8_t counter; // controlled by timer 2 latch
150 | bool enabled;
151 | void update(VIA6522 &via6522, uint8_t edge)
152 | {
153 | // a CB1 positive edge caused the data from CB2 is shifted in/out to/from the shift register
154 | // bit 4 of the ACR controls the direction of the shift.
155 | if (enabled) {
156 | if (!via6522.cb1_state_sr && edge) { // positive edge
157 | if ((via6522.registers.ACR & SR_MASK) != SR_OUT_T2_FREE) { // in free run mode, the counter is ignored.
158 | shifted++; // increment bit counter
159 | }
160 |
161 | // do not perform any shifting unless the counter is less than 8
162 | // in free run mode, the counter is not updated and the shift will continue
163 | //via_debug("SR: shifting... (bits: %d) free_run: %d out?: %d = %d\r\n", shifted,
164 | // (via6522.ACR & SR_MASK) == SR_OUT_T2_FREE, via6522.ACR & SR_IN_OUT, via6522.SR >> 7);
165 | if (via6522.registers.ACR & SR_IN_OUT) { // out
166 | // cb2_state becomes the 7th bit of SR if it's an output
167 | //if (via6522.PCR & CB2_IN_OUT)
168 | via6522.cb2_state_sr = via6522.registers.SR >> 7;
169 | // roll bits around the SR
170 | via6522.registers.SR = (via6522.registers.SR << 1) | (via6522.registers.SR >> 7);
171 | } else { // in
172 | // bits start at bit 0 and are shifted towards bit 7
173 | via6522.registers.SR <<= 1;
174 | // if CB2 is set to output the shift in 0s, else shift in the CB2 state - in the Vectrex CB2 is
175 | // always an output.
176 | via6522.registers.SR |= (uint8_t) (via6522.cb2_state) & \
177 | (via6522.registers.PCR & CB2_IN_OUT) ? 0x01 : 0x00;
178 | }
179 |
180 | if (shifted == 8) {
181 | //via_debug("Shifting complete, 8 bits shifted: SR = 0x%02x\r\n", via6522.SR);
182 | via6522.set_ifr(SR_INT, 1);
183 | // disable the shift register
184 | enabled = false;
185 | }
186 | }
187 | // when disable the last state should be HIGH
188 | via6522.cb1_state_sr = edge;
189 | }
190 | }
191 | };
192 |
193 | struct Registers
194 | {
195 | union
196 | {
197 | uint8_t data[0x10];
198 | struct
199 | {
200 | uint8_t ORB, // port b
201 | ORA, // port a
202 | DDRB,
203 | DDRA,
204 | T1CL,
205 | T1CH,
206 | T1LL,
207 | T1LH,
208 | T2CL, // read-only counter, and write-only latch
209 | T2CH,
210 | SR,
211 | ACR,
212 | PCR,
213 | IFR,
214 | IER;
215 | };
216 | };
217 | // internal input ports, and latched ports
218 | uint8_t IRA,
219 | IRB,
220 | IRA_latch,
221 | IRB_latch;
222 | uint8_t PB7;
223 | } registers;
224 |
225 | Timer timer1;
226 | Timer timer2;
227 |
228 | ShiftRegister sr;
229 |
230 | uint8_t ca1_state, ca2_state;
231 | uint8_t cb1_state, cb2_state, cb1_state_sr, cb2_state_sr;
232 |
233 | uint64_t clk;
234 |
235 | // port a/b read callbacks
236 | port_callback_t porta_callback_func = nullptr;
237 | intptr_t porta_callback_ref = 0;
238 | port_callback_t portb_callback_func = nullptr;
239 | intptr_t portb_callback_ref = 0;
240 |
241 | // Signals that need to be updated in the future
242 | UpdateTimer delayed_signals;
243 |
244 | // Update the state of the IFR
245 | inline void update_ifr(void) {
246 | //via_debug("Updating IFR: IER=0x%02x, IFR=0x%02x\r\n", registers.IER, registers.IFR);
247 | // test if any enabled interrupts are set in the interrupt flag register
248 | if ((registers.IFR & ~IRQ_MASK) & (registers.IER & ~IRQ_MASK)) {
249 | // set the MSB high if there is an active interrupt
250 | registers.IFR |= IRQ_MASK;
251 | } else {
252 | // if there are no active interrupts then clear the MSB
253 | registers.IFR &= ~IRQ_MASK;
254 | }
255 | }
256 |
257 | // Set or clear a bit in the IFR. If an interrupt is enabled and set then the MSB of IFR is set.
258 | inline void set_ifr(uint8_t bits, uint8_t state) {
259 | if (state) {
260 | // set the bit in the IFR
261 | registers.IFR |= bits & ~IRQ_MASK;
262 | } else {
263 | // clear the bits
264 | registers.IFR &= ~(bits & ~IRQ_MASK);
265 | }
266 | update_ifr();
267 | }
268 |
269 | // Set or clear a bit in the IER.
270 | inline void set_ier(uint8_t bits, uint8_t state) {
271 | if (state) {
272 | // set the bits in the IER
273 | registers.IER |= bits & ~IRQ_MASK;
274 | } else {
275 | // clear the bits
276 | registers.IER &= ~(bits & ~IRQ_MASK);
277 | }
278 | update_ifr();
279 | }
280 |
281 | inline uint8_t read_porta()
282 | {
283 | // mask the output data
284 | uint8_t ora = registers.ORA & registers.DDRA;
285 | // if PORTA is in latching mode
286 | if (registers.ACR & PA_LATCH_MASK) {
287 | //via_debug("Reading PORTA in latched mode\r\n");
288 | ora |= registers.IRA_latch & ~registers.DDRA;
289 | } else {
290 | ora |= (porta_callback_func) ? porta_callback_func(porta_callback_ref) & ~registers.DDRA : 0;
291 | }
292 | return ora;
293 | }
294 |
295 | inline uint8_t read_portb()
296 | {
297 | uint8_t orb = registers.ORB;
298 | // if Timer 1 has control of PB7
299 | if (registers.ACR & T1_PB7_CONTROL) {
300 | // mask PB7 from ORB and use the Timer 1 controlled PB7
301 | orb = (uint8_t) ((orb & ~0x80) | registers.PB7);
302 | }
303 | // clear pins that are marked as input
304 | orb &= registers.DDRB;
305 |
306 | // if PORTB is in latching mode
307 | if (registers.ACR & PB_LATCH_MASK) {
308 | //via_debug("Reading PORTB in latched mode\r\n");
309 | orb |= registers.IRB_latch & ~registers.DDRB;
310 | } else {
311 | orb |= (portb_callback_func) ? portb_callback_func(portb_callback_ref) & ~registers.DDRB : 0;
312 | }
313 | return orb;
314 | }
315 |
316 | public:
317 |
318 | VIA6522() = default;
319 | void Step();
320 | void Reset();
321 |
322 | // Set callbacks for read and write, must be a static function
323 | void SetPortAReadCallback(port_callback_t func, intptr_t ref);
324 | void SetPortBReadCallback(port_callback_t func, intptr_t ref);
325 | void SetUpdateCallback(update_callback_t func, intptr_t ref);
326 |
327 | uint8_t Read(uint8_t reg); // read from VIA register
328 | void Write(uint8_t reg, uint8_t data); // write to VIA register
329 |
330 | uint8_t GetIRQ();
331 |
332 | uint8_t getPortAState();
333 | uint8_t getPortBState();
334 | uint8_t getCA1State() { return ca1_state; }
335 | uint8_t getCA2State() { return ca2_state; }
336 | uint8_t getCB1State() { return (registers.ACR & SR_EXT) == SR_EXT ? cb1_state : cb1_state_sr; }
337 | uint8_t getCB2State() { return (registers.ACR & SR_IN_OUT) ? cb2_state_sr : cb2_state; };
338 | };
339 |
340 | #endif //VECTREXIA_VIA6522_H
341 |
--------------------------------------------------------------------------------
/src/win32.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 pelorat
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 | #ifndef WIN32_H
20 | #define WIN32_H
21 |
22 | #if _MSC_VER >= 1910 && !__INTEL_COMPILER
23 | #define WIN32_LEAN_AND_MEAN
24 | #define _WIN32_WINNT 0x0600
25 | #define NOMINMAX
26 | #include
27 | #include
28 | #endif
29 |
30 | #endif // WIN32_H
31 |
--------------------------------------------------------------------------------
/tests/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | find_package(Threads REQUIRED)
2 | include(CTest)
3 |
4 | # Find testing and mocking libraries
5 | find_package(Catch2 3 CONFIG REQUIRED)
6 | find_package(trompeloeil CONFIG REQUIRED)
7 |
8 | include_directories(. ../src)
9 |
10 | # Define the tests executable
11 | add_executable(tests m6809opcode_test.cpp m6809_test.cpp cartridge_test.cpp gfxutil_test.cpp)
12 |
13 | # Define the tests output
14 | if (MSVC)
15 | set(LIBRETRO_SRC vectrexia_libretro_static)
16 | set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
17 | set_target_properties(tests PROPERTIES OUTPUT_NAME tests)
18 | else()
19 | set(LIBRETRO_SRC vectrexia_libretro)
20 | endif()
21 |
22 | # Link the tests target with the required libraries
23 | target_link_libraries(tests PRIVATE Catch2::Catch2 Catch2::Catch2WithMain)
24 | target_link_libraries(tests PRIVATE trompeloeil::trompeloeil Threads::Threads)
25 | target_link_libraries(tests PRIVATE ${LIBRETRO_SRC})
26 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
27 | target_link_libraries(tests PRIVATE ${LIBCXX_LIBRARY})
28 | endif()
29 |
30 | include(CTest)
31 | include(Catch)
32 | catch_discover_tests(tests)
33 |
34 | # Enable coverage (optional)
35 | if ("${COVERAGE}" STREQUAL "1")
36 | include("${CMAKE_SOURCE_DIR}/cmake/CodeCoverage.cmake")
37 | APPEND_COVERAGE_COMPILER_FLAGS()
38 | set(COVERAGE_LCOV_EXCLUDES 'tests/*' 'gmock/*' 'gtest/*' '/usr/*')
39 | SETUP_TARGET_FOR_COVERAGE_LCOV(
40 | NAME coverage
41 | EXECUTABLE tests
42 | DEPENDENCIES tests
43 | )
44 | endif()
45 |
--------------------------------------------------------------------------------
/tests/cartridge_test.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016-2024 Team Vectrexia
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 | #include
20 | #include
21 |
22 | TEST_CASE("CartridgeTest Load32K", "[cartridge]") {
23 | auto romdata = std::make_unique>();
24 | romdata->fill(0xde);
25 |
26 | Cartridge cart;
27 | cart.Load((const uint8_t*)romdata->data(), 0x8000);
28 |
29 | REQUIRE(cart.is_loaded());
30 | }
31 |
32 | TEST_CASE("CartridgeTest Load4K", "[cartridge]") {
33 | auto romdata = std::make_unique>();
34 |
35 | romdata->fill(0xad);
36 |
37 | Cartridge cart;
38 | cart.Load((const uint8_t*)romdata->data(), 0x1000);
39 |
40 | REQUIRE(cart.is_loaded());
41 | }
42 |
43 | TEST_CASE("CartridgeTest Load64K", "[cartridge]") {
44 | auto romdata = std::make_unique>();
45 | romdata->fill(0xbe);
46 |
47 | Cartridge cart;
48 | cart.Load((const uint8_t*)romdata->data(), 0x10000);
49 |
50 | REQUIRE(cart.is_loaded());
51 | }
52 |
53 | TEST_CASE("CartridgeTest LoadTooLarge", "[cartridge]") {
54 | auto romdata = std::make_unique>();
55 | romdata->fill(0xbe);
56 |
57 | Cartridge cart;
58 | cart.Load((const uint8_t*)romdata->data(), 0x10001);
59 |
60 | REQUIRE_FALSE(cart.is_loaded());
61 | }
62 |
63 | TEST_CASE("CartridgeTest Read", "[cartridge]") {
64 | auto romdata = std::make_unique>();
65 | romdata->fill(0xef);
66 |
67 | Cartridge cart;
68 | cart.Load((const uint8_t*)romdata->data(), 0x1000);
69 |
70 | REQUIRE(cart.is_loaded());
71 | REQUIRE(cart.Read(0) == 0xef);
72 | }
73 |
74 | TEST_CASE("CartridgeTest Write", "[cartridge]") {
75 | auto romdata = std::make_unique>();
76 | romdata->fill(0xde);
77 |
78 | Cartridge cart;
79 | cart.Load((const uint8_t*)romdata->data(), 0x1000);
80 |
81 | REQUIRE(cart.is_loaded());
82 | cart.Write(0, 0xbe);
83 | // rom should remain unchanged.
84 | REQUIRE(cart.Read(0) == 0xde);
85 | }
86 |
--------------------------------------------------------------------------------
/tests/gfxutil_test.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016-2024 Team Vectrexia
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 |
20 | #include
21 | #include "gfxutil.h"
22 |
23 | TEST_CASE("Mono TestARGBChannels", "[gfxutil]") {
24 | auto pixel = vxgfx::pf_argb_t(0x7f, 0x7f, 0x7f);
25 |
26 | REQUIRE(static_cast(pixel.a() * 0xff) == 0xff);
27 | REQUIRE(static_cast(pixel.r() * 0xff) == 0x7f);
28 | REQUIRE(static_cast(pixel.g() * 0xff) == 0x7f);
29 | REQUIRE(static_cast(pixel.b() * 0xff) == 0x7f);
30 | }
31 |
32 | TEST_CASE("Mono TestRGB565Channels", "[gfxutil]") {
33 | // Round trip GREEN
34 | auto pixel = vxgfx::pf_rgb565_t(0x00, 0xff, 0x00);
35 |
36 | REQUIRE(static_cast(pixel.r() * 0xff) == 0x00);
37 | REQUIRE(static_cast(pixel.g() * 0xff) == 0xff);
38 | REQUIRE(static_cast(pixel.b() * 0xff) == 0x00);
39 | }
40 |
41 | TEST_CASE("ARGB TestExtractChannelARGB", "[gfxutil]") {
42 | auto argb_pixel = vxgfx::pf_argb_t(0x00, 0x10, 0x20, 0x30);
43 |
44 | REQUIRE(argb_pixel.comp_a(argb_pixel) == 0x00);
45 | REQUIRE(argb_pixel.comp_r(argb_pixel) == 0x10);
46 | REQUIRE(argb_pixel.comp_g(argb_pixel) == 0x20);
47 | REQUIRE(argb_pixel.comp_b(argb_pixel) == 0x30);
48 | }
49 |
50 | TEST_CASE("GFXUtil RectIntersect", "[gfxutil]") {
51 | auto a = vxgfx::rect_t(10, 10);
52 | auto b = vxgfx::rect_t(-2, -2, 14, 14);
53 | auto c = vxgfx::rect_t(-1, -1, 6, 6);
54 | auto d = vxgfx::rect_t(5, -1, 6, 6);
55 | auto e = vxgfx::rect_t(5, 5, 6, 6);
56 | auto f = vxgfx::rect_t(-1, 5, 6, 6);
57 |
58 | REQUIRE(vxgfx::intersect(a, b) == vxgfx::rect_t(0, 2, 10, 8));
59 | REQUIRE(vxgfx::intersect(a, c) == vxgfx::rect_t(0, 0, 5, 5));
60 | REQUIRE(vxgfx::intersect(a, d) == vxgfx::rect_t(5, 0, 5, 5));
61 | REQUIRE(vxgfx::intersect(a, e) == vxgfx::rect_t(5, 5, 5, 5));
62 | REQUIRE(vxgfx::intersect(a, f) == vxgfx::rect_t(0, 5, 5, 5));
63 | }
64 |
65 | TEST_CASE("GFXUtil RectArea", "[gfxutil]") {
66 | auto a = vxgfx::rect_t(10, 10);
67 | REQUIRE(a.area() == 100);
68 | }
69 |
70 | TEST_CASE("GFXUtil RectBoolean", "[gfxutil]") {
71 | auto a = vxgfx::rect_t(10, 10);
72 | auto b = vxgfx::rect_t(0, 0);
73 |
74 | REQUIRE(static_cast(a));
75 | REQUIRE_FALSE(static_cast(b));
76 | }
77 |
78 | TEST_CASE("GFXUtil RectMove", "[gfxutil]") {
79 | auto a = vxgfx::rect_t(10, 10);
80 | a.move(10, 10);
81 |
82 | REQUIRE(a == vxgfx::rect_t(10, 10, 10, 10));
83 | }
84 |
85 | TEST_CASE("GFXUtil RectOffset", "[gfxutil]") {
86 | auto a = vxgfx::rect_t(10, 10, 10, 10);
87 | a.offset(-10, -10);
88 |
89 | REQUIRE(a == vxgfx::rect_t(0, 0, 10, 10));
90 | }
91 |
92 | TEST_CASE("GFXUtil RectInit", "[gfxutil]") {
93 | auto a = vxgfx::rect_t();
94 | auto b = vxgfx::rect_t(10, 10);
95 | auto c = vxgfx::rect_t(10, 10, 10, 10);
96 |
97 | REQUIRE(a.left == 0);
98 | REQUIRE(a.top == 0);
99 | REQUIRE(a.right == 0);
100 | REQUIRE(a.bottom == 0);
101 |
102 | REQUIRE(b.left == 0);
103 | REQUIRE(b.top == 0);
104 | REQUIRE(b.right == 10);
105 | REQUIRE(b.bottom == 10);
106 |
107 | REQUIRE(c.left == 10);
108 | REQUIRE(c.top == 10);
109 | REQUIRE(c.right == 20);
110 | REQUIRE(c.bottom == 20);
111 | }
112 |
--------------------------------------------------------------------------------
/tests/m6809_test.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016-2024 Team Vectrexia
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 |
20 | #include
21 | #include
22 |
23 | auto getRegisters() {
24 | static M6809 cpu;
25 | return cpu.getRegisters();
26 | }
27 |
28 | TEST_CASE("ComputeFlags ZeroFlagNotSetOn1", "[flags]") {
29 | auto regs = getRegisters();
30 | regs.UpdateFlagZero(1);
31 | REQUIRE(regs.CC == 0);
32 | }
33 |
34 | TEST_CASE("ComputeFlags ZeroFlagSetOn0", "[flags]") {
35 | auto regs = getRegisters();
36 | regs.UpdateFlagZero(0);
37 | REQUIRE(regs.CC == FLAG_Z);
38 | }
39 |
40 | TEST_CASE("ComputeFlags NegativeFlagNotSetOn1", "[flags]") {
41 | auto regs = getRegisters();
42 | regs.UpdateFlagNegative(0);
43 | REQUIRE(regs.CC == 0);
44 | }
45 |
46 | TEST_CASE("ComputeFlags NegativeFlagSetOnFF", "[flags]") {
47 | auto regs = getRegisters();
48 | regs.UpdateFlagNegative(0xff);
49 | REQUIRE(regs.CC == FLAG_N);
50 | }
51 |
52 | TEST_CASE("Misc SignExtend", "[misc]") {
53 | uint8_t data = 0x10;
54 | uint16_t sdata = (uint16_t)((~(data & 0x80) + 1) | (data & 0xff));
55 |
56 | REQUIRE(sdata == (int16_t)data);
57 | }
58 |
--------------------------------------------------------------------------------
/tests/m6809opcode_test.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016-2024 Team Vectrexia
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 |
20 | #include
21 | #include
22 |
23 | #include
24 | #include /* this should go last */
25 |
26 | using trompeloeil::_;
27 |
28 | struct MockMemory
29 | {
30 | MAKE_MOCK1(Read, auto(uint16_t addr) -> uint8_t);
31 | MAKE_MOCK2(Write, auto(uint16_t addr, uint8_t value) -> void);
32 | };
33 |
34 | static uint8_t read_mem(intptr_t ref, uint16_t addr) {
35 | return reinterpret_cast(ref)->Read(addr);
36 | }
37 |
38 | static void write_mem(intptr_t ref, uint16_t addr, uint8_t data) {
39 | reinterpret_cast(ref)->Write(addr, data);
40 | }
41 |
42 |
43 | M6809 OpCodeTestHelper(MockMemory& mem) {
44 | REQUIRE_CALL(mem, Read(0xffff)).RETURN(0x00);
45 | REQUIRE_CALL(mem, Read(0xfffe)).RETURN(0x00);
46 |
47 | M6809 cpu;
48 | cpu.SetReadCallback(&read_mem, reinterpret_cast(&mem));
49 | cpu.SetWriteCallback(&write_mem, reinterpret_cast(&mem));
50 |
51 | cpu.Reset();
52 |
53 | return cpu;
54 | }
55 | /*
56 | * ABX
57 | */
58 |
59 | TEST_CASE("M6809OpCodes ABXInherent", "[m6809]") {
60 | MockMemory mem;
61 | M6809 cpu = OpCodeTestHelper(mem);
62 | auto& registers = cpu.getRegisters();
63 |
64 | REQUIRE_CALL(mem, Read(0x00)).RETURN(0x3a);
65 |
66 | uint64_t cycles = 0;
67 |
68 | registers.B = 0x10;
69 |
70 | REQUIRE(cpu.Execute(cycles) == E_SUCCESS);
71 | REQUIRE(registers.X == 0x10);
72 | }
73 |
74 | /*
75 | * ADDA
76 | */
77 | TEST_CASE("M6809OpCodes ADDAImmediate", "[m6809]") {
78 | MockMemory mem;
79 | uint64_t cycles = 0;
80 | M6809 cpu = OpCodeTestHelper(mem);
81 | auto& registers = cpu.getRegisters();
82 |
83 | REQUIRE_CALL(mem, Read(0x00)).RETURN(0x8B);
84 | REQUIRE_CALL(mem, Read(0x01)).RETURN(0x10);
85 |
86 | registers.A = 0x10;
87 |
88 | REQUIRE(cpu.Execute(cycles) == E_SUCCESS);
89 | REQUIRE(registers.A == 0x20);
90 | }
91 |
92 | TEST_CASE("M6809OpCodes ADDADirect", "[m6809]") {
93 | MockMemory mem;
94 | uint64_t cycles = 0;
95 | M6809 cpu = OpCodeTestHelper(mem);
96 | auto& registers = cpu.getRegisters();
97 |
98 | REQUIRE_CALL(mem, Read(0x00)).RETURN(0x9B); // Direct ADDA
99 | REQUIRE_CALL(mem, Read(0x01)).RETURN(0x10); // Page offset
100 | REQUIRE_CALL(mem, Read(0x0010)).RETURN(0x15);
101 | registers.A = 0x10;
102 |
103 | REQUIRE(cpu.Execute(cycles) == E_SUCCESS);
104 | REQUIRE(registers.A == 0x25);
105 | }
106 |
107 | TEST_CASE("M6809OpCodes ADDAExtended", "[m6809]") {
108 | MockMemory mem;
109 | uint64_t cycles = 0;
110 | M6809 cpu = OpCodeTestHelper(mem);
111 | auto& registers = cpu.getRegisters();
112 |
113 | REQUIRE_CALL(mem, Read(0x00)).RETURN(0xBB); // Extended ADDA
114 | REQUIRE_CALL(mem, Read(0x01)).RETURN(0x10);
115 | REQUIRE_CALL(mem, Read(0x02)).RETURN(0x1);
116 | REQUIRE_CALL(mem, Read(0x1001)).RETURN(0x15);
117 |
118 | registers.A = 0x10;
119 |
120 | REQUIRE(cpu.Execute(cycles) == E_SUCCESS);
121 | REQUIRE(registers.A == 0x25);
122 | }
123 |
124 | /*
125 | * ADDD
126 | */
127 | TEST_CASE("M6809OpCodes ADDDImmediate", "[m6809]") {
128 | MockMemory mem;
129 | uint64_t cycles = 0;
130 | M6809 cpu = OpCodeTestHelper(mem);
131 | auto& registers = cpu.getRegisters();
132 |
133 | REQUIRE_CALL(mem, Read(0x00)).RETURN(0xC3); // Immediate ADDD
134 | REQUIRE_CALL(mem, Read(0x01)).RETURN(0x10);
135 | REQUIRE_CALL(mem, Read(0x02)).RETURN(0x20);
136 |
137 | registers.D = 0x2010;
138 |
139 | REQUIRE(cpu.Execute(cycles) == E_SUCCESS);
140 | REQUIRE(registers.PC == 3);
141 | REQUIRE(registers.D == 0x3030);
142 | }
143 |
144 | /*
145 | * SUBA
146 | */
147 | TEST_CASE("M6809OpCodes SUBAImmediate", "[m6809]") {
148 | MockMemory mem;
149 | uint64_t cycles = 0;
150 | M6809 cpu = OpCodeTestHelper(mem);
151 | auto& registers = cpu.getRegisters();
152 |
153 | REQUIRE_CALL(mem, Read(0x00)).RETURN(0x80);
154 | REQUIRE_CALL(mem, Read(0x01)).RETURN(0x10);
155 |
156 | registers.A = 0x10;
157 |
158 | REQUIRE(cpu.Execute(cycles) == E_SUCCESS);
159 | REQUIRE(registers.A == 0x00);
160 | }
161 |
162 | TEST_CASE("M6809OpCodes SUBAImmediate2", "[m6809]") {
163 | MockMemory mem;
164 | uint64_t cycles = 0;
165 | M6809 cpu = OpCodeTestHelper(mem);
166 | auto& registers = cpu.getRegisters();
167 |
168 | REQUIRE_CALL(mem, Read(0x00)).RETURN(0x80);
169 | REQUIRE_CALL(mem, Read(0x01)).RETURN(0x12);
170 |
171 | registers.A = 0x10;
172 |
173 | REQUIRE(cpu.Execute(cycles) == E_SUCCESS);
174 | REQUIRE(registers.A == 0xFE);
175 | }
176 |
177 | /*
178 | * BIT - bitwise and, the register should not be updated
179 | */
180 | TEST_CASE("M6809OpCodes BITAImmediate", "[m6809]") {
181 | MockMemory mem;
182 | uint64_t cycles = 0;
183 | M6809 cpu = OpCodeTestHelper(mem);
184 | auto& registers = cpu.getRegisters();
185 |
186 | REQUIRE_CALL(mem, Read(0x00)).RETURN(0x85);
187 | REQUIRE_CALL(mem, Read(0x01)).RETURN(0x12);
188 |
189 | registers.A = 0xFF;
190 |
191 | REQUIRE(cpu.Execute(cycles) == E_SUCCESS);
192 | REQUIRE(registers.A == 0xFF);
193 | REQUIRE((registers.CC & (FLAG_Z | FLAG_N | FLAG_V)) == 0);
194 | }
195 |
196 | /*
197 | * EXG - exchange two registers
198 | */
199 | TEST_CASE("M6809OpCodes EXGRegistersXY", "[m6809]") {
200 | MockMemory mem;
201 | uint64_t cycles = 0;
202 | M6809 cpu = OpCodeTestHelper(mem);
203 | auto& registers = cpu.getRegisters();
204 |
205 | REQUIRE_CALL(mem, Read(0x00)).RETURN(0x1E);
206 | REQUIRE_CALL(mem, Read(0x01)).RETURN(0x12); // X <-> Y
207 |
208 | registers.X = 0x00FF;
209 | registers.Y = 0xFF00;
210 |
211 | REQUIRE(cpu.Execute(cycles) == E_SUCCESS);
212 | REQUIRE(registers.Y == 0x00FF);
213 | REQUIRE(registers.X == 0xFF00);
214 | REQUIRE((registers.CC & (FLAG_Z | FLAG_N | FLAG_V)) == 0);
215 | }
216 |
217 | /*
218 | * TFR - move one register to another
219 | */
220 | TEST_CASE("M6809OpCodes TFRRegistersXY", "[m6809]") {
221 | MockMemory mem;
222 | uint64_t cycles = 0;
223 | M6809 cpu = OpCodeTestHelper(mem);
224 | auto& registers = cpu.getRegisters();
225 |
226 | REQUIRE_CALL(mem, Read(0x00)).RETURN(0x1F);
227 | REQUIRE_CALL(mem, Read(0x01)).RETURN(0x12); // X -> Y
228 |
229 | registers.X = 0x1111;
230 | registers.Y = 0x0000;
231 |
232 | REQUIRE(cpu.Execute(cycles) == E_SUCCESS);
233 | REQUIRE(registers.X == 0x1111);
234 | REQUIRE(registers.Y == 0x1111);
235 | REQUIRE((registers.CC & (FLAG_Z | FLAG_N | FLAG_V)) == 0);
236 | }
237 |
238 | /*
239 | * BSR - Branch Subroutine
240 | */
241 | TEST_CASE("M6809OpCodes BSRCorrectJump", "[m6809]") {
242 | MockMemory mem;
243 | uint64_t cycles = 0;
244 | M6809 cpu = OpCodeTestHelper(mem);
245 | auto& registers = cpu.getRegisters();
246 |
247 | REQUIRE_CALL(mem, Read(0x00)).RETURN(0x8D); // BSR
248 | REQUIRE_CALL(mem, Read(0x01)).RETURN(0x10);
249 |
250 | REQUIRE_CALL(mem, Write(0xffff, 0x02));
251 | REQUIRE_CALL(mem, Write(0xfffe, 0x00));
252 |
253 | REQUIRE(cpu.Execute(cycles) == E_SUCCESS);
254 | REQUIRE(registers.PC == 0x0012);
255 | }
256 |
257 | /*
258 | * Illegal Opcode
259 | */
260 | TEST_CASE("M6809OpCodes IllegalOp", "[m6809]") {
261 | MockMemory mem;
262 | uint64_t cycles = 0;
263 | M6809 cpu = OpCodeTestHelper(mem);
264 | auto& registers = cpu.getRegisters();
265 |
266 | REQUIRE_CALL(mem, Read(0x00)).RETURN(0x05);
267 |
268 | REQUIRE(cpu.Execute(cycles) == E_UNKNOWN_OPCODE);
269 | }
270 |
--------------------------------------------------------------------------------
/tests/vectrex_test.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016-2024 Team Vectrexia
3 |
4 | This file is part of Vectrexia.
5 |
6 | Vectrexia is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | Vectrexia is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with Vectrexia. If not, see .
18 | */
19 |
20 | #include
21 | #include
22 | #include
23 |
--------------------------------------------------------------------------------
/vcpkg-configuration.json:
--------------------------------------------------------------------------------
1 | {
2 | "default-registry": {
3 | "kind": "git",
4 | "baseline": "da5024ed2b7d86be2c41164549d6248ecc682a18",
5 | "repository": "https://github.com/microsoft/vcpkg"
6 | },
7 | "registries": [
8 | {
9 | "kind": "artifact",
10 | "location": "https://github.com/microsoft/vcpkg-ce-catalog/archive/refs/heads/main.zip",
11 | "name": "microsoft"
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/vcpkg.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": [
3 | "catch2",
4 | "cxxopts",
5 | "trompeloeil",
6 | "fmt"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/vectgif/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | find_package(cxxopts CONFIG REQUIRED)
2 | find_package(fmt CONFIG REQUIRED)
3 |
4 | add_executable(vectgif main.cpp)
5 |
6 | include_directories(../src)
7 |
8 | if (MSVC)
9 | set(LIBRETRO_SRC vectrexia_libretro_static)
10 | else()
11 | set(LIBRETRO_SRC vectrexia_libretro)
12 | endif()
13 |
14 | target_link_libraries(vectgif PRIVATE ${LIBRETRO_SRC})
15 | target_link_libraries(vectgif PRIVATE cxxopts::cxxopts)
16 | target_link_libraries(vectgif PRIVATE fmt::fmt)
17 |
--------------------------------------------------------------------------------
/vectgif/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include "gif.h"
13 | #include
14 |
15 | constexpr size_t ROM_SIZE = 65536;
16 | constexpr size_t MAX_FILENAME_SIZE = 2000;
17 |
18 | std::unique_ptr vectrex = std::make_unique();
19 | std::array gif_buffer{};
20 |
21 | int main(int argc, char *argv[])
22 | {
23 | long skipframes = 0;
24 | long outframes = 1000;
25 | std::array rombuffer{};
26 | GifWriter gw{};
27 |
28 | // Parse command line arguments using cxxopts
29 | cxxopts::Options options("vectgif", "Generate a GIF from a Vectrex ROM");
30 | options.add_options()
31 | ("s,skipframes", "Number of frames to skip", cxxopts::value()->default_value("0"))
32 | ("n,outframes", "Number of output frames", cxxopts::value()->default_value("1000"))
33 | ("rom", "ROM file", cxxopts::value())
34 | ("gif", "GIF output file", cxxopts::value()->default_value(""));
35 |
36 | auto result = options.parse(argc, argv);
37 |
38 | skipframes = result["skipframes"].as