├── .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 | ![Build/Test Workflow](https://github.com/beardypig/vectrexia-emulator/actions/workflows/build-test.yml/badge.svg) 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(); 39 | outframes = result["outframes"].as(); 40 | 41 | if (!result.count("rom")) { 42 | std::cerr << "vectgif: usage: vectgif [gif]\n"; 43 | return 1; 44 | } 45 | 46 | std::string rom_filename = result["rom"].as(); 47 | std::string giffilename; 48 | if (result.count("gif") && !result["gif"].as().empty()) { 49 | giffilename = result["gif"].as(); 50 | } else { 51 | giffilename = fmt::format("{}.gif", rom_filename); 52 | } 53 | 54 | // Load the ROM file 55 | std::ifstream romfile(rom_filename, std::ios::binary); 56 | if (!romfile) { 57 | std::cerr << fmt::format("[ROM]: Failed to open ROM file {}\n", rom_filename); 58 | return 1; 59 | } 60 | 61 | std::cout << fmt::format("[ROM]: loading ROM \"{}\"\n", rom_filename); 62 | romfile.read(reinterpret_cast(rombuffer.data()), ROM_SIZE); 63 | auto r = static_cast(romfile.gcount()); 64 | romfile.close(); 65 | 66 | std::cout << fmt::format("[ROM]: size = {}\n", r); 67 | 68 | if (!vectrex->LoadCartridge(rombuffer.data(), r)) { 69 | std::cerr << "Failed to load the ROM file\n"; 70 | return 1; 71 | } 72 | 73 | GifBegin(&gw, giffilename.c_str(), FRAME_WIDTH, FRAME_HEIGHT, 2, 8, false); 74 | 75 | vectrex->Reset(); 76 | 77 | vectrex->SetPlayerOne(0x80, 0x80, 1, 1, 1, 1); 78 | vectrex->SetPlayerTwo(0x80, 0x80, 1, 1, 1, 1); 79 | 80 | for (int frame = 0; frame < outframes; frame++) { 81 | for (int s = 0; s < skipframes + 1; s++) { 82 | vectrex->Run(30000); 83 | } 84 | 85 | auto framebuffer = vectrex->getFramebuffer(); 86 | 87 | auto gb = gif_buffer.begin(); 88 | for (const auto &fb : *framebuffer) { 89 | *gb++ = static_cast(fb.value * 0xffu); 90 | *gb++ = static_cast(fb.value * 0xffu); 91 | *gb++ = static_cast(fb.value * 0xffu); 92 | *gb++ = static_cast(fb.value * 0xffu); 93 | } 94 | GifWriteFrame(&gw, gif_buffer.data(), FRAME_WIDTH, FRAME_HEIGHT, 2); 95 | if (frame % 100 == 0) { 96 | std::cout << fmt::format("[VECTREX] frame = {}\n", frame); 97 | } 98 | } 99 | 100 | GifEnd(&gw); 101 | 102 | return 0; 103 | } 104 | --------------------------------------------------------------------------------