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);
}