From 542e59be55f64c13ae2593aa796eba0f15a99209 Mon Sep 17 00:00:00 2001 From: oaleshina Date: Sun, 12 Jan 2020 17:59:37 +0300 Subject: [PATCH] [pscx_emulator] GTE and CD-ROM fixes - added GTE unittests - CdRom::getStatus(), fixed status flags m_params.empty() -> m_response.empty() - code cleanup - drawing area (scissor test) fixup - changed viewport dimension - fixed leading zeroes calculation --- gte_tests/gte_tests.vcxproj | 131 ++++ gte_tests/gte_tests.vcxproj.filters | 36 + gte_tests/gte_tests.vcxproj.user | 4 + gte_tests/tests.cpp | 948 +++++++++++++++++++++++ pscx_emulator.sln | 10 + pscx_emulator/assets/vertex.glsl | 4 +- pscx_emulator/pscx_cdrom.cpp | 83 +- pscx_emulator/pscx_cdrom.h | 4 + pscx_emulator/pscx_disc.cpp | 3 + pscx_emulator/pscx_gpu.cpp | 23 +- pscx_emulator/pscx_gpu.h | 2 +- pscx_emulator/pscx_gte.cpp | 4 +- pscx_emulator/pscx_gte_divider.cpp | 12 +- pscx_emulator/pscx_gte_divider.h | 4 +- pscx_emulator/pscx_interconnect.cpp | 4 +- pscx_emulator/pscx_minutesecondframe.cpp | 2 + pscx_emulator/pscx_renderer.cpp | 9 +- pscx_emulator/pscx_spu.cpp | 1 - pscx_emulator/pscx_timekeeper.cpp | 11 +- pscx_emulator/pscx_timekeeper.h | 2 +- pscx_emulator/pscx_timers.cpp | 33 +- pscx_emulator/pscx_timers.h | 8 +- 22 files changed, 1292 insertions(+), 46 deletions(-) create mode 100644 gte_tests/gte_tests.vcxproj create mode 100644 gte_tests/gte_tests.vcxproj.filters create mode 100644 gte_tests/gte_tests.vcxproj.user create mode 100644 gte_tests/tests.cpp diff --git a/gte_tests/gte_tests.vcxproj b/gte_tests/gte_tests.vcxproj new file mode 100644 index 0000000..ad9ee27 --- /dev/null +++ b/gte_tests/gte_tests.vcxproj @@ -0,0 +1,131 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {34E6270F-7045-4CF7-B49C-34AF2BAAC20F} + gtetests + 10.0.18362.0 + + + + Application + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + Application + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + Level3 + MaxSpeed + true + true + true + true + $(ProjectDir)\..\pscx_emulator;%(AdditionalIncludeDirectories) + + + true + true + + + + + Level3 + Disabled + true + true + + + + + Level3 + Disabled + true + true + D:\pscx_emulator\pscx_emulator;%(AdditionalIncludeDirectories) + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gte_tests/gte_tests.vcxproj.filters b/gte_tests/gte_tests.vcxproj.filters new file mode 100644 index 0000000..4020003 --- /dev/null +++ b/gte_tests/gte_tests.vcxproj.filters @@ -0,0 +1,36 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/gte_tests/gte_tests.vcxproj.user b/gte_tests/gte_tests.vcxproj.user new file mode 100644 index 0000000..be25078 --- /dev/null +++ b/gte_tests/gte_tests.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/gte_tests/tests.cpp b/gte_tests/tests.cpp new file mode 100644 index 0000000..d4c8806 --- /dev/null +++ b/gte_tests/tests.cpp @@ -0,0 +1,948 @@ +#include "pscx_gte.h" + +#include +#include +#include +#include + +using RegMap = std::unordered_map; + +inline void CHECK(std::string testCase, bool result) +{ + if (result) + { + std::cout << "Test case passed: "; + } + else + { + std::cout << "Test case failed: "; + } + std::cout << testCase << std::endl; +} + +// GTE register config: slice of couples (register_offset, register_value). +// Missing registers are set to 0 +struct Config +{ + Gte* make_gte() + { + Gte* gte = new Gte; + for (auto& control : controls) + { + gte->setControl(static_cast(control.first), control.second); + } + + for (auto& reg : data) + { + if (reg.first == 15) + { + continue; + } + if (reg.first == 28) + { + continue; + } + if (reg.first == 29) + { + // Read only register + continue; + } + gte->setData(static_cast(reg.first), reg.second); + } + return gte; + } + + uint32_t validate(const Gte* gte) + { + uint32_t errorCount = 0UL; + for (auto& control : controls) + { + uint32_t value = gte->getControl(static_cast(control.first)); + if (value != control.second) + { + std::cout << "Control register " << std::hex << control.first << + " expected " << control.second << " got " << value << std::endl; + errorCount++; + } + } + + for (auto& reg : data) + { + uint32_t value = gte->getData(static_cast(reg.first)); + if (value != reg.second) + { + std::cout << "Data register " << std::hex << reg.first << + " expected " << reg.second << " got " << value << std::endl; + errorCount++; + } + } + + return errorCount; + } + + // Control register + RegMap controls; + // Data registers + RegMap data; +}; + +struct Test +{ + // Test description + std::string desc; + // Initial GTE configuration + Config initial; + // GTE command being executed + uint32_t command; + // GTE configuration post-command + Config result; +}; + +static bool gte_lzcr() +{ + std::unordered_map expected = + { + { 0x00000000, 32 }, + { 0xffffffff, 32 }, + { 0x00000001, 31 }, + { 0x80000000, 1 }, + { 0x7fffffff, 1 }, + { 0xdeadbeef, 2 }, + { 0x000c0ffe, 12 }, + { 0xfffc0ffe, 14 } + }; + + Gte gte; + for (auto exp : expected) + { + gte.setData(30, exp.first); + if (gte.getData(31) != exp.second) + { + return false; + } + } + return true; +} + +std::vector TESTS = +{ + // Test cmdRotateTranslatePerspectiveTransform() + { + "GTE_RTPT, lm=0, cv=0, v=0, mx=0, sf=1", // desc + RegMap // initial.controls + { + { 0, 0x00000ffb }, + { 1, 0xffb7ff44 }, + { 2, 0xf9ca0ebc }, + { 3, 0x063700ad }, + { 4, 0x00000eb7 }, + { 6, 0xfffffeac }, + { 7, 0x00001700 }, + { 9, 0x00000fa0 }, + { 10, 0x0000f060 }, + { 11, 0x0000f060 }, + { 13, 0x00000640 }, + { 14, 0x00000640 }, + { 15, 0x00000640 }, + { 16, 0x0bb80fa0 }, + { 17, 0x0fa00fa0 }, + { 18, 0x0fa00bb8 }, + { 19, 0x0bb80fa0 }, + { 20, 0x00000fa0 }, + { 24, 0x01400000 }, + { 25, 0x00f00000 }, + { 26, 0x00000400 }, + { 27, 0xfffffec8 }, + { 28, 0x01400000 }, + { 29, 0x00000155 }, + { 30, 0x00000100 } + }, + RegMap // initial.data + { + { 0, 0x00e70119 }, + { 1, 0xfffffe65 }, + { 2, 0x00e700d5 }, + { 3, 0xfffffe21 }, + { 4, 0x00b90119 }, + { 5, 0xfffffe65 }, + { 31, 0x00000020 } + }, + 0x00080030, // command + RegMap // result.controls + { + { 0, 0x00000ffb }, + { 1, 0xffb7ff44 }, + { 2, 0xf9ca0ebc }, + { 3, 0x063700ad }, + { 4, 0x00000eb7 }, + { 6, 0xfffffeac }, + { 7, 0x00001700 }, + { 9, 0x00000fa0 }, + { 10, 0x0000f060 }, + { 11, 0x0000f060 }, + { 13, 0x00000640 }, + { 14, 0x00000640 }, + { 15, 0x00000640 }, + { 16, 0x0bb80fa0 }, + { 17, 0x0fa00fa0 }, + { 18, 0x0fa00bb8 }, + { 19, 0x0bb80fa0 }, + { 20, 0x00000fa0 }, + { 24, 0x01400000 }, + { 25, 0x00f00000 }, + { 26, 0x00000400 }, + { 27, 0xfffffec8 }, + { 28, 0x01400000 }, + { 29, 0x00000155 }, + { 30, 0x00000100 }, + { 31, 0x00001000 } + }, + RegMap // result.data + { + { 0, 0x00e70119 }, + { 1, 0xfffffe65 }, + { 2, 0x00e700d5 }, + { 3, 0xfffffe21 }, + { 4, 0x00b90119 }, + { 5, 0xfffffe65 }, + { 8, 0x00001000 }, + { 9, 0x0000012b }, + { 10, 0xfffffff0 }, + { 11, 0x000015d9 }, + { 12, 0x00f40176 }, + { 13, 0x00f9016b }, + { 14, 0x00ed0176 }, + { 15, 0x00ed0176 }, + { 17, 0x000015eb }, + { 18, 0x000015aa }, + { 19, 0x000015d9 }, + { 24, 0x0106e038 }, + { 25, 0x0000012b }, + { 26, 0xfffffff0 }, + { 27, 0x000015d9 }, + { 28, 0x00007c02 }, + { 29, 0x00007c02 }, + { 31, 0x00000020 } + } + }, + // Test cmdNormalClip() + { + "GTE_NCLIP, lm=0, cv=0, v=0, mx=0, sf=0", // desc + RegMap // initial.controls + { + { 0, 0x00000ffb }, + { 1, 0xffb7ff44 }, + { 2, 0xf9ca0ebc }, + { 3, 0x063700ad }, + { 4, 0x00000eb7 }, + { 6, 0xfffffeac }, + { 7, 0x00001700 }, + { 9, 0x00000fa0 }, + { 10, 0x0000f060 }, + { 11, 0x0000f060 }, + { 13, 0x00000640 }, + { 14, 0x00000640 }, + { 15, 0x00000640 }, + { 16, 0x0bb80fa0 }, + { 17, 0x0fa00fa0 }, + { 18, 0x0fa00bb8 }, + { 19, 0x0bb80fa0 }, + { 20, 0x00000fa0 }, + { 24, 0x01400000 }, + { 25, 0x00f00000 }, + { 26, 0x00000400 }, + { 27, 0xfffffec8 }, + { 28, 0x01400000 }, + { 29, 0x00000155 }, + { 30, 0x00000100 }, + { 31, 0x00001000 } + }, + RegMap // initial.data + { + { 0, 0x00e70119 }, + { 1, 0xfffffe65 }, + { 2, 0x00e700d5 }, + { 3, 0xfffffe21 }, + { 4, 0x00b90119 }, + { 5, 0xfffffe65 }, + { 8, 0x00001000 }, + { 9, 0x0000012b }, + { 10, 0xfffffff0 }, + { 11, 0x000015d9 }, + { 12, 0x00f40176 }, + { 13, 0x00f9016b }, + { 14, 0x00ed0176 }, + { 15, 0x00ed0176 }, + { 17, 0x000015eb }, + { 18, 0x000015aa }, + { 19, 0x000015d9 }, + { 24, 0x0106e038 }, + { 25, 0x0000012b }, + { 26, 0xfffffff0 }, + { 27, 0x000015d9 }, + { 28, 0x00007c02 }, + { 29, 0x00007c02 }, + { 31, 0x00000020 } + }, + 0x00000006, // command + RegMap // result.controls + { + { 0, 0x00000ffb }, + { 1, 0xffb7ff44 }, + { 2, 0xf9ca0ebc }, + { 3, 0x063700ad }, + { 4, 0x00000eb7 }, + { 6, 0xfffffeac }, + { 7, 0x00001700 }, + { 9, 0x00000fa0 }, + { 10, 0x0000f060 }, + { 11, 0x0000f060 }, + { 13, 0x00000640 }, + { 14, 0x00000640 }, + { 15, 0x00000640 }, + { 16, 0x0bb80fa0 }, + { 17, 0x0fa00fa0 }, + { 18, 0x0fa00bb8 }, + { 19, 0x0bb80fa0 }, + { 20, 0x00000fa0 }, + { 24, 0x01400000 }, + { 25, 0x00f00000 }, + { 26, 0x00000400 }, + { 27, 0xfffffec8 }, + { 28, 0x01400000 }, + { 29, 0x00000155 }, + { 30, 0x00000100 } + }, + RegMap // result.data + { + { 0, 0x00e70119 }, + { 1, 0xfffffe65 }, + { 2, 0x00e700d5 }, + { 3, 0xfffffe21 }, + { 4, 0x00b90119 }, + { 5, 0xfffffe65 }, + { 8, 0x00001000 }, + { 9, 0x0000012b }, + { 10, 0xfffffff0 }, + { 11, 0x000015d9 }, + { 12, 0x00f40176 }, + { 13, 0x00f9016b }, + { 14, 0x00ed0176 }, + { 15, 0x00ed0176 }, + { 17, 0x000015eb }, + { 18, 0x000015aa }, + { 19, 0x000015d9 }, + { 24, 0x0000004d }, + { 25, 0x0000012b }, + { 26, 0xfffffff0 }, + { 27, 0x000015d9 }, + { 28, 0x00007c02 }, + { 29, 0x00007c02 }, + { 31, 0x00000020 } + } + }, + // Test cmdAverageSingleZ3() + { + "GTE_AVSZ3, lm=0, cv=0, v=0, mx=0, sf=1", // initial.controls + RegMap // initial.controls + { + { 0, 0x00000ffb }, + { 1, 0xffb7ff44 }, + { 2, 0xf9ca0ebc }, + { 3, 0x063700ad }, + { 4, 0x00000eb7 }, + { 6, 0xfffffeac }, + { 7, 0x00001700 }, + { 9, 0x00000fa0 }, + { 10, 0x0000f060 }, + { 11, 0x0000f060 }, + { 13, 0x00000640 }, + { 14, 0x00000640 }, + { 15, 0x00000640 }, + { 16, 0x0bb80fa0 }, + { 17, 0x0fa00fa0 }, + { 18, 0x0fa00bb8 }, + { 19, 0x0bb80fa0 }, + { 20, 0x00000fa0 }, + { 24, 0x01400000 }, + { 25, 0x00f00000 }, + { 26, 0x00000400 }, + { 27, 0xfffffec8 }, + { 28, 0x01400000 }, + { 29, 0x00000155 }, + { 30, 0x00000100 } + }, + RegMap // initial.data + { + { 0, 0x00e70119 }, + { 1, 0xfffffe65 }, + { 2, 0x00e700d5 }, + { 3, 0xfffffe21 }, + { 4, 0x00b90119 }, + { 5, 0xfffffe65 }, + { 8, 0x00001000 }, + { 9, 0x0000012b }, + { 10, 0xfffffff0 }, + { 11, 0x000015d9 }, + { 12, 0x00f40176 }, + { 13, 0x00f9016b }, + { 14, 0x00ed0176 }, + { 15, 0x00ed0176 }, + { 17, 0x000015eb }, + { 18, 0x000015aa }, + { 19, 0x000015d9 }, + { 24, 0x0000004d }, + { 25, 0x0000012b }, + { 26, 0xfffffff0 }, + { 27, 0x000015d9 }, + { 28, 0x00007c02 }, + { 29, 0x00007c02 } + }, + 0x0008002d, // command + RegMap // result.controls + { + { 0, 0x00000ffb }, + { 1, 0xffb7ff44 }, + { 2, 0xf9ca0ebc }, + { 3, 0x063700ad }, + { 4, 0x00000eb7 }, + { 6, 0xfffffeac }, + { 7, 0x00001700 }, + { 9, 0x00000fa0 }, + { 10, 0x0000f060 }, + { 11, 0x0000f060 }, + { 13, 0x00000640 }, + { 14, 0x00000640 }, + { 15, 0x00000640 }, + { 16, 0x0bb80fa0 }, + { 17, 0x0fa00fa0 }, + { 18, 0x0fa00bb8 }, + { 19, 0x0bb80fa0 }, + { 20, 0x00000fa0 }, + { 24, 0x01400000 }, + { 25, 0x00f00000 }, + { 26, 0x00000400 }, + { 27, 0xfffffec8 }, + { 28, 0x01400000 }, + { 29, 0x00000155 }, + { 30, 0x00000100 } + }, + RegMap // result.data + { + { 0, 0x00e70119 }, + { 1, 0xfffffe65 }, + { 2, 0x00e700d5 }, + { 3, 0xfffffe21 }, + { 4, 0x00b90119 }, + { 5, 0xfffffe65 }, + { 7, 0x00000572 }, + { 8, 0x00001000 }, + { 9, 0x0000012b }, + { 10, 0xfffffff0 }, + { 11, 0x000015d9 }, + { 12, 0x00f40176 }, + { 13, 0x00f9016b }, + { 14, 0x00ed0176 }, + { 15, 0x00ed0176 }, + { 17, 0x000015eb }, + { 18, 0x000015aa }, + { 19, 0x000015d9 }, + { 24, 0x00572786 }, + { 25, 0x0000012b }, + { 26, 0xfffffff0 }, + { 27, 0x000015d9 }, + { 28, 0x00007c02 }, + { 29, 0x00007c02 }, + { 31, 0x00000020 } + } + }, + // Test cmdNormalColorDepthSingleVector() + { + "GTE_NCDS, lm=1, cv=0, v=0, mx=0, sf=1", // desc + RegMap // initial.controls + { + { 0, 0x00000ffb }, + { 1, 0xffb7ff44 }, + { 2, 0xf9ca0ebc }, + { 3, 0x063700ad }, + { 4, 0x00000eb7 }, + { 6, 0xfffffeac }, + { 7, 0x00001700 }, + { 9, 0x00000fa0 }, + { 10, 0x0000f060 }, + { 11, 0x0000f060 }, + { 13, 0x00000640 }, + { 14, 0x00000640 }, + { 15, 0x00000640 }, + { 16, 0x0bb80fa0 }, + { 17, 0x0fa00fa0 }, + { 18, 0x0fa00bb8 }, + { 19, 0x0bb80fa0 }, + { 20, 0x00000fa0 }, + { 24, 0x01400000 }, + { 25, 0x00f00000 }, + { 26, 0x00000400 }, + { 27, 0xfffffec8 }, + { 28, 0x01400000 }, + { 29, 0x00000155 }, + { 30, 0x00000100 } + }, + RegMap // initial.data + { + { 0, 0x00000b50 }, + { 1, 0xfffff4b0 }, + { 2, 0x00e700d5 }, + { 3, 0xfffffe21 }, + { 4, 0x00b90119 }, + { 5, 0xfffffe65 }, + { 6, 0x2094a539 }, + { 7, 0x00000572 }, + { 8, 0x00001000 }, + { 9, 0x0000012b }, + { 10, 0xfffffff0 }, + { 11, 0x000015d9 }, + { 12, 0x00f40176 }, + { 13, 0x00f9016b }, + { 14, 0x00ed0176 }, + { 15, 0x00ed0176 }, + { 17, 0x000015eb }, + { 18, 0x000015aa }, + { 19, 0x000015d9 }, + { 24, 0x00572786 }, + { 25, 0x0000012b }, + { 26, 0xfffffff0 }, + { 27, 0x000015d9 }, + { 28, 0x00007c02 }, + { 29, 0x00007c02 }, + { 31, 0x00000020 } + }, + 0x00080413, // command + RegMap // result.controls + { + { 0, 0x00000ffb }, + { 1, 0xffb7ff44 }, + { 2, 0xf9ca0ebc }, + { 3, 0x063700ad }, + { 4, 0x00000eb7 }, + { 6, 0xfffffeac }, + { 7, 0x00001700 }, + { 9, 0x00000fa0 }, + { 10, 0x0000f060 }, + { 11, 0x0000f060 }, + { 13, 0x00000640 }, + { 14, 0x00000640 }, + { 15, 0x00000640 }, + { 16, 0x0bb80fa0 }, + { 17, 0x0fa00fa0 }, + { 18, 0x0fa00bb8 }, + { 19, 0x0bb80fa0 }, + { 20, 0x00000fa0 }, + { 24, 0x01400000 }, + { 25, 0x00f00000 }, + { 26, 0x00000400 }, + { 27, 0xfffffec8 }, + { 28, 0x01400000 }, + { 29, 0x00000155 }, + { 30, 0x00000100 }, + { 31, 0x81f00000 } + }, + RegMap // result.data + { + { 0, 0x00000b50 }, + { 1, 0xfffff4b0 }, + { 2, 0x00e700d5 }, + { 3, 0xfffffe21 }, + { 4, 0x00b90119 }, + { 5, 0xfffffe65 }, + { 6, 0x2094a539 }, + { 7, 0x00000572 }, + { 8, 0x00001000 }, + { 12, 0x00f40176 }, + { 13, 0x00f9016b }, + { 14, 0x00ed0176 }, + { 15, 0x00ed0176 }, + { 17, 0x000015eb }, + { 18, 0x000015aa }, + { 19, 0x000015d9 }, + { 22, 0x20000000 }, + { 24, 0x00572786 }, + { 25, 0xffffffff }, + { 26, 0xffffffff }, + { 31, 0x00000020 } + } + }, + // Test cmdDepthQueueSingle() + { + "GTE_DCPS, lm=0, cv=0, v=0, mx=0, sf=1", // desc + RegMap // initial.controls + { + { 0, 0x00000ffb }, + { 1, 0xffb7ff44 }, + { 2, 0xf9ca0ebc }, + { 3, 0x063700ad }, + { 4, 0x00000eb7 }, + { 6, 0xfffffeac }, + { 7, 0x00001700 }, + { 9, 0x00000fa0 }, + { 10, 0x0000f060 }, + { 11, 0x0000f060 }, + { 13, 0x00000640 }, + { 14, 0x00000640 }, + { 15, 0x00000640 }, + { 16, 0x0bb80fa0 }, + { 17, 0x0fa00fa0 }, + { 18, 0x0fa00bb8 }, + { 19, 0x0bb80fa0 }, + { 20, 0x00000fa0 }, + { 24, 0x01400000 }, + { 25, 0x00f00000 }, + { 26, 0x00000400 }, + { 27, 0xfffffec8 }, + { 28, 0x01400000 }, + { 29, 0x00000155 }, + { 30, 0x00000100 } + }, + RegMap // initial.data + { + { 0, 0x00000b50 }, + { 1, 0xfffff4b0 }, + { 2, 0x00e700d5 }, + { 3, 0xfffffe21 }, + { 4, 0x00b90119 }, + { 5, 0xfffffe65 }, + { 6, 0x2094a539 }, + { 7, 0x00000572 }, + { 8, 0x00001000 }, + { 9, 0x0000012b }, + { 10, 0xfffffff0 }, + { 11, 0x000015d9 }, + { 12, 0x00f40176 }, + { 13, 0x00f9016b }, + { 14, 0x00ed0176 }, + { 15, 0x00ed0176 }, + { 17, 0x000015eb }, + { 18, 0x000015aa }, + { 19, 0x000015d9 }, + { 24, 0x00572786 }, + { 25, 0x0000012b }, + { 26, 0xfffffff0 }, + { 27, 0x000015d9 }, + { 28, 0x00007c02 }, + { 29, 0x00007c02 }, + { 31, 0x00000020 } + }, + 0x00080010, // command + RegMap // result.controls + { + { 0, 0x00000ffb }, + { 1, 0xffb7ff44 }, + { 2, 0xf9ca0ebc }, + { 3, 0x063700ad }, + { 4, 0x00000eb7 }, + { 6, 0xfffffeac }, + { 7, 0x00001700 }, + { 9, 0x00000fa0 }, + { 10, 0x0000f060 }, + { 11, 0x0000f060 }, + { 13, 0x00000640 }, + { 14, 0x00000640 }, + { 15, 0x00000640 }, + { 16, 0x0bb80fa0 }, + { 17, 0x0fa00fa0 }, + { 18, 0x0fa00bb8 }, + { 19, 0x0bb80fa0 }, + { 20, 0x00000fa0 }, + { 24, 0x01400000 }, + { 25, 0x00f00000 }, + { 26, 0x00000400 }, + { 27, 0xfffffec8 }, + { 28, 0x01400000 }, + { 29, 0x00000155 }, + { 30, 0x00000100 } + }, + RegMap // result.data + { + { 0, 0x00000b50 }, + { 1, 0xfffff4b0 }, + { 2, 0x00e700d5 }, + { 3, 0xfffffe21 }, + { 4, 0x00b90119 }, + { 5, 0xfffffe65 }, + { 6, 0x2094a539 }, + { 7, 0x00000572 }, + { 8, 0x00001000 }, + { 12, 0x00f40176 }, + { 13, 0x00f9016b }, + { 14, 0x00ed0176 }, + { 15, 0x00ed0176 }, + { 17, 0x000015eb }, + { 18, 0x000015aa }, + { 19, 0x000015d9 }, + { 22, 0x20000000 }, + { 24, 0x00572786 }, + { 31, 0x00000020 } + } + }, + // Test cmdRotateTranslatePerspectiveTransformSingle(config) + { + "GTE_RTPS, lm=0, cv=0, v=0, mx=0, sf=1", // desc + RegMap // initial.controls + { + { 0, 0x00000ffb }, + { 1, 0xffb7ff44 }, + { 2, 0xf9ca0ebc }, + { 3, 0x063700ad }, + { 4, 0x00000eb7 }, + { 6, 0xfffffeac }, + { 7, 0x00001700 }, + { 9, 0x00000fa0 }, + { 10, 0x0000f060 }, + { 11, 0x0000f060 }, + { 13, 0x00000640 }, + { 14, 0x00000640 }, + { 15, 0x00000640 }, + { 16, 0x0bb80fa0 }, + { 17, 0x0fa00fa0 }, + { 18, 0x0fa00bb8 }, + { 19, 0x0bb80fa0 }, + { 20, 0x00000fa0 }, + { 24, 0x01400000 }, + { 25, 0x00f00000 }, + { 26, 0x00000400 }, + { 27, 0xfffffec8 }, + { 28, 0x01400000 }, + { 29, 0x00000155 }, + { 30, 0x00000100 } + }, + RegMap // initial.data + { + { 0, 0x00000b50 }, + { 1, 0xfffff4b0 }, + { 2, 0x00e700d5 }, + { 3, 0xfffffe21 }, + { 4, 0x00b90119 }, + { 5, 0xfffffe65 }, + { 6, 0x2094a539 }, + { 8, 0x00001000 }, + { 31, 0x00000020 } + }, + 0x00080001, // command + RegMap // result.controls + { + { 0, 0x00000ffb }, + { 1, 0xffb7ff44 }, + { 2, 0xf9ca0ebc }, + { 3, 0x063700ad }, + { 4, 0x00000eb7 }, + { 6, 0xfffffeac }, + { 7, 0x00001700 }, + { 9, 0x00000fa0 }, + { 10, 0x0000f060 }, + { 11, 0x0000f060 }, + { 13, 0x00000640 }, + { 14, 0x00000640 }, + { 15, 0x00000640 }, + { 16, 0x0bb80fa0 }, + { 17, 0x0fa00fa0 }, + { 18, 0x0fa00bb8 }, + { 19, 0x0bb80fa0 }, + { 20, 0x00000fa0 }, + { 24, 0x01400000 }, + { 25, 0x00f00000 }, + { 26, 0x00000400 }, + { 27, 0xfffffec8 }, + { 28, 0x01400000 }, + { 29, 0x00000155 }, + { 30, 0x00000100 }, + { 31, 0x80004000 } + }, + RegMap // result.data + { + { 0, 0x00000b50 }, + { 1, 0xfffff4b0 }, + { 2, 0x00e700d5 }, + { 3, 0xfffffe21 }, + { 4, 0x00b90119 }, + { 5, 0xfffffe65 }, + { 6, 0x2094a539 }, + { 8, 0x00000e08 }, + { 9, 0x00000bd1 }, + { 10, 0x000002dc }, + { 11, 0x00000d12 }, + { 14, 0x01d003ff }, + { 15, 0x01d003ff }, + { 19, 0x00000d12 }, + { 24, 0x00e08388 }, + { 25, 0x00000bd1 }, + { 26, 0x000002dc }, + { 27, 0x00000d12 }, + { 28, 0x000068b7 }, + { 29, 0x000068b7 }, + { 31, 0x00000020 } + } + }, + // Test cmdNormalColorColorTriple(config) + { + "GTE_NCCT, lm=0, cv=0, v=0, mx=0, sf=1", // desc + RegMap // initial.controls + { + { 0, 0x00000ffb }, + { 1, 0xffb7ff44 }, + { 2, 0xf9ca0ebc }, + { 3, 0x063700ad }, + { 4, 0x00000eb7 }, + { 6, 0xfffffeac }, + { 7, 0x00001700 }, + { 9, 0x00000fa0 }, + { 10, 0x0000f060 }, + { 11, 0x0000f060 }, + { 13, 0x00000640 }, + { 14, 0x00000640 }, + { 15, 0x00000640 }, + { 16, 0x0bb80fa0 }, + { 17, 0x0fa00fa0 }, + { 18, 0x0fa00bb8 }, + { 19, 0x0bb80fa0 }, + { 20, 0x00000fa0 }, + { 24, 0x01400000 }, + { 25, 0x00f00000 }, + { 26, 0x00000400 }, + { 27, 0xfffffec8 }, + { 28, 0x01400000 }, + { 29, 0x00000155 }, + { 30, 0x00000100 } + }, + RegMap // initial.data + { + { 0, 0x00000b50 }, + { 1, 0xfffff4b0 }, + { 2, 0x00e700d5 }, + { 3, 0xfffffe21 }, + { 4, 0x00b90119 }, + { 5, 0xfffffe65 }, + { 6, 0x2094a539 }, + { 7, 0x00000572 }, + { 8, 0x00001000 }, + { 12, 0x00f40176 }, + { 13, 0x00f9016b }, + { 14, 0x00ed0176 }, + { 15, 0x00ed0176 }, + { 17, 0x000015eb }, + { 18, 0x000015aa }, + { 19, 0x000015d9 }, + { 24, 0x00572786 }, + { 31, 0x00000020 } + }, + 0x0008003f, // command + RegMap // result.controls + { + { 0, 0x00000ffb }, + { 1, 0xffb7ff44 }, + { 2, 0xf9ca0ebc }, + { 3, 0x063700ad }, + { 4, 0x00000eb7 }, + { 6, 0xfffffeac }, + { 7, 0x00001700 }, + { 9, 0x00000fa0 }, + { 10, 0x0000f060 }, + { 11, 0x0000f060 }, + { 13, 0x00000640 }, + { 14, 0x00000640 }, + { 15, 0x00000640 }, + { 16, 0x0bb80fa0 }, + { 17, 0x0fa00fa0 }, + { 18, 0x0fa00bb8 }, + { 19, 0x0bb80fa0 }, + { 20, 0x00000fa0 }, + { 24, 0x01400000 }, + { 25, 0x00f00000 }, + { 26, 0x00000400 }, + { 27, 0xfffffec8 }, + { 28, 0x01400000 }, + { 29, 0x00000155 }, + { 30, 0x00000100 }, + { 31, 0x00380000 } + }, + RegMap // result.data + { + { 0, 0x00000b50 }, + { 1, 0xfffff4b0 }, + { 2, 0x00e700d5 }, + { 3, 0xfffffe21 }, + { 4, 0x00b90119 }, + { 5, 0xfffffe65 }, + { 6, 0x2094a539 }, + { 7, 0x00000572 }, + { 8, 0x00001000 }, + { 9, 0x000000b3 }, + { 10, 0x00000207 }, + { 11, 0x000001d1 }, + { 12, 0x00f40176 }, + { 13, 0x00f9016b }, + { 14, 0x00ed0176 }, + { 15, 0x00ed0176 }, + { 17, 0x000015eb }, + { 18, 0x000015aa }, + { 19, 0x000015d9 }, + { 20, 0x20000000 }, + { 21, 0x201b1f0a }, + { 22, 0x201d200b }, + { 24, 0x00572786 }, + { 25, 0x000000b3 }, + { 26, 0x00000207 }, + { 27, 0x000001d1 }, + { 28, 0x00000c81 }, + { 29, 0x00000c81 }, + { 31, 0x00000020 } + } + } +}; + +static uint32_t gte_ops() +{ + uint32_t returnCode(0x0); + for (auto& test : TESTS) + { + std::cout << "Test: " << test.desc << std::endl; + std::cout << "Command: " << test.command << std::endl; + + std::unique_ptr gte(test.initial.make_gte()); + gte->command(test.command); + returnCode = test.result.validate(gte.get()); + + if (returnCode > 0x0) + break; + } + + return returnCode; +} + +static void test_divider() +{ + // Tested against mednafen's "Divide" function's output. We only + // reach the division if numerator < (divisor * 2). + CHECK("divide(0, 1)", divide(0, 1) == 0); + CHECK("divide(0, 1234)", divide(0, 1234) == 0); + CHECK("divide(1, 1)", divide(1, 1) == 0x10000); + CHECK("divide(2, 2)", divide(2, 2) == 0x10000); + CHECK("divide(0xffff, 0xffff)", divide(0xffff, 0xffff) == 0xffff); + CHECK("divide(0xffff, 0xfffe)", divide(0xffff, 0xfffe) == 0x10000); + CHECK("divide(1, 2)", divide(1, 2) == 0x8000); + CHECK("divide(1, 3)", divide(1, 3) == 0x5555); + CHECK("divide(5, 6)", divide(5, 6) == 0xd555); + CHECK("divide(1, 4)", divide(1, 4) == 0x4000); + CHECK("divide(10, 40)", divide(10, 40) == 0x4000); + CHECK("divide(0xf00, 0xbeef)", divide(0xf00, 0xbeef) == 0x141d); + CHECK("divide(9876, 8765)", divide(9876, 8765) == 0x12072); + CHECK("divide(200, 10000)", divide(200, 10000) == 0x51f); + CHECK("divide(0xffff, 0x8000)", divide(0xffff, 0x8000) == 0x1fffe); + CHECK("divide(0xe5d7, 0x72ec)", divide(0xe5d7, 0x72ec) == 0x1ffff); +} + +int main() +{ + CHECK("Calculate leading zeroes", gte_lzcr() == true); + CHECK("Test commands", gte_ops() == 0x0); + test_divider(); + return EXIT_SUCCESS; +} diff --git a/pscx_emulator.sln b/pscx_emulator.sln index 0117a42..98aeeaa 100644 --- a/pscx_emulator.sln +++ b/pscx_emulator.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 15.0.27130.2027 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pscx_emulator", "pscx_emulator\pscx_emulator.vcxproj", "{47DF9237-FA74-4433-AE57-F7A88E9EB1D8}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gte_tests", "gte_tests\gte_tests.vcxproj", "{34E6270F-7045-4CF7-B49C-34AF2BAAC20F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -21,6 +23,14 @@ Global {47DF9237-FA74-4433-AE57-F7A88E9EB1D8}.Release|x64.Build.0 = Release|x64 {47DF9237-FA74-4433-AE57-F7A88E9EB1D8}.Release|x86.ActiveCfg = Release|Win32 {47DF9237-FA74-4433-AE57-F7A88E9EB1D8}.Release|x86.Build.0 = Release|Win32 + {34E6270F-7045-4CF7-B49C-34AF2BAAC20F}.Debug|x64.ActiveCfg = Debug|x64 + {34E6270F-7045-4CF7-B49C-34AF2BAAC20F}.Debug|x64.Build.0 = Debug|x64 + {34E6270F-7045-4CF7-B49C-34AF2BAAC20F}.Debug|x86.ActiveCfg = Debug|Win32 + {34E6270F-7045-4CF7-B49C-34AF2BAAC20F}.Debug|x86.Build.0 = Debug|Win32 + {34E6270F-7045-4CF7-B49C-34AF2BAAC20F}.Release|x64.ActiveCfg = Release|x64 + {34E6270F-7045-4CF7-B49C-34AF2BAAC20F}.Release|x64.Build.0 = Release|x64 + {34E6270F-7045-4CF7-B49C-34AF2BAAC20F}.Release|x86.ActiveCfg = Release|Win32 + {34E6270F-7045-4CF7-B49C-34AF2BAAC20F}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/pscx_emulator/assets/vertex.glsl b/pscx_emulator/assets/vertex.glsl index c1d0b6b..3c4ce4b 100644 --- a/pscx_emulator/assets/vertex.glsl +++ b/pscx_emulator/assets/vertex.glsl @@ -15,11 +15,11 @@ void main() // Convert VRAM coordinates (0; 1023, 0; 511) into // OpenGL coordinates (-1; 1, -1; 1) - float xpos = (float(position.x) / 320.0) - 1.0; + float xpos = (float(position.x) / 512.0) - 1.0; // VRAM puts 0 at the top, OpenGL at the bottom // just mirror it vertically - float ypos = 1.0 - (float(position.y) / 240.0); + float ypos = 1.0 - (float(position.y) / 256.0); gl_Position = vec4(xpos, ypos, 0.0, 1.0); diff --git a/pscx_emulator/pscx_cdrom.cpp b/pscx_emulator/pscx_cdrom.cpp index 163fa66..d992590 100644 --- a/pscx_emulator/pscx_cdrom.cpp +++ b/pscx_emulator/pscx_cdrom.cpp @@ -9,7 +9,9 @@ Fifo Fifo::fromBytes(const std::vector& bytes) { Fifo fifo; for (auto byte : bytes) + { fifo.push(byte); + } return fifo; } @@ -80,9 +82,13 @@ T CdRom::load(TimeKeeper& timeKeeper, InterruptState& irqState, uint32_t offset) { // IRQ mask/flags have the 3 MSB set when read. if (m_index == 0x0) + { value = m_irqMask | 0xe0; + } else if (m_index == 0x1) + { value = m_irqFlags | 0xe0; + } break; } } @@ -123,9 +129,11 @@ void CdRom::store(TimeKeeper& timeKeeper, InterruptState& irqState, uint32_t off } case 0x2: { + //std::cout << (uint32_t)m_index << " pushParam= " << (uint32_t)valueToStore << std::endl; if (m_index == 0x0) { pushParam(valueToStore); + //std::cout << "pushParam= " << (uint32_t)valueToStore << std::endl; } else if (m_index == 0x1) { @@ -293,6 +301,7 @@ void CdRom::doSeek() assert(("Seek to track 1 pregap", m_seekTarget >= MinuteSecondFrame::fromBCD(0x0, 0x2, 0x0))); m_readPosition = m_seekTarget; + //std::cout << "seekTarget= " << (uint32_t)m_seekTarget.getMinute() << " " << (uint32_t)m_seekTarget.getSecond() << " " << (uint32_t)m_seekTarget.getFrame() << std::endl; m_seekTargetPending = false; } @@ -304,6 +313,7 @@ const Disc* CdRom::getDiscOrDie() void CdRom::sectorRead(InterruptState& irqState) { LOG("CDROM: read sector at position " << std::hex << m_readPosition.getMinute() << ":" << m_readPosition.getSecond() << ":" << m_readPosition.getFrame()); + //std::cout << "readPosition before= " << (uint32_t)m_readPosition.getMinute() << " " << (uint32_t)m_readPosition.getSecond() << " " << (uint32_t)m_readPosition.getFrame() << std::endl; XaSector::ResultXaSector resultXaSector = const_cast(getDiscOrDie())->readDataSector(m_readPosition); assert(("Couldn't read sector", resultXaSector.getSectorStatus() == XaSector::XaSectorStatus::XA_SECTOR_STATUS_OK)); @@ -332,6 +342,7 @@ void CdRom::sectorRead(InterruptState& irqState) // Move on to the next segment. m_readPosition = m_readPosition.getNextSector(); + //std::cout << "readPosition= " << (uint32_t)m_readPosition.getMinute() << " " << (uint32_t)m_readPosition.getSecond() << " " << (uint32_t)m_readPosition.getFrame() << std::endl; } uint8_t CdRom::getStatus() @@ -341,13 +352,15 @@ uint8_t CdRom::getStatus() status |= 0 << 2; status |= ((uint8_t)m_params.isEmpty()) << 3; status |= ((uint8_t)(!m_params.isFull())) << 4; - status |= ((uint8_t)(!m_params.isEmpty())) << 5; + status |= ((uint8_t)(!m_response.isEmpty())) << 5; bool dataAvailable = m_rxIndex < m_rxLen; status |= (uint8_t)dataAvailable << 6; // "Busy" flag if (m_commandState == CommandState::COMMAND_STATE_RX_PENDING) + { status |= 1 << 7; + } return status; } @@ -434,42 +447,66 @@ void CdRom::command(TimeKeeper& timeKeeper, uint8_t cmd) switch (cmd) { case 0x01: + { onAcknowledge = &CdRom::cmdGetStat; break; + } case 0x02: + { onAcknowledge = &CdRom::cmdSetLoc; break; + } case 0x06: + { onAcknowledge = &CdRom::cmdReadN; break; + } case 0x09: + { onAcknowledge = &CdRom::cmdPause; break; + } case 0x0a: + { onAcknowledge = &CdRom::cmdInit; break; + } case 0x0c: + { onAcknowledge = &CdRom::cmdDemute; break; + } case 0x0e: + { onAcknowledge = &CdRom::cmdSetMode; break; + } case 0x15: + { onAcknowledge = &CdRom::cmdSeekl; break; + } case 0x1a: + { onAcknowledge = &CdRom::cmdGetId; break; + } case 0x1e: + { onAcknowledge = &CdRom::cmdReadToc; break; + } case 0x19: + { onAcknowledge = &CdRom::cmdTest; break; + } default: + { assert(("Unhandled CDROM command", false)); break; } + } if (m_irqFlags == 0) { @@ -551,21 +588,22 @@ CommandState CdRom::cmdSetLoc() uint8_t second = m_params.pop(); uint8_t frame = m_params.pop(); + //std::cout << "cmdSetLoc= " << (uint32_t)minute << " " << (uint32_t)second << " " << (uint32_t)frame << std::endl; m_seekTarget = MinuteSecondFrame::fromBCD(minute, second, frame); - m_seekTargetPending = true; + //std::cout << "cmdSetLoc= " << (uint32_t)m_seekTarget.getMinute() << " " << (uint32_t)m_seekTarget.getSecond() << " " << (uint32_t)m_seekTarget.getFrame() << std::endl; m_seekTargetPending = true; if (m_disc) { m_helperRxPending.m_rxDelay = 35'000; - m_helperRxPending.m_irqDelay = 35'000 + 5399; + m_helperRxPending.m_irqDelay = 35'000; //+ 5399; m_helperRxPending.m_irqCode = IrqCode::IRQ_CODE_OK; m_helperRxPending.m_response = Fifo::fromBytes({ getDriveStatus() }); } else { m_helperRxPending.m_rxDelay = 25'000; - m_helperRxPending.m_irqDelay = 25'000 + 6763; + m_helperRxPending.m_irqDelay = 25'000; //+ 6763; m_helperRxPending.m_irqCode = IrqCode::IRQ_CODE_ERROR; m_helperRxPending.m_response = Fifo::fromBytes({ 0x11, 0x80 }); } @@ -575,12 +613,15 @@ CommandState CdRom::cmdSetLoc() CommandState CdRom::cmdReadN() { assert(("CDROM read command while we're already reading", m_readState == ReadState::READ_STATE_IDLE)); - if (m_seekTargetPending) doSeek(); + if (m_seekTargetPending) + { + doSeek(); + } m_helperReading.m_delay = getCyclesPerSector(); m_readState = ReadState::READ_STATE_READING; m_helperRxPending.m_rxDelay = 28'000; - m_helperRxPending.m_irqDelay = 28'000 + 5401; + m_helperRxPending.m_irqDelay = 28'000; //+ 5401; m_helperRxPending.m_irqCode = IrqCode::IRQ_CODE_OK; m_helperRxPending.m_response = Fifo::fromBytes({ getDriveStatus() }); return CommandState::COMMAND_STATE_RX_PENDING; @@ -590,10 +631,12 @@ CommandState CdRom::cmdPause() { assert(("Pause when we're not reading", m_readState != ReadState::READ_STATE_IDLE)); + std::cout << "Pause when we're not reading" << std::endl; + m_onAcknowledge = &CdRom::ackPause; m_helperRxPending.m_rxDelay = 25'000; - m_helperRxPending.m_irqDelay = 25'000 + 5393; + m_helperRxPending.m_irqDelay = 25'000; //+ 5393; m_helperRxPending.m_irqCode = IrqCode::IRQ_CODE_OK; m_helperRxPending.m_response = Fifo::fromBytes({ getDriveStatus() }); return CommandState::COMMAND_STATE_RX_PENDING; @@ -604,7 +647,7 @@ CommandState CdRom::cmdInit() m_onAcknowledge = &CdRom::ackInit; m_helperRxPending.m_rxDelay = 58'000; - m_helperRxPending.m_irqDelay = 58'000 + 5401; + m_helperRxPending.m_irqDelay = 58'000; //+ 5401; m_helperRxPending.m_irqCode = IrqCode::IRQ_CODE_OK; m_helperRxPending.m_response = Fifo::fromBytes({ getDriveStatus() }); return CommandState::COMMAND_STATE_RX_PENDING; @@ -614,7 +657,7 @@ CommandState CdRom::cmdDemute() { // Fixme: irq delay. m_helperRxPending.m_rxDelay = 32'000; - m_helperRxPending.m_irqDelay = 32'000 + 5401; + m_helperRxPending.m_irqDelay = 32'000; //+ 5401; m_helperRxPending.m_irqCode = IrqCode::IRQ_CODE_OK; m_helperRxPending.m_response = Fifo::fromBytes({ getDriveStatus() }); return CommandState::COMMAND_STATE_RX_PENDING; @@ -629,11 +672,11 @@ CommandState CdRom::cmdSetMode() m_doubleSpeed = mode & 0x80; m_readWholeSector = mode & 0x20; - assert(("CDROM: unhandled mode", (mode & 0x7f) == 0x0)); + assert(("CDROM: unhandled mode", (mode & 0x5f) == 0x0)); // Fixme: irq delay. m_helperRxPending.m_rxDelay = 22'000; - m_helperRxPending.m_irqDelay = 22'000 + 5391; + m_helperRxPending.m_irqDelay = 22'000; //+ 5391; m_helperRxPending.m_irqCode = IrqCode::IRQ_CODE_OK; m_helperRxPending.m_response = Fifo::fromBytes({ getDriveStatus() }); return CommandState::COMMAND_STATE_RX_PENDING; @@ -645,7 +688,7 @@ CommandState CdRom::cmdSeekl() m_onAcknowledge = &CdRom::ackSeekl; m_helperRxPending.m_rxDelay = 35'000; - m_helperRxPending.m_irqDelay = 35'000 + 5401; + m_helperRxPending.m_irqDelay = 35'000; //+ 5401; m_helperRxPending.m_irqCode = IrqCode::IRQ_CODE_OK; m_helperRxPending.m_response = Fifo::fromBytes({ getDriveStatus() }); return CommandState::COMMAND_STATE_RX_PENDING; @@ -662,7 +705,7 @@ CommandState CdRom::cmdGetId() // First response: status byte m_helperRxPending.m_rxDelay = 26'000; - m_helperRxPending.m_irqDelay = 26'000 + 5401; + m_helperRxPending.m_irqDelay = 26'000; //+ 5401; m_helperRxPending.m_irqCode = IrqCode::IRQ_CODE_OK; m_helperRxPending.m_response = Fifo::fromBytes({ getDriveStatus() }); } @@ -670,7 +713,7 @@ CommandState CdRom::cmdGetId() { // Pretend the shell is open. m_helperRxPending.m_rxDelay = 20'000; - m_helperRxPending.m_irqDelay = 20'000 + 6776; + m_helperRxPending.m_irqDelay = 20'000; //+ 6776; m_helperRxPending.m_irqCode = IrqCode::IRQ_CODE_ERROR; m_helperRxPending.m_response = Fifo::fromBytes({ 0x11, 0x80 }); } @@ -682,7 +725,7 @@ CommandState CdRom::cmdReadToc() m_onAcknowledge = &CdRom::ackReadToc; m_helperRxPending.m_rxDelay = 45'000; - m_helperRxPending.m_irqDelay = 45'000 + 5401; + m_helperRxPending.m_irqDelay = 45'000; //+ 5401; m_helperRxPending.m_irqCode = IrqCode::IRQ_CODE_OK; m_helperRxPending.m_response = Fifo::fromBytes({ getDriveStatus() }); return CommandState::COMMAND_STATE_RX_PENDING; @@ -736,7 +779,7 @@ CommandState CdRom::ackSeekl() // to physically move the head. // Fixme: irq delay. m_helperRxPending.m_rxDelay = 1'000'000; - m_helperRxPending.m_irqDelay = 1'000'000;// +1859; + m_helperRxPending.m_irqDelay = 1'000'000; //+ 1859; m_helperRxPending.m_irqCode = IrqCode::IRQ_CODE_DONE; m_helperRxPending.m_response = Fifo::fromBytes({ getDriveStatus() }); return CommandState::COMMAND_STATE_RX_PENDING; @@ -782,8 +825,8 @@ CommandState CdRom::ackGetId() }); // Fixme: irq delay. - m_helperRxPending.m_rxDelay = 7'336; - m_helperRxPending.m_irqDelay = 7'336;// +12'376; + m_helperRxPending.m_rxDelay = 7336; + m_helperRxPending.m_irqDelay = 7336; //+ 12376; m_helperRxPending.m_irqCode = IrqCode::IRQ_CODE_DONE; m_helperRxPending.m_response = response; return CommandState::COMMAND_STATE_RX_PENDING; @@ -796,7 +839,9 @@ CommandState CdRom::ackReadToc() { uint32_t rxDelay = 11'000; if (m_disc) + { rxDelay = 16'000'000; + } m_readState = ReadState::READ_STATE_IDLE; @@ -830,7 +875,7 @@ CommandState CdRom::ackInit() // Fixme: irq delay. m_helperRxPending.m_rxDelay = 2'000'000; - m_helperRxPending.m_irqDelay = 2'000'000 + 1870; + m_helperRxPending.m_irqDelay = 2'000'000; //+ 1870; m_helperRxPending.m_irqCode = IrqCode::IRQ_CODE_DONE; m_helperRxPending.m_response = Fifo::fromBytes({ getDriveStatus() }); return CommandState::COMMAND_STATE_RX_PENDING; diff --git a/pscx_emulator/pscx_cdrom.h b/pscx_emulator/pscx_cdrom.h index 65ca1f5..37b2056 100644 --- a/pscx_emulator/pscx_cdrom.h +++ b/pscx_emulator/pscx_cdrom.h @@ -118,6 +118,7 @@ struct CdRom m_seekTargetPending(false), m_readPosition(MinuteSecondFrame::createZeroTimestamp()), m_doubleSpeed(false), + m_xaAdpcmToSpu(false), m_rxActive(false), m_rxIndex(0x0), m_rxOffset(0x0), @@ -277,6 +278,9 @@ struct CdRom // second ), otherwise we're in the default 1x ( 75 sectors per second ). bool m_doubleSpeed; + // If true, send ADPCM samples to spu + bool m_xaAdpcmToSpu; + // Sector in the RX buffer. const XaSector* m_rxSector; diff --git a/pscx_emulator/pscx_disc.cpp b/pscx_emulator/pscx_disc.cpp index 4c11da5..17299ba 100644 --- a/pscx_emulator/pscx_disc.cpp +++ b/pscx_emulator/pscx_disc.cpp @@ -29,8 +29,11 @@ XaSector::ResultXaSector XaSector::validateMode1_2(const MinuteSecondFrame& minu } // Check that the expected MSF matches the one we have in the header. + //if (getMinuteSecondFrame() != MinuteSecondFrame::fromBCD(minuteSecondFrame.getMinute(), minuteSecondFrame.getSecond(), minuteSecondFrame.getFrame())) if (getMinuteSecondFrame() != minuteSecondFrame) + { return ResultXaSector(nullptr, XaSectorStatus::XA_SECTOR_STATUS_INVALID_DATA); + } uint8_t mode = m_raw[15]; switch (mode) diff --git a/pscx_emulator/pscx_gpu.cpp b/pscx_emulator/pscx_gpu.cpp index 70e80ff..5436666 100644 --- a/pscx_emulator/pscx_gpu.cpp +++ b/pscx_emulator/pscx_gpu.cpp @@ -9,7 +9,9 @@ std::pair Gpu::getVModeTimings() const // average line length recorded by the timer1 using the // "hsync" clock source. if (m_vmode == VMode::VMODE_NTSC) + { return std::make_pair(3412, 263); + } return std::make_pair(3404, 314); // VMode::Pal } @@ -19,7 +21,9 @@ FracCycles Gpu::gpuToCpuClockRatio() const // GPU clock in Hz uint32_t gpuClock = 53'200'000; // HardwareType::HARDWARE_TYPE_PAL if (m_hardwareType == HardwareType::HARDWARE_TYPE_NTSC) + { gpuClock = 53'690'000; + } // Clock ratio shifted 16 bits to the left return FracCycles::fromF32(gpuClock / (float)CPU_FREQ_HZ); @@ -124,7 +128,7 @@ void Gpu::sync(TimeKeeper& timeKeeper, InterruptState& irqState) predictNextSync(timeKeeper); } -void Gpu::predictNextSync(TimeKeeper timeKeeper) +void Gpu::predictNextSync(TimeKeeper& timeKeeper) { std::pair vModeTimings = getVModeTimings(); @@ -191,7 +195,9 @@ uint16_t Gpu::displayedVramLine() const { uint16_t offset = m_displayLine; if (m_interlaced) + { offset = m_displayLine * 2 + (uint16_t)m_field; + } // The VRAM "wraps around" so in the case of an overflow, // we simply truncate to 9 bits @@ -374,6 +380,7 @@ void Gpu::gp0(uint32_t value) { commandParameters.gp0WordsRemaining = 5; commandParameters.gp0CommandMethod = &Gpu::gp0QuadMonoSemiTransparent; + std::cout << "gp0QuadMonoSemiTransparent" << std::endl; break; } case 0x2c: @@ -392,6 +399,7 @@ void Gpu::gp0(uint32_t value) { commandParameters.gp0WordsRemaining = 9; commandParameters.gp0CommandMethod = &Gpu::gp0QuadTextureBlendSemiTransparent; + std::cout << "gp0QuadTextureBlendSemiTransparent" << std::endl; break; } case 0x2f: @@ -410,6 +418,7 @@ void Gpu::gp0(uint32_t value) { commandParameters.gp0WordsRemaining = 9; commandParameters.gp0CommandMethod = &Gpu::gp0TriangleTextureBlendOpaque; + std::cout << "gp0TriangleTextureBlendOpaque" << std::endl; break; } case 0x36: @@ -909,18 +918,26 @@ void Gpu::gp0DrawMode() switch (textureDepthValue) { case 0: + { textureDepth = TextureDepth::TEXTURE_DEPTH_4_BIT; break; + } case 1: + { textureDepth = TextureDepth::TEXTURE_DEPTH_8_BIT; break; + } case 2: + { textureDepth = TextureDepth::TEXTURE_DEPTH_15_BIT; break; + } default: + { LOG("Unhandled texture depth 0x" << std::hex << textureDepthValue); return; } + } m_textureDepth = textureDepth; m_dithering = (value >> 9) & 1; @@ -936,7 +953,7 @@ void Gpu::gp0DrawingAreaTopLeft() m_drawingAreaTop = (value >> 10) & 0x3ff; m_drawingAreaLeft = value & 0x3ff; - //updateDrawingArea(); + updateDrawingArea(); } void Gpu::gp0DrawingAreaBottomRight() @@ -945,7 +962,7 @@ void Gpu::gp0DrawingAreaBottomRight() m_drawingAreaBottom = (value >> 10) & 0x3ff; m_drawingAreaRight = value & 0x3ff; - //updateDrawingArea(); + updateDrawingArea(); } void Gpu::updateDrawingArea() diff --git a/pscx_emulator/pscx_gpu.h b/pscx_emulator/pscx_gpu.h index 4db19b6..3e46728 100644 --- a/pscx_emulator/pscx_gpu.h +++ b/pscx_emulator/pscx_gpu.h @@ -266,7 +266,7 @@ struct Gpu void sync(TimeKeeper& timeKeeper, InterruptState& irqState); // Predict when the next "forced" sync should take place - void predictNextSync(TimeKeeper timeKeeper); + void predictNextSync(TimeKeeper& timeKeeper); // Return true if we're currently in the video blanking period bool inVblank() const; diff --git a/pscx_emulator/pscx_gte.cpp b/pscx_emulator/pscx_gte.cpp index 836b45d..5f3f751 100644 --- a/pscx_emulator/pscx_gte.cpp +++ b/pscx_emulator/pscx_gte.cpp @@ -737,7 +737,7 @@ void Gte::setData(uint32_t reg, uint32_t value) // If "value" is negative, we count the leading ones, // otherwise we count the leading zeroes. - uint16_t leadingZerosCountValue = (value >> 31) & 1 ? ~value : value; + uint32_t leadingZerosCountValue = (value >> 31) & 1 ? ~value : value; m_leadingZerosCountResult = calculateLeadingZeros(leadingZerosCountValue); break; @@ -1181,7 +1181,9 @@ int16_t Gte::truncatei32Toi16Saturate(const CommandConfig& config, uint8_t flag, { int32_t minValue(0x0); if (!config.isClampNegative()) + { minValue = SHRT_MIN; + } if (value > SHRT_MAX) { diff --git a/pscx_emulator/pscx_gte_divider.cpp b/pscx_emulator/pscx_gte_divider.cpp index 990e3be..bb10a73 100644 --- a/pscx_emulator/pscx_gte_divider.cpp +++ b/pscx_emulator/pscx_gte_divider.cpp @@ -37,13 +37,19 @@ static const uint8_t UNSIGNED_NEWTOWN_RAPHSON_TABLE[] = { 0x00, }; -uint32_t calculateLeadingZeros(uint16_t value) +template +uint32_t calculateLeadingZeros(T value) { - uint16_t inputValue = value; + T inputValue = value; // Keep shifting x by one until leftmost bit // does not become 1. uint32_t totalBits = sizeof(inputValue) * 8; + if (inputValue == 0x0) + { + return totalBits; + } + uint32_t numLeadingZeros = 0x0; while (!(inputValue & (1 << (totalBits - 1)))) { @@ -54,6 +60,8 @@ uint32_t calculateLeadingZeros(uint16_t value) return numLeadingZeros; } +template uint32_t calculateLeadingZeros(uint32_t value); + uint32_t divide(uint16_t numerator, uint16_t divisor) { uint32_t shift = calculateLeadingZeros(divisor); diff --git a/pscx_emulator/pscx_gte_divider.h b/pscx_emulator/pscx_gte_divider.h index 8f595ee..cfb89cd 100644 --- a/pscx_emulator/pscx_gte_divider.h +++ b/pscx_emulator/pscx_gte_divider.h @@ -7,4 +7,6 @@ // The algorithm is based on Newton-Raphson. uint32_t divide(uint16_t numerator, uint16_t divisor); uint32_t calculateReciprocal(uint16_t divisor); -uint32_t calculateLeadingZeros(uint16_t value); + +template +uint32_t calculateLeadingZeros(T value); diff --git a/pscx_emulator/pscx_interconnect.cpp b/pscx_emulator/pscx_interconnect.cpp index 4b8bf84..12f11da 100644 --- a/pscx_emulator/pscx_interconnect.cpp +++ b/pscx_emulator/pscx_interconnect.cpp @@ -23,7 +23,7 @@ Interconnect::Interconnect(Bios bios, HardwareType hardwareType, const Disc* dis template Instruction Interconnect::load(TimeKeeper& timeKeeper, uint32_t addr) { - // timeKeeper.tick(5); + //timeKeeper.tick(5); uint32_t targetPeripheralAddress = maskRegion(addr); uint32_t offset = 0; @@ -152,7 +152,7 @@ void Interconnect::store(TimeKeeper& timeKeeper, uint32_t addr, T value) } default: { - LOG("Unhandled write to MEM_CONTROL register"); + std::cout << "Unhandled write to MEM_CONTROL register " << std::hex << value << std::endl; return; } } diff --git a/pscx_emulator/pscx_minutesecondframe.cpp b/pscx_emulator/pscx_minutesecondframe.cpp index eb75d71..5d3f39e 100644 --- a/pscx_emulator/pscx_minutesecondframe.cpp +++ b/pscx_emulator/pscx_minutesecondframe.cpp @@ -58,6 +58,8 @@ MinuteSecondFrame MinuteSecondFrame::fromBCD(uint8_t minute, uint8_t second, uin // There are only 75 frames per second and 60 seconds per minute. assert(("Invalid MSF", !(second >= 0x60 || frame >= 0x75))); return MinuteSecondFrame(minute, second, frame); + //auto fromBCD = [](uint8_t bcd) -> uint8_t { return (bcd >> 4) * 10 + (bcd & 0xf); }; + //return MinuteSecondFrame(fromBCD(minute), fromBCD(second), fromBCD(frame)); } uint32_t MinuteSecondFrame::getSectorIndex() const diff --git a/pscx_emulator/pscx_renderer.cpp b/pscx_emulator/pscx_renderer.cpp index 9972f0f..19020fd 100644 --- a/pscx_emulator/pscx_renderer.cpp +++ b/pscx_emulator/pscx_renderer.cpp @@ -33,7 +33,7 @@ Renderer::Renderer() SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); m_framebufferXResolution = 1024; - m_framebufferYResolution = 768; + m_framebufferYResolution = 512; m_window = SDL_CreateWindow("PSX", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, m_framebufferXResolution, m_framebufferYResolution, SDL_WINDOW_OPENGL); @@ -43,7 +43,7 @@ Renderer::Renderer() SDL_GL_MakeCurrent(m_window, m_glContext); - //glViewport(0, 0, 1024, 512); + //glViewport(0, 0, 320, 240); // Clear the window glClearColor(0, 0, 0, 1); @@ -220,8 +220,8 @@ void Renderer::setDrawingArea(uint16_t left, uint16_t top, uint16_t right, uint1 GLint leftCoordinate = ((GLint)left * (GLint)m_framebufferXResolution) / 1024; GLint rightCoordinate = ((GLint)right * (GLint)m_framebufferXResolution) / 1024; - GLint topCoordinate = ((GLint)top * (GLint)m_framebufferYResolution) / 768; - GLint bottomCoordinate = ((GLint)bottom * (GLint)m_framebufferYResolution) / 768; + GLint topCoordinate = ((GLint)top * (GLint)m_framebufferYResolution) / 512; + GLint bottomCoordinate = ((GLint)bottom * (GLint)m_framebufferYResolution) / 512; // Width and height are inclusive GLint width = rightCoordinate - leftCoordinate + 1; @@ -263,7 +263,6 @@ void Renderer::draw() // Reset the buffers m_numOfVertices = 0x0; - //std::cout << "Here\n"; } } diff --git a/pscx_emulator/pscx_spu.cpp b/pscx_emulator/pscx_spu.cpp index f1c00dd..1b39e5b 100644 --- a/pscx_emulator/pscx_spu.cpp +++ b/pscx_emulator/pscx_spu.cpp @@ -218,7 +218,6 @@ void Spu::setControl(uint16_t value) assert(("Unhandled SPU control", (value & 0x3f4a) == 0x0)); } - void Spu::setTransferControl(uint16_t value) { assert(("Unhandled SPU RAM access pattern", value == 0x4)); diff --git a/pscx_emulator/pscx_timekeeper.cpp b/pscx_emulator/pscx_timekeeper.cpp index a98fa57..b4c8863 100644 --- a/pscx_emulator/pscx_timekeeper.cpp +++ b/pscx_emulator/pscx_timekeeper.cpp @@ -1,5 +1,6 @@ #include "pscx_timekeeper.h" #include +#include // ********************** TimeSheet implementation ********************** Cycles TimeSheet::sync(Cycles now) @@ -41,14 +42,18 @@ void TimeKeeper::setNextSyncDelta(Peripheral who, Cycles delta) m_timesheets[who].setNextSync(date); if (date < m_nextSync) + { m_nextSync = date; + } } void TimeKeeper::setNextSyncDeltaIfCloser(Peripheral who, Cycles delta) { Cycles date = m_now + delta; if (m_timesheets[who].getNextSync() > date) + { m_timesheets[who].setNextSync(date); + } } void TimeKeeper::noSyncNeeded(Peripheral who) @@ -74,6 +79,10 @@ void TimeKeeper::updateSyncPending() for (size_t i = 0; i < _countof(m_timesheets); ++i) { Cycles nextSync = m_timesheets[i].getNextSync(); - if (minNextSync > nextSync) minNextSync = nextSync; + if (minNextSync > nextSync) + { + minNextSync = nextSync; + } } + m_nextSync = minNextSync; } diff --git a/pscx_emulator/pscx_timekeeper.h b/pscx_emulator/pscx_timekeeper.h index d3ed2ed..1f47b72 100644 --- a/pscx_emulator/pscx_timekeeper.h +++ b/pscx_emulator/pscx_timekeeper.h @@ -92,7 +92,7 @@ struct FracCycles static FracCycles fromF32(float value) { - float precision = (float)(1 << FracCycles::fracBits()); + float precision = (float)(1UL << FracCycles::fracBits()); return (FracCycles)FracCycles(static_cast(value * precision)); } diff --git a/pscx_emulator/pscx_timers.cpp b/pscx_emulator/pscx_timers.cpp index 8071d9f..2fa9a8d 100644 --- a/pscx_emulator/pscx_timers.cpp +++ b/pscx_emulator/pscx_timers.cpp @@ -37,15 +37,21 @@ Clock ClockSource::clock(Peripheral instance) const switch (instance) { case Peripheral::PERIPHERAL_TIMER0: + { clock = lookup[0][m_clockSource]; break; + } case Peripheral::PERIPHERAL_TIMER1: + { clock = lookup[1][m_clockSource]; break; + } case Peripheral::PERIPHERAL_TIMER2: + { clock = lookup[2][m_clockSource]; break; } + } return clock; } @@ -60,22 +66,30 @@ void Timer::reconfigure(const Gpu& gpu, TimeKeeper& timeKeeper) switch (m_clockSource.clock(m_instance)) { case Clock::CLOCK_SOURCE_SYS_CLOCK: + { m_period = FracCycles::fromCycles(1); m_phase = FracCycles::fromCycles(0); break; + } case Clock::CLOCK_SOURCE_SYS_CLOCK_DIV_8: + { m_period = FracCycles::fromCycles(8); m_phase = FracCycles::fromCycles(0); break; + } case Clock::CLOCK_SOURCE_GPU_DOT_CLOCK: + { m_period = gpu.dotclockPeriod(); m_phase = gpu.dotclockPhase(); break; + } case Clock::CLOCK_SOURCE_GPU_HSYNC: + { m_period = gpu.hsyncPeriod(); m_phase = gpu.hsyncPhase(); break; } + } predictNextSync(timeKeeper); } @@ -288,21 +302,30 @@ void Timers::store(TimeKeeper& timeKeeper, InterruptState& irqState, switch (offset & 0xf) { case 0: + { timer.setCounter(value); break; + } case 4: + { timer.setMode(value); break; + } case 8: + { timer.setTarget(value); break; + } default: - LOG("Unhandled timer register"); - return; + { + assert(("Unhandled timer register", false)); + } } if (timer.needsGpu()) + { gpu.sync(timeKeeper, irqState); + } timer.reconfigure(gpu, timeKeeper); } @@ -370,9 +393,15 @@ void Timers::videoTimingsChanged(TimeKeeper& timeKeeper, InterruptState& irqStat void Timers::sync(TimeKeeper& timeKeeper, InterruptState& irqState) { if (timeKeeper.needsSync(Peripheral::PERIPHERAL_TIMER0)) + { m_timers[0].sync(timeKeeper, irqState); + } if (timeKeeper.needsSync(Peripheral::PERIPHERAL_TIMER1)) + { m_timers[1].sync(timeKeeper, irqState); + } if (timeKeeper.needsSync(Peripheral::PERIPHERAL_TIMER2)) + { m_timers[2].sync(timeKeeper, irqState); + } } diff --git a/pscx_emulator/pscx_timers.h b/pscx_emulator/pscx_timers.h index 94c596b..3c968c1 100644 --- a/pscx_emulator/pscx_timers.h +++ b/pscx_emulator/pscx_timers.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "pscx_common.h" #include "pscx_timekeeper.h" #include "pscx_interrupts.h" @@ -45,11 +47,7 @@ struct ClockSource static ClockSource fromField(uint16_t field) { - if (field & ~3) - { - LOG("Invalid clock source: 0x" << std::hex << field); - ClockSource(~0); - } + assert(("Invalid clock source", !(field & ~3))); return ClockSource((uint8_t)field); }