From 65551bb95bc711ceb574044cdd5b2ce87ab9674e Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Wed, 10 Nov 2021 23:20:05 +0100 Subject: [PATCH 01/72] Project: Update to .NET 6.0 Benchmark on base tri is 10% faster. OpenTK seems to crash after a while... --- ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj | 2 +- ProjectPSX.WinForms/ProjectPSX.WinForms.csproj | 2 +- ProjectPSX/ProjectPSX.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj index b163b1c..467e7bf 100644 --- a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj +++ b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net5.0</TargetFramework> + <TargetFramework>net6.0</TargetFramework> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> diff --git a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj index 5276ddd..115ca71 100644 --- a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj +++ b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net5.0-windows</TargetFramework> + <TargetFramework>net6.0-windows</TargetFramework> <UseWindowsForms>true</UseWindowsForms> <DisableWinExeOutputInference>true</DisableWinExeOutputInference> </PropertyGroup> diff --git a/ProjectPSX/ProjectPSX.csproj b/ProjectPSX/ProjectPSX.csproj index ad133fa..85d1ea8 100644 --- a/ProjectPSX/ProjectPSX.csproj +++ b/ProjectPSX/ProjectPSX.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Library</OutputType> - <TargetFramework>net5.0</TargetFramework> + <TargetFramework>net6.0</TargetFramework> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> From fe6469ff5111db4d9cd61dcc9cbdaa49a77650ca Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sat, 13 Nov 2021 16:07:37 +0100 Subject: [PATCH 02/72] Controller: Remove unneded IHostWindow ref --- ProjectPSX/Devices/Input/Controller.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ProjectPSX/Devices/Input/Controller.cs b/ProjectPSX/Devices/Input/Controller.cs index 8094873..4b979d0 100644 --- a/ProjectPSX/Devices/Input/Controller.cs +++ b/ProjectPSX/Devices/Input/Controller.cs @@ -3,7 +3,6 @@ namespace ProjectPSX { public abstract class Controller { - protected IHostWindow window; protected Queue<byte> transferDataFifo = new Queue<byte>(); protected ushort buttons = 0xFFFF; From b09a11407e2962b8b740a359b22e7be0bec58116 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sat, 13 Nov 2021 16:08:07 +0100 Subject: [PATCH 03/72] Seal class --- ProjectPSX/Devices/DMA.cs | 4 ++-- ProjectPSX/Devices/Input/DigitalController.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ProjectPSX/Devices/DMA.cs b/ProjectPSX/Devices/DMA.cs index a0a8974..3c57d85 100644 --- a/ProjectPSX/Devices/DMA.cs +++ b/ProjectPSX/Devices/DMA.cs @@ -10,7 +10,7 @@ public abstract class AChannel { public abstract uint load(uint regiter); } - private class InterruptChannel : AChannel { + private sealed class InterruptChannel : AChannel { private uint control; @@ -97,7 +97,7 @@ public bool tick() { } } - private class Channel : AChannel { + private sealed class Channel : AChannel { private uint baseAddress; private uint blockSize; diff --git a/ProjectPSX/Devices/Input/DigitalController.cs b/ProjectPSX/Devices/Input/DigitalController.cs index 3387aad..a2182f2 100644 --- a/ProjectPSX/Devices/Input/DigitalController.cs +++ b/ProjectPSX/Devices/Input/DigitalController.cs @@ -1,5 +1,5 @@ namespace ProjectPSX { - public class DigitalController : Controller { + public sealed class DigitalController : Controller { private const ushort CONTROLLER_TYPE = 0x5A41; //digital From f916e22160f530683e3a1fba4dcaad4f55472d2d Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 14 Nov 2021 20:44:16 +0100 Subject: [PATCH 04/72] GPU: Render Rect refactor --- ProjectPSX/Devices/GPU/GPU.cs | 63 +++++++++++++++-------------------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index 6f9acda..6579979 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -734,16 +734,21 @@ private void GP0_RenderRectangle(Span<uint> buffer) { short xo = (short)(vertex & 0xFFFF); short yo = (short)(vertex >> 16); - ushort palette = 0; - byte textureX = 0; - byte textureY = 0; if (isTextured) { uint texture = buffer[pointer++]; - palette = (ushort)((texture >> 16) & 0xFFFF); - textureX = (byte)(texture & 0xFF); - textureY = (byte)((texture >> 8) & 0xFF); + t[0].x = (byte)(texture & 0xFF); + t[0].y = (byte)((texture >> 8) & 0xFF); + + ushort palette = (ushort)((texture >> 16) & 0xFFFF); + primitive.clut.x = (short)((palette & 0x3f) << 4); + primitive.clut.y = (short)((palette >> 6) & 0x1FF); } + primitive.depth = textureDepth; + primitive.textureBase.x = (short)(textureXBase << 6); + primitive.textureBase.y = (short)(textureYBase << 8); + primitive.semiTransparencyMode = transparencyMode; + short width = 0; short heigth = 0; @@ -764,44 +769,28 @@ private void GP0_RenderRectangle(Span<uint> buffer) { break; } - int y = signed11bit((uint)(yo + drawingYOffset)); - int x = signed11bit((uint)(xo + drawingXOffset)); + short y = signed11bit((uint)(yo + drawingYOffset)); + short x = signed11bit((uint)(xo + drawingXOffset)); - v[0].x = (short)x; - v[0].y = (short)y; + v[0].x = x; + v[0].y = y; v[3].x = (short)(x + width); v[3].y = (short)(y + heigth); - t[0].x = textureX; - t[0].y = textureY; - - uint texpage = getTexpageFromGPU(); - - rasterizeRect(v, t[0], color, palette, texpage, primitive); + rasterizeRect(v[0], v[3], t[0], color, primitive); } - private void rasterizeRect(Point2D[] vec, TextureData t, uint c, ushort palette, uint texpage, Primitive primitive) { - int xOrigin = Math.Max(vec[0].x, drawingAreaLeft); - int yOrigin = Math.Max(vec[0].y, drawingAreaTop); - int width = Math.Min(vec[3].x, drawingAreaRight); - int height = Math.Min(vec[3].y, drawingAreaBottom); - - int depth = (int)(texpage >> 7) & 0x3; - int semiTransparencyMode = (int)((texpage >> 5) & 0x3); - - Point2D clut = new Point2D(); - clut.x = (short)((palette & 0x3f) << 4); - clut.y = (short)((palette >> 6) & 0x1FF); - - Point2D textureBase = new Point2D(); - textureBase.x = (short)((texpage & 0xF) << 6); - textureBase.y = (short)(((texpage >> 4) & 0x1) << 8); + private void rasterizeRect(Point2D origin, Point2D size, TextureData texture, uint bgrColor, Primitive primitive) { + int xOrigin = Math.Max(origin.x, drawingAreaLeft); + int yOrigin = Math.Max(origin.y, drawingAreaTop); + int width = Math.Min(size.x, drawingAreaRight); + int height = Math.Min(size.y, drawingAreaBottom); - int uOrigin = t.x + (xOrigin - vec[0].x); - int vOrigin = t.y + (yOrigin - vec[0].y); + int uOrigin = texture.x + (xOrigin - origin.x); + int vOrigin = texture.y + (yOrigin - origin.y); - int baseColor = GetRgbColor(c); + int baseColor = GetRgbColor(bgrColor); for (int y = yOrigin, v = vOrigin; y < height; y++, v++) { for (int x = xOrigin, u = uOrigin; x < width; x++, u++) { @@ -815,7 +804,7 @@ private void rasterizeRect(Point2D[] vec, TextureData t, uint c, ushort palette, if (primitive.isTextured) { //int texel = getTexel(u, v, clut, textureBase, depth); - int texel = getTexel(maskTexelAxis(u, preMaskX, postMaskX), maskTexelAxis(v, preMaskY, postMaskY), clut, textureBase, depth); + int texel = getTexel(maskTexelAxis(u, preMaskX, postMaskX),maskTexelAxis(v, preMaskY, postMaskY),primitive.clut, primitive.textureBase, primitive.depth); if (texel == 0) { continue; } @@ -834,7 +823,7 @@ private void rasterizeRect(Point2D[] vec, TextureData t, uint c, ushort palette, } if (primitive.isSemiTransparent && (!primitive.isTextured || (color & 0xFF00_0000) != 0)) { - color = handleSemiTransp(x, y, color, semiTransparencyMode); + color = handleSemiTransp(x, y, color, primitive.semiTransparencyMode); } color |= maskWhileDrawing << 24; From 865030948afcbcfea31fc47914509feec267bef9 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 14 Nov 2021 21:00:47 +0100 Subject: [PATCH 05/72] GPU: Reuse interpolate on shaded tri --- ProjectPSX/Devices/GPU/GPU.cs | 39 +++++++++++++++-------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index 6579979..9bf79e1 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -551,11 +551,19 @@ private void rasterizeTri(Point2D v0, Point2D v1, Point2D v2, TextureData t0, Te // reset default color of the triangle calculated outside the for as it gets overwriten as follows... int color = baseColor; - if (primitive.isShaded) color = getShadedColor(w0, w1, w2, c0, c1, c2, area); + if (primitive.isShaded) { + color0.val = c0; + color1.val = c1; + color2.val = c2; + int r = interpolate(w0, w1, w2, color0.r, color1.r, color2.r, area); + int g = interpolate(w0, w1, w2, color0.g, color1.g, color2.g, area); + int b = interpolate(w0, w1, w2, color0.b, color1.b, color2.b, area); + color = r << 16 | g << 8 | b; + } if (primitive.isTextured) { - int texelX = interpolateCoords(w0, w1, w2, t0.x, t1.x, t2.x, area); - int texelY = interpolateCoords(w0, w1, w2, t0.y, t1.y, t2.y, area); + int texelX = interpolate(w0, w1, w2, t0.x, t1.x, t2.x, area); + int texelY = interpolate(w0, w1, w2, t0.y, t1.y, t2.y, area); int texel = getTexel(maskTexelAxis(texelX, preMaskX, postMaskX), maskTexelAxis(texelY, preMaskY, postMaskY), primitive.clut, primitive.textureBase, primitive.depth); if (texel == 0) { w0 += A12; @@ -907,25 +915,6 @@ private void GP0_MemCopyRectVRAMtoCPU(Span<uint> buffer) { vramTransfer.halfWords = w * h; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int getShadedColor(int w0, int w1, int w2, uint c0, uint c1, uint c2, int area) { - color0.val = c0; - color1.val = c1; - color2.val = c2; - - int r = (color0.r * w0 + color1.r * w1 + color2.r * w2) / area; - int g = (color0.g * w0 + color1.g * w1 + color2.g * w2) / area; - int b = (color0.b * w0 + color1.b * w1 + color2.b * w2) / area; - - return (r << 16 | g << 8 | b); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int interpolateCoords(int w0, int w1, int w2, int t0, int t1, int t2, int area) { - //https://codeplea.com/triangular-interpolation - return (t0 * w0 + t1 * w1 + t2 * w2) / area; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private int maskTexelAxis(int axis, int preMaskAxis, int postMaskAxis) { return axis & 0xFF & preMaskAxis | postMaskAxis; @@ -1207,6 +1196,12 @@ private int interpolate(uint c1, uint c2, float ratio) { return (r << 16 | g << 8 | b); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int interpolate(int w0, int w1, int w2, int t0, int t1, int t2, int area) { + //https://codeplea.com/triangular-interpolation + return (t0 * w0 + t1 * w1 + t2 * w2) / area; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static short signed11bit(uint n) { return (short)(((int)n << 21) >> 21); From d9320edc62d6d03e9838f0e50b8edd2a051e8806 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 14 Nov 2021 21:12:19 +0100 Subject: [PATCH 06/72] GPU: Remove forceSetE1 The functionality is still the same: Textured tris force set E1 globally for further commands but remove the actual funct as it was doing the same work and reuse the gpu fields for the primitive instead of reshift everything again. --- ProjectPSX/Devices/GPU/GPU.cs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index 9bf79e1..eb46732 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -468,12 +468,17 @@ public void GP0_RenderPolygon(Span<uint> buffer) { } else if (i == 1) { uint texpage = textureData >> 16; - primitive.depth = (int)(texpage >> 7) & 0x3; - primitive.textureBase.x = (short)((texpage & 0xF) << 6); - primitive.textureBase.y = (short)(((texpage >> 4) & 0x1) << 8); - primitive.semiTransparencyMode = (int)((texpage >> 5) & 0x3); - - forceSetE1(texpage); + //SET GLOBAL GPU E1 + textureXBase = (byte)(texpage & 0xF); + textureYBase = (byte)((texpage >> 4) & 0x1); + transparencyMode = (byte)((texpage >> 5) & 0x3); + textureDepth = (byte)((texpage >> 7) & 0x3); + isTextureDisabled = isTextureDisabledAllowed && ((texpage >> 11) & 0x1) != 0; + + primitive.depth = textureDepth; + primitive.textureBase.x = (short)(textureXBase << 6); + primitive.textureBase.y = (short)(textureYBase << 8); + primitive.semiTransparencyMode = transparencyMode; } } } @@ -955,16 +960,6 @@ private static int orient2d(Point2D a, Point2D b, Point2D c) { return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); } - private void forceSetE1(uint texpage) { - textureXBase = (byte)(texpage & 0xF); - textureYBase = (byte)((texpage >> 4) & 0x1); - transparencyMode = (byte)((texpage >> 5) & 0x3); - textureDepth = (byte)((texpage >> 7) & 0x3); - isTextureDisabled = isTextureDisabledAllowed && ((texpage >> 11) & 0x1) != 0; - - //Console.WriteLine("[GPU] [GP0] Force DrawMode "); - } - private void GP0_E1_SetDrawMode(uint val) { textureXBase = (byte)(val & 0xF); textureYBase = (byte)((val >> 4) & 0x1); From 2868eb5534a0c20843fc4cd6412a1a2dceca4c32 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sat, 20 Nov 2021 12:51:00 +0100 Subject: [PATCH 07/72] GPU: Rasterize Optimizations: - Removed Point2D Vertex and Texture heap array structs (they are stack allocated on tris and rects) - Moved the bias addition of topLeft rules on tris to the edge function result. Even thought this means that it needs to be unbiassed on textured and shaded tris it produces faster code on the actual critical if checkinf if it's inside the tri. (On my test envirovement this was a bump from around 110 to 120 fps on Jakub plain 100 tri benchmark. --- ProjectPSX/Devices/GPU/GPU.cs | 50 ++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index eb46732..3b68bd1 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -31,7 +31,7 @@ private enum Mode { } private Mode mode; - private ref struct Primitive { + private struct Primitive { public bool isShaded; public bool isTextured; public bool isSemiTransparent; @@ -54,12 +54,9 @@ private struct VramTransfer { [StructLayout(LayoutKind.Explicit)] private struct Point2D { - [FieldOffset(0)] public uint val; [FieldOffset(0)] public short x; [FieldOffset(2)] public short y; } - - private Point2D[] v = new Point2D[4]; private Point2D min = new Point2D(); private Point2D max = new Point2D(); @@ -69,7 +66,7 @@ private struct TextureData { [FieldOffset(0)] public byte x; [FieldOffset(1)] public byte y; } - private TextureData[] t = new TextureData[4]; + TextureData textureData = new TextureData(); [StructLayout(LayoutKind.Explicit)] private struct Color { @@ -441,6 +438,8 @@ public void GP0_RenderPolygon(Span<uint> buffer) { int vertexN = isQuad ? 4 : 3; Span<uint> c = stackalloc uint[vertexN]; + Span<Point2D> v = stackalloc Point2D[vertexN]; + Span<TextureData> t = stackalloc TextureData[vertexN]; if (!isShaded) { uint color = buffer[pointer++]; @@ -453,9 +452,9 @@ public void GP0_RenderPolygon(Span<uint> buffer) { for (int i = 0; i < vertexN; i++) { if (isShaded) c[i] = buffer[pointer++]; - v[i].val = buffer[pointer++]; - v[i].x = (short)(signed11bit((uint)v[i].x) + drawingXOffset); - v[i].y = (short)(signed11bit((uint)v[i].y) + drawingYOffset); + uint xy = buffer[pointer++]; + v[i].x = (short)(signed11bit(xy & 0xFFFF) + drawingXOffset); + v[i].y = (short)(signed11bit(xy >> 16) + drawingYOffset); if (isTextured) { uint textureData = buffer[pointer++]; @@ -522,9 +521,9 @@ private void rasterizeTri(Point2D v0, Point2D v1, Point2D v2, TextureData t0, Te int bias1 = isTopLeft(v2, v0) ? 0 : -1; int bias2 = isTopLeft(v0, v1) ? 0 : -1; - int w0_row = orient2d(v1, v2, min); - int w1_row = orient2d(v2, v0, min); - int w2_row = orient2d(v0, v1, min); + int w0_row = orient2d(v1, v2, min) + bias0; + int w1_row = orient2d(v2, v0, min) + bias1; + int w2_row = orient2d(v0, v1, min) + bias2; int baseColor = GetRgbColor(c0); @@ -537,7 +536,7 @@ private void rasterizeTri(Point2D v0, Point2D v1, Point2D v2, TextureData t0, Te for (int x = min.x; x < max.x; x++) { // If p is on or inside all edges, render pixel. - if ((w0 + bias0 | w1 + bias1 | w2 + bias2) >= 0) { + if ((w0 | w1 | w2) >= 0) { //Adjustements per triangle instead of per pixel can be done at area level //but it still does some little by 1 error apreciable on some textured quads //I assume it could be handled recalculating AXX and BXX offsets but those maths are beyond my scope @@ -560,15 +559,16 @@ private void rasterizeTri(Point2D v0, Point2D v1, Point2D v2, TextureData t0, Te color0.val = c0; color1.val = c1; color2.val = c2; - int r = interpolate(w0, w1, w2, color0.r, color1.r, color2.r, area); - int g = interpolate(w0, w1, w2, color0.g, color1.g, color2.g, area); - int b = interpolate(w0, w1, w2, color0.b, color1.b, color2.b, area); + + int r = interpolate(w0 - bias0, w1 - bias1, w2 - bias2, color0.r, color1.r, color2.r, area); + int g = interpolate(w0 - bias0, w1 - bias1, w2 - bias2, color0.g, color1.g, color2.g, area); + int b = interpolate(w0 - bias0, w1 - bias1, w2 - bias2, color0.b, color1.b, color2.b, area); color = r << 16 | g << 8 | b; } if (primitive.isTextured) { - int texelX = interpolate(w0, w1, w2, t0.x, t1.x, t2.x, area); - int texelY = interpolate(w0, w1, w2, t0.y, t1.y, t2.y, area); + int texelX = interpolate(w0 - bias0, w1 - bias1, w2 - bias2, t0.x, t1.x, t2.x, area); + int texelY = interpolate(w0 - bias0, w1 - bias1, w2 - bias2, t0.y, t1.y, t2.y, area); int texel = getTexel(maskTexelAxis(texelX, preMaskX, postMaskX), maskTexelAxis(texelY, preMaskY, postMaskY), primitive.clut, primitive.textureBase, primitive.depth); if (texel == 0) { w0 += A12; @@ -749,8 +749,8 @@ private void GP0_RenderRectangle(Span<uint> buffer) { if (isTextured) { uint texture = buffer[pointer++]; - t[0].x = (byte)(texture & 0xFF); - t[0].y = (byte)((texture >> 8) & 0xFF); + textureData.x = (byte)(texture & 0xFF); + textureData.y = (byte)((texture >> 8) & 0xFF); ushort palette = (ushort)((texture >> 16) & 0xFFFF); primitive.clut.x = (short)((palette & 0x3f) << 4); @@ -785,13 +785,15 @@ private void GP0_RenderRectangle(Span<uint> buffer) { short y = signed11bit((uint)(yo + drawingYOffset)); short x = signed11bit((uint)(xo + drawingXOffset)); - v[0].x = x; - v[0].y = y; + Point2D origin; + origin.x = x; + origin.y = y; - v[3].x = (short)(x + width); - v[3].y = (short)(y + heigth); + Point2D size; + size.x = (short)(x + width); + size.y = (short)(y + heigth); - rasterizeRect(v[0], v[3], t[0], color, primitive); + rasterizeRect(origin, size, textureData, color, primitive); } private void rasterizeRect(Point2D origin, Point2D size, TextureData texture, uint bgrColor, Primitive primitive) { From 3da05b2f0595d4847bb586233b4faf74ef320e63 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Tue, 7 Dec 2021 16:12:59 +0100 Subject: [PATCH 08/72] Window: Blit24bpp optimizations --- ProjectPSX.WinForms/UI/Window.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/ProjectPSX.WinForms/UI/Window.cs b/ProjectPSX.WinForms/UI/Window.cs index be20845..5734835 100644 --- a/ProjectPSX.WinForms/UI/Window.cs +++ b/ProjectPSX.WinForms/UI/Window.cs @@ -169,16 +169,20 @@ public void Render(int[] vram) { } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void blit24bpp(int[] vramBits) { + private unsafe void blit24bpp(int[] vramBits) { int yRangeOffset = (240 - (displayY2 - displayY1)) >> (verticalRes == 480 ? 0 : 1); if (yRangeOffset < 0) yRangeOffset = 0; + var display = new Span<int>(this.display.BitmapData.ToPointer(), 0x80000); + Span<int> scanLine = stackalloc int[horizontalRes]; + for (int y = yRangeOffset; y < verticalRes - yRangeOffset; y++) { int offset = 0; + var startXYPosition = displayVRAMXStart + ((y - yRangeOffset + displayVRAMYStart) * 1024); for (int x = 0; x < horizontalRes; x += 2) { - int p0rgb = vramBits[(offset++ + displayVRAMXStart) + ((y - yRangeOffset + displayVRAMYStart) * 1024)]; - int p1rgb = vramBits[(offset++ + displayVRAMXStart) + ((y - yRangeOffset + displayVRAMYStart) * 1024)]; - int p2rgb = vramBits[(offset++ + displayVRAMXStart) + ((y - yRangeOffset + displayVRAMYStart) * 1024)]; + int p0rgb = vramBits[startXYPosition + offset++]; + int p1rgb = vramBits[startXYPosition + offset++]; + int p2rgb = vramBits[startXYPosition + offset++]; ushort p0bgr555 = GetPixelBGR555(p0rgb); ushort p1bgr555 = GetPixelBGR555(p1rgb); @@ -197,9 +201,10 @@ private void blit24bpp(int[] vramBits) { int p0rgb24bpp = p0R << 16 | p0G << 8 | p0B; int p1rgb24bpp = p1R << 16 | p1G << 8 | p1B; - display.DrawPixel(x, y, p0rgb24bpp); - display.DrawPixel(x + 1, y, p1rgb24bpp); + scanLine[x] = p0rgb24bpp; + scanLine[x + 1] = p1rgb24bpp; } + scanLine.CopyTo(display.Slice(y * 1024)); } } From f61cbeb2153853cffcbc28316660fc57770f15e8 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sat, 11 Dec 2021 21:54:46 +0100 Subject: [PATCH 09/72] GPU: Add GP0_02_FillRectVram fastpath for non wrap --- ProjectPSX/Devices/GPU/GPU.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index 3b68bd1..b386e8c 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -404,11 +404,18 @@ private void GP0_02_FillRectVRAM(Span<uint> buffer) { ushort w = (ushort)(((hw & 0x3FF) + 0xF) & ~0xF); ushort h = (ushort)((hw >> 16) & 0x1FF); - int color = (color0.r << 16 | color0.g << 8 | color0.b); + int color = color0.r << 16 | color0.g << 8 | color0.b; - for (int yPos = y; yPos < h + y; yPos++) { - for (int xPos = x; xPos < w + x; xPos++) { - vram.SetPixel(xPos & 0x3FF, yPos & 0x1FF, color); + if(x + w <= 0x3FF && y + h <= 0x1FF) { + var vramSpan = new Span<int>(vram.Bits); + for (int yPos = y; yPos < h + y; yPos++) { + vramSpan.Slice(x + (yPos * 1024), w).Fill(color); + } + } else { + for (int yPos = y; yPos < h + y; yPos++) { + for (int xPos = x; xPos < w + x; xPos++) { + vram.SetPixel(xPos & 0x3FF, yPos & 0x1FF, color); + } } } } From 7797944f89ee6bc9a7957602c5701f61337f666c Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 12 Dec 2021 19:55:36 +0100 Subject: [PATCH 10/72] CPU: Make CPU_EXCEPTIONS optional They were very helpful to validate CPU behaviour on tests but no known game triggers them so make them optional Enabling them, will pass CPU tests, disabling them will give a nice bump on speed as unnecesary ops are no longer triggered. --- ProjectPSX/Core/CPU.cs | 52 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/ProjectPSX/Core/CPU.cs b/ProjectPSX/Core/CPU.cs index 380c4c3..77d10fb 100644 --- a/ProjectPSX/Core/CPU.cs +++ b/ProjectPSX/Core/CPU.cs @@ -1,4 +1,5 @@ -using System; +//#define CPU_EXCEPTIONS +using System; using System.Runtime.CompilerServices; using ProjectPSX.Disassembler; @@ -247,11 +248,13 @@ private void fetchDecode() { opcodeIsBranch = false; opcodeTookBranch = false; +#if CPU_EXCEPTIONS if ((PC_Now & 0x3) != 0) { COP0_GPR[BADA] = PC_Now; EXCEPTION(EX.LOAD_ADRESS_ERROR); return; } +#endif instr.value = load; //cycle++; @@ -337,11 +340,16 @@ private void ADDI() { uint rs = GPR[instr.rs]; uint imm_s = instr.imm_s; uint result = rs + imm_s; + +#if CPU_EXCEPTIONS if(checkOverflow(rs, imm_s, result)) { EXCEPTION(EX.OVERFLOW, instr.id); } else { setGPR(instr.rt, result); } +#else + setGPR(instr.rt, result); +#endif } private void ADDIU() => setGPR(instr.rt, GPR[instr.rs] + instr.imm_s); @@ -480,6 +488,7 @@ private void COP2() { private void LWC2() { //TODO WARNING THIS SHOULD HAVE DELAY? uint addr = GPR[instr.rs] + instr.imm_s; +#if CPU_EXCEPTIONS if ((addr & 0x3) == 0) { uint value = bus.load32(addr); gte.writeData(instr.rt, value); @@ -487,17 +496,25 @@ private void LWC2() { //TODO WARNING THIS SHOULD HAVE DELAY? COP0_GPR[BADA] = addr; EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id); } +#else + uint value = bus.load32(addr); + gte.writeData(instr.rt, value); +#endif } private void SWC2() { //TODO WARNING THIS SHOULD HAVE DELAY? uint addr = GPR[instr.rs] + instr.imm_s; +#if CPU_EXCEPTIONS if ((addr & 0x3) == 0) { bus.write32(addr, gte.loadData(instr.rt)); } else { COP0_GPR[BADA] = addr; EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id); } +#else + bus.write32(addr, gte.loadData(instr.rt)); +#endif } private void LB() { //todo redo this as it unnecesary load32 @@ -518,6 +535,7 @@ private void LH() { if (dontIsolateCache) { uint addr = GPR[instr.rs] + instr.imm_s; +#if CPU_EXCEPTIONS if ((addr & 0x1) == 0) { uint value = (uint)(short)bus.load32(addr); delayedLoad(instr.rt, value); @@ -525,6 +543,10 @@ private void LH() { COP0_GPR[BADA] = addr; EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id); } +#else + uint value = (uint)(short)bus.load32(addr); + delayedLoad(instr.rt, value); +#endif } //else Console.WriteLine("IsolatedCache: Ignoring Load"); } @@ -533,6 +555,7 @@ private void LHU() { if (dontIsolateCache) { uint addr = GPR[instr.rs] + instr.imm_s; +#if CPU_EXCEPTIONS if ((addr & 0x1) == 0) { uint value = (ushort)bus.load32(addr); delayedLoad(instr.rt, value); @@ -540,6 +563,10 @@ private void LHU() { COP0_GPR[BADA] = addr; EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id); } +#else + uint value = (ushort)bus.load32(addr); + delayedLoad(instr.rt, value); +#endif } //else Console.WriteLine("IsolatedCache: Ignoring Load"); } @@ -548,6 +575,7 @@ private void LW() { if (dontIsolateCache) { uint addr = GPR[instr.rs] + instr.imm_s; +#if CPU_EXCEPTIONS if ((addr & 0x3) == 0) { uint value = bus.load32(addr); delayedLoad(instr.rt, value); @@ -555,6 +583,10 @@ private void LW() { COP0_GPR[BADA] = addr; EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id); } +#else + uint value = bus.load32(addr); + delayedLoad(instr.rt, value); +#endif } //else Console.WriteLine("IsolatedCache: Ignoring Load"); } @@ -613,12 +645,16 @@ private void SH() { if (dontIsolateCache) { uint addr = GPR[instr.rs] + instr.imm_s; +#if CPU_EXCEPTIONS if ((addr & 0x1) == 0) { bus.write16(addr, (ushort)GPR[instr.rt]); } else { COP0_GPR[BADA] = addr; EXCEPTION(EX.STORE_ADRESS_ERROR, instr.id); } +#else + bus.write16(addr, (ushort)GPR[instr.rt]); +#endif } //else Console.WriteLine("IsolatedCache: Ignoring Write"); } @@ -626,12 +662,16 @@ private void SW() { if (dontIsolateCache) { uint addr = GPR[instr.rs] + instr.imm_s; +#if CPU_EXCEPTIONS if ((addr & 0x3) == 0) { bus.write32(addr, GPR[instr.rt]); } else { COP0_GPR[BADA] = addr; EXCEPTION(EX.STORE_ADRESS_ERROR, instr.id); } +#else + bus.write32(addr, GPR[instr.rt]); +#endif } //else Console.WriteLine("IsolatedCache: Ignoring Write"); } @@ -763,11 +803,16 @@ private void ADD() { uint rs = GPR[instr.rs]; uint rt = GPR[instr.rt]; uint result = rs + rt; + +#if CPU_EXCEPTIONS if (checkOverflow(rs, rt, result)) { EXCEPTION(EX.OVERFLOW, instr.id); } else { setGPR(instr.rd, result); } +#else + setGPR(instr.rd, result); +#endif } private void ADDU() => setGPR(instr.rd, GPR[instr.rs] + GPR[instr.rt]); @@ -776,11 +821,16 @@ private void SUB() { uint rs = GPR[instr.rs]; uint rt = GPR[instr.rt]; uint result = rs - rt; + +#if CPU_EXCEPTIONS if (checkUnderflow(rs, rt, result)) { EXCEPTION(EX.OVERFLOW, instr.id); } else { setGPR(instr.rd, result); } +#else + setGPR(instr.rd, result); +#endif } private void SUBU() => setGPR(instr.rd, GPR[instr.rs] - GPR[instr.rt]); From 3ec554ae26f817b0a47ec51a890bcb37ef119db5 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 9 Jan 2022 14:53:27 +0100 Subject: [PATCH 11/72] Update .editorconfig to match core --- ProjectPSX.WinForms/.editorconfig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ProjectPSX.WinForms/.editorconfig b/ProjectPSX.WinForms/.editorconfig index 89f1f31..ce299b4 100644 --- a/ProjectPSX.WinForms/.editorconfig +++ b/ProjectPSX.WinForms/.editorconfig @@ -91,9 +91,9 @@ csharp_style_inlined_variable_declaration = true:suggestion ############################### # New line preferences csharp_new_line_before_open_brace = none -csharp_new_line_before_else = none -csharp_new_line_before_catch = none -csharp_new_line_before_finally = none +csharp_new_line_before_else = false +csharp_new_line_before_catch =false +csharp_new_line_before_finally =false csharp_new_line_before_members_in_object_initializers = none csharp_new_line_before_members_in_anonymous_types = none csharp_new_line_between_query_expression_clauses = none From aae00df3c95cbfd672238830eb6dc70d2fcdc3e8 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 9 Jan 2022 14:54:01 +0100 Subject: [PATCH 12/72] CD: Sense track changes --- ProjectPSX/Devices/CDROM/CD.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ProjectPSX/Devices/CDROM/CD.cs b/ProjectPSX/Devices/CDROM/CD.cs index b131bd9..88ee60d 100644 --- a/ProjectPSX/Devices/CDROM/CD.cs +++ b/ProjectPSX/Devices/CDROM/CD.cs @@ -14,6 +14,8 @@ public class CD { public List<Track> tracks; + public bool isTrackChange; + public CD(string diskFilename) { string ext = Path.GetExtension(diskFilename); @@ -53,8 +55,9 @@ public byte[] Read(int loc) { public Track getTrackFromLoc(int loc) { foreach (Track track in tracks) { - //Console.WriteLine(loc + " " + track.file + track.lbaEnd); - if (track.lbaEnd > loc) return track; + isTrackChange = loc == track.lbaEnd; + //Console.WriteLine(loc + " " + track.number + " " + track.lbaEnd + " " + isTrackChange); + if (track.lbaEnd >= loc) return track; } Console.WriteLine("[CD] WARNING: LBA beyond tracks!"); return tracks[0]; //and explode ¯\_(ツ)_/¯ From 163c701e9bb160bcd9d0c649faea361aa8e63090 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Mon, 10 Jan 2022 19:52:49 +0100 Subject: [PATCH 13/72] CDROM: CDDA autoPause on trackChange, Change disc. Arguably the change disc functionality is on the core but the frontends lacks the capability to use it. This has been tested with Final Fantasy 7, 8 and Metal Gear Solid, and MGS VR Missions --- ProjectPSX/Core/ProjectPSX.cs | 4 + ProjectPSX/Devices/CDROM/CDROM.cs | 124 +++++++++++++++++++++++++++--- 2 files changed, 117 insertions(+), 11 deletions(-) diff --git a/ProjectPSX/Core/ProjectPSX.cs b/ProjectPSX/Core/ProjectPSX.cs index abbe1c2..97ab245 100644 --- a/ProjectPSX/Core/ProjectPSX.cs +++ b/ProjectPSX/Core/ProjectPSX.cs @@ -66,5 +66,9 @@ public void toggleDebug() { gpu.debug = !gpu.debug; } + public void toggleCdRomLid() { + cdrom.toggleLid(); + } + } } diff --git a/ProjectPSX/Devices/CDROM/CDROM.cs b/ProjectPSX/Devices/CDROM/CDROM.cs index c20b2c2..b2543ce 100644 --- a/ProjectPSX/Devices/CDROM/CDROM.cs +++ b/ProjectPSX/Devices/CDROM/CDROM.cs @@ -68,6 +68,7 @@ public class CDROM { private byte volumeRtoR = 0xFF; private bool cdDebug = false; + private bool isLidOpen = false; private struct SectorHeader { public byte mm; @@ -134,8 +135,8 @@ public bool tick(int cycles) { switch (mode) { case Mode.Idle: - counter = 0; - return false; + counter = 0; + return false; case Mode.Seek: //Hardcoded seek time... if (counter < 33868800 / 3 || interruptQueue.Count != 0) { @@ -150,7 +151,7 @@ public bool tick(int cycles) { break; case Mode.Read: - case Mode.Play: + case Mode.Play: if (counter < (33868800 / (isDoubleSpeed ? 150 : 75)) || interruptQueue.Count != 0) { return false; } @@ -166,11 +167,22 @@ public bool tick(int cycles) { //Handle Mode.Play: if (mode == Mode.Play) { - if (!mutedAudio) { + if (!mutedAudio && isCDDA) { applyVolume(readSector); spu.pushCdBufferSamples(readSector); } + if (isAutoPause && cd.isTrackChange) { + responseBuffer.Enqueue(STAT); + interruptQueue.Enqueue(0x4); + + pause(); + } + + if (isReport) { + //Console.WriteLine("Report Not Handled"); + } + return false; //CDDA isn't delivered to CPU and doesn't raise interrupt } @@ -207,7 +219,7 @@ public bool tick(int cycles) { if (cdDebug) Console.WriteLine("[CDROM] XA ON: Realtime + Audio"); //todo flag to pass to SPU? - if(!mutedAudio && !mutedXAADPCM) { + if (!mutedAudio && !mutedXAADPCM) { byte[] decodedXaAdpcm = XaAdpcm.Decode(readSector, sectorSubHeader.codingInfo); applyVolume(decodedXaAdpcm); spu.pushCdBufferSamples(decodedXaAdpcm); @@ -378,8 +390,8 @@ public void write(uint addr, uint value) { bool applyVolume = (value & 0x20) != 0; if (applyVolume) { volumeLtoL = pendingVolumeLtoL; - volumeLtoR = pendingVolumeLtoR; - volumeRtoL = pendingVolumeRtoL; + volumeLtoR = pendingVolumeLtoR; + volumeRtoL = pendingVolumeRtoL; volumeRtoR = pendingVolumeRtoR; } break; @@ -438,6 +450,12 @@ private void mute() { } private void readTOC() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } + mode = Mode.TOC; responseBuffer.Enqueue(STAT); interruptQueue.Enqueue(0x3); @@ -459,6 +477,12 @@ private void getLocL() { $" file: {sectorSubHeader.file} channel: {sectorSubHeader.channel} subMode: {sectorSubHeader.subMode} codingInfo: {sectorSubHeader.codingInfo}"); } + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } + Span<byte> response = stackalloc byte[] { sectorHeader.mm, sectorHeader.ss, @@ -476,6 +500,11 @@ private void getLocL() { } private void getLocP() { //SubQ missing... + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } Track track = cd.getTrackFromLoc(readLoc); (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(readLoc - track.lbaStart); @@ -509,6 +538,12 @@ private void lockUnlock() { private void setSession() { //broken parameterBuffer.Clear(); + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } + STAT = 0x42; responseBuffer.Enqueue(STAT); @@ -527,6 +562,12 @@ private void setFilter() { } private void readS() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } + readLoc = seekLoc; STAT = 0x2; @@ -539,6 +580,12 @@ private void readS() { } private void seekP() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } + readLoc = seekLoc; STAT = 0x42; // seek @@ -550,6 +597,11 @@ private void seekP() { private void play() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } //If theres a trackN param it seeks and plays from the start location of it int track = 0; if (parameterBuffer.Count > 0) { @@ -582,6 +634,12 @@ private void stop() { } private void getTD() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } + int track = BcdToDec(parameterBuffer.Dequeue()); if (track == 0) { //returns CD LBA / End of last track @@ -601,6 +659,11 @@ private void getTD() { } private void getTN() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } //if (cdDebug) Console.WriteLine($"[CDROM] getTN First Track: 1 (Hardcoded) - Last Track: {cd.tracks.Count}"); //Console.ReadLine(); @@ -637,6 +700,12 @@ private void pause() { } private void readN() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } + readLoc = seekLoc; STAT = 0x2; @@ -675,6 +744,12 @@ private void setMode() { } private void seekL() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } + readLoc = seekLoc; STAT = 0x42; // seek @@ -685,6 +760,12 @@ private void seekL() { } private void setLoc() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } + byte mm = parameterBuffer.Dequeue(); byte ss = parameterBuffer.Dequeue(); byte ff = parameterBuffer.Dequeue(); @@ -724,9 +805,11 @@ private void getID() { //interruptQueue.Enqueue(0x5); //Door Open INT5(11h,80h) N/A - //STAT = 0x10; //Shell Open - //responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80, 0x00, 0x00 }); - //interruptQueue.Enqueue(0x5); + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } //Licensed: Mode2 INT3(stat) INT2(02h, 00h, 20h, 00h, 53h, 43h, 45h, 4xh) STAT = 0x40; //0x40 seek @@ -740,6 +823,11 @@ private void getID() { } private void getStat() { + if (!isLidOpen) { + STAT = (byte)(STAT & (~0x18)); + STAT |= 0x2; + } + responseBuffer.Enqueue(STAT); interruptQueue.Enqueue(0x3); } @@ -842,7 +930,7 @@ private static (byte mm, byte ss, byte ff) getMMSSFFfromLBA(int lba) { private void applyVolume(byte[] rawSector) { var samples = MemoryMarshal.Cast<byte, short>(rawSector); - for(int i = 0; i < samples.Length; i+=2) { + for (int i = 0; i < samples.Length; i += 2) { short l = samples[i]; short r = samples[i + 1]; @@ -859,5 +947,19 @@ public Span<uint> processDmaLoad(int size) { return currentSector.read(size); } + internal void toggleLid() { + isLidOpen = !isLidOpen; + if (isLidOpen) { + STAT = 0x18; + mode = Mode.Idle; + interruptQueue.Clear(); + responseBuffer.Clear(); + } else { + //todo handle the Cd load and not this hardcoded test: + //cd = new CD(@"cd_change_path"); + } + Console.WriteLine($"[CDROM] Shell is Open: {isLidOpen} STAT: {STAT}"); + } + } } From 166025c6561cad7866e3666c9c195cc9219bbf46 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Fri, 18 Feb 2022 22:43:25 +0100 Subject: [PATCH 14/72] BUS: Cleanup --- ProjectPSX/Core/BUS.cs | 64 +++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/ProjectPSX/Core/BUS.cs b/ProjectPSX/Core/BUS.cs index a851143..09ac4cb 100644 --- a/ProjectPSX/Core/BUS.cs +++ b/ProjectPSX/Core/BUS.cs @@ -3,15 +3,11 @@ using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading.Tasks; namespace ProjectPSX { - //TODO: - //Do loadX and WriteX simple functions that return a value based on a generic load and write giant switch pointer return - // WIP: Already got rid of the multiple loadX writeX variants need to adress the giant switches but then how to handle the individual - // components? they expect allways an uint and they transform to variables. (uint)(object) is out probably because it kills perf and - // Unsafe.As is still noticeable so... some rework needed on the interaction between components and bus. + public class BUS { + //todo write32/16/8 unification pending .NET 7 IBinaryNumber //Memory private unsafe byte* ramPtr = (byte*)Marshal.AllocHGlobal(2048 * 1024); @@ -67,7 +63,7 @@ public unsafe uint load32(uint address) { return load<uint>(addr & 0xF, sio); } else if (addr < 0x1F80_1070) { return load<uint>(addr & 0xF, memoryControl2); - } else if (addr < 0x1F801080) { + } else if (addr < 0x1F80_1080) { return interruptController.load(addr); } else if (addr < 0x1F80_1100) { return dma.load(addr); @@ -83,8 +79,11 @@ public unsafe uint load32(uint address) { return mdec.readMDEC0_Data(); } else if (addr == 0x1F80_1824) { return mdec.readMDEC1_Status(); - } else if (addr < 0x1F802000) { + } else if (addr < 0x1F80_2000) { return spu.load(addr); + } else if (addr < 0x1F80_4000) { + Console.WriteLine($"[BUS] Read Unsupported to EXP2 address: {addr:x8}"); + return 0xFFFF_FFFF; } else if (addr < 0x1FC8_0000) { return load<uint>(addr & 0x7_FFFF, biosPtr); } else if (addr == 0xFFFE0130) { @@ -113,7 +112,7 @@ public unsafe void write32(uint address, uint value) { write(addr & 0xF, value, sio); } else if (addr < 0x1F80_1070) { write(addr & 0xF, value, memoryControl2); - } else if (addr < 0x1F801080) { + } else if (addr < 0x1F80_1080) { interruptController.write(addr, value); } else if (addr < 0x1F80_1100) { dma.write(addr, value); @@ -125,9 +124,11 @@ public unsafe void write32(uint address, uint value) { gpu.write(addr, value); } else if (addr < 0x1F80_1830) { mdec.write(addr, value); - } else if (addr < 0x1F802000) { + } else if (addr < 0x1F80_2000) { spu.write(addr, (ushort)value); - } else if (addr == 0xFFFE0130) { + } else if (addr < 0x1F80_4000) { + Console.WriteLine($"[BUS] Write Unsupported to EXP2: {addr:x8} Value: {value:x8}"); + } else if (addr == 0xFFFE_0130) { memoryCache = value; } else { Console.WriteLine($"[BUS] Write32 Unsupported: {addr:x8}"); @@ -141,7 +142,7 @@ public unsafe void write16(uint address, ushort value) { write(addr & 0x1F_FFFF, value, ramPtr); } else if (addr < 0x1F80_0000) { write(addr & 0x7_FFFF, value, ex1Ptr); - } else if (addr < 0x1f80_0400) { + } else if (addr < 0x1F80_0400) { write(addr & 0x3FF, value, scrathpadPtr); } else if (addr < 0x1F80_1040) { write(addr & 0x3F, value, memoryControl1); @@ -152,7 +153,7 @@ public unsafe void write16(uint address, ushort value) { write(addr & 0xF, value, sio); } else if (addr < 0x1F80_1070) { write(addr & 0xF, value, memoryControl2); - } else if (addr < 0x1F801080) { + } else if (addr < 0x1F80_1080) { interruptController.write(addr, value); } else if (addr < 0x1F80_1100) { dma.write(addr, value); @@ -164,9 +165,11 @@ public unsafe void write16(uint address, ushort value) { gpu.write(addr, value); } else if (addr < 0x1F80_1830) { mdec.write(addr, value); - } else if (addr < 0x1F802000) { - spu.write(addr, (ushort)value); - } else if (addr == 0xFFFE0130) { + } else if (addr < 0x1F80_2000) { + spu.write(addr, value); + } else if (addr < 0x1F80_4000) { + Console.WriteLine($"[BUS] Write Unsupported to EXP2: {addr:x8} Value: {value:x8}"); + } else if (addr == 0xFFFE_0130) { memoryCache = value; } else { Console.WriteLine($"[BUS] Write16 Unsupported: {addr:x8}"); @@ -191,7 +194,7 @@ public unsafe void write8(uint address, byte value) { write(addr & 0xF, value, sio); } else if (addr < 0x1F80_1070) { write(addr & 0xF, value, memoryControl2); - } else if (addr < 0x1F801080) { + } else if (addr < 0x1F80_1080) { interruptController.write(addr, value); } else if (addr < 0x1F80_1100) { dma.write(addr, value); @@ -203,20 +206,17 @@ public unsafe void write8(uint address, byte value) { gpu.write(addr, value); } else if (addr < 0x1F80_1830) { mdec.write(addr, value); - } else if (addr < 0x1F802000) { - spu.write(addr, (ushort)value); - } else if (addr == 0xFFFE0130) { + } else if (addr < 0x1F80_2000) { + spu.write(addr, value); + } else if (addr < 0x1F80_4000) { + Console.WriteLine($"[BUS] Write Unsupported to EXP2: {addr:x8} Value: {value:x8}"); + } else if (addr == 0xFFFE_0130) { memoryCache = value; } else { Console.WriteLine($"[BUS] Write8 Unsupported: {addr:x8}"); } } - private uint maskAddr(uint addr) { - uint i = addr >> 29; - return addr & RegionMask[i]; - } - internal unsafe void loadBios() { try { byte[] rom = File.ReadAllBytes(bios); @@ -278,7 +278,7 @@ public unsafe void loadEXE(String fileName) { write(0x6FF0 + 8, 0x3C1C0000 | R28 >> 16, biosPtr); write(0x6FF0 + 12, 0x379C0000 | R28 & 0xFFFF, biosPtr); - if(R29 != 0) { + if (R29 != 0) { write(0x6FF0 + 16, 0x3C1D0000 | R29 >> 16, biosPtr); write(0x6FF0 + 20, 0x37BD0000 | R29 & 0xFFFF, biosPtr); @@ -410,19 +410,19 @@ public unsafe void DmaOTC(uint baseAddress, int size) { //var dest = new Span<uint>(ramPtr + (destAddress & 0x1F_FFFC), size); //dma.CopyTo(dest); - for(int i = 0; i < size - 1; i++) { + for (int i = 0; i < size - 1; i++) { DmaToRam(baseAddress, baseAddress - 4); baseAddress -= 4; } - + DmaToRam(baseAddress, 0xFF_FFFF); } private static uint[] RegionMask = { - 0xFFFF_FFFF, 0xFFFF_FFFF, 0xFFFF_FFFF, 0xFFFF_FFFF, // KUSEG: 2048MB - 0x7FFF_FFFF, // KSEG0: 512MB - 0x1FFF_FFFF, // KSEG1: 512MB - 0xFFFF_FFFF, 0xFFFF_FFFF, // KSEG2: 1024MB + 0xFFFF_FFFF, 0xFFFF_FFFF, 0xFFFF_FFFF, 0xFFFF_FFFF, // KUSEG: 2048MB + 0x7FFF_FFFF, // KSEG0: 512MB + 0x1FFF_FFFF, // KSEG1: 512MB + 0xFFFF_FFFF, 0xFFFF_FFFF, // KSEG2: 1024MB }; } } From cb4a557d2a1aee51250a1fe3761e655051442c90 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 27 Feb 2022 21:43:45 +0100 Subject: [PATCH 15/72] OpenTK: Update to 4.7.1 --- ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj index 467e7bf..47d7623 100644 --- a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj +++ b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj @@ -15,7 +15,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="OpenTK" Version="4.6.7" /> + <PackageReference Include="OpenTK" Version="4.7.1" /> </ItemGroup> <ItemGroup> From 3a5a1c68f9e09eecbdd7be7b547e4b748f5102e4 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sat, 30 Apr 2022 18:18:37 +0200 Subject: [PATCH 16/72] GPU: Fix GpuStat.19 misalignment --- ProjectPSX/Devices/GPU/GPU.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index b386e8c..0097b5c 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -195,7 +195,7 @@ public uint loadGPUSTAT() { GPUSTAT |= (uint)(isTextureDisabled ? 1 : 0) << 15; GPUSTAT |= (uint)horizontalResolution2 << 16; GPUSTAT |= (uint)horizontalResolution1 << 17; - GPUSTAT |= (uint)(isVerticalResolution480 ? 1 : 0); + GPUSTAT |= (uint)(isVerticalResolution480 ? 1 : 0) << 19; GPUSTAT |= (uint)(isPal ? 1 : 0) << 20; GPUSTAT |= (uint)(is24BitDepth ? 1 : 0) << 21; GPUSTAT |= (uint)(isVerticalInterlace ? 1 : 0) << 22; From 953b5b05a2385e047baa93480417eb18a38cb094 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sat, 30 Apr 2022 18:50:37 +0200 Subject: [PATCH 17/72] GPU: Handle GPUSTAT.25 on GP1_04 --- ProjectPSX/Devices/GPU/GPU.cs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index 0097b5c..4fb0c7d 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -104,9 +104,9 @@ private struct Color { private bool isInterruptRequested; private bool isDmaRequest; - private bool isReadyToReceiveCommand; - private bool isReadyToSendVRAMToCPU; - private bool isReadyToReceiveDMABlock; + private bool isReadyToReceiveCommand = true; //todo + private bool isReadyToSendVRAMToCPU = true; //todo + private bool isReadyToReceiveDMABlock = true; //todo private byte dmaDirection; private bool isOddLine; @@ -203,9 +203,9 @@ public uint loadGPUSTAT() { GPUSTAT |= (uint)(isInterruptRequested ? 1 : 0) << 24; GPUSTAT |= (uint)(isDmaRequest ? 1 : 0) << 25; - GPUSTAT |= (uint)/*(isReadyToReceiveCommand ? 1 : 0)*/1 << 26; - GPUSTAT |= (uint)/*(isReadyToSendVRAMToCPU ? 1 : 0)*/1 << 27; - GPUSTAT |= (uint)/*(isReadyToReceiveDMABlock ? 1 : 0)*/1 << 28; + GPUSTAT |= (uint)(isReadyToReceiveCommand ? 1 : 0) << 26; + GPUSTAT |= (uint)(isReadyToSendVRAMToCPU ? 1 : 0) << 27; + GPUSTAT |= (uint)(isReadyToReceiveDMABlock ? 1 : 0) << 28; GPUSTAT |= (uint)dmaDirection << 29; GPUSTAT |= (uint)(isOddLine ? 1 : 0) << 31; @@ -1065,7 +1065,18 @@ private void GP1_00_ResetGPU() { private void GP1_03_DisplayEnable(uint value) => isDisplayDisabled = (value & 1) != 0; - private void GP1_04_DMADirection(uint value) => dmaDirection = (byte)(value & 0x3); + private void GP1_04_DMADirection(uint value) { + dmaDirection = (byte)(value & 0x3); + + isDmaRequest = dmaDirection switch { + 0 => false, + 1 => isReadyToReceiveDMABlock, + 2 => isReadyToReceiveDMABlock, + 3 => isReadyToSendVRAMToCPU, + _ => false, + }; + } + private void GP1_05_DisplayVRAMStart(uint value) { displayVRAMXStart = (ushort)(value & 0x3FE); From 5402159b3781450f7ff73ac7bed85a979d80e3d5 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sat, 30 Apr 2022 19:14:50 +0200 Subject: [PATCH 18/72] GPU: Handle GPUSTAT.27 --- ProjectPSX/Devices/GPU/GPU.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index 4fb0c7d..375b1f9 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -105,7 +105,7 @@ private struct Color { private bool isDmaRequest; private bool isReadyToReceiveCommand = true; //todo - private bool isReadyToSendVRAMToCPU = true; //todo + private bool isReadyToSendVRAMToCPU; private bool isReadyToReceiveDMABlock = true; //todo private byte dmaDirection; @@ -286,11 +286,19 @@ private void WriteToVRAM(uint value) { private uint readFromVRAM() { ushort pixel0 = vram.GetPixelBGR555(vramTransfer.x++ & 0x3FF, vramTransfer.y & 0x1FF); ushort pixel1 = vram.GetPixelBGR555(vramTransfer.x++ & 0x3FF, vramTransfer.y & 0x1FF); + if (vramTransfer.x == vramTransfer.origin_x + vramTransfer.w) { vramTransfer.x -= vramTransfer.w; vramTransfer.y++; } + vramTransfer.halfWords -= 2; + + if(vramTransfer.halfWords == 0) { + isReadyToSendVRAMToCPU = false; + isReadyToReceiveDMABlock = true; + } + return (uint)(pixel1 << 16 | pixel0); } @@ -927,6 +935,9 @@ private void GP0_MemCopyRectVRAMtoCPU(Span<uint> buffer) { vramTransfer.origin_x = x; vramTransfer.origin_y = y; vramTransfer.halfWords = w * h; + + isReadyToSendVRAMToCPU = true; + isReadyToReceiveDMABlock = false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] From 446ac4f26494fbde4cc351908441d88eaa37abd3 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 15 May 2022 20:34:09 +0200 Subject: [PATCH 19/72] SPU: fix lower 0..5 bits of status<->control --- ProjectPSX/Devices/SPU/SPU.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ProjectPSX/Devices/SPU/SPU.cs b/ProjectPSX/Devices/SPU/SPU.cs index e26337e..c37b129 100644 --- a/ProjectPSX/Devices/SPU/SPU.cs +++ b/ProjectPSX/Devices/SPU/SPU.cs @@ -278,9 +278,9 @@ internal void write(uint addr, ushort value) { if (!control.irq9Enabled) status.irq9Flag = false; - //Status lower 5 bits are the same as control - status.register &= 0xFFE0; - status.register |= (ushort)(value & 0x1F); + //Status 0..5 bits are the same as control + status.register &= 0xFFC0; + status.register |= (ushort)(value & 0x3F); break; case 0x1F801DAC: From ac9508b4a6281c8e6f05956a9087760aee67e49c Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 15 May 2022 20:41:06 +0200 Subject: [PATCH 20/72] SPU: Turn off voices when not enabled --- ProjectPSX/Devices/SPU/SPU.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ProjectPSX/Devices/SPU/SPU.cs b/ProjectPSX/Devices/SPU/SPU.cs index c37b129..cb967e8 100644 --- a/ProjectPSX/Devices/SPU/SPU.cs +++ b/ProjectPSX/Devices/SPU/SPU.cs @@ -274,6 +274,13 @@ internal void write(uint addr, ushort value) { case 0x1F801DAA: control.register = value; + if (!control.spuEnabled) { + foreach (Voice v in voices) { + v.adsrPhase = Voice.Phase.Off; + v.adsrVolume = 0; + } + } + //Irq Flag is reseted on ack if (!control.irq9Enabled) status.irq9Flag = false; @@ -555,10 +562,10 @@ public bool tick(int cycles) { spuOutputPointer = 0; } - if (control.spuEnabled && control.irq9Enabled && edgeTrigger) { + if (control.irq9Enabled && edgeTrigger) { status.irq9Flag = true; } - return control.spuEnabled && control.irq9Enabled && edgeTrigger; //todo move spuEnabled outside + return control.irq9Enabled && edgeTrigger; } private bool handleCaptureBuffer(int address, short sample) { From ba44f589b67f47936e1b78477e4b2a1a4a6d52b0 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 22 May 2022 18:29:57 +0200 Subject: [PATCH 21/72] OpenTK: Update to 4.7.2 --- ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj index 47d7623..27e7ab3 100644 --- a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj +++ b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj @@ -15,7 +15,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="OpenTK" Version="4.7.1" /> + <PackageReference Include="OpenTK" Version="4.7.2" /> </ItemGroup> <ItemGroup> From f23511c97132b9b62f857fe87c837f4fd69199c2 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 22 May 2022 18:57:32 +0200 Subject: [PATCH 22/72] TrackBuilder: Identify Audio tracks --- ProjectPSX/Devices/CDROM/TrackBuilder.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/ProjectPSX/Devices/CDROM/TrackBuilder.cs b/ProjectPSX/Devices/CDROM/TrackBuilder.cs index 5146205..9ee1539 100644 --- a/ProjectPSX/Devices/CDROM/TrackBuilder.cs +++ b/ProjectPSX/Devices/CDROM/TrackBuilder.cs @@ -15,14 +15,16 @@ public class Track { public int lba { get; private set; } public int lbaStart { get; private set; } public int lbaEnd { get; private set; } + public bool isAudio { get; private set; } - public Track(String file, long size, byte number, int lba, int lbaStart, int lbaEnd) { + public Track(String file, long size, byte number, int lba, int lbaStart, int lbaEnd, bool isAudio) { this.file = file; this.size = size; this.number = number; this.lba = lba; this.lbaStart = lbaStart; this.lbaEnd = lbaEnd; + this.isAudio = isAudio; } } @@ -52,9 +54,12 @@ public static List<Track> fromCue(String cue) { lbaCounter += lba; - tracks.Add(new Track(file, size, number, lba, lbaStart, lbaEnd)); ; + string trackTypeLine = cueFile.ReadLine(); + bool isAudio = trackTypeLine.Contains("AUDIO"); - Console.WriteLine($"File: {file} Size: {size} Number: {number} LbaStart: {lbaStart} LbaEnd: {lbaEnd}"); + tracks.Add(new Track(file, size, number, lba, lbaStart, lbaEnd, isAudio)); + + Console.WriteLine($"File: {file} Size: {size} Number: {number} LbaStart: {lbaStart} LbaEnd: {lbaEnd} isAudio {isAudio}"); } } @@ -71,10 +76,11 @@ public static List<Track> fromBin(string file) { int lbaStart = 150; // 150 frames (2 seconds) offset from track 1 int lbaEnd = lba; byte number = 1; + bool isAudio = false; - tracks.Add(new Track(file, size, number, lba, lbaStart, lbaEnd)); + tracks.Add(new Track(file, size, number, lba, lbaStart, lbaEnd, isAudio)); - Console.WriteLine($"File: {file} Size: {size} Number: {number} LbaStart: {lbaStart} LbaEnd: {lbaEnd}"); + Console.WriteLine($"File: {file} Size: {size} Number: {number} LbaStart: {lbaStart} LbaEnd: {lbaEnd} isAudio {isAudio}"); return tracks; } From af0bc8bd04bc23996e8a4b575b0a106e8c179047 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 22 May 2022 18:58:02 +0200 Subject: [PATCH 23/72] CD: Handle AudioCDs wip --- ProjectPSX/Devices/CDROM/CD.cs | 4 ++++ ProjectPSX/Devices/CDROM/CDROM.cs | 39 ++++++++++++++++++++----------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/ProjectPSX/Devices/CDROM/CD.cs b/ProjectPSX/Devices/CDROM/CD.cs index 88ee60d..26a3361 100644 --- a/ProjectPSX/Devices/CDROM/CD.cs +++ b/ProjectPSX/Devices/CDROM/CD.cs @@ -73,5 +73,9 @@ public int getLBA() { return lba; } + public bool isAudioCD() { + return tracks[0].isAudio; + } + } } diff --git a/ProjectPSX/Devices/CDROM/CDROM.cs b/ProjectPSX/Devices/CDROM/CDROM.cs index b2543ce..4d4e9b0 100644 --- a/ProjectPSX/Devices/CDROM/CDROM.cs +++ b/ProjectPSX/Devices/CDROM/CDROM.cs @@ -604,9 +604,13 @@ private void play() { } //If theres a trackN param it seeks and plays from the start location of it int track = 0; - if (parameterBuffer.Count > 0) { + if (parameterBuffer.Count > 0 && parameterBuffer.Peek() != 0) { track = BcdToDec(parameterBuffer.Dequeue()); - readLoc = seekLoc = cd.tracks[track].lbaStart; + if (cd.isAudioCD()) { + readLoc = seekLoc = cd.tracks[track - 1].lbaStart; + } else { + readLoc = seekLoc = cd.tracks[track].lbaStart; + } //else it plays from the previously seekLoc and seeks if not done (actually not checking if already seeked) } else { readLoc = seekLoc; @@ -622,11 +626,11 @@ private void play() { } private void stop() { - STAT = 0; - + STAT = 0x2; responseBuffer.Enqueue(STAT); interruptQueue.Enqueue(0x3); + STAT = 0; responseBuffer.Enqueue(STAT); interruptQueue.Enqueue(0x2); @@ -796,6 +800,13 @@ private void setLoc() { } private void getID() { + //Door Open INT5(11h,80h) N/A + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } + //No Disk INT3(stat) INT5(08h, 40h, 00h, 00h, 00h, 00h, 00h, 00h) //STAT = 0x2; //0x40 seek //responseBuffer.Enqueue(STAT); @@ -804,21 +815,23 @@ private void getID() { //responseBuffer.EnqueueRange(stackalloc byte[] { 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); //interruptQueue.Enqueue(0x5); - //Door Open INT5(11h,80h) N/A - if (isLidOpen) { - responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); - return; - } - //Licensed: Mode2 INT3(stat) INT2(02h, 00h, 20h, 00h, 53h, 43h, 45h, 4xh) STAT = 0x40; //0x40 seek STAT |= 0x2; responseBuffer.Enqueue(STAT); interruptQueue.Enqueue(0x3); - Span<byte> response = stackalloc byte[] { 0x02, 0x00, 0x20, 0x00, 0x53, 0x43, 0x45, 0x41 }; //SCE | //A 0x41 (America) - I 0x49 (Japan) - E 0x45 (Europe) - responseBuffer.EnqueueRange(response); + // Audio Disk INT3(stat) INT5(0Ah,90h, 00h,00h, 00h,00h,00h,00h) + if (cd.isAudioCD()) { + Span<byte> audioCdResponse = stackalloc byte[] { 0x0A, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + responseBuffer.EnqueueRange(audioCdResponse); + interruptQueue.Enqueue(0x5); + return; + } + + // Licensed: Mode2 INT3(stat) INT2(02h, 00h, 20h, 00h, 53h, 43h, 45h, 4xh) + Span<byte> gameResponse = stackalloc byte[] { 0x02, 0x00, 0x20, 0x00, 0x53, 0x43, 0x45, 0x41 }; //SCE | //A 0x41 (America) - I 0x49 (Japan) - E 0x45 (Europe) + responseBuffer.EnqueueRange(gameResponse); interruptQueue.Enqueue(0x2); } From 9db088f25c500d7ed3b1f3349bcff34a08f7fbc0 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Fri, 27 May 2022 21:21:51 +0200 Subject: [PATCH 24/72] SPU: Fix dma load size This fixes Gran Turismo going ingame and Marvel vs Capcom freezing before main screen --- ProjectPSX/Devices/SPU/SPU.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ProjectPSX/Devices/SPU/SPU.cs b/ProjectPSX/Devices/SPU/SPU.cs index cb967e8..339fc33 100644 --- a/ProjectPSX/Devices/SPU/SPU.cs +++ b/ProjectPSX/Devices/SPU/SPU.cs @@ -660,15 +660,16 @@ public short sampleVoice(int v) { public Span<uint> processDmaLoad(int size) { //todo trigger interrupt - Span<byte> dma = ram.AsSpan().Slice((int)ramDataTransferAddressInternal, size); + int dmaLength = size * 4; + Span<byte> dma = ram.AsSpan().Slice((int)ramDataTransferAddressInternal, dmaLength); //ramDataTransferAddressInternal and ramIrqAddress already are >> 3 //so check if it's in the size range and trigger int - if (ramIrqAddress > ramDataTransferAddressInternal && ramIrqAddress < ramDataTransferAddressInternal + size) { + if (ramIrqAddress > ramDataTransferAddressInternal && ramIrqAddress < ramDataTransferAddressInternal + dmaLength) { interruptController.set(Interrupt.SPU); } - ramDataTransferAddressInternal = (uint)(ramDataTransferAddressInternal + size * 4); + ramDataTransferAddressInternal = (uint)(ramDataTransferAddressInternal + dmaLength); return MemoryMarshal.Cast<byte, uint>(dma); } From 824fc838d48fa9131a3459cb0c7879e0fe62a683 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Fri, 27 May 2022 21:31:42 +0200 Subject: [PATCH 25/72] CDROM: Refactor commands --- ProjectPSX/Devices/CDROM/CDROM.cs | 533 +++++++++++++++--------------- 1 file changed, 270 insertions(+), 263 deletions(-) diff --git a/ProjectPSX/Devices/CDROM/CDROM.cs b/ProjectPSX/Devices/CDROM/CDROM.cs index 4d4e9b0..e509e6a 100644 --- a/ProjectPSX/Devices/CDROM/CDROM.cs +++ b/ProjectPSX/Devices/CDROM/CDROM.cs @@ -176,7 +176,7 @@ public bool tick(int cycles) { responseBuffer.Enqueue(STAT); interruptQueue.Enqueue(0x4); - pause(); + Cmd_09_Pause(); } if (isReport) { @@ -264,12 +264,15 @@ public uint load(uint addr) { return STATUS(); case 0x1F801801: - if (cdDebug) Console.WriteLine("[CDROM] [L01] RESPONSE " /*+ responseBuffer.Peek().ToString("x8")*/); //Console.ReadLine(); //if (w == Width.HALF || w == Width.WORD) Console.WriteLine("WARNING RESPONSE BUFFER LOAD " + w); - if (responseBuffer.Count > 0) + if (responseBuffer.Count > 0) { + if (cdDebug) Console.WriteLine("[CDROM] [L01] RESPONSE " + responseBuffer.Peek().ToString("x8")); return responseBuffer.Dequeue(); + } + + if (cdDebug) Console.WriteLine("[CDROM] [L01] RESPONSE 0xFF"); return 0xFF; @@ -409,159 +412,120 @@ public void write(uint addr, uint value) { private void ExecuteCommand(uint value) { if (cdDebug) Console.WriteLine($"[CDROM] Command {value:x4}"); + //Console.WriteLine($"PRE STAT {STAT:x2}"); interruptQueue.Clear(); responseBuffer.Clear(); isBusy = true; switch (value) { - case 0x01: getStat(); break; - case 0x02: setLoc(); break; - case 0x03: play(); break; - case 0x06: readN(); break; - case 0x07: motorOn(); break; - case 0x08: stop(); break; - case 0x09: pause(); break; - case 0x0A: init(); break; - case 0x0B: mute(); break; - case 0x0C: demute(); break; - case 0x0D: setFilter(); break; - case 0x0E: setMode(); break; - case 0x10: getLocL(); break; - case 0x11: getLocP(); break; - case 0x12: setSession(); break; - case 0x13: getTN(); break; - case 0x14: getTD(); break; - case 0x15: seekL(); break; - case 0x16: seekP(); break; - case 0x19: test(); break; - case 0x1A: getID(); break; - case 0x1B: readS(); break; - case 0x1E: readTOC(); break; - case 0x1F: videoCD(); break; - case uint _ when value >= 0x50 && value <= 0x57: lockUnlock(); break; + case 0x01: Cmd_01_GetStat(); break; + case 0x02: Cmd_02_SetLoc(); break; + case 0x03: Cmd_03_Play(); break; + //case 0x04: Cmd_04_Forward(); break; //todo + //case 0x05: Cmd_05_Backward(); break; //todo + case 0x06: Cmd_06_ReadN(); break; + case 0x07: Cmd_07_MotorOn(); break; + case 0x08: Cmd_08_Stop(); break; + case 0x09: Cmd_09_Pause(); break; + case 0x0A: Cmd_0A_Init(); break; + case 0x0B: Cmd_0B_Mute(); break; + case 0x0C: Cmd_0C_Demute(); break; + case 0x0D: Cmd_0D_SetFilter(); break; + case 0x0E: Cmd_0E_SetMode(); break; + //case 0x0F: Cmd_0F_GetParam(); break; //todo + case 0x10: Cmd_10_GetLocL(); break; + case 0x11: Cmd_11_GetLocP(); break; + case 0x12: Cmd_12_SetSession(); break; + case 0x13: Cmd_13_GetTN(); break; + case 0x14: Cmd_14_GetTD(); break; + case 0x15: Cmd_15_SeekL(); break; + case 0x16: Cmd_16_SeekP(); break; + case 0x19: Cmd_19_Test(); break; + case 0x1A: Cmd_1A_GetID(); break; + case 0x1B: Cmd_1B_ReadS(); break; + case 0x1E: Cmd_1E_ReadTOC(); break; + case 0x1F: Cmd_1F_VideoCD(); break; + case uint _ when value >= 0x50 && value <= 0x57: Cmd_5x_lockUnlock(); break; default: UnimplementedCDCommand(value); break; } + //Console.WriteLine($"POST STAT {STAT:x2}"); } - private void mute() { - mutedAudio = true; + private void Cmd_01_GetStat() { + if (!isLidOpen) { + STAT = (byte)(STAT & (~0x18)); + STAT |= 0x2; + } responseBuffer.Enqueue(STAT); interruptQueue.Enqueue(0x3); } - private void readTOC() { + private void Cmd_02_SetLoc() { if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); interruptQueue.Enqueue(0x5); return; } - mode = Mode.TOC; - responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); - } - - private void motorOn() { - STAT = 0x2; - - responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); - - responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x2); - } - - private void getLocL() { - if (cdDebug) { - Console.WriteLine($"mm: {sectorHeader.mm} ss: {sectorHeader.ss} ff: {sectorHeader.ff} mode: {sectorHeader.mode}" + - $" file: {sectorSubHeader.file} channel: {sectorSubHeader.channel} subMode: {sectorSubHeader.subMode} codingInfo: {sectorSubHeader.codingInfo}"); - } + byte mm = parameterBuffer.Dequeue(); + byte ss = parameterBuffer.Dequeue(); + byte ff = parameterBuffer.Dequeue(); - if (isLidOpen) { - responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); - return; - } + //Console.WriteLine($"[CDROM] setLoc BCD {mm:x2}:{ss:x2}:{ff:x2}"); - Span<byte> response = stackalloc byte[] { - sectorHeader.mm, - sectorHeader.ss, - sectorHeader.ff, - sectorHeader.mode, - sectorSubHeader.file, - sectorSubHeader.channel, - sectorSubHeader.subMode, - sectorSubHeader.codingInfo - }; - responseBuffer.EnqueueRange(response); + int minute = BcdToDec(mm); + int second = BcdToDec(ss); + int sector = BcdToDec(ff); - interruptQueue.Enqueue(0x3); - } + //There are 75 sectors on a second + seekLoc = sector + (second * 75) + (minute * 60 * 75); - private void getLocP() { //SubQ missing... - if (isLidOpen) { - responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); - return; + if (seekLoc < 0) { + Console.WriteLine($"[CDROM] WARNING NEGATIVE setLOC {seekLoc:x8}"); + seekLoc = 0; } - Track track = cd.getTrackFromLoc(readLoc); - (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(readLoc - track.lbaStart); - (byte amm, byte ass, byte aff) = getMMSSFFfromLBA(readLoc); - if (cdDebug) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"track: {track.number} index: {1} mm: {mm} ss: {ss}" + - $" ff: {ff} amm: {amm} ass: {ass} aff: {aff}"); - Console.WriteLine($"track: {track.number} index: {1} mm: {DecToBcd(mm)} ss: {DecToBcd(ss)}" + - $" ff: {DecToBcd(ff)} amm: {DecToBcd(amm)} ass: {DecToBcd(ass)} aff: {DecToBcd(aff)}"); + Console.ForegroundColor = ConsoleColor.DarkGreen; + Console.WriteLine($"[CDROM] setLoc {mm:x2}:{ss:x2}:{ff:x2} Loc: {seekLoc:x8}"); Console.ResetColor(); } - Span<byte> response = stackalloc byte[] { track.number, 1, DecToBcd(mm), DecToBcd(ss), DecToBcd(ff), DecToBcd(amm), DecToBcd(ass), DecToBcd(aff) }; - responseBuffer.EnqueueRange(response); - + responseBuffer.Enqueue(STAT); interruptQueue.Enqueue(0x3); } - private void lockUnlock() { - interruptQueue.Enqueue(0x5); - } - - private void videoCD() { //INT5(11h,40h) ;-Unused/invalid - responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x40 }); - - interruptQueue.Enqueue(0x5); - } - - private void setSession() { //broken - parameterBuffer.Clear(); - + private void Cmd_03_Play() { if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); interruptQueue.Enqueue(0x5); return; } + //If theres a trackN param it seeks and plays from the start location of it + int track = 0; + if (parameterBuffer.Count > 0 && parameterBuffer.Peek() != 0) { + track = BcdToDec(parameterBuffer.Dequeue()); + if (cd.isAudioCD()) { + readLoc = seekLoc = cd.tracks[track - 1].lbaStart; + } else { + readLoc = seekLoc = cd.tracks[track].lbaStart; + } + //else it plays from the previously seekLoc and seeks if not done (actually not checking if already seeked) + } else { + readLoc = seekLoc; + } - STAT = 0x42; - - responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); - - responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x2); - } + Console.WriteLine($"[CDROM] CDDA Play Triggered Track: {track} readLoc: {readLoc}"); - private void setFilter() { - filterFile = parameterBuffer.Dequeue(); - filterChannel = parameterBuffer.Dequeue(); + STAT = 0x82; + mode = Mode.Play; responseBuffer.Enqueue(STAT); interruptQueue.Enqueue(0x3); } - private void readS() { + private void Cmd_06_ReadN() { if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); interruptQueue.Enqueue(0x5); @@ -579,53 +543,17 @@ private void readS() { mode = Mode.Read; } - private void seekP() { - if (isLidOpen) { - responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); - return; - } - - readLoc = seekLoc; - STAT = 0x42; // seek - - mode = Mode.Seek; + private void Cmd_07_MotorOn() { + STAT = 0x2; responseBuffer.Enqueue(STAT); interruptQueue.Enqueue(0x3); - } - - - private void play() { - if (isLidOpen) { - responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); - return; - } - //If theres a trackN param it seeks and plays from the start location of it - int track = 0; - if (parameterBuffer.Count > 0 && parameterBuffer.Peek() != 0) { - track = BcdToDec(parameterBuffer.Dequeue()); - if (cd.isAudioCD()) { - readLoc = seekLoc = cd.tracks[track - 1].lbaStart; - } else { - readLoc = seekLoc = cd.tracks[track].lbaStart; - } - //else it plays from the previously seekLoc and seeks if not done (actually not checking if already seeked) - } else { - readLoc = seekLoc; - } - - Console.WriteLine($"[CDROM] CDDA Play Triggered Track: {track} readLoc: {readLoc}"); - - STAT = 0x82; - mode = Mode.Play; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.Enqueue(0x2); } - private void stop() { + private void Cmd_08_Stop() { STAT = 0x2; responseBuffer.Enqueue(STAT); interruptQueue.Enqueue(0x3); @@ -637,52 +565,18 @@ private void stop() { mode = Mode.Idle; } - private void getTD() { - if (isLidOpen) { - responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); - return; - } - - int track = BcdToDec(parameterBuffer.Dequeue()); - - if (track == 0) { //returns CD LBA / End of last track - (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(cd.getLBA()); - responseBuffer.EnqueueRange(stackalloc byte[] { STAT, DecToBcd(mm), DecToBcd(ss) }); - //if (cdDebug) - Console.WriteLine($"[CDROM] getTD Track: {track} STAT: {STAT:x2} {mm}:{ss}"); - } else { //returns Track Start - (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(cd.tracks[track - 1].lbaStart); - responseBuffer.EnqueueRange(stackalloc byte[] { STAT, DecToBcd(mm), DecToBcd(ss) }); - //if (cdDebug) - Console.WriteLine($"[CDROM] getTD Track: {track} STAT: {STAT:x2} {mm}:{ss}"); - } - - //Console.ReadLine(); - interruptQueue.Enqueue(0x3); - } - - private void getTN() { - if (isLidOpen) { - responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); - return; - } - //if (cdDebug) - Console.WriteLine($"[CDROM] getTN First Track: 1 (Hardcoded) - Last Track: {cd.tracks.Count}"); - //Console.ReadLine(); - responseBuffer.EnqueueRange(stackalloc byte[] { STAT, 1, DecToBcd((byte)cd.tracks.Count) }); + private void Cmd_09_Pause() { + responseBuffer.Enqueue(STAT); interruptQueue.Enqueue(0x3); - } - private void demute() { - mutedAudio = false; + STAT = 0x2; + mode = Mode.Idle; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.Enqueue(0x2); } - private void init() { + private void Cmd_0A_Init() { STAT = 0x2; responseBuffer.Enqueue(STAT); @@ -692,36 +586,29 @@ private void init() { interruptQueue.Enqueue(0x2); } - private void pause() { + private void Cmd_0B_Mute() { + mutedAudio = true; + responseBuffer.Enqueue(STAT); interruptQueue.Enqueue(0x3); + } - STAT = 0x2; - mode = Mode.Idle; + private void Cmd_0C_Demute() { + mutedAudio = false; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x2); + interruptQueue.Enqueue(0x3); } - private void readN() { - if (isLidOpen) { - responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); - return; - } - - readLoc = seekLoc; - - STAT = 0x2; - STAT |= 0x20; + private void Cmd_0D_SetFilter() { + filterFile = parameterBuffer.Dequeue(); + filterChannel = parameterBuffer.Dequeue(); responseBuffer.Enqueue(STAT); interruptQueue.Enqueue(0x3); - - mode = Mode.Read; } - private void setMode() { + private void Cmd_0E_SetMode() { //7 Speed(0 = Normal speed, 1 = Double speed) //6 XA - ADPCM(0 = Off, 1 = Send XA - ADPCM sectors to SPU Audio Input) //5 Sector Size(0 = 800h = DataOnly, 1 = 924h = WholeSectorExceptSyncBytes) @@ -747,110 +634,149 @@ private void setMode() { interruptQueue.Enqueue(0x3); } - private void seekL() { + private void Cmd_10_GetLocL() { + if (cdDebug) { + Console.WriteLine($"mm: {sectorHeader.mm} ss: {sectorHeader.ss} ff: {sectorHeader.ff} mode: {sectorHeader.mode}" + + $" file: {sectorSubHeader.file} channel: {sectorSubHeader.channel} subMode: {sectorSubHeader.subMode} codingInfo: {sectorSubHeader.codingInfo}"); + } + if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); interruptQueue.Enqueue(0x5); return; } - readLoc = seekLoc; - STAT = 0x42; // seek + Span<byte> response = stackalloc byte[] { + sectorHeader.mm, + sectorHeader.ss, + sectorHeader.ff, + sectorHeader.mode, + sectorSubHeader.file, + sectorSubHeader.channel, + sectorSubHeader.subMode, + sectorSubHeader.codingInfo + }; - mode = Mode.Seek; + responseBuffer.EnqueueRange(response); - responseBuffer.Enqueue(STAT); interruptQueue.Enqueue(0x3); } - private void setLoc() { + private void Cmd_11_GetLocP() { //SubQ missing... if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); interruptQueue.Enqueue(0x5); return; } - byte mm = parameterBuffer.Dequeue(); - byte ss = parameterBuffer.Dequeue(); - byte ff = parameterBuffer.Dequeue(); + Track track = cd.getTrackFromLoc(readLoc); + (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(readLoc - track.lbaStart); + (byte amm, byte ass, byte aff) = getMMSSFFfromLBA(readLoc); - //Console.WriteLine($"[CDROM] setLoc BCD {mm:x2}:{ss:x2}:{ff:x2}"); + if (cdDebug) { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"track: {track.number} index: {1} mm: {mm} ss: {ss}" + + $" ff: {ff} amm: {amm} ass: {ass} aff: {aff}"); + Console.WriteLine($"track: {track.number} index: {1} mm: {DecToBcd(mm)} ss: {DecToBcd(ss)}" + + $" ff: {DecToBcd(ff)} amm: {DecToBcd(amm)} ass: {DecToBcd(ass)} aff: {DecToBcd(aff)}"); + Console.ResetColor(); + } + Span<byte> response = stackalloc byte[] { track.number, 1, DecToBcd(mm), DecToBcd(ss), DecToBcd(ff), DecToBcd(amm), DecToBcd(ass), DecToBcd(aff) }; + responseBuffer.EnqueueRange(response); - int minute = BcdToDec(mm); - int second = BcdToDec(ss); - int sector = BcdToDec(ff); + interruptQueue.Enqueue(0x3); + } - //There are 75 sectors on a second - seekLoc = sector + (second * 75) + (minute * 60 * 75); + private void Cmd_12_SetSession() { //broken + parameterBuffer.Clear(); - if (seekLoc < 0) { - Console.WriteLine($"[CDROM] WARNING NEGATIVE setLOC {seekLoc:x8}"); - seekLoc = 0; + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; } - if (cdDebug) { - Console.ForegroundColor = ConsoleColor.DarkGreen; - Console.WriteLine($"[CDROM] setLoc {mm:x2}:{ss:x2}:{ff:x2} Loc: {seekLoc:x8}"); - Console.ResetColor(); - } + STAT = 0x42; responseBuffer.Enqueue(STAT); interruptQueue.Enqueue(0x3); + + responseBuffer.Enqueue(STAT); + interruptQueue.Enqueue(0x2); } - private void getID() { - //Door Open INT5(11h,80h) N/A + private void Cmd_13_GetTN() { if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); interruptQueue.Enqueue(0x5); return; } - - //No Disk INT3(stat) INT5(08h, 40h, 00h, 00h, 00h, 00h, 00h, 00h) - //STAT = 0x2; //0x40 seek - //responseBuffer.Enqueue(STAT); - //interruptQueue.Enqueue(0x3); - // - //responseBuffer.EnqueueRange(stackalloc byte[] { 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); - //interruptQueue.Enqueue(0x5); - - //Licensed: Mode2 INT3(stat) INT2(02h, 00h, 20h, 00h, 53h, 43h, 45h, 4xh) - STAT = 0x40; //0x40 seek - STAT |= 0x2; - responseBuffer.Enqueue(STAT); + //if (cdDebug) + Console.WriteLine($"[CDROM] getTN First Track: 1 (Hardcoded) - Last Track: {cd.tracks.Count}"); + //Console.ReadLine(); + responseBuffer.EnqueueRange(stackalloc byte[] { STAT, 1, DecToBcd((byte)cd.tracks.Count) }); interruptQueue.Enqueue(0x3); + } - // Audio Disk INT3(stat) INT5(0Ah,90h, 00h,00h, 00h,00h,00h,00h) - if (cd.isAudioCD()) { - Span<byte> audioCdResponse = stackalloc byte[] { 0x0A, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - responseBuffer.EnqueueRange(audioCdResponse); + private void Cmd_14_GetTD() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); interruptQueue.Enqueue(0x5); return; } - // Licensed: Mode2 INT3(stat) INT2(02h, 00h, 20h, 00h, 53h, 43h, 45h, 4xh) - Span<byte> gameResponse = stackalloc byte[] { 0x02, 0x00, 0x20, 0x00, 0x53, 0x43, 0x45, 0x41 }; //SCE | //A 0x41 (America) - I 0x49 (Japan) - E 0x45 (Europe) - responseBuffer.EnqueueRange(gameResponse); - interruptQueue.Enqueue(0x2); + int track = BcdToDec(parameterBuffer.Dequeue()); + + if (track == 0) { //returns CD LBA / End of last track + (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(cd.getLBA()); + responseBuffer.EnqueueRange(stackalloc byte[] { STAT, DecToBcd(mm), DecToBcd(ss) }); + //if (cdDebug) + Console.WriteLine($"[CDROM] getTD Track: {track} STAT: {STAT:x2} {mm}:{ss}"); + } else { //returns Track Start + (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(cd.tracks[track - 1].lbaStart); + responseBuffer.EnqueueRange(stackalloc byte[] { STAT, DecToBcd(mm), DecToBcd(ss) }); + //if (cdDebug) + Console.WriteLine($"[CDROM] getTD Track: {track} STAT: {STAT:x2} {mm}:{ss}"); + } + + //Console.ReadLine(); + interruptQueue.Enqueue(0x3); } - private void getStat() { - if (!isLidOpen) { - STAT = (byte)(STAT & (~0x18)); - STAT |= 0x2; + private void Cmd_15_SeekL() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; } + readLoc = seekLoc; + STAT = 0x42; // seek + + mode = Mode.Seek; + responseBuffer.Enqueue(STAT); interruptQueue.Enqueue(0x3); } - private void UnimplementedCDCommand(uint value) { - Console.WriteLine($"[CDROM] Unimplemented CD Command {value}"); - //Console.ReadLine(); + private void Cmd_16_SeekP() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } + + readLoc = seekLoc; + STAT = 0x42; // seek + + mode = Mode.Seek; + + responseBuffer.Enqueue(STAT); + interruptQueue.Enqueue(0x3); } - private void test() { + private void Cmd_19_Test() { uint command = parameterBuffer.Dequeue(); responseBuffer.Clear(); //we need to clear the delay on response to get the actual 0 0 to bypass antimodchip protection switch (command) { @@ -883,6 +809,87 @@ private void test() { } } + private void Cmd_1A_GetID() { + //Door Open INT5(11h,80h) N/A + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } + + //No Disk INT3(stat) INT5(08h, 40h, 00h, 00h, 00h, 00h, 00h, 00h) + //STAT = 0x2; //0x40 seek + //responseBuffer.Enqueue(STAT); + //interruptQueue.Enqueue(0x3); + // + //responseBuffer.EnqueueRange(stackalloc byte[] { 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + //interruptQueue.Enqueue(0x5); + + //Licensed: Mode2 INT3(stat) INT2(02h, 00h, 20h, 00h, 53h, 43h, 45h, 4xh) + STAT = 0x40; //0x40 seek + STAT |= 0x2; + responseBuffer.Enqueue(STAT); + interruptQueue.Enqueue(0x3); + + // Audio Disk INT3(stat) INT5(0Ah,90h, 00h,00h, 00h,00h,00h,00h) + if (cd.isAudioCD()) { + Span<byte> audioCdResponse = stackalloc byte[] { 0x0A, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + responseBuffer.EnqueueRange(audioCdResponse); + interruptQueue.Enqueue(0x5); + return; + } + + // Licensed: Mode2 INT3(stat) INT2(02h, 00h, 20h, 00h, 53h, 43h, 45h, 4xh) + Span<byte> gameResponse = stackalloc byte[] { 0x02, 0x00, 0x20, 0x00, 0x53, 0x43, 0x45, 0x41 }; //SCE | //A 0x41 (America) - I 0x49 (Japan) - E 0x45 (Europe) + responseBuffer.EnqueueRange(gameResponse); + interruptQueue.Enqueue(0x2); + } + + private void Cmd_1B_ReadS() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } + + readLoc = seekLoc; + + STAT = 0x2; + STAT |= 0x20; + + responseBuffer.Enqueue(STAT); + interruptQueue.Enqueue(0x3); + + mode = Mode.Read; + } + + private void Cmd_1E_ReadTOC() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.Enqueue(0x5); + return; + } + + mode = Mode.TOC; + responseBuffer.Enqueue(STAT); + interruptQueue.Enqueue(0x3); + } + + private void Cmd_1F_VideoCD() { //INT5(11h,40h) ;-Unused/invalid + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x40 }); + + interruptQueue.Enqueue(0x5); + } + + private void Cmd_5x_lockUnlock() { + interruptQueue.Enqueue(0x5); + } + + private void UnimplementedCDCommand(uint value) { + Console.WriteLine($"[CDROM] Unimplemented CD Command {value}"); + Console.ReadLine(); + } + private byte STATUS() { //1F801800h - Index/Status Register (Bit0-1 R/W) (Bit2-7 Read Only) //0 - 1 Index Port 1F801801h - 1F801803h index(0..3 = Index0..Index3) (R / W) From 31646d8762e5d87f4939873305972df52f9794d1 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Fri, 27 May 2022 21:55:55 +0200 Subject: [PATCH 26/72] ADPCM: Handle shift 9..12 quirk --- ProjectPSX/Devices/CDROM/XaAdpcm.cs | 4 +++- ProjectPSX/Devices/SPU/Voice.cs | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ProjectPSX/Devices/CDROM/XaAdpcm.cs b/ProjectPSX/Devices/CDROM/XaAdpcm.cs index cffefb2..2e84e08 100644 --- a/ProjectPSX/Devices/CDROM/XaAdpcm.cs +++ b/ProjectPSX/Devices/CDROM/XaAdpcm.cs @@ -149,7 +149,9 @@ private static short zigZagInterpolate(int resamplePointer, int table, int chann } public static short[] decodeNibbles(byte[] xaapdcm, int position, int blk, int nibble, ref short old, ref short older) { - int shift = 12 - (xaapdcm[position + 4 + blk * 2 + nibble] & 0x0F); + int headerShift = xaapdcm[position + 4 + blk * 2 + nibble] & 0x0F; + if (headerShift > 12) headerShift = 9; + int shift = 12 - headerShift; int filter = (xaapdcm[position + 4 + blk * 2 + nibble] & 0x30) >> 4; int f0 = positiveXaAdpcmTable[filter]; diff --git a/ProjectPSX/Devices/SPU/Voice.cs b/ProjectPSX/Devices/SPU/Voice.cs index 7d5107d..0941d50 100644 --- a/ProjectPSX/Devices/SPU/Voice.cs +++ b/ProjectPSX/Devices/SPU/Voice.cs @@ -108,9 +108,12 @@ internal void decodeSamples(byte[] ram, ushort ramIrqAddress) { Array.Copy(ram, currentAddress * 8, spuAdpcm, 0, 16); //ramIrqAddress is >> 8 so we only need to check for currentAddress and + 1 - readRamIrq |= currentAddress == ramIrqAddress || currentAddress + 1 == ramIrqAddress; + readRamIrq |= currentAddress == ramIrqAddress || currentAddress + 1 == ramIrqAddress; + + int headerShift = spuAdpcm[0] & 0x0F; + if (headerShift > 12) headerShift = 9; + int shift = 12 - headerShift; - int shift = 12 - (spuAdpcm[0] & 0x0F); int filter = (spuAdpcm[0] & 0x70) >> 4; //filter on SPU adpcm is 0-4 vs XA wich is 0-3 if (filter > 4) filter = 4; //Crash Bandicoot sets this to 7 at the end of the first level and overflows the filter From ed2b7c240eac67f6445b6e5dd3aa469dcd9c56cf Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sat, 28 May 2022 12:09:32 +0200 Subject: [PATCH 27/72] XADPCM: Handle 18900 resampling It fixes Tony Hawks intro MDEC audio, and Dino Crisis voices being 2x speed. --- ProjectPSX/Devices/CDROM/Sector.cs | 7 ++++--- ProjectPSX/Devices/CDROM/XaAdpcm.cs | 7 +++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ProjectPSX/Devices/CDROM/Sector.cs b/ProjectPSX/Devices/CDROM/Sector.cs index cc5f9bf..843a242 100644 --- a/ProjectPSX/Devices/CDROM/Sector.cs +++ b/ProjectPSX/Devices/CDROM/Sector.cs @@ -8,9 +8,10 @@ class Sector { // Standard size for a raw sector / CDDA public const int RAW_BUFFER = 2352; - // This sector data is already pre decoded and resampled so we need a bigger buffer (RAW_BUFFER * 4) - // and on the case of mono even a bigger one, as samples are mirrored to L/R as our output is allways stereo - public const int XA_BUFFER = RAW_BUFFER * 8; + // Only for the SPU: It represents a sector of data already pre decoded AND resampled so we need a bigger buffer (RAW_BUFFER * 4) + // and on the case of mono even a bigger one, as samples are mirrored to L/R as our output is allways stereo (that would be * 8) + // but on the special case of 18900 resampling we need even a bigger one... so go * 16 + public const int XA_BUFFER = RAW_BUFFER * 16; private byte[] sectorBuffer; diff --git a/ProjectPSX/Devices/CDROM/XaAdpcm.cs b/ProjectPSX/Devices/CDROM/XaAdpcm.cs index 2e84e08..8a4f8ab 100644 --- a/ProjectPSX/Devices/CDROM/XaAdpcm.cs +++ b/ProjectPSX/Devices/CDROM/XaAdpcm.cs @@ -122,7 +122,6 @@ private static List<short> resampleTo44100Hz(List<short> samples, bool isStereo, List<short> resamples = isStereo ? stereoResamples : monoResamples; resamples.Clear(); - //todo handle 18900hz for (int i = 0; i < samples.Count; i++) { resampleRingBuffer[channel][resamplePointer++ & 0x1F] = samples[i]; @@ -131,7 +130,11 @@ private static List<short> resampleTo44100Hz(List<short> samples, bool isStereo, if (sixStep == 0) { sixStep = 6; for (int table = 0; table < 7; table++) { - resamples.Add(zigZagInterpolate(resamplePointer, table, channel)); + short sample = zigZagInterpolate(resamplePointer, table, channel); + resamples.Add(sample); + if(is18900hz) { + resamples.Add(sample); + } } } } From 5e8438a7c17925bf9b43ece367dd5b9dfac59869 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sat, 25 Jun 2022 16:37:09 +0200 Subject: [PATCH 28/72] SPU: Support Reverb --- ProjectPSX/Devices/SPU/SPU.cs | 469 ++++++++++++++++++++++++++++---- ProjectPSX/Devices/SPU/Voice.cs | 6 +- 2 files changed, 425 insertions(+), 50 deletions(-) diff --git a/ProjectPSX/Devices/SPU/SPU.cs b/ProjectPSX/Devices/SPU/SPU.cs index 339fc33..93af4d8 100644 --- a/ProjectPSX/Devices/SPU/SPU.cs +++ b/ProjectPSX/Devices/SPU/SPU.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using ProjectPSX.Devices.CdRom; using ProjectPSX.Devices.Spu; @@ -8,11 +8,8 @@ namespace ProjectPSX.Devices { public class SPU { // Todo: - // Spu Enable/Disable (koff voices? Ints?) // lr sweep envelope - // reverb // clean up queue/list dequeues enqueues and casts - // ... private static short[] gaussTable = new short[] { -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, @@ -86,13 +83,13 @@ public class SPU { private Sector cdBuffer = new Sector(Sector.XA_BUFFER); - private byte[] ram = new byte[512 * 1024]; + private unsafe byte* ram = (byte*)Marshal.AllocHGlobal(512 * 1024); private Voice[] voices = new Voice[24]; - private ushort mainVolumeLeft; - private ushort mainVolumeRight; - private ushort reverbOutputLeft; - private ushort reverbOutputRight; + private short mainVolumeLeft; + private short mainVolumeRight; + private short reverbOutputVolumeLeft; + private short reverbOutputVolumeRight; private uint keyOn; private uint keyOff; @@ -103,7 +100,8 @@ public class SPU { private ushort unknownA0; - private ushort ramReverbStartAddress; + private uint ramReverbStartAddress; + private uint ramReverbInternalAddress; private ushort ramIrqAddress; private ushort ramDataTransferAddress; private uint ramDataTransferAddressInternal; @@ -122,6 +120,40 @@ public class SPU { private int captureBufferPos; + //Reverb Area + private uint dAPF1; // Reverb APF Offset 1 + private uint dAPF2; // Reverb APF Offset 2 + private short vIIR; // Reverb Reflection Volume 1 + private short vCOMB1; // Reverb Comb Volume 1 + private short vCOMB2; // Reverb Comb Volume 2 + private short vCOMB3; // Reverb Comb Volume 3 + private short vCOMB4; // Reverb Comb Volume 4 + private short vWALL; // Reverb Reflection Volume 2 + private short vAPF1; // Reverb APF Volume 1 + private short vAPF2; // Reverb APF Volume 2 + private uint mLSAME; // Reverb Same Side Reflection Address 1 Left + private uint mRSAME; // Reverb Same Side Reflection Address 1 Right + private uint mLCOMB1; // Reverb Comb Address 1 Left + private uint mLCOMB2; // Reverb Comb Address 2 Left + private uint mRCOMB1; // Reverb Comb Address 1 Right + private uint mRCOMB2; // Reverb Comb Address 2 Right + private uint dLSAME; // Reverb Same Side Reflection Address 2 Left + private uint dRSAME; // Reverb Same Side Reflection Address 2 Right + private uint mLDIFF; // Reverb Different Side Reflection Address 1 Left + private uint mRDIFF; // Reverb Different Side Reflection Address 1 Right + private uint mLCOMB3; // Reverb Comb Address 3 Left + private uint mRCOMB3; // Reverb Comb Address 3 Right + private uint mLCOMB4; // Reverb Comb Address 4 Left + private uint mRCOMB4; // Reverb Comb Address 4 Right + private uint dLDIFF; // Reverb Different Side Reflection Address 2 Left + private uint dRDIFF; // Reverb Different Side Reflection Address 2 Right + private uint mLAPF1; // Reverb APF Address 1 Left + private uint mRAPF1; // Reverb APF Address 1 Right + private uint mLAPF2; // Reverb APF Address 2 Left + private uint mRAPF2; // Reverb APF Address 2 Right + private short vLIN; // Reverb Input Volume Left + private short vRIN; // Reverb Input Volume Right + private struct Control { public ushort register; public bool spuEnabled => ((register >> 15) & 0x1) != 0; @@ -144,11 +176,12 @@ private struct Status { public bool dataTransferBusyFlag => ((register >> 10) & 0x1) != 0; public bool dataTransferDmaReadRequest => ((register >> 9) & 0x1) != 0; public bool dataTransferDmaWriteRequest => ((register >> 8) & 0x1) != 0; - // 7 Data Transfer DMA Read/Write Request ;seems to be same as SPUCNT.Bit5 todo + // 7 Data Transfer DMA Read/Write Request ;seems to be same as SPUCNT.Bit5 public bool irq9Flag { get { return ((register >> 6) & 0x1) != 0; } set { register = value ? (ushort)(register | (1 << 6)) : (ushort)(register & ~(1 << 6)); } } + // 0..5 Mode same as SPUCNT 0..5 } Status status; @@ -183,19 +216,19 @@ internal void write(uint addr, ushort value) { break; case 0x1F801D80: - mainVolumeLeft = value; + mainVolumeLeft = (short)value; break; case 0x1F801D82: - mainVolumeRight = value; + mainVolumeRight = (short)value; break; case 0x1F801D84: - reverbOutputLeft = value; + reverbOutputVolumeLeft = (short)value; break; case 0x1F801D86: - reverbOutputRight = value; + reverbOutputVolumeRight = (short)value; break; case 0x1F801D88: @@ -252,7 +285,8 @@ internal void write(uint addr, ushort value) { break; case 0x1F801DA2: - ramReverbStartAddress = value; + ramReverbStartAddress = (uint)(value << 3); + ramReverbInternalAddress = (uint)(value << 3); break; case 0x1F801DA4: @@ -267,8 +301,8 @@ internal void write(uint addr, ushort value) { case 0x1F801DA8: //Console.WriteLine($"[SPU] Manual DMA Write {ramDataTransferAddressInternal:x8} {value:x4}"); ramDataTransferFifo = value; - ram[ramDataTransferAddressInternal++] = (byte)value; - ram[ramDataTransferAddressInternal++] = (byte)(value >> 8); + writeRam(ramDataTransferAddressInternal, value); + ramDataTransferAddressInternal += 2; break; case 0x1F801DAA: @@ -329,6 +363,140 @@ internal void write(uint addr, ushort value) { case 0x1F801DBE: unknownBC = (unknownBC & 0xFFFF) | (uint)(value << 16); break; + + // SPU Reverb Configuration Area + case 0x1F801DC0: + dAPF1 = (uint)(value << 3); + break; + + case 0x1F801DC2: + dAPF2 = (uint)(value << 3); + break; + + case 0x1F801DC4: + vIIR = (short)value; + break; + + case 0x1F801DC6: + vCOMB1 = (short)value; + break; + + case 0x1F801DC8: + vCOMB2 = (short)value; + break; + + case 0x1F801DCA: + vCOMB3 = (short)value; + break; + + case 0x1F801DCC: + vCOMB4 = (short)value; + break; + + case 0x1F801DCE: + vWALL = (short)value; + break; + + case 0x1F801DD0: + vAPF1 = (short)value; + break; + + case 0x1F801DD2: + vAPF2 = (short)value; + break; + + case 0x1F801DD4: + mLSAME = (uint)(value << 3); + break; + + case 0x1F801DD6: + mRSAME = (uint)(value << 3); + break; + + case 0x1F801DD8: + mLCOMB1 = (uint)(value << 3); + break; + + case 0x1F801DDA: + mRCOMB1 = (uint)(value << 3); + break; + + case 0x1F801DDC: + mLCOMB2 = (uint)(value << 3); + break; + + case 0x1F801DDE: + mRCOMB2 = (uint)(value << 3); + break; + + case 0x1F801DE0: + dLSAME = (uint)(value << 3); + break; + + case 0x1F801DE2: + dRSAME = (uint)(value << 3); + break; + + case 0x1F801DE4: + mLDIFF = (uint)(value << 3); + break; + + case 0x1F801DE6: + mRDIFF = (uint)(value << 3); + break; + + case 0x1F801DE8: + mLCOMB3 = (uint)(value << 3); + break; + + case 0x1F801DEA: + mRCOMB3 = (uint)(value << 3); + break; + + case 0x1F801DEC: + mLCOMB4 = (uint)(value << 3); + break; + + case 0x1F801DEE: + mRCOMB4 = (uint)(value << 3); + break; + + case 0x1F801DF0: + dLDIFF = (uint)(value << 3); + break; + + case 0x1F801DF2: + dRDIFF = (uint)(value << 3); + break; + + case 0x1F801DF4: + mLAPF1 = (uint)(value << 3); + break; + + case 0x1F801DF6: + mRAPF1 = (uint)(value << 3); + break; + + case 0x1F801DF8: + mLAPF2 = (uint)(value << 3); + break; + + case 0x1F801DFA: + mRAPF2 = (uint)(value << 3); + break; + + case 0x1F801DFC: + vLIN = (short)value; + break; + + case 0x1F801DFE: + vRIN = (short)value; + break; + + default: + Console.WriteLine($"[SPU] Warning write:{addr:x8} value:{value:x8}"); + writeRam(addr, value); + break; } } @@ -339,29 +507,29 @@ internal ushort load(uint addr) { uint index = ((addr & 0xFF0) >> 4) - 0xC0; - switch (addr & 0xF) { - case 0x0: return voices[index].volumeLeft.register; - case 0x2: return voices[index].volumeRight.register; - case 0x4: return voices[index].pitch; - case 0x6: return voices[index].startAddress; - case 0x8: return voices[index].adsr.lo; - case 0xA: return voices[index].adsr.hi; - case 0xC: return voices[index].adsrVolume; - case 0xE: return voices[index].adpcmRepeatAddress; - } - return 0xFFFF; + return (addr & 0xF) switch { + 0x0 => voices[index].volumeLeft.register, + 0x2 => voices[index].volumeRight.register, + 0x4 => voices[index].pitch, + 0x6 => voices[index].startAddress, + 0x8 => voices[index].adsr.lo, + 0xA => voices[index].adsr.hi, + 0xC => voices[index].adsrVolume, + 0xE => voices[index].adpcmRepeatAddress, + _ => 0xFFFF, + }; case 0x1F801D80: - return mainVolumeLeft; + return (ushort)mainVolumeLeft; case 0x1F801D82: - return mainVolumeRight; + return (ushort)mainVolumeRight; case 0x1F801D84: - return reverbOutputLeft; + return (ushort)reverbOutputVolumeLeft; case 0x1F801D86: - return reverbOutputRight; + return (ushort)reverbOutputVolumeRight; case 0x1F801D88: return (ushort)keyOn; @@ -403,7 +571,7 @@ internal ushort load(uint addr) { return unknownA0; case 0x1F801DA2: - return ramReverbStartAddress; + return (ushort)(ramReverbStartAddress >> 3); case 0x1F801DA4: return ramIrqAddress; @@ -447,8 +615,105 @@ internal ushort load(uint addr) { case 0x1F801DBE: return (ushort)(unknownBC >> 16); + // SPU Reverb Configuration Area + case 0x1F801DC0: + return (ushort)(dAPF1 >> 3); + + case 0x1F801DC2: + return (ushort)(dAPF2 >> 3); + + case 0x1F801DC4: + return (ushort)vIIR; + + case 0x1F801DC6: + return (ushort)vCOMB1; + + case 0x1F801DC8: + return (ushort)vCOMB2; + + case 0x1F801DCA: + return (ushort)vCOMB3; + + case 0x1F801DCC: + return (ushort)vCOMB4; + + case 0x1F801DCE: + return (ushort)vWALL; + + case 0x1F801DD0: + return (ushort)vAPF1; + + case 0x1F801DD2: + return (ushort)vAPF2; + + case 0x1F801DD4: + return (ushort)(mLSAME >> 3); + + case 0x1F801DD6: + return (ushort)(mRSAME >> 3); + + case 0x1F801DD8: + return (ushort)(mLCOMB1 >> 3); + + case 0x1F801DDA: + return (ushort)(mRCOMB1 >> 3); + + case 0x1F801DDC: + return (ushort)(mLCOMB2 >> 3); + + case 0x1F801DDE: + return (ushort)(mRCOMB2 >> 3); + + case 0x1F801DE0: + return (ushort)(dLSAME >> 3); + + case 0x1F801DE2: + return (ushort)(dRSAME >> 3); + + case 0x1F801DE4: + return (ushort)(mLDIFF >> 3); + + case 0x1F801DE6: + return (ushort)(mRDIFF >> 3); + + case 0x1F801DE8: + return (ushort)(mLCOMB3 >> 3); + + case 0x1F801DEA: + return (ushort)(mRCOMB3 >> 3); + + case 0x1F801DEC: + return (ushort)(mLCOMB4 >> 3); + + case 0x1F801DEE: + return (ushort)(mRCOMB4 >> 3); + + case 0x1F801DF0: + return (ushort)(dLDIFF >> 3); + + case 0x1F801DF2: + return (ushort)(dRDIFF >> 3); + + case 0x1F801DF4: + return (ushort)(mLAPF1 >> 3); + + case 0x1F801DF6: + return (ushort)(mRAPF1 >> 3); + + case 0x1F801DF8: + return (ushort)(mLAPF2 >> 3); + + case 0x1F801DFA: + return (ushort)(mRAPF2 >> 3); + + case 0x1F801DFC: + return (ushort)vLIN; + + case 0x1F801DFE: + return (ushort)vRIN; + default: - return 0xFFFF; + return (ushort)loadRam(addr); } } @@ -457,7 +722,8 @@ internal void pushCdBufferSamples(byte[] decodedXaAdpcm) { } private int counter = 0; - private int CYCLES_PER_SAMPLE = 0x300; //33868800 / 44100hz + private const int CYCLES_PER_SAMPLE = 0x300; //33868800 / 44100hz + private int reverbCounter = 0; public bool tick(int cycles) { bool edgeTrigger = false; counter += cycles; @@ -470,6 +736,9 @@ public bool tick(int cycles) { int sumLeft = 0; int sumRight = 0; + int sumLeftReverb = 0; + int sumRightReverb = 0; + uint edgeKeyOn = keyOn; uint edgeKeyOff = keyOff; keyOn = 0; @@ -516,6 +785,11 @@ public bool tick(int cycles) { //Sum each voice sample sumLeft += (sample * v.processVolume(v.volumeLeft)) >> 15; sumRight += (sample * v.processVolume(v.volumeRight)) >> 15; + + if((channelReverbMode & (0x1 << i)) != 0) { + sumLeftReverb += (sample * v.processVolume(v.volumeLeft)) >> 15; + sumRightReverb += (sample * v.processVolume(v.volumeRight)) >> 15; + } } if (!control.spuUnmuted) { //todo merge this on the for voice loop @@ -538,8 +812,23 @@ public bool tick(int cycles) { sumLeft += cdL; sumRight += cdR; + + if(control.cdAudioReverb) { + sumLeftReverb += cdL; + sumRightReverb += cdR; + } } + if (reverbCounter == 0) { + var (reverbL, reverbR) = processReverb(sumLeftReverb, sumRightReverb); + + sumLeft += reverbL; + sumRight += reverbR; + } + + // reverb is on a 22050hz clock + reverbCounter = (reverbCounter + 1) & 0x1; + //Write to capture buffers and check ram irq edgeTrigger |= handleCaptureBuffer(0 * 1024 + captureBufferPos, cdL); edgeTrigger |= handleCaptureBuffer(1 * 1024 + captureBufferPos, cdR); @@ -569,9 +858,7 @@ public bool tick(int cycles) { } private bool handleCaptureBuffer(int address, short sample) { - ram[address] = (byte)(sample & 0xFF); - ram[address + 1] = (byte)((sample >> 8) & 0xFF); - + writeRam((uint)address, sample); return address >> 3 == ramIrqAddress; } @@ -594,7 +881,7 @@ private void tickNoiseGenerator() { if (noiseTimer < 0) noiseTimer += 0x20000 >> noiseShift; } - public short sampleVoice(int v) { + public unsafe short sampleVoice(int v) { Voice voice = voices[v]; //Decode samples if its empty / next block @@ -658,30 +945,92 @@ public short sampleVoice(int v) { return (short)interpolated; } + public (short, short) processReverb(int lInput, int rInput) { + // Input from mixer + int Lin = (vLIN * lInput) >> 15; + int Rin = (vRIN * rInput) >> 15; + + // Same side reflection LtoL and RtoR + short mlSame = saturateSample(Lin + ((loadReverb(dLSAME) * vWALL) >> 15) - ((loadReverb(mLSAME - 2) * vIIR) >> 15) + loadReverb(mLSAME - 2)); + short mrSame = saturateSample(Rin + ((loadReverb(dRSAME) * vWALL) >> 15) - ((loadReverb(mRSAME - 2) * vIIR) >> 15) + loadReverb(mRSAME - 2)); + writeReverb(mLSAME, mlSame); + writeReverb(mRSAME, mrSame); + + // Different side reflection LtoR and RtoL + short mldiff = saturateSample(Lin + ((loadReverb(dRDIFF) * vWALL) >> 15) - ((loadReverb(mLDIFF - 2) * vIIR) >> 15) + loadReverb(mLDIFF - 2)); + short mrdiff = saturateSample(Rin + ((loadReverb(dLDIFF) * vWALL) >> 15) - ((loadReverb(mRDIFF - 2) * vIIR) >> 15) + loadReverb(mRDIFF - 2)); + writeReverb(mLDIFF, mldiff); + writeReverb(mRDIFF, mrdiff); + + // Early echo (comb filter with input from buffer) + short l = saturateSample((vCOMB1 * loadReverb(mLCOMB1) >> 15) + (vCOMB2 * loadReverb(mLCOMB2) >> 15) + (vCOMB3 * loadReverb(mLCOMB3) >> 15) + (vCOMB4 * loadReverb(mLCOMB4) >> 15)); + short r = saturateSample((vCOMB1 * loadReverb(mRCOMB1) >> 15) + (vCOMB2 * loadReverb(mRCOMB2) >> 15) + (vCOMB3 * loadReverb(mRCOMB3) >> 15) + (vCOMB4 * loadReverb(mRCOMB4) >> 15)); + + // Late reverb APF1 (All pass filter 1 with input from COMB) + l = saturateSample(l - saturateSample((vAPF1 * loadReverb(mLAPF1 - dAPF1)) >> 15)); + r = saturateSample(r - saturateSample((vAPF1 * loadReverb(mRAPF1 - dAPF1)) >> 15)); + + writeReverb(mLAPF1, l); + writeReverb(mRAPF1, r); + + l = saturateSample((l * vAPF1 >> 15) + loadReverb(mLAPF1 - dAPF1)); + r = saturateSample((r * vAPF1 >> 15) + loadReverb(mRAPF1 - dAPF1)); + + // Late reverb APF2 (All pass filter 2 with input from APF1) + l = saturateSample(l - saturateSample((vAPF2 * loadReverb(mLAPF2 - dAPF2)) >> 15)); + r = saturateSample(r - saturateSample((vAPF2 * loadReverb(mRAPF2 - dAPF2)) >> 15)); + + writeReverb(mLAPF2, l); + writeReverb(mRAPF2, r); + + l = saturateSample((l * vAPF2 >> 15) + loadReverb(mLAPF2 - dAPF2)); + r = saturateSample((r * vAPF2 >> 15) + loadReverb(mRAPF2 - dAPF2)); + + // Output to mixer (output volume multiplied with input from APF2) + l = saturateSample(l * reverbOutputVolumeLeft >> 15); + r = saturateSample(r * reverbOutputVolumeRight >> 15); + + // Saturate address + ramReverbInternalAddress = Math.Max(ramReverbStartAddress, (ramReverbInternalAddress + 2) & 0x7_FFFE); + + return (l, r); + } + + public short saturateSample(int sample) { + if(sample < -0x8000) { + return -0x8000; + } + + if(sample > 0x7FFF) { + return 0x7FFF; + } + + return (short)sample; + } + - public Span<uint> processDmaLoad(int size) { //todo trigger interrupt - int dmaLength = size * 4; - Span<byte> dma = ram.AsSpan().Slice((int)ramDataTransferAddressInternal, dmaLength); + public unsafe Span<uint> processDmaLoad(int size) { //todo trigger interrupt + Span<byte> dma = new Span<byte>(ram, 1024*512).Slice((int)ramDataTransferAddressInternal, size * 4); //ramDataTransferAddressInternal and ramIrqAddress already are >> 3 //so check if it's in the size range and trigger int - if (ramIrqAddress > ramDataTransferAddressInternal && ramIrqAddress < ramDataTransferAddressInternal + dmaLength) { + if (ramIrqAddress > ramDataTransferAddressInternal && ramIrqAddress < ramDataTransferAddressInternal + (size * 4)) { interruptController.set(Interrupt.SPU); } - ramDataTransferAddressInternal = (uint)(ramDataTransferAddressInternal + dmaLength); + ramDataTransferAddressInternal = (uint)(ramDataTransferAddressInternal + (size * 4)); return MemoryMarshal.Cast<byte, uint>(dma); } - public void processDmaWrite(Span<uint> dma) { //todo trigger interrupt + public unsafe void processDmaWrite(Span<uint> dma) { //todo trigger interrupt //Tekken 3 and FF8 overflows SPU Ram int size = dma.Length * 4; int destAddress = (int)ramDataTransferAddressInternal + size - 1; Span<byte> dmaSpan = MemoryMarshal.Cast<uint, byte>(dma); - Span<byte> ramStartSpan = ram.AsSpan(); + Span<byte> ramStartSpan = new Span<byte>(ram, 1024 * 512); Span<byte> ramDestSpan = ramStartSpan.Slice((int)ramDataTransferAddressInternal); if (ramIrqAddress > ramDataTransferAddressInternal && ramIrqAddress < ramDataTransferAddressInternal + size) { @@ -703,5 +1052,31 @@ public void processDmaWrite(Span<uint> dma) { //todo trigger interrupt ramDataTransferAddressInternal = (uint)(ramDataTransferAddressInternal + size); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void writeRam<T>(uint addr, T value) where T : unmanaged { + *(T*)(ram + addr) = value; + } + + private unsafe short loadRam(uint addr) { + //Console.WriteLine($"loadSPURam from addr {(addr & 0x3_FFFF):x8}"); + return *(short*)(ram + (addr & 0x7_FFFF)); + } + + private unsafe void writeReverb(uint addr, short value) { + if (!control.reverbMasterEnabled) return; + + uint relative = (addr + ramReverbInternalAddress - ramReverbStartAddress) % (0x8_0000 - ramReverbStartAddress); + uint wrapped = (ramReverbStartAddress + relative) & 0x7_FFFE; + + *(short*)(ram + wrapped) = value; + } + + private unsafe short loadReverb(uint addr) { + uint relative = (addr + ramReverbInternalAddress - ramReverbStartAddress) % (0x8_0000 - ramReverbStartAddress); + uint wrapped = (ramReverbStartAddress + relative) & 0x7_FFFE; + + return *(short*)(ram + wrapped); + } + } } diff --git a/ProjectPSX/Devices/SPU/Voice.cs b/ProjectPSX/Devices/SPU/Voice.cs index 0941d50..e07bef2 100644 --- a/ProjectPSX/Devices/SPU/Voice.cs +++ b/ProjectPSX/Devices/SPU/Voice.cs @@ -98,14 +98,14 @@ public enum Phase { public byte[] spuAdpcm = new byte[16]; public short[] decodedSamples = new short[31]; //28 samples from current block + 3 to make room for interpolation - internal void decodeSamples(byte[] ram, ushort ramIrqAddress) { + internal unsafe void decodeSamples(byte* ram, ushort ramIrqAddress) { //save the last 3 samples from the last decoded block //this are needed for interpolation in case the voice.counter.currentSampleIndex is 0 1 or 2 decodedSamples[2] = decodedSamples[decodedSamples.Length - 1]; decodedSamples[1] = decodedSamples[decodedSamples.Length - 2]; decodedSamples[0] = decodedSamples[decodedSamples.Length - 3]; - Array.Copy(ram, currentAddress * 8, spuAdpcm, 0, 16); + new Span<byte>(ram, 1024 * 512).Slice(currentAddress * 8, 16).CopyTo(spuAdpcm); //ramIrqAddress is >> 8 so we only need to check for currentAddress and + 1 readRamIrq |= currentAddress == ramIrqAddress || currentAddress + 1 == ramIrqAddress; @@ -147,7 +147,7 @@ internal short processVolume(Volume volume) { if (!volume.isSweepMode) { return volume.fixedVolume; } else { - return 0; //todo handle sweep mode volume envelope + return 0x7FFF; //todo handle sweep mode volume envelope } } From d4a4c422d9883b6e10d412d3c1a3c2fad0ca4eb7 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sat, 25 Jun 2022 16:40:39 +0200 Subject: [PATCH 29/72] OpenTK: Update to 4.7.4 --- ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj index 27e7ab3..1cdf697 100644 --- a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj +++ b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj @@ -15,7 +15,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="OpenTK" Version="4.7.2" /> + <PackageReference Include="OpenTK" Version="4.7.4" /> </ItemGroup> <ItemGroup> From 72aeb4fcd908eafd99e5cf949e1d7827831fbbad Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Mon, 1 Aug 2022 22:30:06 +0200 Subject: [PATCH 30/72] Winform: Update to Naudio 2.1.0 --- ProjectPSX.WinForms/ProjectPSX.WinForms.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj index 115ca71..9010221 100644 --- a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj +++ b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj @@ -12,7 +12,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="NAudio.WinMM" Version="2.0.1" /> + <PackageReference Include="NAudio.WinMM" Version="2.1.0" /> </ItemGroup> <ItemGroup> From f71eec9494345d19a3bb8f60f4f33be736942639 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 28 Aug 2022 17:42:12 +0200 Subject: [PATCH 31/72] Devices: Add Exp2 --- ProjectPSX/Core/BUS.cs | 16 ++++++++------- ProjectPSX/Core/ProjectPSX.cs | 5 ++++- ProjectPSX/Devices/Expansion/Exp2.cs | 29 ++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 ProjectPSX/Devices/Expansion/Exp2.cs diff --git a/ProjectPSX/Core/BUS.cs b/ProjectPSX/Core/BUS.cs index 09ac4cb..1ff2c0c 100644 --- a/ProjectPSX/Core/BUS.cs +++ b/ProjectPSX/Core/BUS.cs @@ -1,4 +1,5 @@ using ProjectPSX.Devices; +using ProjectPSX.Devices.Expansion; using System; using System.IO; using System.Runtime.CompilerServices; @@ -29,12 +30,13 @@ public class BUS { private JOYPAD joypad; private MDEC mdec; private SPU spu; + private Exp2 exp2; //temporary hardcoded bios/ex1 - private static string bios = "./SCPH1001.BIN"; + private static string bios = "./SCPH1001.BIN"; //SCPH1001 //openbios private static string ex1 = "./caetlaEXP.BIN"; - public BUS(GPU gpu, CDROM cdrom, SPU spu, JOYPAD joypad, TIMERS timers, MDEC mdec, InterruptController interruptController) { + public BUS(GPU gpu, CDROM cdrom, SPU spu, JOYPAD joypad, TIMERS timers, MDEC mdec, InterruptController interruptController, Exp2 exp2) { dma = new DMA(this); this.gpu = gpu; this.cdrom = cdrom; @@ -43,6 +45,7 @@ public BUS(GPU gpu, CDROM cdrom, SPU spu, JOYPAD joypad, TIMERS timers, MDEC mde this.spu = spu; this.joypad = joypad; this.interruptController = interruptController; + this.exp2 = exp2; } public unsafe uint load32(uint address) { @@ -82,8 +85,7 @@ public unsafe uint load32(uint address) { } else if (addr < 0x1F80_2000) { return spu.load(addr); } else if (addr < 0x1F80_4000) { - Console.WriteLine($"[BUS] Read Unsupported to EXP2 address: {addr:x8}"); - return 0xFFFF_FFFF; + return exp2.load(addr); } else if (addr < 0x1FC8_0000) { return load<uint>(addr & 0x7_FFFF, biosPtr); } else if (addr == 0xFFFE0130) { @@ -127,7 +129,7 @@ public unsafe void write32(uint address, uint value) { } else if (addr < 0x1F80_2000) { spu.write(addr, (ushort)value); } else if (addr < 0x1F80_4000) { - Console.WriteLine($"[BUS] Write Unsupported to EXP2: {addr:x8} Value: {value:x8}"); + exp2.write(addr, value); } else if (addr == 0xFFFE_0130) { memoryCache = value; } else { @@ -168,7 +170,7 @@ public unsafe void write16(uint address, ushort value) { } else if (addr < 0x1F80_2000) { spu.write(addr, value); } else if (addr < 0x1F80_4000) { - Console.WriteLine($"[BUS] Write Unsupported to EXP2: {addr:x8} Value: {value:x8}"); + exp2.write(addr, value); } else if (addr == 0xFFFE_0130) { memoryCache = value; } else { @@ -209,7 +211,7 @@ public unsafe void write8(uint address, byte value) { } else if (addr < 0x1F80_2000) { spu.write(addr, value); } else if (addr < 0x1F80_4000) { - Console.WriteLine($"[BUS] Write Unsupported to EXP2: {addr:x8} Value: {value:x8}"); + exp2.write(addr, value); } else if (addr == 0xFFFE_0130) { memoryCache = value; } else { diff --git a/ProjectPSX/Core/ProjectPSX.cs b/ProjectPSX/Core/ProjectPSX.cs index 97ab245..e03d902 100644 --- a/ProjectPSX/Core/ProjectPSX.cs +++ b/ProjectPSX/Core/ProjectPSX.cs @@ -1,5 +1,6 @@ using ProjectPSX.Devices; using ProjectPSX.Devices.CdRom; +using ProjectPSX.Devices.Expansion; using ProjectPSX.Devices.Input; namespace ProjectPSX { @@ -22,6 +23,7 @@ public class ProjectPSX { private MemoryCard memoryCard; private CD cd; private InterruptController interruptController; + private Exp2 exp2; public ProjectPSX(IHostWindow window, string diskFilename) { controller = new DigitalController(); @@ -36,7 +38,8 @@ public ProjectPSX(IHostWindow window, string diskFilename) { joypad = new JOYPAD(controller, memoryCard); timers = new TIMERS(); mdec = new MDEC(); - bus = new BUS(gpu, cdrom, spu, joypad, timers, mdec, interruptController); + exp2 = new Exp2(); + bus = new BUS(gpu, cdrom, spu, joypad, timers, mdec, interruptController, exp2); cpu = new CPU(bus); bus.loadBios(); diff --git a/ProjectPSX/Devices/Expansion/Exp2.cs b/ProjectPSX/Devices/Expansion/Exp2.cs new file mode 100644 index 0000000..7e274a0 --- /dev/null +++ b/ProjectPSX/Devices/Expansion/Exp2.cs @@ -0,0 +1,29 @@ +using System; + +namespace ProjectPSX.Devices.Expansion; +public class Exp2 { + + public uint load(uint addr) { + //Console.WriteLine($"[BUS] Read Unsupported to EXP2 address: {addr:x8}"); + return 0xFF; + } + + public void write(uint addr, uint value) { + switch (addr) { + case 0x1F802041: + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine($"[EXP2] PSX: POST [{value:x1}]"); + Console.ResetColor(); + break; + case 0x1F802023: + case 0x1F802080: + Console.ForegroundColor = ConsoleColor.Cyan; + Console.Write((char)value); + Console.ResetColor(); + break; + default: + Console.WriteLine($"[BUS] Write Unsupported to EXP2: {addr:x8} Value: {value:x8}"); + break; + } + } +} From 8e0a2bd47ec2a06cf2a062afd2f13531a03eab44 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 28 Aug 2022 17:44:39 +0200 Subject: [PATCH 32/72] GPU: Flip GpuStat.13 when isVerticalInterlace. Fixes openbios boot. --- ProjectPSX/Devices/GPU/GPU.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index 375b1f9..0b2eb38 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -162,6 +162,7 @@ public bool tick(int cycles) { if (isVerticalInterlace && isVerticalResolution480) { isOddLine = !isOddLine; + isInterlaceField = !isOddLine; } window.Render(vram.Bits); From f726df2c54f74e2da21c99ca14ae901b7ccbb336 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Fri, 7 Oct 2022 21:25:51 +0200 Subject: [PATCH 33/72] Add 1555 to 8888 LUT --- ProjectPSX/Devices/GPU/GPU.cs | 42 +++++++++++++++++------------------ 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index 0b2eb38..252757e 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -23,6 +23,8 @@ public class GPU { private VRAM vram = new VRAM(1024, 512); //Vram is 8888 and we transform everything to it private VRAM1555 vram1555 = new VRAM1555(1024, 512); //an un transformed 1555 to 8888 vram so we can fetch clut indexes without reverting to 1555 + private int[] color1555to8888LUT; + public bool debug; private enum Mode { @@ -141,9 +143,23 @@ private struct Color { public GPU(IHostWindow window) { this.window = window; mode = Mode.COMMAND; + initColorTable(); GP1_00_ResetGPU(); } + public void initColorTable() { + color1555to8888LUT = new int[ushort.MaxValue + 1]; + for (int m = 0; m < 2; m++) { + for (int r = 0; r < 32; r++) { + for (int g = 0; g < 32; g++) { + for (int b = 0; b < 32; b++) { + color1555to8888LUT[m << 15 | b << 10 | g << 5 | r] = m << 24 | r << 16 + 3 | g << 8 + 3| b << 3; + } + } + } + } + } + public bool tick(int cycles) { //Video clock is the cpu clock multiplied by 11/7. videoCycles += cycles * 11 / 7; @@ -304,17 +320,10 @@ private uint readFromVRAM() { } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void drawVRAMPixel(ushort val) { - if (checkMaskBeforeDraw) { - int bg = vram.GetPixelRGB888(vramTransfer.x, vramTransfer.y); - - if (bg >> 24 == 0) { - vram.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, color1555to8888(val)); - vram1555.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, val); - } - } else { - vram.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, color1555to8888(val)); - vram1555.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, val); + private void drawVRAMPixel(ushort color1555) { + if (!checkMaskBeforeDraw || vram.GetPixelRGB888(vramTransfer.x, vramTransfer.y) >> 24 == 0) { + vram.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, color1555to8888LUT[color1555]); + vram1555.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, color1555); } vramTransfer.x++; @@ -1234,17 +1243,6 @@ private static short signed11bit(uint n) { return (short)(((int)n << 21) >> 21); } - //This needs to go away once a BGR bitmap is achieved - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int color1555to8888(ushort val) { - byte m = (byte)(val >> 15); - byte r = (byte)((val & 0x1F) << 3); - byte g = (byte)(((val >> 5) & 0x1F) << 3); - byte b = (byte)(((val >> 10) & 0x1F) << 3); - - return (m << 24 | r << 16 | g << 8 | b); - } - //This is only needed for the Direct GP0 commands as the command number needs to be //known ahead of the first command on queue. private static ReadOnlySpan<byte> CommandSizeTable => new byte[] { From a82e0e003bdfa4fc602d4c036d92c3f66024ef4f Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Fri, 7 Oct 2022 21:29:47 +0200 Subject: [PATCH 34/72] Ellide bounds checking on pipeline MA and WB --- ProjectPSX/Core/CPU.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ProjectPSX/Core/CPU.cs b/ProjectPSX/Core/CPU.cs index 77d10fb..e19ddb7 100644 --- a/ProjectPSX/Core/CPU.cs +++ b/ProjectPSX/Core/CPU.cs @@ -1,6 +1,7 @@ //#define CPU_EXCEPTIONS using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using ProjectPSX.Disassembler; namespace ProjectPSX { @@ -263,7 +264,8 @@ private void fetchDecode() { [MethodImpl(MethodImplOptions.AggressiveInlining)] private void MemAccess() { if (delayedMemoryLoad.register != memoryLoad.register) { //if loadDelay on same reg it is lost/overwritten (amidog tests) - GPR[memoryLoad.register] = memoryLoad.value; + ref uint r0 = ref MemoryMarshal.GetArrayDataReference(GPR); + Unsafe.Add(ref r0, (nint)memoryLoad.register) = memoryLoad.value; } memoryLoad = delayedMemoryLoad; delayedMemoryLoad.register = 0; @@ -271,9 +273,10 @@ private void MemAccess() { [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteBack() { - GPR[writeBack.register] = writeBack.value; + ref uint r0 = ref MemoryMarshal.GetArrayDataReference(GPR); + Unsafe.Add(ref r0, (nint)writeBack.register) = writeBack.value; writeBack.register = 0; - GPR[0] = 0; + r0 = 0; } // Non Implemented by the CPU Opcodes From de3935b944dcd2db9d2a9138a717980b444eac2c Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Fri, 7 Oct 2022 21:33:48 +0200 Subject: [PATCH 35/72] VRAM: Ellide bounds check --- ProjectPSX/Devices/GPU/VRAM.cs | 16 ++++++++++++---- ProjectPSX/Devices/GPU/VRAM1555.cs | 12 +++++++++--- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/ProjectPSX/Devices/GPU/VRAM.cs b/ProjectPSX/Devices/GPU/VRAM.cs index 6c22e1a..c9654ba 100644 --- a/ProjectPSX/Devices/GPU/VRAM.cs +++ b/ProjectPSX/Devices/GPU/VRAM.cs @@ -19,19 +19,27 @@ public VRAM(int width, int height) { [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetPixel(int x, int y, int color) { int index = x + (y * Width); - Bits[index] = color; + + ref int r0 = ref MemoryMarshal.GetArrayDataReference(Bits); + Unsafe.Add(ref r0, (nint)index) = color; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetPixelRGB888(int x, int y) { + public ref int GetPixelRGB888(int x, int y) { int index = x + (y * Width); - return Bits[index]; + + ref int r0 = ref MemoryMarshal.GetArrayDataReference(Bits); + ref int ri = ref Unsafe.Add(ref r0, (nint)index); + + return ref ri; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort GetPixelBGR555(int x, int y) { int index = x + (y * Width); - int color = Bits[index]; + + ref int r0 = ref MemoryMarshal.GetArrayDataReference(Bits); + ref int color = ref Unsafe.Add(ref r0, (nint)index); byte m = (byte)((color & 0xFF000000) >> 24); byte r = (byte)((color & 0x00FF0000) >> 16 + 3); diff --git a/ProjectPSX/Devices/GPU/VRAM1555.cs b/ProjectPSX/Devices/GPU/VRAM1555.cs index 4c28f2f..91080fb 100644 --- a/ProjectPSX/Devices/GPU/VRAM1555.cs +++ b/ProjectPSX/Devices/GPU/VRAM1555.cs @@ -19,13 +19,19 @@ public VRAM1555(int width, int height) { [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetPixel(int x, int y, ushort color) { int index = x + (y * Width); - Bits[index] = color; + + ref ushort r0 = ref MemoryMarshal.GetArrayDataReference(Bits); + Unsafe.Add(ref r0, (nint)index) = color; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort GetPixel(int x, int y) { + public ref ushort GetPixel(int x, int y) { int index = x + (y * Width); - return Bits[index]; + + ref ushort r0 = ref MemoryMarshal.GetArrayDataReference(Bits); + ref ushort ri = ref Unsafe.Add(ref r0, (nint)index); + + return ref ri; } } From f77e755df6feb0d0f13c6b11531efbc9964556d7 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Fri, 7 Oct 2022 21:40:26 +0200 Subject: [PATCH 36/72] VRAM: make H and W const As multiple of 2 the JIT will drop an IMUL for a plain SHL --- ProjectPSX/Devices/GPU/GPU.cs | 4 ++-- ProjectPSX/Devices/GPU/VRAM.cs | 8 +++----- ProjectPSX/Devices/GPU/VRAM1555.cs | 8 +++----- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index 252757e..42dea5e 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -20,8 +20,8 @@ public class GPU { private IHostWindow window; - private VRAM vram = new VRAM(1024, 512); //Vram is 8888 and we transform everything to it - private VRAM1555 vram1555 = new VRAM1555(1024, 512); //an un transformed 1555 to 8888 vram so we can fetch clut indexes without reverting to 1555 + private VRAM vram = new VRAM(); //Vram is 8888 and we transform everything to it + private VRAM1555 vram1555 = new VRAM1555(); //an un transformed 1555 to 8888 vram so we can fetch clut indexes without reverting to 1555 private int[] color1555to8888LUT; diff --git a/ProjectPSX/Devices/GPU/VRAM.cs b/ProjectPSX/Devices/GPU/VRAM.cs index c9654ba..4b05a6f 100644 --- a/ProjectPSX/Devices/GPU/VRAM.cs +++ b/ProjectPSX/Devices/GPU/VRAM.cs @@ -4,14 +4,12 @@ namespace ProjectPSX { public class VRAM { public int[] Bits { get; private set; } - public int Height; - public int Width; + public const int Height = 512; + public const int Width = 1024; protected GCHandle BitsHandle { get; private set; } - public VRAM(int width, int height) { - Height = height; - Width = width; + public VRAM() { Bits = new int[Width * Height]; BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned); } diff --git a/ProjectPSX/Devices/GPU/VRAM1555.cs b/ProjectPSX/Devices/GPU/VRAM1555.cs index 91080fb..a1e4780 100644 --- a/ProjectPSX/Devices/GPU/VRAM1555.cs +++ b/ProjectPSX/Devices/GPU/VRAM1555.cs @@ -4,14 +4,12 @@ namespace ProjectPSX { public class VRAM1555 { public ushort[] Bits { get; private set; } - public int Height; - public int Width; + public const int Height = 512; + public const int Width = 1024; protected GCHandle BitsHandle { get; private set; } - public VRAM1555(int width, int height) { - Height = height; - Width = width; + public VRAM1555() { Bits = new ushort[Width * Height]; BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned); } From 68758345206446615fc034d38f80fd367ec2aed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Cort=C3=A9s?= <bluestorm.android@gmail.com> Date: Sat, 12 Nov 2022 11:15:23 +0100 Subject: [PATCH 37/72] OpenTK: Update to 4.7.5 --- ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj index 1cdf697..6ac85a0 100644 --- a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj +++ b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj @@ -15,7 +15,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="OpenTK" Version="4.7.4" /> + <PackageReference Include="OpenTK" Version="4.7.5" /> </ItemGroup> <ItemGroup> From 4b82acd68897820ef5d92309285253e5362024e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Cort=C3=A9s?= <bluestorm.android@gmail.com> Date: Sat, 12 Nov 2022 11:17:01 +0100 Subject: [PATCH 38/72] Project: Update to .NET 7 net7 go brrrrr... --- ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj | 2 +- ProjectPSX.WinForms/ProjectPSX.WinForms.csproj | 2 +- ProjectPSX/ProjectPSX.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj index 6ac85a0..af03732 100644 --- a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj +++ b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net6.0</TargetFramework> + <TargetFramework>net7.0</TargetFramework> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> diff --git a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj index 9010221..718dfcf 100644 --- a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj +++ b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net6.0-windows</TargetFramework> + <TargetFramework>net7.0-windows</TargetFramework> <UseWindowsForms>true</UseWindowsForms> <DisableWinExeOutputInference>true</DisableWinExeOutputInference> </PropertyGroup> diff --git a/ProjectPSX/ProjectPSX.csproj b/ProjectPSX/ProjectPSX.csproj index 85d1ea8..2514a86 100644 --- a/ProjectPSX/ProjectPSX.csproj +++ b/ProjectPSX/ProjectPSX.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Library</OutputType> - <TargetFramework>net6.0</TargetFramework> + <TargetFramework>net7.0</TargetFramework> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> From 99360876b7463d31b06af1fb2124d05584c771cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Cort=C3=A9s?= <bluestorm.android@gmail.com> Date: Sun, 13 Nov 2022 16:27:33 +0100 Subject: [PATCH 39/72] CPU: Remove fun ptr initialization Turns out the indirection is not always elided and the profiler shows both calls. In the end making all the cpu instructions static and mutating the state is (marginally) faster thought the instruction code is a bit more verbose. --- ProjectPSX/Core/CPU.cs | 635 ++++++++++++++++++----------------------- 1 file changed, 282 insertions(+), 353 deletions(-) diff --git a/ProjectPSX/Core/CPU.cs b/ProjectPSX/Core/CPU.cs index e19ddb7..fb3b64a 100644 --- a/ProjectPSX/Core/CPU.cs +++ b/ProjectPSX/Core/CPU.cs @@ -81,50 +81,9 @@ public CPU(BUS bus) { gte = new GTE(); COP0_GPR[15] = 0x2; //PRID Processor ID + } - initOpCodeTable(); - } - - private static delegate*<CPU, void>[] opcodeMainTable; - private static delegate*<CPU, void>[] opcodeSpecialTable; - - private void initOpCodeTable() { - static void SPECIAL(CPU cpu) => cpu.SPECIAL(); - static void BCOND(CPU cpu) => cpu.BCOND(); - static void J(CPU cpu) => cpu.J(); - static void JAL(CPU cpu) => cpu.JAL(); - static void BEQ(CPU cpu) => cpu.BEQ(); - static void BNE(CPU cpu) => cpu.BNE(); - static void BLEZ(CPU cpu) => cpu.BLEZ(); - static void BGTZ(CPU cpu) => cpu.BGTZ(); - static void ADDI(CPU cpu) => cpu.ADDI(); - static void ADDIU(CPU cpu) => cpu.ADDIU(); - static void SLTI(CPU cpu) => cpu.SLTI(); - static void SLTIU(CPU cpu) => cpu.SLTIU(); - static void ANDI(CPU cpu) => cpu.ANDI(); - static void ORI(CPU cpu) => cpu.ORI(); - static void XORI(CPU cpu) => cpu.XORI(); - static void LUI(CPU cpu) => cpu.LUI(); - static void COP0(CPU cpu) => cpu.COP0(); - static void NOP(CPU cpu) => cpu.NOP(); - static void COP2(CPU cpu) => cpu.COP2(); - static void NA(CPU cpu) => cpu.NA(); - static void LB(CPU cpu) => cpu.LB(); - static void LH(CPU cpu) => cpu.LH(); - static void LWL(CPU cpu) => cpu.LWL(); - static void LW(CPU cpu) => cpu.LW(); - static void LBU(CPU cpu) => cpu.LBU(); - static void LHU(CPU cpu) => cpu.LHU(); - static void LWR(CPU cpu) => cpu.LWR(); - static void SB(CPU cpu) => cpu.SB(); - static void SH(CPU cpu) => cpu.SH(); - static void SWL(CPU cpu) => cpu.SWL(); - static void SW(CPU cpu) => cpu.SW(); - static void SWR(CPU cpu) => cpu.SWR(); - static void LWC2(CPU cpu) => cpu.LWC2(); - static void SWC2(CPU cpu) => cpu.SWC2(); - - opcodeMainTable = new delegate*<CPU, void>[] { + private static delegate*<CPU, void>[] opcodeMainTable = new delegate*<CPU, void>[] { &SPECIAL, &BCOND, &J, &JAL, &BEQ, &BNE, &BLEZ, &BGTZ, &ADDI, &ADDIU, &SLTI, &SLTIU, &ANDI, &ORI, &XORI, &LUI, &COP0, &NOP, &COP2, &NOP, &NA, &NA, &NA, &NA, @@ -135,36 +94,7 @@ private void initOpCodeTable() { &NOP, &NOP, &SWC2, &NOP, &NA, &NA, &NA, &NA, }; - static void SLL(CPU cpu) => cpu.SLL(); - static void SRL(CPU cpu) => cpu.SRL(); - static void SRA(CPU cpu) => cpu.SRA(); - static void SLLV(CPU cpu) => cpu.SLLV(); - static void SRLV(CPU cpu) => cpu.SRLV(); - static void SRAV(CPU cpu) => cpu.SRAV(); - static void JR(CPU cpu) => cpu.JR(); - static void SYSCALL(CPU cpu) => cpu.SYSCALL(); - static void BREAK(CPU cpu) => cpu.BREAK(); - static void JALR(CPU cpu) => cpu.JALR(); - static void MFHI(CPU cpu) => cpu.MFHI(); - static void MTHI(CPU cpu) => cpu.MTHI(); - static void MFLO(CPU cpu) => cpu.MFLO(); - static void MTLO(CPU cpu) => cpu.MTLO(); - static void MULT(CPU cpu) => cpu.MULT(); - static void MULTU(CPU cpu) => cpu.MULTU(); - static void DIV(CPU cpu) => cpu.DIV(); - static void DIVU(CPU cpu) => cpu.DIVU(); - static void ADD(CPU cpu) => cpu.ADD(); - static void ADDU(CPU cpu) => cpu.ADDU(); - static void SUB(CPU cpu) => cpu.SUB(); - static void SUBU(CPU cpu) => cpu.SUBU(); - static void AND(CPU cpu) => cpu.AND(); - static void OR(CPU cpu) => cpu.OR(); - static void XOR(CPU cpu) => cpu.XOR(); - static void NOR(CPU cpu) => cpu.NOR(); - static void SLT(CPU cpu) => cpu.SLT(); - static void SLTU(CPU cpu) => cpu.SLTU(); - - opcodeSpecialTable = new delegate*<CPU, void>[] { + private static delegate*<CPU, void>[] opcodeSpecialTable = new delegate*<CPU, void>[] { &SLL, &NA, &SRL, &SRA, &SLLV, &NA, &SRLV, &SRAV, &JR, &JALR, &NA, &NA, &SYSCALL, &BREAK, &NA, &NA, &MFHI, &MTHI, &MFLO, &MTLO, &NA, &NA, &NA, &NA, @@ -174,7 +104,6 @@ private void initOpCodeTable() { &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, }; - } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Run() { @@ -225,7 +154,7 @@ public void handleInterrupts() { uint IP = (COP0_GPR[CAUSE] >> 8) & 0xFF; if (IEC && (IM & IP) > 0) { - EXCEPTION(EX.INTERRUPT); + EXCEPTION(this, EX.INTERRUPT); } } @@ -252,7 +181,7 @@ private void fetchDecode() { #if CPU_EXCEPTIONS if ((PC_Now & 0x3) != 0) { COP0_GPR[BADA] = PC_Now; - EXCEPTION(EX.LOAD_ADRESS_ERROR); + EXCEPTION(this, EX.LOAD_ADRESS_ERROR); return; } #endif @@ -280,330 +209,330 @@ private void WriteBack() { } // Non Implemented by the CPU Opcodes - private void NOP() { /*nop*/ } + private static void NOP(CPU cpu) { /*nop*/ } - private void NA() => EXCEPTION(EX.ILLEGAL_INSTR, instr.id); + private static void NA(CPU cpu) => EXCEPTION(cpu, EX.ILLEGAL_INSTR, cpu.instr.id); // Main Table Opcodes - private void SPECIAL() => opcodeSpecialTable[instr.function](this); + private static void SPECIAL(CPU cpu) => opcodeSpecialTable[cpu.instr.function](cpu); - private void BCOND() { - opcodeIsBranch = true; - uint op = instr.rt; + private static void BCOND(CPU cpu) { + cpu.opcodeIsBranch = true; + uint op = cpu.instr.rt; bool should_link = (op & 0x1E) == 0x10; - bool should_branch = (int)(GPR[instr.rs] ^ (op << 31)) < 0; + bool should_branch = (int)(cpu.GPR[cpu.instr.rs] ^ (op << 31)) < 0; - if (should_link) GPR[31] = PC_Predictor; - if (should_branch) BRANCH(); + if (should_link) cpu.GPR[31] = cpu.PC_Predictor; + if (should_branch) BRANCH(cpu); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void J() { - opcodeIsBranch = true; - opcodeTookBranch = true; - PC_Predictor = (PC_Predictor & 0xF000_0000) | (instr.addr << 2); + private static void J(CPU cpu) { + cpu.opcodeIsBranch = true; + cpu.opcodeTookBranch = true; + cpu.PC_Predictor = (cpu.PC_Predictor & 0xF000_0000) | (cpu.instr.addr << 2); } - private void JAL() { - setGPR(31, PC_Predictor); - J(); + private static void JAL(CPU cpu) { + cpu.setGPR(31, cpu.PC_Predictor); + J(cpu); } - private void BEQ() { - opcodeIsBranch = true; - if (GPR[instr.rs] == GPR[instr.rt]) { - BRANCH(); + private static void BEQ(CPU cpu) { + cpu.opcodeIsBranch = true; + if (cpu.GPR[cpu.instr.rs] == cpu.GPR[cpu.instr.rt]) { + BRANCH(cpu); } } - private void BNE() { - opcodeIsBranch = true; - if (GPR[instr.rs] != GPR[instr.rt]) { - BRANCH(); + private static void BNE(CPU cpu) { + cpu.opcodeIsBranch = true; + if (cpu.GPR[cpu.instr.rs] != cpu.GPR[cpu.instr.rt]) { + BRANCH(cpu); } } - private void BLEZ() { - opcodeIsBranch = true; - if (((int)GPR[instr.rs]) <= 0) { - BRANCH(); + private static void BLEZ(CPU cpu) { + cpu.opcodeIsBranch = true; + if (((int)cpu.GPR[cpu.instr.rs]) <= 0) { + BRANCH(cpu); } } - private void BGTZ() { - opcodeIsBranch = true; - if (((int)GPR[instr.rs]) > 0) { - BRANCH(); + private static void BGTZ(CPU cpu) { + cpu.opcodeIsBranch = true; + if (((int)cpu.GPR[cpu.instr.rs]) > 0) { + BRANCH(cpu); } } - private void ADDI() { - uint rs = GPR[instr.rs]; - uint imm_s = instr.imm_s; + private static void ADDI(CPU cpu) { + uint rs = cpu.GPR[cpu.instr.rs]; + uint imm_s = cpu.instr.imm_s; uint result = rs + imm_s; #if CPU_EXCEPTIONS if(checkOverflow(rs, imm_s, result)) { - EXCEPTION(EX.OVERFLOW, instr.id); + EXCEPTION(cpu, EX.OVERFLOW, cpu.instr.id); } else { - setGPR(instr.rt, result); + cpu.setGPR(cpu.instr.rt, result); } #else - setGPR(instr.rt, result); + cpu.setGPR(cpu.instr.rt, result); #endif } - private void ADDIU() => setGPR(instr.rt, GPR[instr.rs] + instr.imm_s); + private static void ADDIU(CPU cpu) => cpu.setGPR(cpu.instr.rt, cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s); - private void SLTI() { - bool condition = (int)GPR[instr.rs] < (int)instr.imm_s; - setGPR(instr.rt, Unsafe.As<bool, uint>(ref condition)); + private static void SLTI(CPU cpu) { + bool condition = (int)cpu.GPR[cpu.instr.rs] < (int)cpu.instr.imm_s; + cpu.setGPR(cpu.instr.rt, Unsafe.As<bool, uint>(ref condition)); } - private void SLTIU() { - bool condition = GPR[instr.rs] < instr.imm_s; - setGPR(instr.rt, Unsafe.As<bool, uint>(ref condition)); + private static void SLTIU(CPU cpu) { + bool condition = cpu.GPR[cpu.instr.rs] < cpu.instr.imm_s; + cpu.setGPR(cpu.instr.rt, Unsafe.As<bool, uint>(ref condition)); } - private void ANDI() => setGPR(instr.rt, GPR[instr.rs] & instr.imm); + private static void ANDI(CPU cpu) => cpu.setGPR(cpu.instr.rt, cpu.GPR[cpu.instr.rs] & cpu.instr.imm); - private void ORI() => setGPR(instr.rt, GPR[instr.rs] | instr.imm); + private static void ORI(CPU cpu) => cpu.setGPR(cpu.instr.rt, cpu.GPR[cpu.instr.rs] | cpu.instr.imm); - private void XORI() => setGPR(instr.rt, GPR[instr.rs] ^ instr.imm); + private static void XORI(CPU cpu) => cpu.setGPR(cpu.instr.rt, cpu.GPR[cpu.instr.rs] ^ cpu.instr.imm); - private void LUI() => setGPR(instr.rt, instr.imm << 16); + private static void LUI(CPU cpu) => cpu.setGPR(cpu.instr.rt, cpu.instr.imm << 16); - private void COP0() { - if (instr.rs == 0b0_0000) MFC0(); - else if (instr.rs == 0b0_0100) MTC0(); - else if (instr.rs == 0b1_0000) RFE(); - else EXCEPTION(EX.ILLEGAL_INSTR, instr.id); + private static void COP0(CPU cpu) { + if (cpu.instr.rs == 0b0_0000) MFC0(cpu); + else if (cpu.instr.rs == 0b0_0100) MTC0(cpu); + else if (cpu.instr.rs == 0b1_0000) RFE(cpu); + else EXCEPTION(cpu, EX.ILLEGAL_INSTR, cpu.instr.id); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void MFC0() { - uint mfc = instr.rd; + private static void MFC0(CPU cpu) { + uint mfc = cpu.instr.rd; if (mfc == 3 || mfc >= 5 && mfc <= 9 || mfc >= 11 && mfc <= 15) { - delayedLoad(instr.rt, COP0_GPR[mfc]); + delayedLoad(cpu, cpu.instr.rt, cpu.COP0_GPR[mfc]); } else { - EXCEPTION(EX.ILLEGAL_INSTR, instr.id); + EXCEPTION(cpu, EX.ILLEGAL_INSTR, cpu.instr.id); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void MTC0() { - uint value = GPR[instr.rt]; - uint register = instr.rd; + private static void MTC0(CPU cpu) { + uint value = cpu.GPR[cpu.instr.rt]; + uint register = cpu.instr.rd; if (register == CAUSE) { //only bits 8 and 9 are writable - COP0_GPR[CAUSE] &= ~(uint)0x300; - COP0_GPR[CAUSE] |= value & 0x300; + cpu.COP0_GPR[CAUSE] &= ~(uint)0x300; + cpu.COP0_GPR[CAUSE] |= value & 0x300; } else if (register == SR) { //This can trigger soft interrupts - dontIsolateCache = (value & 0x10000) == 0; - bool prevIEC = (COP0_GPR[SR] & 0x1) == 1; + cpu.dontIsolateCache = (value & 0x10000) == 0; + bool prevIEC = (cpu.COP0_GPR[SR] & 0x1) == 1; bool currentIEC = (value & 0x1) == 1; - COP0_GPR[SR] = value; + cpu.COP0_GPR[SR] = value; uint IM = (value >> 8) & 0x3; - uint IP = (COP0_GPR[CAUSE] >> 8) & 0x3; + uint IP = (cpu.COP0_GPR[CAUSE] >> 8) & 0x3; if (!prevIEC && currentIEC && (IM & IP) > 0) { - PC = PC_Predictor; - EXCEPTION(EX.INTERRUPT, instr.id); + cpu.PC = cpu.PC_Predictor; + EXCEPTION(cpu, EX.INTERRUPT, cpu.instr.id); } } else { - COP0_GPR[register] = value; + cpu.COP0_GPR[register] = value; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void RFE() { - uint mode = COP0_GPR[SR] & 0x3F; - COP0_GPR[SR] &= ~(uint)0xF; - COP0_GPR[SR] |= mode >> 2; + private static void RFE(CPU cpu) { + uint mode = cpu.COP0_GPR[SR] & 0x3F; + cpu.COP0_GPR[SR] &= ~(uint)0xF; + cpu.COP0_GPR[SR] |= mode >> 2; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void EXCEPTION(EX cause, uint coprocessor = 0) { - uint mode = COP0_GPR[SR] & 0x3F; - COP0_GPR[SR] &= ~(uint)0x3F; - COP0_GPR[SR] |= (mode << 2) & 0x3F; + private static void EXCEPTION(CPU cpu, EX cause, uint coprocessor = 0) { + uint mode = cpu.COP0_GPR[SR] & 0x3F; + cpu.COP0_GPR[SR] &= ~(uint)0x3F; + cpu.COP0_GPR[SR] |= (mode << 2) & 0x3F; - uint OldCause = COP0_GPR[CAUSE] & 0xff00; - COP0_GPR[CAUSE] = (uint)cause << 2; - COP0_GPR[CAUSE] |= OldCause; - COP0_GPR[CAUSE] |= coprocessor << 28; + uint OldCause = cpu.COP0_GPR[CAUSE] & 0xff00; + cpu.COP0_GPR[CAUSE] = (uint)cause << 2; + cpu.COP0_GPR[CAUSE] |= OldCause; + cpu.COP0_GPR[CAUSE] |= coprocessor << 28; if (cause == EX.INTERRUPT) { - COP0_GPR[EPC] = PC; + cpu.COP0_GPR[EPC] = cpu.PC; //hack: related to the delay of the ex interrupt - opcodeIsDelaySlot = opcodeIsBranch; - opcodeInDelaySlotTookBranch = opcodeTookBranch; + cpu.opcodeIsDelaySlot = cpu.opcodeIsBranch; + cpu.opcodeInDelaySlotTookBranch = cpu.opcodeTookBranch; } else { - COP0_GPR[EPC] = PC_Now; + cpu.COP0_GPR[EPC] = cpu.PC_Now; } - if (opcodeIsDelaySlot) { - COP0_GPR[EPC] -= 4; - COP0_GPR[CAUSE] |= (uint)1 << 31; - COP0_GPR[JUMPDEST] = PC; + if (cpu.opcodeIsDelaySlot) { + cpu.COP0_GPR[EPC] -= 4; + cpu.COP0_GPR[CAUSE] |= (uint)1 << 31; + cpu.COP0_GPR[JUMPDEST] = cpu.PC; - if (opcodeInDelaySlotTookBranch) { - COP0_GPR[CAUSE] |= (1 << 30); + if (cpu.opcodeInDelaySlotTookBranch) { + cpu.COP0_GPR[CAUSE] |= (1 << 30); } } - PC = ExceptionAdress[COP0_GPR[SR] & 0x400000 >> 22]; - PC_Predictor = PC + 4; + cpu.PC = ExceptionAdress[cpu.COP0_GPR[SR] & 0x400000 >> 22]; + cpu.PC_Predictor = cpu.PC + 4; } - private void COP2() { - if ((instr.rs & 0x10) == 0) { - switch (instr.rs) { - case 0b0_0000: MFC2(); break; - case 0b0_0010: CFC2(); break; - case 0b0_0100: MTC2(); break; - case 0b0_0110: CTC2(); break; - default: EXCEPTION(EX.ILLEGAL_INSTR, instr.id); break; + private static void COP2(CPU cpu) { + if ((cpu.instr.rs & 0x10) == 0) { + switch (cpu.instr.rs) { + case 0b0_0000: MFC2(cpu); break; + case 0b0_0010: CFC2(cpu); break; + case 0b0_0100: MTC2(cpu); break; + case 0b0_0110: CTC2(cpu); break; + default: EXCEPTION(cpu, EX.ILLEGAL_INSTR, cpu.instr.id); break; } } else { - gte.execute(instr.value); + cpu.gte.execute(cpu.instr.value); } } - private void MFC2() => delayedLoad(instr.rt, gte.loadData(instr.rd)); + private static void MFC2(CPU cpu) => delayedLoad(cpu, cpu.instr.rt, cpu.gte.loadData(cpu.instr.rd)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CFC2() => delayedLoad(instr.rt, gte.loadControl(instr.rd)); + private static void CFC2(CPU cpu) => delayedLoad(cpu, cpu.instr.rt, cpu.gte.loadControl(cpu.instr.rd)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void MTC2() => gte.writeData(instr.rd, GPR[instr.rt]); + private static void MTC2(CPU cpu) => cpu.gte.writeData(cpu.instr.rd, cpu.GPR[cpu.instr.rt]); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CTC2() => gte.writeControl(instr.rd, GPR[instr.rt]); + private static void CTC2(CPU cpu) => cpu.gte.writeControl(cpu.instr.rd, cpu.GPR[cpu.instr.rt]); - private void LWC2() { //TODO WARNING THIS SHOULD HAVE DELAY? - uint addr = GPR[instr.rs] + instr.imm_s; + private static void LWC2(CPU cpu) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; #if CPU_EXCEPTIONS if ((addr & 0x3) == 0) { - uint value = bus.load32(addr); - gte.writeData(instr.rt, value); + uint value = cpu.bus.load32(addr); + cpu.gte.writeData(cpu.instr.rt, value); } else { - COP0_GPR[BADA] = addr; - EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id); + cpu.COP0_GPR[BADA] = addr; + EXCEPTION(cpu, EX.LOAD_ADRESS_ERROR, cpu.instr.id); } #else - uint value = bus.load32(addr); - gte.writeData(instr.rt, value); + uint value = cpu.bus.load32(addr); + cpu.gte.writeData(cpu.instr.rt, value); #endif } - private void SWC2() { //TODO WARNING THIS SHOULD HAVE DELAY? - uint addr = GPR[instr.rs] + instr.imm_s; + private static void SWC2(CPU cpu) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; #if CPU_EXCEPTIONS if ((addr & 0x3) == 0) { - bus.write32(addr, gte.loadData(instr.rt)); + cpu.bus.write32(addr, cpu.gte.loadData(cpu.instr.rt)); } else { - COP0_GPR[BADA] = addr; - EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id); + cpu.COP0_GPR[BADA] = addr; + EXCEPTION(cpu, EX.LOAD_ADRESS_ERROR, cpu.instr.id); } #else - bus.write32(addr, gte.loadData(instr.rt)); + cpu.bus.write32(addr, cpu.gte.loadData(cpu.instr.rt)); #endif } - private void LB() { //todo redo this as it unnecesary load32 - if (dontIsolateCache) { - uint value = (uint)(sbyte)bus.load32(GPR[instr.rs] + instr.imm_s); - delayedLoad(instr.rt, value); + private static void LB(CPU cpu) { //todo redo this as it unnecesary load32 + if (cpu.dontIsolateCache) { + uint value = (uint)(sbyte)cpu.bus.load32(cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s); + delayedLoad(cpu, cpu.instr.rt, value); } //else Console.WriteLine("IsolatedCache: Ignoring Load"); } - private void LBU() { - if (dontIsolateCache) { - uint value = (byte)bus.load32(GPR[instr.rs] + instr.imm_s); - delayedLoad(instr.rt, value); + private static void LBU(CPU cpu) { + if (cpu.dontIsolateCache) { + uint value = (byte)cpu.bus.load32(cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s); + delayedLoad(cpu, cpu.instr.rt, value); } //else Console.WriteLine("IsolatedCache: Ignoring Load"); } - private void LH() { - if (dontIsolateCache) { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void LH(CPU cpu) { + if (cpu.dontIsolateCache) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; #if CPU_EXCEPTIONS if ((addr & 0x1) == 0) { - uint value = (uint)(short)bus.load32(addr); - delayedLoad(instr.rt, value); + uint value = (uint)(short)cpu.bus.load32(addr); + delayedLoad(cpu, cpu.instr.rt, value); } else { - COP0_GPR[BADA] = addr; - EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id); + cpu.COP0_GPR[BADA] = addr; + EXCEPTION(cpu, EX.LOAD_ADRESS_ERROR, cpu.instr.id); } #else - uint value = (uint)(short)bus.load32(addr); - delayedLoad(instr.rt, value); + uint value = (uint)(short)cpu.bus.load32(addr); + delayedLoad(cpu, cpu.instr.rt, value); #endif } //else Console.WriteLine("IsolatedCache: Ignoring Load"); } - private void LHU() { - if (dontIsolateCache) { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void LHU(CPU cpu) { + if (cpu.dontIsolateCache) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; #if CPU_EXCEPTIONS if ((addr & 0x1) == 0) { - uint value = (ushort)bus.load32(addr); - delayedLoad(instr.rt, value); + uint value = (ushort)cpu.bus.load32(addr); + delayedLoad(cpu, cpu.instr.rt, value); } else { - COP0_GPR[BADA] = addr; - EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id); + cpu.COP0_GPR[BADA] = addr; + EXCEPTION(cpu, EX.LOAD_ADRESS_ERROR, cpu.instr.id); } #else - uint value = (ushort)bus.load32(addr); - delayedLoad(instr.rt, value); + uint value = (ushort)cpu.bus.load32(addr); + delayedLoad(cpu, cpu.instr.rt, value); #endif } //else Console.WriteLine("IsolatedCache: Ignoring Load"); } - private void LW() { - if (dontIsolateCache) { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void LW(CPU cpu) { + if (cpu.dontIsolateCache) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; #if CPU_EXCEPTIONS if ((addr & 0x3) == 0) { - uint value = bus.load32(addr); - delayedLoad(instr.rt, value); + uint value = cpu.bus.load32(addr); + delayedLoad(cpu, cpu.instr.rt, value); } else { - COP0_GPR[BADA] = addr; - EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id); + cpu.COP0_GPR[BADA] = addr; + EXCEPTION(cpu, EX.LOAD_ADRESS_ERROR, cpu.instr.id); } #else - uint value = bus.load32(addr); - delayedLoad(instr.rt, value); + uint value = cpu.bus.load32(addr); + delayedLoad(cpu, cpu.instr.rt, value); #endif } //else Console.WriteLine("IsolatedCache: Ignoring Load"); } - private void LWL() { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void LWL(CPU cpu) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; uint aligned_addr = addr & 0xFFFF_FFFC; - uint aligned_load = bus.load32(aligned_addr); + uint aligned_load = cpu.bus.load32(aligned_addr); uint value = 0; - uint LRValue = GPR[instr.rt]; + uint LRValue = cpu.GPR[cpu.instr.rt]; - if (instr.rt == memoryLoad.register) { - LRValue = memoryLoad.value; + if (cpu.instr.rt == cpu.memoryLoad.register) { + LRValue = cpu.memoryLoad.value; } switch (addr & 0b11) { @@ -613,19 +542,19 @@ private void LWL() { case 3: value = aligned_load; break; } - delayedLoad(instr.rt, value); + delayedLoad(cpu, cpu.instr.rt, value); } - private void LWR() { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void LWR(CPU cpu) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; uint aligned_addr = addr & 0xFFFF_FFFC; - uint aligned_load = bus.load32(aligned_addr); + uint aligned_load = cpu.bus.load32(aligned_addr); uint value = 0; - uint LRValue = GPR[instr.rt]; + uint LRValue = cpu.GPR[cpu.instr.rt]; - if (instr.rt == memoryLoad.register) { - LRValue = memoryLoad.value; + if (cpu.instr.rt == cpu.memoryLoad.register) { + LRValue = cpu.memoryLoad.value; } switch (addr & 0b11) { @@ -635,225 +564,225 @@ private void LWR() { case 3: value = (LRValue & 0xFFFF_FF00) | (aligned_load >> 24); break; } - delayedLoad(instr.rt, value); + delayedLoad(cpu, cpu.instr.rt, value); } - private void SB() { - if (dontIsolateCache) - bus.write8(GPR[instr.rs] + instr.imm_s, (byte)GPR[instr.rt]); + private static void SB(CPU cpu) { + if (cpu.dontIsolateCache) + cpu.bus.write8(cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s, (byte)cpu.GPR[cpu.instr.rt]); //else Console.WriteLine("IsolatedCache: Ignoring Write"); } - private void SH() { - if (dontIsolateCache) { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void SH(CPU cpu) { + if (cpu.dontIsolateCache) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; #if CPU_EXCEPTIONS if ((addr & 0x1) == 0) { - bus.write16(addr, (ushort)GPR[instr.rt]); + cpu.bus.write16(addr, (ushort)cpu.GPR[cpu.instr.rt]); } else { - COP0_GPR[BADA] = addr; - EXCEPTION(EX.STORE_ADRESS_ERROR, instr.id); + cpu.COP0_GPR[BADA] = addr; + EXCEPTION(cpu, EX.STORE_ADRESS_ERROR, cpu.instr.id); } #else - bus.write16(addr, (ushort)GPR[instr.rt]); + cpu.bus.write16(addr, (ushort)cpu.GPR[cpu.instr.rt]); #endif } //else Console.WriteLine("IsolatedCache: Ignoring Write"); } - private void SW() { - if (dontIsolateCache) { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void SW(CPU cpu) { + if (cpu.dontIsolateCache) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; #if CPU_EXCEPTIONS if ((addr & 0x3) == 0) { - bus.write32(addr, GPR[instr.rt]); + cpu.bus.write32(addr, cpu.GPR[cpu.instr.rt]); } else { - COP0_GPR[BADA] = addr; - EXCEPTION(EX.STORE_ADRESS_ERROR, instr.id); + cpu.COP0_GPR[BADA] = addr; + EXCEPTION(cpu, EX.STORE_ADRESS_ERROR, cpu.instr.id); } #else - bus.write32(addr, GPR[instr.rt]); + cpu.bus.write32(addr, cpu.GPR[cpu.instr.rt]); #endif } //else Console.WriteLine("IsolatedCache: Ignoring Write"); } - private void SWR() { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void SWR(CPU cpu) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; uint aligned_addr = addr & 0xFFFF_FFFC; - uint aligned_load = bus.load32(aligned_addr); + uint aligned_load = cpu.bus.load32(aligned_addr); uint value = 0; switch (addr & 0b11) { - case 0: value = GPR[instr.rt]; break; - case 1: value = (aligned_load & 0x0000_00FF) | (GPR[instr.rt] << 8); break; - case 2: value = (aligned_load & 0x0000_FFFF) | (GPR[instr.rt] << 16); break; - case 3: value = (aligned_load & 0x00FF_FFFF) | (GPR[instr.rt] << 24); break; + case 0: value = cpu.GPR[cpu.instr.rt]; break; + case 1: value = (aligned_load & 0x0000_00FF) | (cpu.GPR[cpu.instr.rt] << 8); break; + case 2: value = (aligned_load & 0x0000_FFFF) | (cpu.GPR[cpu.instr.rt] << 16); break; + case 3: value = (aligned_load & 0x00FF_FFFF) | (cpu.GPR[cpu.instr.rt] << 24); break; } - bus.write32(aligned_addr, value); + cpu.bus.write32(aligned_addr, value); } - private void SWL() { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void SWL(CPU cpu) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; uint aligned_addr = addr & 0xFFFF_FFFC; - uint aligned_load = bus.load32(aligned_addr); + uint aligned_load = cpu.bus.load32(aligned_addr); uint value = 0; switch (addr & 0b11) { - case 0: value = (aligned_load & 0xFFFF_FF00) | (GPR[instr.rt] >> 24); break; - case 1: value = (aligned_load & 0xFFFF_0000) | (GPR[instr.rt] >> 16); break; - case 2: value = (aligned_load & 0xFF00_0000) | (GPR[instr.rt] >> 8); break; - case 3: value = GPR[instr.rt]; break; + case 0: value = (aligned_load & 0xFFFF_FF00) | (cpu.GPR[cpu.instr.rt] >> 24); break; + case 1: value = (aligned_load & 0xFFFF_0000) | (cpu.GPR[cpu.instr.rt] >> 16); break; + case 2: value = (aligned_load & 0xFF00_0000) | (cpu.GPR[cpu.instr.rt] >> 8); break; + case 3: value = cpu.GPR[cpu.instr.rt]; break; } - bus.write32(aligned_addr, value); + cpu.bus.write32(aligned_addr, value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void BRANCH() { - opcodeTookBranch = true; - PC_Predictor = PC + (instr.imm_s << 2); + private static void BRANCH(CPU cpu) { + cpu.opcodeTookBranch = true; + cpu.PC_Predictor = cpu.PC + (cpu.instr.imm_s << 2); } // Special Table Opcodes (Nested on Opcode 0x00 with additional function param) - private void SLL() => setGPR(instr.rd, GPR[instr.rt] << (int)instr.sa); + private static void SLL(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rt] << (int)cpu.instr.sa); - private void SRL() => setGPR(instr.rd, GPR[instr.rt] >> (int)instr.sa); + private static void SRL(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rt] >> (int)cpu.instr.sa); - private void SRA() => setGPR(instr.rd, (uint)((int)GPR[instr.rt] >> (int)instr.sa)); + private static void SRA(CPU cpu) => cpu.setGPR(cpu.instr.rd, (uint)((int)cpu.GPR[cpu.instr.rt] >> (int)cpu.instr.sa)); - private void SLLV() => setGPR(instr.rd, GPR[instr.rt] << (int)(GPR[instr.rs] & 0x1F)); + private static void SLLV(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rt] << (int)(cpu.GPR[cpu.instr.rs] & 0x1F)); - private void SRLV() => setGPR(instr.rd, GPR[instr.rt] >> (int)(GPR[instr.rs] & 0x1F)); + private static void SRLV(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rt] >> (int)(cpu.GPR[cpu.instr.rs] & 0x1F)); - private void SRAV() => setGPR(instr.rd, (uint)((int)GPR[instr.rt] >> (int)(GPR[instr.rs] & 0x1F))); + private static void SRAV(CPU cpu) => cpu.setGPR(cpu.instr.rd, (uint)((int)cpu.GPR[cpu.instr.rt] >> (int)(cpu.GPR[cpu.instr.rs] & 0x1F))); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void JR() { - opcodeIsBranch = true; - opcodeTookBranch = true; - PC_Predictor = GPR[instr.rs]; + private static void JR(CPU cpu) { + cpu.opcodeIsBranch = true; + cpu.opcodeTookBranch = true; + cpu.PC_Predictor = cpu.GPR[cpu.instr.rs]; } - private void SYSCALL() => EXCEPTION(EX.SYSCALL, instr.id); + private static void SYSCALL(CPU cpu) => EXCEPTION(cpu, EX.SYSCALL, cpu.instr.id); - private void BREAK() => EXCEPTION(EX.BREAK); + private static void BREAK(CPU cpu) => EXCEPTION(cpu, EX.BREAK); - private void JALR() { - setGPR(instr.rd, PC_Predictor); - JR(); + private static void JALR(CPU cpu) { + cpu.setGPR(cpu.instr.rd, cpu.PC_Predictor); + JR(cpu); } - private void MFHI() => setGPR(instr.rd, HI); + private static void MFHI(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.HI); - private void MTHI() => HI = GPR[instr.rs]; + private static void MTHI(CPU cpu) => cpu.HI = cpu.GPR[cpu.instr.rs]; - private void MFLO() => setGPR(instr.rd, LO); + private static void MFLO(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.LO); - private void MTLO() => LO = GPR[instr.rs]; + private static void MTLO(CPU cpu) => cpu.LO = cpu.GPR[cpu.instr.rs]; - private void MULT() { - long value = (long)(int)GPR[instr.rs] * (long)(int)GPR[instr.rt]; //sign extend to pass amidog cpu test + private static void MULT(CPU cpu) { + long value = (long)(int)cpu.GPR[cpu.instr.rs] * (long)(int)cpu.GPR[cpu.instr.rt]; //sign extend to pass amidog cpu test - HI = (uint)(value >> 32); - LO = (uint)value; + cpu.HI = (uint)(value >> 32); + cpu.LO = (uint)value; } - private void MULTU() { - ulong value = (ulong)GPR[instr.rs] * (ulong)GPR[instr.rt]; //sign extend to pass amidog cpu test + private static void MULTU(CPU cpu) { + ulong value = (ulong)cpu.GPR[cpu.instr.rs] * (ulong)cpu.GPR[cpu.instr.rt]; //sign extend to pass amidog cpu test - HI = (uint)(value >> 32); - LO = (uint)value; + cpu.HI = (uint)(value >> 32); + cpu.LO = (uint)value; } - private void DIV() { - int n = (int)GPR[instr.rs]; - int d = (int)GPR[instr.rt]; + private static void DIV(CPU cpu) { + int n = (int)cpu.GPR[cpu.instr.rs]; + int d = (int)cpu.GPR[cpu.instr.rt]; if (d == 0) { - HI = (uint)n; + cpu.HI = (uint)n; if (n >= 0) { - LO = 0xFFFF_FFFF; + cpu.LO = 0xFFFF_FFFF; } else { - LO = 1; + cpu.LO = 1; } } else if ((uint)n == 0x8000_0000 && d == -1) { - HI = 0; - LO = 0x8000_0000; + cpu.HI = 0; + cpu.LO = 0x8000_0000; } else { - HI = (uint)(n % d); - LO = (uint)(n / d); + cpu.HI = (uint)(n % d); + cpu.LO = (uint)(n / d); } } - private void DIVU() { - uint n = GPR[instr.rs]; - uint d = GPR[instr.rt]; + private static void DIVU(CPU cpu) { + uint n = cpu.GPR[cpu.instr.rs]; + uint d = cpu.GPR[cpu.instr.rt]; if (d == 0) { - HI = n; - LO = 0xFFFF_FFFF; + cpu.HI = n; + cpu.LO = 0xFFFF_FFFF; } else { - HI = n % d; - LO = n / d; + cpu.HI = n % d; + cpu.LO = n / d; } } - private void ADD() { - uint rs = GPR[instr.rs]; - uint rt = GPR[instr.rt]; + private static void ADD(CPU cpu) { + uint rs = cpu.GPR[cpu.instr.rs]; + uint rt = cpu.GPR[cpu.instr.rt]; uint result = rs + rt; #if CPU_EXCEPTIONS if (checkOverflow(rs, rt, result)) { - EXCEPTION(EX.OVERFLOW, instr.id); + EXCEPTION(cpu, EX.OVERFLOW, cpu.instr.id); } else { - setGPR(instr.rd, result); + cpu.setGPR(cpu.instr.rd, result); } #else - setGPR(instr.rd, result); + cpu.setGPR(cpu.instr.rd, result); #endif } - private void ADDU() => setGPR(instr.rd, GPR[instr.rs] + GPR[instr.rt]); + private static void ADDU(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rs] + cpu.GPR[cpu.instr.rt]); - private void SUB() { - uint rs = GPR[instr.rs]; - uint rt = GPR[instr.rt]; + private static void SUB(CPU cpu) { + uint rs = cpu.GPR[cpu.instr.rs]; + uint rt = cpu.GPR[cpu.instr.rt]; uint result = rs - rt; #if CPU_EXCEPTIONS if (checkUnderflow(rs, rt, result)) { - EXCEPTION(EX.OVERFLOW, instr.id); + EXCEPTION(cpu, EX.OVERFLOW, cpu.instr.id); } else { - setGPR(instr.rd, result); + cpu.setGPR(cpu.instr.rd, result); } #else - setGPR(instr.rd, result); + cpu.setGPR(cpu.instr.rd, result); #endif } - private void SUBU() => setGPR(instr.rd, GPR[instr.rs] - GPR[instr.rt]); + private static void SUBU(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rs] - cpu.GPR[cpu.instr.rt]); - private void AND() => setGPR(instr.rd, GPR[instr.rs] & GPR[instr.rt]); + private static void AND(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rs] & cpu.GPR[cpu.instr.rt]); - private void OR() => setGPR(instr.rd, GPR[instr.rs] | GPR[instr.rt]); + private static void OR(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rs] | cpu.GPR[cpu.instr.rt]); - private void XOR() => setGPR(instr.rd, GPR[instr.rs] ^ GPR[instr.rt]); + private static void XOR(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rs] ^ cpu.GPR[cpu.instr.rt]); - private void NOR() => setGPR(instr.rd, ~(GPR[instr.rs] | GPR[instr.rt])); + private static void NOR(CPU cpu) => cpu.setGPR(cpu.instr.rd, ~(cpu.GPR[cpu.instr.rs] | cpu.GPR[cpu.instr.rt])); - private void SLT() { - bool condition = (int)GPR[instr.rs] < (int)GPR[instr.rt]; - setGPR(instr.rd, Unsafe.As<bool, uint>(ref condition)); + private static void SLT(CPU cpu) { + bool condition = (int)cpu.GPR[cpu.instr.rs] < (int)cpu.GPR[cpu.instr.rt]; + cpu.setGPR(cpu.instr.rd, Unsafe.As<bool, uint>(ref condition)); } - private void SLTU() { - bool condition = GPR[instr.rs] < GPR[instr.rt]; - setGPR(instr.rd, Unsafe.As<bool, uint>(ref condition)); + private static void SLTU(CPU cpu) { + bool condition = cpu.GPR[cpu.instr.rs] < cpu.GPR[cpu.instr.rt]; + cpu.setGPR(cpu.instr.rd, Unsafe.As<bool, uint>(ref condition)); } @@ -872,9 +801,9 @@ private void setGPR(uint regN, uint value) { } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void delayedLoad(uint regN, uint value) { - delayedMemoryLoad.register = regN; - delayedMemoryLoad.value = value; + private static void delayedLoad(CPU cpu, uint regN, uint value) { + cpu.delayedMemoryLoad.register = regN; + cpu.delayedMemoryLoad.value = value; } private void TTY() { From 9040eb51ab8640aa884b0b26b2059a328c299141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Cort=C3=A9s?= <bluestorm.android@gmail.com> Date: Fri, 25 Nov 2022 19:47:19 +0100 Subject: [PATCH 40/72] CDROM: Add basic delayed interrupt handling Among others things like being able to play cd audio from bios (as it detects the tracks) it makes GT1, RE2 and RE3 playable. --- ProjectPSX/Devices/CDROM/CDROM.cs | 157 ++++++++++++++++++------------ ProjectPSX/Util/Extension.cs | 5 + 2 files changed, 101 insertions(+), 61 deletions(-) diff --git a/ProjectPSX/Devices/CDROM/CDROM.cs b/ProjectPSX/Devices/CDROM/CDROM.cs index e509e6a..9c0e1a4 100644 --- a/ProjectPSX/Devices/CDROM/CDROM.cs +++ b/ProjectPSX/Devices/CDROM/CDROM.cs @@ -105,7 +105,37 @@ private enum Mode { private Mode mode = Mode.Idle; private int counter; - private Queue<byte> interruptQueue = new Queue<byte>(); + private Queue<DelayedInterrupt> interruptQueue = new Queue<DelayedInterrupt>(); + + public class DelayedInterrupt { + public int delay; + public byte interrupt; + + public DelayedInterrupt(int delay, byte interrupt) { + this.delay = delay; + this.interrupt = interrupt; + } + } + + //INT0 No response received(no interrupt request) + //INT1 Received SECOND(or further) response to ReadS/ReadN(and Play+Report) + //INT2 Received SECOND response(to various commands) + //INT3 Received FIRST response(to any command) + //INT4 DataEnd(when Play/Forward reaches end of disk) (maybe also for Read?) + //INT5 Received error-code(in FIRST or SECOND response) + //INT5 also occurs on SECOND GetID response, on unlicensed disks + //INT5 also occurs when opening the drive door(even if no command + // was sent, ie.even if no read-command or other command is active) + // INT6 N/A + //INT7 N/A + public class Interrupt { + public const byte INT0_NO_RESPONSE = 0; + public const byte INT1_SECOND_RESPONSE_READ_PLAY = 1; + public const byte INT2_SECOND_RESPONSE = 2; + public const byte INT3_FIRST_RESPONSE = 3; + public const byte INT4_DATA_END = 4; + public const byte INT5_ERROR = 5; + } private CD cd; private SPU spu; @@ -120,9 +150,14 @@ public CDROM(CD cd, SPU spu) { public bool tick(int cycles) { counter += cycles; - if (interruptQueue.Count != 0 && IF == 0) { + if(interruptQueue.Count != 0) { + var delayedInterrupt = interruptQueue.Peek(); + delayedInterrupt.delay -= cycles; + } + + if (interruptQueue.Count != 0 && IF == 0 && interruptQueue.Peek().delay <= 0) { if (cdDebug) Console.WriteLine($"[CDROM] Interrupt Queue is size: {interruptQueue.Count} dequeue to IF next Interrupt: {interruptQueue.Peek()}"); - IF |= interruptQueue.Dequeue(); + IF |= interruptQueue.Dequeue().interrupt; //edgeTrigger = true; } @@ -147,7 +182,7 @@ public bool tick(int cycles) { STAT = (byte)(STAT & (~0x40)); responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x2); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE); break; case Mode.Read: @@ -174,7 +209,7 @@ public bool tick(int cycles) { if (isAutoPause && cd.isTrackChange) { responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x4); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT4_DATA_END); Cmd_09_Pause(); } @@ -239,7 +274,7 @@ public bool tick(int cycles) { } responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x1); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT1_SECOND_RESPONSE_READ_PLAY); break; @@ -249,7 +284,7 @@ public bool tick(int cycles) { } mode = Mode.Idle; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x2); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE); counter = 0; break; } @@ -372,8 +407,8 @@ public void write(uint addr, uint value) { case 1: IF &= (byte)~(value & 0x1F); if (cdDebug) Console.WriteLine($"[CDROM] [W03.1] Set IF: {value:x8} -> IF = {IF:x8}"); - if (interruptQueue.Count > 0) { - IF |= interruptQueue.Dequeue(); + if (interruptQueue.Count > 0 && interruptQueue.Peek().delay <= 0) { + IF |= interruptQueue.Dequeue().interrupt; } if ((value & 0x40) == 0x40) { @@ -457,13 +492,13 @@ private void Cmd_01_GetStat() { } responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } private void Cmd_02_SetLoc() { if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); return; } @@ -493,13 +528,13 @@ private void Cmd_02_SetLoc() { } responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } private void Cmd_03_Play() { if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); return; } //If theres a trackN param it seeks and plays from the start location of it @@ -522,13 +557,13 @@ private void Cmd_03_Play() { mode = Mode.Play; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } private void Cmd_06_ReadN() { if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); return; } @@ -538,7 +573,7 @@ private void Cmd_06_ReadN() { STAT |= 0x20; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); mode = Mode.Read; } @@ -547,57 +582,57 @@ private void Cmd_07_MotorOn() { STAT = 0x2; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x2); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE); } private void Cmd_08_Stop() { STAT = 0x2; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); STAT = 0; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x2); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE); mode = Mode.Idle; } private void Cmd_09_Pause() { responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); STAT = 0x2; mode = Mode.Idle; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x2); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE); } private void Cmd_0A_Init() { STAT = 0x2; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x2); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE); } private void Cmd_0B_Mute() { mutedAudio = true; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } private void Cmd_0C_Demute() { mutedAudio = false; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } private void Cmd_0D_SetFilter() { @@ -605,7 +640,7 @@ private void Cmd_0D_SetFilter() { filterChannel = parameterBuffer.Dequeue(); responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } private void Cmd_0E_SetMode() { @@ -631,7 +666,7 @@ private void Cmd_0E_SetMode() { isCDDA = (mode & 0x1) == 1; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } private void Cmd_10_GetLocL() { @@ -642,7 +677,7 @@ private void Cmd_10_GetLocL() { if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); return; } @@ -659,13 +694,13 @@ private void Cmd_10_GetLocL() { responseBuffer.EnqueueRange(response); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } private void Cmd_11_GetLocP() { //SubQ missing... if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); return; } @@ -685,7 +720,7 @@ private void Cmd_11_GetLocP() { //SubQ missing... Span<byte> response = stackalloc byte[] { track.number, 1, DecToBcd(mm), DecToBcd(ss), DecToBcd(ff), DecToBcd(amm), DecToBcd(ass), DecToBcd(aff) }; responseBuffer.EnqueueRange(response); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } private void Cmd_12_SetSession() { //broken @@ -693,36 +728,36 @@ private void Cmd_12_SetSession() { //broken if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); return; } STAT = 0x42; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x2); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE); } private void Cmd_13_GetTN() { if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); return; } //if (cdDebug) Console.WriteLine($"[CDROM] getTN First Track: 1 (Hardcoded) - Last Track: {cd.tracks.Count}"); //Console.ReadLine(); responseBuffer.EnqueueRange(stackalloc byte[] { STAT, 1, DecToBcd((byte)cd.tracks.Count) }); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } private void Cmd_14_GetTD() { if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); return; } @@ -741,13 +776,13 @@ private void Cmd_14_GetTD() { } //Console.ReadLine(); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } private void Cmd_15_SeekL() { if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); return; } @@ -757,13 +792,13 @@ private void Cmd_15_SeekL() { mode = Mode.Seek; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } private void Cmd_16_SeekP() { if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); return; } @@ -773,7 +808,7 @@ private void Cmd_16_SeekP() { mode = Mode.Seek; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } private void Cmd_19_Test() { @@ -784,24 +819,24 @@ private void Cmd_19_Test() { Console.WriteLine("[CDROM] Command 19 04 ResetSCExInfo Anti Mod Chip Meassures"); STAT = 0x2; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); break; case 0x05:// 05h - INT3(total,success);Stop SCEx reading and get counters Console.WriteLine("[CDROM] Command 19 05 GetSCExInfo Hack 0 0 Bypass Response"); responseBuffer.EnqueueRange(stackalloc byte[] { 0, 0 }); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); break; case 0x20: //INT3(yy,mm,dd,ver) ;Get cdrom BIOS date/version (yy,mm,dd,ver) http://www.psxdev.net/forum/viewtopic.php?f=70&t=557 responseBuffer.EnqueueRange(stackalloc byte[] { 0x94, 0x09, 0x19, 0xC0 }); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); break; case 0x22: //INT3("for US/AEP") --> Region-free debug version --> accepts unlicensed CDRs responseBuffer.EnqueueRange(stackalloc byte[] { 0x66, 0x6F, 0x72, 0x20, 0x55, 0x53, 0x2F, 0x41, 0x45, 0x50 }); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); break; case 0x60:// 60h lo,hi INT3(databyte) ;HC05 SUB-CPU read RAM and I/O ports responseBuffer.Enqueue(0); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); break; default: Console.WriteLine($"[CDROM] Unimplemented 0x19 Test Command {command:x8}"); @@ -813,42 +848,42 @@ private void Cmd_1A_GetID() { //Door Open INT5(11h,80h) N/A if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); return; } //No Disk INT3(stat) INT5(08h, 40h, 00h, 00h, 00h, 00h, 00h, 00h) //STAT = 0x2; //0x40 seek //responseBuffer.Enqueue(STAT); - //interruptQueue.Enqueue(0x3); + //interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_RECEIVED_FIRST_RESPONSE); // //responseBuffer.EnqueueRange(stackalloc byte[] { 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); - //interruptQueue.Enqueue(0x5); + //interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); //Licensed: Mode2 INT3(stat) INT2(02h, 00h, 20h, 00h, 53h, 43h, 45h, 4xh) STAT = 0x40; //0x40 seek STAT |= 0x2; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); // Audio Disk INT3(stat) INT5(0Ah,90h, 00h,00h, 00h,00h,00h,00h) if (cd.isAudioCD()) { Span<byte> audioCdResponse = stackalloc byte[] { 0x0A, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; responseBuffer.EnqueueRange(audioCdResponse); - interruptQueue.Enqueue(0x5); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); return; } // Licensed: Mode2 INT3(stat) INT2(02h, 00h, 20h, 00h, 53h, 43h, 45h, 4xh) Span<byte> gameResponse = stackalloc byte[] { 0x02, 0x00, 0x20, 0x00, 0x53, 0x43, 0x45, 0x41 }; //SCE | //A 0x41 (America) - I 0x49 (Japan) - E 0x45 (Europe) responseBuffer.EnqueueRange(gameResponse); - interruptQueue.Enqueue(0x2); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE); } private void Cmd_1B_ReadS() { if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); return; } @@ -858,7 +893,7 @@ private void Cmd_1B_ReadS() { STAT |= 0x20; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); mode = Mode.Read; } @@ -866,23 +901,23 @@ private void Cmd_1B_ReadS() { private void Cmd_1E_ReadTOC() { if (isLidOpen) { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); - interruptQueue.Enqueue(0x5); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); return; } mode = Mode.TOC; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } private void Cmd_1F_VideoCD() { //INT5(11h,40h) ;-Unused/invalid responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x40 }); - interruptQueue.Enqueue(0x5); - } + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + } private void Cmd_5x_lockUnlock() { - interruptQueue.Enqueue(0x5); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); } private void UnimplementedCDCommand(uint value) { diff --git a/ProjectPSX/Util/Extension.cs b/ProjectPSX/Util/Extension.cs index 723feca..d21651c 100644 --- a/ProjectPSX/Util/Extension.cs +++ b/ProjectPSX/Util/Extension.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using static ProjectPSX.Devices.CDROM; namespace ProjectPSX.Devices { static class Extension { @@ -7,5 +8,9 @@ public static void EnqueueRange<T>(this Queue<T> queue, Span<T> parameters) { foreach (T parameter in parameters) queue.Enqueue(parameter); } + + public static void EnqueueDelayedInterrupt(this Queue<DelayedInterrupt> queue, byte interrupt, int delay = 5000) { + queue.Enqueue(new DelayedInterrupt(delay, interrupt)); + } } } From 6d64c82979829fca0fd0d1b90b6ad7222bbaf5a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Cort=C3=A9s?= <bluestorm.android@gmail.com> Date: Fri, 25 Nov 2022 19:48:41 +0100 Subject: [PATCH 41/72] CPU: Avoid Instr imm and imm_s and --- ProjectPSX/Core/CPU.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ProjectPSX/Core/CPU.cs b/ProjectPSX/Core/CPU.cs index fb3b64a..e8cf372 100644 --- a/ProjectPSX/Core/CPU.cs +++ b/ProjectPSX/Core/CPU.cs @@ -52,10 +52,10 @@ public struct Instr { public uint opcode => value >> 26; //Instr opcode //I-Type - public uint rs => (value >> 21) & 0x1F;//Register Source - public uint rt => (value >> 16) & 0x1F;//Register Target - public uint imm => value & 0xFFFF; //Immediate value - public uint imm_s => (uint)(short)imm; //Immediate value sign extended + public uint rs => (value >> 21) & 0x1F; //Register Source + public uint rt => (value >> 16) & 0x1F; //Register Target + public uint imm => (ushort)value; //Immediate value + public uint imm_s => (uint)(short)value; //Immediate value sign extended //R-Type public uint rd => (value >> 11) & 0x1F; @@ -150,8 +150,8 @@ public void handleInterrupts() { } bool IEC = (COP0_GPR[SR] & 0x1) == 1; - uint IM = (COP0_GPR[SR] >> 8) & 0xFF; - uint IP = (COP0_GPR[CAUSE] >> 8) & 0xFF; + byte IM = (byte)((COP0_GPR[SR] >> 8) & 0xFF); + byte IP = (byte)((COP0_GPR[CAUSE] >> 8) & 0xFF); if (IEC && (IM & IP) > 0) { EXCEPTION(this, EX.INTERRUPT); From 65e55f11376806c1c9f51571c88993f92b502924 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 4 Dec 2022 16:29:31 +0100 Subject: [PATCH 42/72] DMA: Support size 0 transfers being actually size 0x10000 --- ProjectPSX/Devices/DMA.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPSX/Devices/DMA.cs b/ProjectPSX/Devices/DMA.cs index 3c57d85..860bb9a 100644 --- a/ProjectPSX/Devices/DMA.cs +++ b/ProjectPSX/Devices/DMA.cs @@ -171,7 +171,7 @@ private void writeChannelControl(uint value) { private void handleDMA() { if (!isActive()) return; if (syncMode == 0) { - blockCopy(blockSize); + blockCopy(blockSize == 0 ? 0x10_000 : blockSize); } else if (syncMode == 1) { blockCopy(blockSize * blockCount); } else if (syncMode == 2) { From c96c95b5209852917c2deb35d2df967cb2c244c3 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 4 Dec 2022 16:51:23 +0100 Subject: [PATCH 43/72] DMA: Support hardwired OTC control bits --- ProjectPSX/Devices/DMA.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ProjectPSX/Devices/DMA.cs b/ProjectPSX/Devices/DMA.cs index 860bb9a..7e0544a 100644 --- a/ProjectPSX/Devices/DMA.cs +++ b/ProjectPSX/Devices/DMA.cs @@ -111,6 +111,7 @@ private sealed class Channel : AChannel { private uint choppingCPUWindowSize; private bool enable; private bool trigger; + private uint unknownBit30; private BUS bus; private InterruptChannel interrupt; @@ -142,6 +143,11 @@ private uint loadChannelControl() { channelControl |= choppingCPUWindowSize << 20; channelControl |= (enable ? 1u : 0) << 24; channelControl |= (trigger ? 1u : 0) << 28; + channelControl |= unknownBit30 << 30; + + if (channelNumber == 6) { + return channelControl & 0x5000_0002 | 0x2; + } return channelControl; } @@ -164,6 +170,7 @@ private void writeChannelControl(uint value) { choppingCPUWindowSize = (value >> 20) & 0x7; enable = ((value >> 24) & 0x1) != 0; trigger = ((value >> 28) & 0x1) != 0; + unknownBit30 = (value >> 30) & 0x1; handleDMA(); } From 20251d25584c1b103f5191ce4722703866c6880e Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 4 Dec 2022 19:21:59 +0100 Subject: [PATCH 44/72] DMA: Handle dma master enable --- ProjectPSX/Devices/DMA.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ProjectPSX/Devices/DMA.cs b/ProjectPSX/Devices/DMA.cs index 7e0544a..d8e311f 100644 --- a/ProjectPSX/Devices/DMA.cs +++ b/ProjectPSX/Devices/DMA.cs @@ -81,6 +81,10 @@ public void handleInterrupt(int channel) { } } + public bool isDMAControlMasterEnabled(int channelNumber) { + return (((control >> 3) >> 4 * channelNumber) & 0x1) != 0; + } + private bool updateMasterFlag() { //Bit31 is a simple readonly flag that follows the following rules: //IF b15 = 1 OR(b23 = 1 AND(b16 - 22 AND b24 - 30) > 0) THEN b31 = 1 ELSE b31 = 0 @@ -111,6 +115,8 @@ private sealed class Channel : AChannel { private uint choppingCPUWindowSize; private bool enable; private bool trigger; + + private uint unknownBit29; private uint unknownBit30; private BUS bus; @@ -143,6 +149,7 @@ private uint loadChannelControl() { channelControl |= choppingCPUWindowSize << 20; channelControl |= (enable ? 1u : 0) << 24; channelControl |= (trigger ? 1u : 0) << 28; + channelControl |= unknownBit29 << 29; channelControl |= unknownBit30 << 30; if (channelNumber == 6) { @@ -170,13 +177,14 @@ private void writeChannelControl(uint value) { choppingCPUWindowSize = (value >> 20) & 0x7; enable = ((value >> 24) & 0x1) != 0; trigger = ((value >> 28) & 0x1) != 0; + unknownBit29 = (value >> 29) & 0x1; unknownBit30 = (value >> 30) & 0x1; handleDMA(); } private void handleDMA() { - if (!isActive()) return; + if (!isActive() || !interrupt.isDMAControlMasterEnabled(channelNumber)) return; if (syncMode == 0) { blockCopy(blockSize == 0 ? 0x10_000 : blockSize); } else if (syncMode == 1) { From adb81d776a016adc8748df49d7188c8acc1791e7 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 18 Dec 2022 00:39:36 +0100 Subject: [PATCH 45/72] MDEC: Support word by word decoding It passes all Jakub's MDEC tests even though we are not handling actual dma data requests. --- ProjectPSX/Devices/MDEC.cs | 89 +++++++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 30 deletions(-) diff --git a/ProjectPSX/Devices/MDEC.cs b/ProjectPSX/Devices/MDEC.cs index e684a6f..6fc74ee 100644 --- a/ProjectPSX/Devices/MDEC.cs +++ b/ProjectPSX/Devices/MDEC.cs @@ -13,8 +13,8 @@ public class MDEC { private const int MACRO_BLOCK_DECODED_BYTES = 256 * 3; //Status Register - //private bool isDataOutFifoEmpty; private bool isDataInFifoFull; + private bool isDataOutFifoEmpty; private bool isCommandBusy; private bool isDataInRequested; private bool isDataOutRequested; @@ -41,6 +41,8 @@ public class MDEC { private IMemoryOwner<byte> outBuffer = MemoryPool<byte>.Shared.Rent(0x30000); //wild guess while resumable dmas come... private int outBufferPos = 0; + private int pendingBytesToTransfer; + public void write(uint addr, uint value) { uint register = addr & 0xF; if (register == 0) { @@ -53,7 +55,8 @@ public void write(uint addr, uint value) { } public void writeMDEC0_Command(uint value) { //1F801820h - MDEC0 - MDEC Command/Parameter Register (W) - //Console.WriteLine("[MDEC] Write " + value.ToString("x8")); + isCommandBusy = true; + if (remainingDataWords == 0) { decodeCommand(value); } else { @@ -61,18 +64,15 @@ public void writeMDEC0_Command(uint value) { //1F801820h - MDEC0 - MDEC Command/ inBuffer.Enqueue((ushort)(value >> 16)); remainingDataWords--; - //Console.WriteLine("[MDEC] remaining " + remainingDataWords); if (command == decodeMacroBlocks) { command(); } } if (remainingDataWords == 0) { - isCommandBusy = true; - yuvToRgbBlockPos = 0; - outBufferPos = 0; if (command != decodeMacroBlocks) { command(); + isCommandBusy = false; } } @@ -107,17 +107,19 @@ private void decodeMacroBlocks() { currentBlock = 0; + blockPointer = 64; + q_scale = 0; + val = 0; + n = 0; + yuv_to_rgb(block[2], 0, 0); yuv_to_rgb(block[3], 8, 0); yuv_to_rgb(block[4], 0, 8); yuv_to_rgb(block[5], 8, 8); + isDataOutFifoEmpty = false; yuvToRgbBlockPos += MACRO_BLOCK_DECODED_BYTES; - - blockPointer = 64; - q_scale = 0; - val = 0; - n = 0; + pendingBytesToTransfer += MACRO_BLOCK_DECODED_BYTES; } } @@ -158,7 +160,7 @@ private void yuv_to_rgb(short[] Yblk, int xx, int yy) { private int val; private ushort n; public bool rl_decode_block(short[] blk, byte[] qt) { - if (blockPointer >= 64) { //Start of new block + if (blockPointer >= 63) { //Start of new block for (int i = 0; i < blk.Length; i++) { blk[i] = 0; } @@ -177,7 +179,7 @@ public bool rl_decode_block(short[] blk, byte[] qt) { } - while (blockPointer < blk.Length) { + while (blockPointer < 63) { if (q_scale == 0) { val = signed10bit(n & 0x3FF) * 2; } @@ -253,6 +255,11 @@ public void writeMDEC1_Control(uint value) { //1F801824h - MDEC1 - MDEC Control/ outBufferPos = 0; currentBlock = 0; remainingDataWords = 0; + pendingBytesToTransfer = 0; + yuvToRgbBlockPos = 0; + + inBuffer.Clear(); + //outBuffer.Memory.Span.Clear(); blockPointer = 64; q_scale = 0; @@ -262,20 +269,28 @@ public void writeMDEC1_Control(uint value) { //1F801824h - MDEC1 - MDEC Control/ command = null; } - isDataInRequested = ((value >> 30) & 0x1) == 1; //todo enable dma + isDataInRequested = ((value >> 30) & 0x1) == 1; isDataOutRequested = ((value >> 29) & 0x1) == 1; - - //Console.WriteLine("[MDEC] dataInRequest " + isDataInRequested + " dataOutRequested " + isDataOutRequested); } public uint readMDEC0_Data() { //1F801820h.Read - MDEC Data/Response Register (R) if (dataOutputDepth == 2) { //2 24b - var span = outBuffer.Memory.Span.Slice(outBufferPos++ * 4, 4); + int size = 4; + var span = outBuffer.Memory.Span.Slice(outBufferPos, size); + outBufferPos += size; + pendingBytesToTransfer -= size; + + handlePossibleDataOutEnd(); return Unsafe.ReadUnaligned<uint>(ref MemoryMarshal.GetReference(span)); } else if (dataOutputDepth == 3) { //3 15b - var span = outBuffer.Memory.Span.Slice(outBufferPos++ * 6, 6); + int size = 6; // 6 bytes for 2 packed 15b + var span = outBuffer.Memory.Span.Slice(outBufferPos, size); + outBufferPos += size; + pendingBytesToTransfer -= size; + + handlePossibleDataOutEnd(); ushort lo = (ushort)(bit15 << 15 | convert24to15bpp(span[0], span[1], span[2])); ushort hi = (ushort)(bit15 << 15 | convert24to15bpp(span[3], span[4], span[5])); @@ -287,13 +302,23 @@ public uint readMDEC0_Data() { //1F801820h.Read - MDEC Data/Response Register (R } - public Span<uint> processDmaLoad(int size) { + public Span<uint> processDmaLoad(int dmaSize) { if (dataOutputDepth == 2) { //2 24b - var byteSpan = outBuffer.Memory.Span.Slice(outBufferPos++ * 4 * size, 4 * size); //4 = RGBR, GBRG, BRGB... + int size = dmaSize * 4; // DMA Size in words to bytes extracting 4 bytes of packed RGB data + var byteSpan = outBuffer.Memory.Span.Slice(outBufferPos, size); //4 = RGBR, GBRG, BRGB... + outBufferPos += size; + pendingBytesToTransfer -= size; + + handlePossibleDataOutEnd(); return MemoryMarshal.Cast<byte, uint>(byteSpan); } else if (dataOutputDepth == 3) { //3 15b - var byteSpan = outBuffer.Memory.Span.Slice(outBufferPos++ * 6 * size, 6 * size); //6 = RGB * 2 => b15|b15 + int size = dmaSize * 6; // DMA Size in words to bytes as packed 15b data (RGB * 2 => b15|b15) + var byteSpan = outBuffer.Memory.Span.Slice(outBufferPos, size); + outBufferPos += size; + pendingBytesToTransfer -= size; + + handlePossibleDataOutEnd(); for (int b24 = 0, b15 = 0; b24 < byteSpan.Length; b24 += 3, b15 += 2) { var r = byteSpan[b24 + 0] >> 3; @@ -308,11 +333,11 @@ public Span<uint> processDmaLoad(int size) { byteSpan[b15 + 1] = hi; } - var dma = MemoryMarshal.Cast<byte, uint>(byteSpan.Slice(0, 4 * size)); + var dma = MemoryMarshal.Cast<byte, uint>(byteSpan.Slice(0, 4 * dmaSize)); return dma; } else { //unsupported mode allocate and return garbage so can be seen - uint[] garbage = new uint[size]; + uint[] garbage = new uint[dmaSize]; var dma = new Span<uint>(garbage); dma.Fill(0xFF00FF00); return dma; @@ -330,25 +355,29 @@ public ushort convert24to15bpp(byte sr, byte sg, byte sb) { public uint readMDEC1_Status() {//1F801824h - MDEC1 - MDEC Status Register (R) uint status = 0; - status |= (isDataOutFifoEmpty() ? 1u : 0) << 31; + status |= (isDataOutFifoEmpty ? 1u : 0) << 31; status |= (isDataInFifoFull ? 1u : 0) << 30; status |= (isCommandBusy ? 1u : 0) << 29; - status |= (isDataInRequested ? 1u : 0) << 28; - status |= (isDataOutRequested ? 1u : 0) << 27; + status |= (isDataInRequested ? 1u : 0) << 28; // this should be && "enough space in the inQueue" + status |= ((isDataOutRequested && pendingBytesToTransfer != 0) ? 1u : 0) << 27; status |= dataOutputDepth << 25; status |= (isSigned ? 1u : 0) << 24; status |= bit15 << 23; status |= (currentBlock + 4) % NUM_BLOCKS << 16; status |= (ushort)(remainingDataWords - 1); //Console.WriteLine("[MDEC] Load Status " + status.ToString("x8")); - //Console.ReadLine(); - - isCommandBusy = false; return status; } - private bool isDataOutFifoEmpty() => outBufferPos == 0; //Todo compare to inbufferpos when we handle full dma in + private void handlePossibleDataOutEnd() { + if (pendingBytesToTransfer <= 0 && remainingDataWords == 0) { + outBufferPos = 0; + yuvToRgbBlockPos = 0; + isCommandBusy = false; + isDataOutFifoEmpty = true; + } + } private static ReadOnlySpan<byte> zagzig => new byte[] { 0, 1, 8, 16, 9, 2, 3, 10, From 7d1b924bfd163a3bc82e37afc1926356a23d01c3 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 18 Dec 2022 00:41:20 +0100 Subject: [PATCH 46/72] DMA: Simplify edgeInterruptTrigger --- ProjectPSX/Devices/DMA.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ProjectPSX/Devices/DMA.cs b/ProjectPSX/Devices/DMA.cs index d8e311f..1f61785 100644 --- a/ProjectPSX/Devices/DMA.cs +++ b/ProjectPSX/Devices/DMA.cs @@ -75,9 +75,7 @@ public void handleInterrupt(int channel) { //Console.WriteLine($"MasterFlag: {masterFlag} irqEnable16: {irqEnable:x8} irqFlag24: {irqFlag:x8} {forceIRQ} {masterEnable} {((irqEnable & irqFlag) > 0)}"); masterFlag = updateMasterFlag(); - - if (masterFlag) { - edgeInterruptTrigger = true; + edgeInterruptTrigger |= masterFlag; } } From 8e44e3f9bf0653cb739dfde71f9a7811f1018dfc Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 18 Dec 2022 00:43:50 +0100 Subject: [PATCH 47/72] DMA: Handle resumable dmas This makes Final Fantasy 9 playable --- ProjectPSX/Devices/DMA.cs | 51 +++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/ProjectPSX/Devices/DMA.cs b/ProjectPSX/Devices/DMA.cs index 1f61785..8b44c27 100644 --- a/ProjectPSX/Devices/DMA.cs +++ b/ProjectPSX/Devices/DMA.cs @@ -1,6 +1,5 @@ using System; -using System.Collections.Generic; namespace ProjectPSX.Devices { public class DMA { @@ -76,7 +75,6 @@ public void handleInterrupt(int channel) { //Console.WriteLine($"MasterFlag: {masterFlag} irqEnable16: {irqEnable:x8} irqFlag24: {irqFlag:x8} {forceIRQ} {masterEnable} {((irqEnable & irqFlag) > 0)}"); masterFlag = updateMasterFlag(); edgeInterruptTrigger |= masterFlag; - } } public bool isDMAControlMasterEnabled(int channelNumber) { @@ -121,6 +119,8 @@ private sealed class Channel : AChannel { private InterruptChannel interrupt; private int channelNumber; + private uint pendingBlocks; + public Channel(int channelNumber, InterruptChannel interrupt, BUS bus) { this.channelNumber = channelNumber; this.interrupt = interrupt; @@ -178,27 +178,47 @@ private void writeChannelControl(uint value) { unknownBit29 = (value >> 29) & 0x1; unknownBit30 = (value >> 30) & 0x1; + if (!enable) pendingBlocks = 0; + handleDMA(); } private void handleDMA() { if (!isActive() || !interrupt.isDMAControlMasterEnabled(channelNumber)) return; if (syncMode == 0) { + //if (choppingEnable == 1) { + // Console.WriteLine($"[DMA] Chopping Syncmode 0 not supported. DmaWindow: {choppingDMAWindowSize} CpuWindow: {choppingCPUWindowSize}"); + //} + blockCopy(blockSize == 0 ? 0x10_000 : blockSize); + finishDMA(); + } else if (syncMode == 1) { - blockCopy(blockSize * blockCount); + // HACK: + // GPUIn: Bypass blocks to elude mdec/gpu desync as MDEC is actually too fast decoding blocks + // MdecIn: GranTurismo produces some artifacts that still needs to be checked otherwise it's ok on other games i've checked + if (channelNumber == 2 && transferDirection == 1 || channelNumber == 0) { + blockCopy(blockSize * blockCount); + finishDMA(); + return; + } + + trigger = false; + pendingBlocks = blockCount; + transferBlockIfPending(); } else if (syncMode == 2) { linkedList(); + finishDMA(); } + } - //disable channel + private void finishDMA() { enable = false; trigger = false; interrupt.handleInterrupt(channelNumber); } - private void blockCopy(uint size) { if (transferDirection == 0) { //To Ram @@ -217,7 +237,7 @@ private void blockCopy(uint size) { Span<uint> dma = bus.DmaFromRam(baseAddress & 0x1F_FFFC, size); - switch(channelNumber) { + switch (channelNumber) { case 0: bus.DmaToMdecIn(dma); break; case 2: bus.DmaToGpu(dma); break; case 4: bus.DmaToSpu(dma); break; @@ -254,6 +274,18 @@ private void linkedList() { //0 Start immediately and transfer all at once (used for CDROM, OTC) needs TRIGGER private bool isActive() => syncMode == 0 ? enable && trigger : enable; + public void transferBlockIfPending() { + //TODO: check if device can actually transfer. Here we assume devices are always + // capable of processing the dmas and never busy. + if (pendingBlocks > 0) { + pendingBlocks--; + blockCopy(blockSize); + + if (pendingBlocks == 0) { + finishDMA(); + } + } + } } AChannel[] channels = new AChannel[8]; @@ -285,7 +317,12 @@ public void write(uint addr, uint value) { channels[channel].write(register, value); } - public bool tick() => ((InterruptChannel)channels[7]).tick(); + public bool tick() { + for (int i = 0; i < 7; i++) { + ((Channel)channels[i]).transferBlockIfPending(); + } + return ((InterruptChannel)channels[7]).tick(); + } } } From 37fabbd0577b6c15dc63a82e35aa81b51287117a Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 18 Dec 2022 00:45:51 +0100 Subject: [PATCH 48/72] SPU: Handle ramDataTransferAddressInternal overflow --- ProjectPSX/Devices/SPU/SPU.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ProjectPSX/Devices/SPU/SPU.cs b/ProjectPSX/Devices/SPU/SPU.cs index 93af4d8..defc44f 100644 --- a/ProjectPSX/Devices/SPU/SPU.cs +++ b/ProjectPSX/Devices/SPU/SPU.cs @@ -1041,7 +1041,7 @@ public unsafe void processDmaWrite(Span<uint> dma) { //todo trigger interrupt dmaSpan.CopyTo(ramDestSpan); } else { int overflow = destAddress - 0x7FFFF; - + Span<byte> firstSlice = dmaSpan.Slice(0, dmaSpan.Length - overflow); Span<byte> overflowSpan = dmaSpan.Slice(dmaSpan.Length - overflow); @@ -1049,7 +1049,7 @@ public unsafe void processDmaWrite(Span<uint> dma) { //todo trigger interrupt overflowSpan.CopyTo(ramStartSpan); } - ramDataTransferAddressInternal = (uint)(ramDataTransferAddressInternal + size); + ramDataTransferAddressInternal = (uint)((ramDataTransferAddressInternal + size) & 0x7FFFF); } [MethodImpl(MethodImplOptions.AggressiveInlining)] From 79c6de9c5a6fdb98484bcadc14c75b1267466fcf Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Mon, 19 Dec 2022 19:30:39 +0100 Subject: [PATCH 49/72] DMA: Refactor --- ProjectPSX/Devices/DMA.cs | 328 --------------------- ProjectPSX/Devices/DMA/Channel.cs | 5 + ProjectPSX/Devices/DMA/DMA.cs | 42 +++ ProjectPSX/Devices/DMA/DmaChannel.cs | 193 ++++++++++++ ProjectPSX/Devices/DMA/InterruptChannel.cs | 92 ++++++ 5 files changed, 332 insertions(+), 328 deletions(-) delete mode 100644 ProjectPSX/Devices/DMA.cs create mode 100644 ProjectPSX/Devices/DMA/Channel.cs create mode 100644 ProjectPSX/Devices/DMA/DMA.cs create mode 100644 ProjectPSX/Devices/DMA/DmaChannel.cs create mode 100644 ProjectPSX/Devices/DMA/InterruptChannel.cs diff --git a/ProjectPSX/Devices/DMA.cs b/ProjectPSX/Devices/DMA.cs deleted file mode 100644 index 8b44c27..0000000 --- a/ProjectPSX/Devices/DMA.cs +++ /dev/null @@ -1,328 +0,0 @@ - -using System; - -namespace ProjectPSX.Devices { - public class DMA { - - public abstract class AChannel { - public abstract void write(uint register, uint value); - public abstract uint load(uint regiter); - } - - private sealed class InterruptChannel : AChannel { - - private uint control; - - private bool forceIRQ; - private uint irqEnable; - private bool masterEnable; - private uint irqFlag; - private bool masterFlag; - - private bool edgeInterruptTrigger; - - public InterruptChannel() { - control = 0x07654321; - } - - public override uint load(uint register) { - switch (register) { - case 0: return control; - case 4: return loadInterrupt(); - case 6: return loadInterrupt() >> 16; //castlevania symphony of the night and dino crisis 2 ask for this - default: Console.WriteLine("Unhandled register on interruptChannel DMA load " + register); return 0xFFFF_FFFF; - } - } - - private uint loadInterrupt() { - uint interruptRegister = 0; - - interruptRegister |= (forceIRQ ? 1u : 0) << 15; - interruptRegister |= irqEnable << 16; - interruptRegister |= (masterEnable ? 1u : 0) << 23; - interruptRegister |= irqFlag << 24; - interruptRegister |= (masterFlag ? 1u : 0) << 31; - - return interruptRegister; - } - - public override void write(uint register, uint value) { - //Console.WriteLine("irqflag pre: " + irqFlag.ToString("x8")); - switch (register) { - case 0: control = value; break; - case 4: writeInterrupt(value); break; - case 6: writeInterrupt(value << 16 | (forceIRQ ? 1u : 0) << 15); break; - default: Console.WriteLine($"Unhandled write on DMA Interrupt register {register}"); break; - } - //Console.WriteLine("irqflag post: " + irqFlag.ToString("x8")); - } - - private void writeInterrupt(uint value) { - forceIRQ = ((value >> 15) & 0x1) != 0; - irqEnable = (value >> 16) & 0x7F; - masterEnable = ((value >> 23) & 0x1) != 0; - irqFlag &= ~((value >> 24) & 0x7F); - - masterFlag = updateMasterFlag(); - } - - public void handleInterrupt(int channel) { - //IRQ flags in Bit(24 + n) are set upon DMAn completion - but caution - they are set ONLY if enabled in Bit(16 + n). - if ((irqEnable & (1 << channel)) != 0) { - irqFlag |= (uint)(1 << channel); - } - - //Console.WriteLine($"MasterFlag: {masterFlag} irqEnable16: {irqEnable:x8} irqFlag24: {irqFlag:x8} {forceIRQ} {masterEnable} {((irqEnable & irqFlag) > 0)}"); - masterFlag = updateMasterFlag(); - edgeInterruptTrigger |= masterFlag; - } - - public bool isDMAControlMasterEnabled(int channelNumber) { - return (((control >> 3) >> 4 * channelNumber) & 0x1) != 0; - } - - private bool updateMasterFlag() { - //Bit31 is a simple readonly flag that follows the following rules: - //IF b15 = 1 OR(b23 = 1 AND(b16 - 22 AND b24 - 30) > 0) THEN b31 = 1 ELSE b31 = 0 - return forceIRQ || (masterEnable && ((irqEnable & irqFlag) > 0)); - } - - public bool tick() { - if (edgeInterruptTrigger) { - edgeInterruptTrigger = false; - //Console.WriteLine("[IRQ] Triggering DMA"); - return true; - } - return false; - } - } - - private sealed class Channel : AChannel { - - private uint baseAddress; - private uint blockSize; - private uint blockCount; - - private uint transferDirection; - private uint memoryStep; - private uint choppingEnable; - private uint syncMode; - private uint choppingDMAWindowSize; - private uint choppingCPUWindowSize; - private bool enable; - private bool trigger; - - private uint unknownBit29; - private uint unknownBit30; - - private BUS bus; - private InterruptChannel interrupt; - private int channelNumber; - - private uint pendingBlocks; - - public Channel(int channelNumber, InterruptChannel interrupt, BUS bus) { - this.channelNumber = channelNumber; - this.interrupt = interrupt; - this.bus = bus; - } - - public override uint load(uint register) { - switch (register) { - case 0: return baseAddress; - case 4: return blockCount << 16 | blockSize; - case 8: return loadChannelControl(); - default: return 0; - } - } - - private uint loadChannelControl() { - uint channelControl = 0; - - channelControl |= transferDirection; - channelControl |= (memoryStep == 4 ? 0 : 1u) << 1; - channelControl |= choppingEnable << 8; - channelControl |= syncMode << 9; - channelControl |= choppingDMAWindowSize << 16; - channelControl |= choppingCPUWindowSize << 20; - channelControl |= (enable ? 1u : 0) << 24; - channelControl |= (trigger ? 1u : 0) << 28; - channelControl |= unknownBit29 << 29; - channelControl |= unknownBit30 << 30; - - if (channelNumber == 6) { - return channelControl & 0x5000_0002 | 0x2; - } - - return channelControl; - } - - public override void write(uint register, uint value) { - switch (register) { - case 0: baseAddress = value & 0xFFFFFF; break; - case 4: blockCount = value >> 16; blockSize = value & 0xFFFF; break; - case 8: writeChannelControl(value); break; - default: Console.WriteLine($"Unhandled Write on DMA Channel: {channelNumber} register: {register} value: {value}"); break; - } - } - - private void writeChannelControl(uint value) { - transferDirection = value & 0x1; - memoryStep = (uint)(((value >> 1) & 0x1) == 0 ? 4 : -4); - choppingEnable = (value >> 8) & 0x1; - syncMode = (value >> 9) & 0x3; - choppingDMAWindowSize = (value >> 16) & 0x7; - choppingCPUWindowSize = (value >> 20) & 0x7; - enable = ((value >> 24) & 0x1) != 0; - trigger = ((value >> 28) & 0x1) != 0; - unknownBit29 = (value >> 29) & 0x1; - unknownBit30 = (value >> 30) & 0x1; - - if (!enable) pendingBlocks = 0; - - handleDMA(); - } - - private void handleDMA() { - if (!isActive() || !interrupt.isDMAControlMasterEnabled(channelNumber)) return; - if (syncMode == 0) { - //if (choppingEnable == 1) { - // Console.WriteLine($"[DMA] Chopping Syncmode 0 not supported. DmaWindow: {choppingDMAWindowSize} CpuWindow: {choppingCPUWindowSize}"); - //} - - blockCopy(blockSize == 0 ? 0x10_000 : blockSize); - finishDMA(); - - } else if (syncMode == 1) { - // HACK: - // GPUIn: Bypass blocks to elude mdec/gpu desync as MDEC is actually too fast decoding blocks - // MdecIn: GranTurismo produces some artifacts that still needs to be checked otherwise it's ok on other games i've checked - if (channelNumber == 2 && transferDirection == 1 || channelNumber == 0) { - blockCopy(blockSize * blockCount); - finishDMA(); - return; - } - - trigger = false; - pendingBlocks = blockCount; - transferBlockIfPending(); - } else if (syncMode == 2) { - linkedList(); - finishDMA(); - } - } - - private void finishDMA() { - enable = false; - trigger = false; - - interrupt.handleInterrupt(channelNumber); - } - - private void blockCopy(uint size) { - if (transferDirection == 0) { //To Ram - - switch (channelNumber) { - case 1: bus.DmaFromMdecOut(baseAddress, (int)size); break; - case 2: bus.DmaFromGpu(baseAddress, (int)size); break; - case 3: bus.DmaFromCD(baseAddress, (int)size); break; - case 4: bus.DmaFromSpu(baseAddress, (int)size); break; - case 6: bus.DmaOTC(baseAddress, (int)size); break; - default: Console.WriteLine($"[DMA] [BLOCK COPY] Unsupported Channel (to Ram) {channelNumber}"); break; - } - - baseAddress += memoryStep * size; - - } else { //From Ram - - Span<uint> dma = bus.DmaFromRam(baseAddress & 0x1F_FFFC, size); - - switch (channelNumber) { - case 0: bus.DmaToMdecIn(dma); break; - case 2: bus.DmaToGpu(dma); break; - case 4: bus.DmaToSpu(dma); break; - default: Console.WriteLine($"[DMA] [BLOCK COPY] Unsupported Channel (from Ram) {channelNumber}"); break; - } - - baseAddress += memoryStep * size; - } - - } - - private void linkedList() { - uint header = 0; - uint linkedListHardStop = 0xFFFF; //an arbitrary value to avoid infinity linked lists as we don't run the cpu in between blocks - - while ((header & 0x800000) == 0 && linkedListHardStop-- != 0) { - header = bus.LoadFromRam(baseAddress); - uint size = header >> 24; - //Console.WriteLine($"[DMA] [LinkedList] Header: {baseAddress:x8} size: {size}"); - - if (size > 0) { - baseAddress = (baseAddress + 4) & 0x1ffffc; - Span<uint> load = bus.DmaFromRam(baseAddress, size); - //Console.WriteLine($"[DMA] [LinkedList] DMAtoGPU size: {load.Length}"); - bus.DmaToGpu(load); - } - - if (baseAddress == (header & 0x1ffffc)) break; //Tekken2 hangs here if not handling this posible forever loop - baseAddress = header & 0x1ffffc; - } - - } - - //0 Start immediately and transfer all at once (used for CDROM, OTC) needs TRIGGER - private bool isActive() => syncMode == 0 ? enable && trigger : enable; - - public void transferBlockIfPending() { - //TODO: check if device can actually transfer. Here we assume devices are always - // capable of processing the dmas and never busy. - if (pendingBlocks > 0) { - pendingBlocks--; - blockCopy(blockSize); - - if (pendingBlocks == 0) { - finishDMA(); - } - } - } - } - - AChannel[] channels = new AChannel[8]; - - public DMA(BUS bus) { - InterruptChannel interrupt = new InterruptChannel(); - channels[0] = new Channel(0, interrupt, bus); - channels[1] = new Channel(1, interrupt, bus); - channels[2] = new Channel(2, interrupt, bus); - channels[3] = new Channel(3, interrupt, bus); - channels[4] = new Channel(4, interrupt, bus); - channels[5] = new Channel(5, interrupt, bus); - channels[6] = new Channel(6, interrupt, bus); - channels[7] = interrupt; - } - - public uint load(uint addr) { - uint channel = (addr & 0x70) >> 4; - uint register = addr & 0xF; - //Console.WriteLine("DMA load " + channel + " " + register + ":" + channels[channel].load(register).ToString("x8")); - return channels[channel].load(register); - } - - public void write(uint addr, uint value) { - uint channel = (addr & 0x70) >> 4; - uint register = addr & 0xF; - //Console.WriteLine("DMA write " + channel + " " + register + ":" + value.ToString("x8")); - - channels[channel].write(register, value); - } - - public bool tick() { - for (int i = 0; i < 7; i++) { - ((Channel)channels[i]).transferBlockIfPending(); - } - return ((InterruptChannel)channels[7]).tick(); - } - - } -} diff --git a/ProjectPSX/Devices/DMA/Channel.cs b/ProjectPSX/Devices/DMA/Channel.cs new file mode 100644 index 0000000..ea34d55 --- /dev/null +++ b/ProjectPSX/Devices/DMA/Channel.cs @@ -0,0 +1,5 @@ +namespace ProjectPSX.Devices; +public abstract class Channel { + public abstract void write(uint register, uint value); + public abstract uint load(uint regiter); +} diff --git a/ProjectPSX/Devices/DMA/DMA.cs b/ProjectPSX/Devices/DMA/DMA.cs new file mode 100644 index 0000000..d619e56 --- /dev/null +++ b/ProjectPSX/Devices/DMA/DMA.cs @@ -0,0 +1,42 @@ +using System; + +namespace ProjectPSX.Devices; +public class DMA { + + Channel[] channels = new Channel[8]; + + public DMA(BUS bus) { + var interrupt = new InterruptChannel(); + channels[0] = new DmaChannel(0, interrupt, bus); + channels[1] = new DmaChannel(1, interrupt, bus); + channels[2] = new DmaChannel(2, interrupt, bus); + channels[3] = new DmaChannel(3, interrupt, bus); + channels[4] = new DmaChannel(4, interrupt, bus); + channels[5] = new DmaChannel(5, interrupt, bus); + channels[6] = new DmaChannel(6, interrupt, bus); + channels[7] = interrupt; + } + + public uint load(uint addr) { + var channel = (addr & 0x70) >> 4; + var register = addr & 0xF; + //Console.WriteLine("DMA load " + channel + " " + register + ":" + channels[channel].load(register).ToString("x8")); + return channels[channel].load(register); + } + + public void write(uint addr, uint value) { + var channel = (addr & 0x70) >> 4; + var register = addr & 0xF; + //Console.WriteLine("DMA write " + channel + " " + register + ":" + value.ToString("x8")); + + channels[channel].write(register, value); + } + + public bool tick() { + for (var i = 0; i < 7; i++) { + ((DmaChannel)channels[i]).transferBlockIfPending(); + } + return ((InterruptChannel)channels[7]).tick(); + } + +} diff --git a/ProjectPSX/Devices/DMA/DmaChannel.cs b/ProjectPSX/Devices/DMA/DmaChannel.cs new file mode 100644 index 0000000..63ea45e --- /dev/null +++ b/ProjectPSX/Devices/DMA/DmaChannel.cs @@ -0,0 +1,193 @@ +using System; + +namespace ProjectPSX.Devices; +public sealed class DmaChannel : Channel { + + private uint baseAddress; + private uint blockSize; + private uint blockCount; + + private uint transferDirection; + private uint memoryStep; + private uint choppingEnable; + private uint syncMode; + private uint choppingDMAWindowSize; + private uint choppingCPUWindowSize; + private bool enable; + private bool trigger; + + private uint unknownBit29; + private uint unknownBit30; + + private BUS bus; + private InterruptChannel interrupt; + private int channelNumber; + + private uint pendingBlocks; + + public DmaChannel(int channelNumber, InterruptChannel interrupt, BUS bus) { + this.channelNumber = channelNumber; + this.interrupt = interrupt; + this.bus = bus; + } + + public override uint load(uint register) { + switch (register) { + case 0: return baseAddress; + case 4: return blockCount << 16 | blockSize; + case 8: return loadChannelControl(); + default: return 0; + } + } + + private uint loadChannelControl() { + uint channelControl = 0; + + channelControl |= transferDirection; + channelControl |= (memoryStep == 4 ? 0 : 1u) << 1; + channelControl |= choppingEnable << 8; + channelControl |= syncMode << 9; + channelControl |= choppingDMAWindowSize << 16; + channelControl |= choppingCPUWindowSize << 20; + channelControl |= (enable ? 1u : 0) << 24; + channelControl |= (trigger ? 1u : 0) << 28; + channelControl |= unknownBit29 << 29; + channelControl |= unknownBit30 << 30; + + if (channelNumber == 6) { + return channelControl & 0x5000_0002 | 0x2; + } + + return channelControl; + } + + public override void write(uint register, uint value) { + switch (register) { + case 0: baseAddress = value & 0xFFFFFF; break; + case 4: blockCount = value >> 16; blockSize = value & 0xFFFF; break; + case 8: writeChannelControl(value); break; + default: Console.WriteLine($"Unhandled Write on DMA Channel: {channelNumber} register: {register} value: {value}"); break; + } + } + + private void writeChannelControl(uint value) { + transferDirection = value & 0x1; + memoryStep = (uint)((value >> 1 & 0x1) == 0 ? 4 : -4); + choppingEnable = value >> 8 & 0x1; + syncMode = value >> 9 & 0x3; + choppingDMAWindowSize = value >> 16 & 0x7; + choppingCPUWindowSize = value >> 20 & 0x7; + enable = (value >> 24 & 0x1) != 0; + trigger = (value >> 28 & 0x1) != 0; + unknownBit29 = value >> 29 & 0x1; + unknownBit30 = value >> 30 & 0x1; + + if (!enable) pendingBlocks = 0; + + handleDMA(); + } + + private void handleDMA() { + if (!isActive() || !interrupt.isDMAControlMasterEnabled(channelNumber)) return; + if (syncMode == 0) { + //if (choppingEnable == 1) { + // Console.WriteLine($"[DMA] Chopping Syncmode 0 not supported. DmaWindow: {choppingDMAWindowSize} CpuWindow: {choppingCPUWindowSize}"); + //} + + blockCopy(blockSize == 0 ? 0x10_000 : blockSize); + finishDMA(); + + } else if (syncMode == 1) { + // HACK: + // GPUIn: Bypass blocks to elude mdec/gpu desync as MDEC is actually too fast decoding blocks + // MdecIn: GranTurismo produces some artifacts that still needs to be checked otherwise it's ok on other games i've checked + if (channelNumber == 2 && transferDirection == 1 || channelNumber == 0) { + blockCopy(blockSize * blockCount); + finishDMA(); + return; + } + + trigger = false; + pendingBlocks = blockCount; + transferBlockIfPending(); + } else if (syncMode == 2) { + linkedList(); + finishDMA(); + } + } + + private void finishDMA() { + enable = false; + trigger = false; + + interrupt.handleInterrupt(channelNumber); + } + + private void blockCopy(uint size) { + if (transferDirection == 0) { //To Ram + + switch (channelNumber) { + case 1: bus.DmaFromMdecOut(baseAddress, (int)size); break; + case 2: bus.DmaFromGpu(baseAddress, (int)size); break; + case 3: bus.DmaFromCD(baseAddress, (int)size); break; + case 4: bus.DmaFromSpu(baseAddress, (int)size); break; + case 6: bus.DmaOTC(baseAddress, (int)size); break; + default: Console.WriteLine($"[DMA] [BLOCK COPY] Unsupported Channel (to Ram) {channelNumber}"); break; + } + + baseAddress += memoryStep * size; + + } else { //From Ram + + var dma = bus.DmaFromRam(baseAddress & 0x1F_FFFC, size); + + switch (channelNumber) { + case 0: bus.DmaToMdecIn(dma); break; + case 2: bus.DmaToGpu(dma); break; + case 4: bus.DmaToSpu(dma); break; + default: Console.WriteLine($"[DMA] [BLOCK COPY] Unsupported Channel (from Ram) {channelNumber}"); break; + } + + baseAddress += memoryStep * size; + } + + } + + private void linkedList() { + uint header = 0; + uint linkedListHardStop = 0xFFFF; //an arbitrary value to avoid infinity linked lists as we don't run the cpu in between blocks + + while ((header & 0x800000) == 0 && linkedListHardStop-- != 0) { + header = bus.LoadFromRam(baseAddress); + var size = header >> 24; + //Console.WriteLine($"[DMA] [LinkedList] Header: {baseAddress:x8} size: {size}"); + + if (size > 0) { + baseAddress = baseAddress + 4 & 0x1ffffc; + var load = bus.DmaFromRam(baseAddress, size); + //Console.WriteLine($"[DMA] [LinkedList] DMAtoGPU size: {load.Length}"); + bus.DmaToGpu(load); + } + + if (baseAddress == (header & 0x1ffffc)) break; //Tekken2 hangs here if not handling this posible forever loop + baseAddress = header & 0x1ffffc; + } + + } + + //0 Start immediately and transfer all at once (used for CDROM, OTC) needs TRIGGER + private bool isActive() => syncMode == 0 ? enable && trigger : enable; + + public void transferBlockIfPending() { + //TODO: check if device can actually transfer. Here we assume devices are always + // capable of processing the dmas and never busy. + if (pendingBlocks > 0) { + pendingBlocks--; + blockCopy(blockSize); + + if (pendingBlocks == 0) { + finishDMA(); + } + } + } +} diff --git a/ProjectPSX/Devices/DMA/InterruptChannel.cs b/ProjectPSX/Devices/DMA/InterruptChannel.cs new file mode 100644 index 0000000..7e45e27 --- /dev/null +++ b/ProjectPSX/Devices/DMA/InterruptChannel.cs @@ -0,0 +1,92 @@ +using System; + +namespace ProjectPSX.Devices; +public sealed class InterruptChannel : Channel { + + // 1F8010F0h DPCR - DMA Control register + private uint control; + + // 1F8010F4h DICR - DMA Interrupt register + private bool forceIRQ; + private uint irqEnable; + private bool masterEnable; + private uint irqFlag; + private bool masterFlag; + + private bool edgeInterruptTrigger; + + public InterruptChannel() { + control = 0x07654321; + } + + public override uint load(uint register) { + switch (register) { + case 0: return control; + case 4: return loadInterrupt(); + case 6: return loadInterrupt() >> 16; //castlevania symphony of the night and dino crisis 2 ask for this + default: Console.WriteLine("Unhandled register on interruptChannel DMA load " + register); return 0xFFFF_FFFF; + } + } + + private uint loadInterrupt() { + uint interruptRegister = 0; + + interruptRegister |= (forceIRQ ? 1u : 0) << 15; + interruptRegister |= irqEnable << 16; + interruptRegister |= (masterEnable ? 1u : 0) << 23; + interruptRegister |= irqFlag << 24; + interruptRegister |= (masterFlag ? 1u : 0) << 31; + + return interruptRegister; + } + + public override void write(uint register, uint value) { + //Console.WriteLine("irqflag pre: " + irqFlag.ToString("x8")); + switch (register) { + case 0: control = value; break; + case 4: writeInterrupt(value); break; + case 6: writeInterrupt(value << 16 | (forceIRQ ? 1u : 0) << 15); break; + default: Console.WriteLine($"Unhandled write on DMA Interrupt register {register}"); break; + } + //Console.WriteLine("irqflag post: " + irqFlag.ToString("x8")); + } + + private void writeInterrupt(uint value) { + forceIRQ = (value >> 15 & 0x1) != 0; + irqEnable = value >> 16 & 0x7F; + masterEnable = (value >> 23 & 0x1) != 0; + irqFlag &= ~(value >> 24 & 0x7F); + + masterFlag = updateMasterFlag(); + } + + public void handleInterrupt(int channel) { + //IRQ flags in Bit(24 + n) are set upon DMAn completion - but caution - they are set ONLY if enabled in Bit(16 + n). + if ((irqEnable & 1 << channel) != 0) { + irqFlag |= (uint)(1 << channel); + } + + //Console.WriteLine($"MasterFlag: {masterFlag} irqEnable16: {irqEnable:x8} irqFlag24: {irqFlag:x8} {forceIRQ} {masterEnable} {((irqEnable & irqFlag) > 0)}"); + masterFlag = updateMasterFlag(); + edgeInterruptTrigger |= masterFlag; + } + + public bool isDMAControlMasterEnabled(int channelNumber) { + return (control >> 3 >> 4 * channelNumber & 0x1) != 0; + } + + private bool updateMasterFlag() { + //Bit31 is a simple readonly flag that follows the following rules: + //IF b15 = 1 OR(b23 = 1 AND(b16 - 22 AND b24 - 30) > 0) THEN b31 = 1 ELSE b31 = 0 + return forceIRQ || masterEnable && (irqEnable & irqFlag) > 0; + } + + public bool tick() { + if (edgeInterruptTrigger) { + edgeInterruptTrigger = false; + //Console.WriteLine("[IRQ] Triggering DMA"); + return true; + } + return false; + } +} From 73b1bf799830ed1cab1fd1a9616107c9811e6327 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Mon, 19 Dec 2022 19:33:34 +0100 Subject: [PATCH 50/72] GPU: Handle 1w xh VRAM reads This fixes Final Fantasy 9 hang after first combat --- ProjectPSX/Devices/GPU/GPU.cs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index 42dea5e..36cd9e1 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -301,17 +301,14 @@ private void WriteToVRAM(uint value) { [MethodImpl(MethodImplOptions.AggressiveInlining)] private uint readFromVRAM() { - ushort pixel0 = vram.GetPixelBGR555(vramTransfer.x++ & 0x3FF, vramTransfer.y & 0x1FF); - ushort pixel1 = vram.GetPixelBGR555(vramTransfer.x++ & 0x3FF, vramTransfer.y & 0x1FF); - - if (vramTransfer.x == vramTransfer.origin_x + vramTransfer.w) { - vramTransfer.x -= vramTransfer.w; - vramTransfer.y++; - } + ushort pixel0 = vram.GetPixelBGR555(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF); + stepVramTransfer(); + ushort pixel1 = vram.GetPixelBGR555(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF); + stepVramTransfer(); vramTransfer.halfWords -= 2; - if(vramTransfer.halfWords == 0) { + if (vramTransfer.halfWords == 0) { isReadyToSendVRAMToCPU = false; isReadyToReceiveDMABlock = true; } @@ -319,6 +316,14 @@ private uint readFromVRAM() { return (uint)(pixel1 << 16 | pixel0); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void stepVramTransfer() { + if (++vramTransfer.x == vramTransfer.origin_x + vramTransfer.w) { + vramTransfer.x -= vramTransfer.w; + vramTransfer.y++; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void drawVRAMPixel(ushort color1555) { if (!checkMaskBeforeDraw || vram.GetPixelRGB888(vramTransfer.x, vramTransfer.y) >> 24 == 0) { @@ -326,12 +331,7 @@ private void drawVRAMPixel(ushort color1555) { vram1555.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, color1555); } - vramTransfer.x++; - - if (vramTransfer.x == vramTransfer.origin_x + vramTransfer.w) { - vramTransfer.x -= vramTransfer.w; - vramTransfer.y++; - } + stepVramTransfer(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] From a797a254314f3931aa2d50abc4d4eb694de51deb Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Fri, 23 Dec 2022 18:27:45 +0100 Subject: [PATCH 51/72] Winforms: Make project debugable again This has been broken since the GDI merge. Somehow on release was working. --- ProjectPSX.WinForms/ProjectPSX.WinForms.csproj | 1 + ProjectPSX.WinForms/UI/Window.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj index 718dfcf..9996dd1 100644 --- a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj +++ b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj @@ -5,6 +5,7 @@ <TargetFramework>net7.0-windows</TargetFramework> <UseWindowsForms>true</UseWindowsForms> <DisableWinExeOutputInference>true</DisableWinExeOutputInference> + <AllowUnsafeBlocks>True</AllowUnsafeBlocks> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> diff --git a/ProjectPSX.WinForms/UI/Window.cs b/ProjectPSX.WinForms/UI/Window.cs index 5734835..73ce2f2 100644 --- a/ProjectPSX.WinForms/UI/Window.cs +++ b/ProjectPSX.WinForms/UI/Window.cs @@ -68,6 +68,8 @@ public Window() { screen.MouseDoubleClick += new MouseEventHandler(toggleDebug); Controls.Add(screen); + // Debug will crash if not: + CheckForIllegalCrossThreadCalls = false; KeyDown += new KeyEventHandler(handleJoyPadDown); KeyUp += new KeyEventHandler(handleJoyPadUp); From b955d691f06534ea191004ce5cc19f2b8100c811 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Fri, 23 Dec 2022 21:29:19 +0100 Subject: [PATCH 52/72] CDROM: Debug commands dictionary --- ProjectPSX/Devices/CDROM/CDROM.cs | 44 +++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/ProjectPSX/Devices/CDROM/CDROM.cs b/ProjectPSX/Devices/CDROM/CDROM.cs index 9c0e1a4..9c7a36a 100644 --- a/ProjectPSX/Devices/CDROM/CDROM.cs +++ b/ProjectPSX/Devices/CDROM/CDROM.cs @@ -107,7 +107,7 @@ private enum Mode { private int counter; private Queue<DelayedInterrupt> interruptQueue = new Queue<DelayedInterrupt>(); - public class DelayedInterrupt { + public class DelayedInterrupt { public int delay; public byte interrupt; @@ -147,10 +147,44 @@ public CDROM(CD cd, SPU spu) { private bool edgeTrigger; + private Dictionary<uint, string> commands = new Dictionary<uint, string> { + [0x01] = "Cmd_01_GetStat", + [0x02] = "Cmd_02_SetLoc", + [0x03] = "Cmd_03_Play", + [0x04] = "Cmd_04_Forward", + [0x05] = "Cmd_05_Backward", + [0x06] = "Cmd_06_ReadN", + [0x07] = "Cmd_07_MotorOn", + [0x08] = "Cmd_08_Stop", + [0x09] = "Cmd_09_Pause", + [0x0A] = "Cmd_0A_Init", + [0x0B] = "Cmd_0B_Mute", + [0x0C] = "Cmd_0C_Demute", + [0x0D] = "Cmd_0D_SetFilter", + [0x0E] = "Cmd_0E_SetMode", + [0x0F] = "Cmd_0F_GetParam", + [0x10] = "Cmd_10_GetLocL", + [0x11] = "Cmd_11_GetLocP", + [0x12] = "Cmd_12_SetSession", + [0x13] = "Cmd_13_GetTN", + [0x14] = "Cmd_14_GetTD", + [0x15] = "Cmd_15_SeekL", + [0x16] = "Cmd_16_SeekP", + [0x17] = "--- [Unimplemented]", + [0x18] = "--- [Unimplemented]", + [0x19] = "Cmd_19_Test", + [0x1A] = "Cmd_1A_GetID", + [0x1B] = "Cmd_1B_ReadS", + [0x1C] = "Cmd_1C_Reset [Unimplemented]", + [0x1D] = "Cmd_1D_GetQ [Unimplemented]", + [0x1E] = "Cmd_1E_ReadTOC", + [0x1F] = "Cmd_1F_VideoCD", + }; + public bool tick(int cycles) { counter += cycles; - if(interruptQueue.Count != 0) { + if (interruptQueue.Count != 0) { var delayedInterrupt = interruptQueue.Peek(); delayedInterrupt.delay -= cycles; } @@ -345,8 +379,8 @@ public void write(uint addr, uint value) { case 0x1F801801: if (INDEX == 0) { if (cdDebug) { - Console.BackgroundColor = ConsoleColor.Yellow; - Console.WriteLine($"[CDROM] [W01.0] [COMMAND] >>> {value:x2}"); + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($"[CDROM] [W01.0] [COMMAND] {value:x2} {commands.GetValueOrDefault(value, "---")}"); Console.ResetColor(); } ExecuteCommand(value); @@ -914,7 +948,7 @@ private void Cmd_1E_ReadTOC() { responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x40 }); interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); - } + } private void Cmd_5x_lockUnlock() { interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); From 29488cd92b9854768e6f5e2862c714812cabb593 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Mon, 26 Dec 2022 21:56:00 +0100 Subject: [PATCH 53/72] CDROM: Bump default command delay to 50000 PSX SPX and Jakub's CDROM tests average around 50000 cycles on many commands so set it as default. Puzzle Fighter is now playable (previously was hanging on boot because cdrom timmings) --- ProjectPSX/Util/Extension.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPSX/Util/Extension.cs b/ProjectPSX/Util/Extension.cs index d21651c..8008b4c 100644 --- a/ProjectPSX/Util/Extension.cs +++ b/ProjectPSX/Util/Extension.cs @@ -9,7 +9,7 @@ public static void EnqueueRange<T>(this Queue<T> queue, Span<T> parameters) { queue.Enqueue(parameter); } - public static void EnqueueDelayedInterrupt(this Queue<DelayedInterrupt> queue, byte interrupt, int delay = 5000) { + public static void EnqueueDelayedInterrupt(this Queue<DelayedInterrupt> queue, byte interrupt, int delay = 50_000) { queue.Enqueue(new DelayedInterrupt(delay, interrupt)); } } From 6e5315bd95be3a86f6a7e546f255f2b7f8af7d53 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sat, 7 Jan 2023 10:16:25 +0100 Subject: [PATCH 54/72] SPU: Push CDDA samples to capture buffer area even if cdda is disabled Samples from CDDA will end up on the capture buffer area even if the SPU flag for processig them is disabled. This is used by Vib Ribbon to read back the CDDA samples as data to generate the levels. And also probably by any equalizer like software if any... --- ProjectPSX/Devices/SPU/SPU.cs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/ProjectPSX/Devices/SPU/SPU.cs b/ProjectPSX/Devices/SPU/SPU.cs index defc44f..b7d0cf9 100644 --- a/ProjectPSX/Devices/SPU/SPU.cs +++ b/ProjectPSX/Devices/SPU/SPU.cs @@ -801,11 +801,15 @@ public bool tick(int cycles) { //Merge in CD audio (CDDA or XA) short cdL = 0; short cdR = 0; - if(control.cdAudioEnabled && cdBuffer.hasSamples()) { //Be sure theres something on the queue... - //todo refactor the byte/short queues and casts + + // CD Audio samples are always "consumed" even if cdAudio is disabled and will end + // on the capture buffer area of ram, this is needed for VibRibbon to be playable + if(cdBuffer.hasSamples()) { cdL = cdBuffer.readShort(); cdR = cdBuffer.readShort(); + } + if(control.cdAudioEnabled) { //Be sure theres something on the queue... //Apply Spu Cd In (CDDA/XA) Volume cdL = (short)((cdL * cdVolumeLeft) >> 15); cdR = (short)((cdR * cdVolumeRight) >> 15); @@ -1012,13 +1016,17 @@ public short saturateSample(int sample) { public unsafe Span<uint> processDmaLoad(int size) { //todo trigger interrupt Span<byte> dma = new Span<byte>(ram, 1024*512).Slice((int)ramDataTransferAddressInternal, size * 4); - //ramDataTransferAddressInternal and ramIrqAddress already are >> 3 + //ramDataTransferAddressInternal already is >> 3 while ramIrqAddress is set as ushort //so check if it's in the size range and trigger int - if (ramIrqAddress > ramDataTransferAddressInternal && ramIrqAddress < ramDataTransferAddressInternal + (size * 4)) { - interruptController.set(Interrupt.SPU); + uint ramIrqAddress32 = (uint)ramIrqAddress << 3; + if (ramIrqAddress32 > ramDataTransferAddressInternal && ramIrqAddress32 < (ramDataTransferAddressInternal + (size * 4))) { + if(control.irq9Enabled) { + status.irq9Flag = true; + interruptController.set(Interrupt.SPU); + } } - ramDataTransferAddressInternal = (uint)(ramDataTransferAddressInternal + (size * 4)); + ramDataTransferAddressInternal += (uint)(size * 4); return MemoryMarshal.Cast<byte, uint>(dma); } @@ -1033,8 +1041,14 @@ public unsafe void processDmaWrite(Span<uint> dma) { //todo trigger interrupt Span<byte> ramStartSpan = new Span<byte>(ram, 1024 * 512); Span<byte> ramDestSpan = ramStartSpan.Slice((int)ramDataTransferAddressInternal); - if (ramIrqAddress > ramDataTransferAddressInternal && ramIrqAddress < ramDataTransferAddressInternal + size) { - interruptController.set(Interrupt.SPU); + //ramDataTransferAddressInternal already is >> 3 while ramIrqAddress is set as ushort + //so check if it's in the size range and trigger int + uint ramIrqAddress32 = (uint)ramIrqAddress << 3; + if (ramIrqAddress32 > ramDataTransferAddressInternal && ramIrqAddress32 < (ramDataTransferAddressInternal + size)) { + if (control.irq9Enabled) { + status.irq9Flag = true; + interruptController.set(Interrupt.SPU); + } } if (destAddress <= 0x7FFFF) { From d009667d6410fb6790a9cd234f9d00022086b0b0 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sat, 7 Jan 2023 10:24:14 +0100 Subject: [PATCH 55/72] CDROM: Play CDDA even if mode .0 is disabled Somehow Vib Ribbon issues Play without setting this flag. It appears that the flag is only to ignore the missing error detection and correction of CDDA sectors thus enabling it to be Read(N/S). --- ProjectPSX/Devices/CDROM/CDROM.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPSX/Devices/CDROM/CDROM.cs b/ProjectPSX/Devices/CDROM/CDROM.cs index 9c7a36a..427a7df 100644 --- a/ProjectPSX/Devices/CDROM/CDROM.cs +++ b/ProjectPSX/Devices/CDROM/CDROM.cs @@ -236,7 +236,7 @@ public bool tick(int cycles) { //Handle Mode.Play: if (mode == Mode.Play) { - if (!mutedAudio && isCDDA) { + if (!mutedAudio) { applyVolume(readSector); spu.pushCdBufferSamples(readSector); } From b29440e6069fba25d9784129011131f82c1e614c Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 19 Mar 2023 15:17:15 +0100 Subject: [PATCH 56/72] Timers: Sync T1 ClockSource Hblank from GPU --- ProjectPSX/Devices/TIMERS.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ProjectPSX/Devices/TIMERS.cs b/ProjectPSX/Devices/TIMERS.cs index dd7d583..9d4b96f 100644 --- a/ProjectPSX/Devices/TIMERS.cs +++ b/ProjectPSX/Devices/TIMERS.cs @@ -137,8 +137,7 @@ public bool tick(int cyclesTicked) { //todo this needs rework counterValue += (ushort)cycles; cycles = 0; } else { - counterValue += (ushort)(cycles / 2160); - cycles %= 2160; + if (!prevHblank && hblank) counterValue += 1; } return handleIrq(); From 0a5797ed676c8d368bdf361b7c5cf5044766935a Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 19 Mar 2023 15:40:34 +0100 Subject: [PATCH 57/72] CDROM: Handle CDDA Report This fixes games that rely on it. Doom previously hanging on the loading screen is one example. --- ProjectPSX/Devices/CDROM/CDROM.cs | 33 +++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/ProjectPSX/Devices/CDROM/CDROM.cs b/ProjectPSX/Devices/CDROM/CDROM.cs index 427a7df..d0f77f0 100644 --- a/ProjectPSX/Devices/CDROM/CDROM.cs +++ b/ProjectPSX/Devices/CDROM/CDROM.cs @@ -243,16 +243,41 @@ public bool tick(int cycles) { if (isAutoPause && cd.isTrackChange) { responseBuffer.Enqueue(STAT); - interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT4_DATA_END); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT4_DATA_END, 1); - Cmd_09_Pause(); + STAT = 0x2; + mode = Mode.Idle; } if (isReport) { - //Console.WriteLine("Report Not Handled"); + (byte amm, byte ass, byte aff) = getMMSSFFfromLBA(readLoc); + Track track = cd.getTrackFromLoc(readLoc); + bool inPregap = readLoc < track.lbaStart; + byte index = inPregap ? (byte)0 : (byte)1; + + responseBuffer.Enqueue(STAT); + responseBuffer.Enqueue(track.number); + responseBuffer.Enqueue(index); + + if ((aff & 0x10) != 0) { + (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(Math.Abs(readLoc - track.lbaStart)); + + responseBuffer.Enqueue(DecToBcd(mm)); + responseBuffer.Enqueue((byte)(DecToBcd(ss) | 0x80)); + responseBuffer.Enqueue(DecToBcd(ff)); + } else { + responseBuffer.Enqueue(DecToBcd(amm)); + responseBuffer.Enqueue(DecToBcd(ass)); + responseBuffer.Enqueue(DecToBcd(aff)); } - return false; //CDDA isn't delivered to CPU and doesn't raise interrupt + responseBuffer.Enqueue(0x80); // peekLo + responseBuffer.Enqueue(0x80); // peekHi + + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT1_SECOND_RESPONSE_READ_PLAY, 1); + } + + return false; //CDDA without report isn't delivered to CPU and doesn't raise interrupt } //Handle Mode.Read: From b6d276087320f70349c39b8e052daa286f7935bd Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Mon, 10 Apr 2023 19:45:07 +0200 Subject: [PATCH 58/72] CPU: Branchless LWL/LWR SWL/SWR Even thought on the grand schema of things isnt that important it is still 2x faster than it's switch counter part. --- ProjectPSX/Core/CPU.cs | 40 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/ProjectPSX/Core/CPU.cs b/ProjectPSX/Core/CPU.cs index e8cf372..48b6de0 100644 --- a/ProjectPSX/Core/CPU.cs +++ b/ProjectPSX/Core/CPU.cs @@ -528,19 +528,15 @@ private static void LWL(CPU cpu) { uint aligned_addr = addr & 0xFFFF_FFFC; uint aligned_load = cpu.bus.load32(aligned_addr); - uint value = 0; uint LRValue = cpu.GPR[cpu.instr.rt]; if (cpu.instr.rt == cpu.memoryLoad.register) { LRValue = cpu.memoryLoad.value; } - switch (addr & 0b11) { - case 0: value = (LRValue & 0x00FF_FFFF) | (aligned_load << 24); break; - case 1: value = (LRValue & 0x0000_FFFF) | (aligned_load << 16); break; - case 2: value = (LRValue & 0x0000_00FF) | (aligned_load << 8); break; - case 3: value = aligned_load; break; - } + int shift = (int)((addr & 0x3) << 3); + uint mask = (uint)0x00FF_FFFF >> shift; + uint value = (LRValue & mask) | (aligned_load << (24 - shift)); delayedLoad(cpu, cpu.instr.rt, value); } @@ -550,19 +546,15 @@ private static void LWR(CPU cpu) { uint aligned_addr = addr & 0xFFFF_FFFC; uint aligned_load = cpu.bus.load32(aligned_addr); - uint value = 0; uint LRValue = cpu.GPR[cpu.instr.rt]; if (cpu.instr.rt == cpu.memoryLoad.register) { LRValue = cpu.memoryLoad.value; } - switch (addr & 0b11) { - case 0: value = aligned_load; break; - case 1: value = (LRValue & 0xFF00_0000) | (aligned_load >> 8); break; - case 2: value = (LRValue & 0xFFFF_0000) | (aligned_load >> 16); break; - case 3: value = (LRValue & 0xFFFF_FF00) | (aligned_load >> 24); break; - } + int shift = (int)((addr & 0x3) << 3); + uint mask = 0xFFFF_FF00 << (24 - shift); + uint value = (LRValue & mask) | (aligned_load >> shift); delayedLoad(cpu, cpu.instr.rt, value); } @@ -612,13 +604,9 @@ private static void SWR(CPU cpu) { uint aligned_addr = addr & 0xFFFF_FFFC; uint aligned_load = cpu.bus.load32(aligned_addr); - uint value = 0; - switch (addr & 0b11) { - case 0: value = cpu.GPR[cpu.instr.rt]; break; - case 1: value = (aligned_load & 0x0000_00FF) | (cpu.GPR[cpu.instr.rt] << 8); break; - case 2: value = (aligned_load & 0x0000_FFFF) | (cpu.GPR[cpu.instr.rt] << 16); break; - case 3: value = (aligned_load & 0x00FF_FFFF) | (cpu.GPR[cpu.instr.rt] << 24); break; - } + int shift = (int)((addr & 0x3) << 3); + uint mask = (uint)0x00FF_FFFF >> (24 - shift); + uint value = (aligned_load & mask) | (cpu.GPR[cpu.instr.rt] << shift); cpu.bus.write32(aligned_addr, value); } @@ -628,13 +616,9 @@ private static void SWL(CPU cpu) { uint aligned_addr = addr & 0xFFFF_FFFC; uint aligned_load = cpu.bus.load32(aligned_addr); - uint value = 0; - switch (addr & 0b11) { - case 0: value = (aligned_load & 0xFFFF_FF00) | (cpu.GPR[cpu.instr.rt] >> 24); break; - case 1: value = (aligned_load & 0xFFFF_0000) | (cpu.GPR[cpu.instr.rt] >> 16); break; - case 2: value = (aligned_load & 0xFF00_0000) | (cpu.GPR[cpu.instr.rt] >> 8); break; - case 3: value = cpu.GPR[cpu.instr.rt]; break; - } + int shift = (int)((addr & 0x3) << 3); + uint mask = 0xFFFF_FF00 << shift; + uint value = (aligned_load & mask) | (cpu.GPR[cpu.instr.rt] >> (24 - shift)); cpu.bus.write32(aligned_addr, value); } From ca49137150f2260823aa1d92b104ec8e7ce6b8ba Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Fri, 16 Jun 2023 21:59:49 +0200 Subject: [PATCH 59/72] GPU: Minor GP0_RenderRectangle refactor --- ProjectPSX/Devices/GPU/GPU.cs | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index 36cd9e1..4ac4132 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -774,8 +774,7 @@ private void GP0_RenderRectangle(Span<uint> buffer) { if (isTextured) { uint texture = buffer[pointer++]; - textureData.x = (byte)(texture & 0xFF); - textureData.y = (byte)((texture >> 8) & 0xFF); + textureData.val = (ushort)texture; ushort palette = (ushort)((texture >> 16) & 0xFFFF); primitive.clut.x = (short)((palette & 0x3f) << 4); @@ -787,24 +786,21 @@ private void GP0_RenderRectangle(Span<uint> buffer) { primitive.textureBase.y = (short)(textureYBase << 8); primitive.semiTransparencyMode = transparencyMode; - short width = 0; - short heigth = 0; + short width; + short heigth; - switch ((opcode & 0x18) >> 3) { - case 0x0: - uint hw = buffer[pointer++]; - width = (short)(hw & 0xFFFF); - heigth = (short)(hw >> 16); - break; - case 0x1: - width = 1; heigth = 1; - break; - case 0x2: - width = 8; heigth = 8; - break; - case 0x3: - width = 16; heigth = 16; - break; + uint type = (opcode & 0x18) >> 3; + + if (type == 0) { + uint hw = buffer[pointer++]; + width = (short)(hw & 0xFFFF); + heigth = (short)(hw >> 16); + } else if (type == 1) { + width = 1; heigth = 1; + } else if (type == 2) { + width = 8; heigth = 8; + } else { + width = 16; heigth = 16; } short y = signed11bit((uint)(yo + drawingYOffset)); From 6e396fc725eb7e81b6474a9762486dec801a78a2 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sat, 17 Jun 2023 15:38:53 +0200 Subject: [PATCH 60/72] GPU: Precalc rgbcolor on tris --- ProjectPSX/Devices/GPU/GPU.cs | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index 4ac4132..50e15f1 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -468,8 +468,9 @@ public void GP0_RenderPolygon(Span<uint> buffer) { if (!isShaded) { uint color = buffer[pointer++]; - c[0] = color; //triangle 1 opaque color - c[1] = color; //triangle 2 opaque color + uint rgbColor = (uint)GetRgbColor(color); + c[0] = rgbColor; //triangle 1 opaque color + c[1] = rgbColor; //triangle 2 opaque color } primitive.semiTransparencyMode = transparencyMode; @@ -550,8 +551,6 @@ private void rasterizeTri(Point2D v0, Point2D v1, Point2D v2, TextureData t0, Te int w1_row = orient2d(v2, v0, min) + bias1; int w2_row = orient2d(v0, v1, min) + bias2; - int baseColor = GetRgbColor(c0); - // Rasterize for (int y = min.y; y < max.y; y++) { // Barycentric coordinates at start of row @@ -578,7 +577,7 @@ private void rasterizeTri(Point2D v0, Point2D v1, Point2D v2, TextureData t0, Te } // reset default color of the triangle calculated outside the for as it gets overwriten as follows... - int color = baseColor; + int color = (int)c0; if (primitive.isShaded) { color0.val = c0; @@ -1151,22 +1150,6 @@ private void GP1_GPUInfo(uint value) { } } - private uint getTexpageFromGPU() { - uint texpage = 0; - - texpage |= (isTexturedRectangleYFlipped ? 1u : 0) << 13; - texpage |= (isTexturedRectangleXFlipped ? 1u : 0) << 12; - texpage |= (isTextureDisabled ? 1u : 0) << 11; - texpage |= (isDrawingToDisplayAllowed ? 1u : 0) << 10; - texpage |= (isDithered ? 1u : 0) << 9; - texpage |= (uint)(textureDepth << 7); - texpage |= (uint)(transparencyMode << 5); - texpage |= (uint)(textureYBase << 4); - texpage |= textureXBase; - - return texpage; - } - private int handleSemiTransp(int x, int y, int color, int semiTranspMode) { color0.val = (uint)vram.GetPixelRGB888(x, y); //back color1.val = (uint)color; //front From 3479510d912a10c4a1663a32218239d11be095c2 Mon Sep 17 00:00:00 2001 From: Blue <bluestorm.android@gmail.com> Date: Sun, 18 Jun 2023 10:06:12 +0200 Subject: [PATCH 61/72] GPU: Fix rect and tri drawingAreaRight/Bottom clipping --- ProjectPSX/Devices/GPU/GPU.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index 50e15f1..9cd30e8 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -536,8 +536,8 @@ private void rasterizeTri(Point2D v0, Point2D v1, Point2D v2, TextureData t0, Te /*clip*/ min.x = (short)Math.Max(minX, drawingAreaLeft); min.y = (short)Math.Max(minY, drawingAreaTop); - max.x = (short)Math.Min(maxX, drawingAreaRight); - max.y = (short)Math.Min(maxY, drawingAreaBottom); + max.x = (short)Math.Min(maxX, drawingAreaRight + 1); + max.y = (short)Math.Min(maxY, drawingAreaBottom + 1); int A01 = v0.y - v1.y, B01 = v1.x - v0.x; int A12 = v1.y - v2.y, B12 = v2.x - v1.x; @@ -819,8 +819,8 @@ private void GP0_RenderRectangle(Span<uint> buffer) { private void rasterizeRect(Point2D origin, Point2D size, TextureData texture, uint bgrColor, Primitive primitive) { int xOrigin = Math.Max(origin.x, drawingAreaLeft); int yOrigin = Math.Max(origin.y, drawingAreaTop); - int width = Math.Min(size.x, drawingAreaRight); - int height = Math.Min(size.y, drawingAreaBottom); + int width = Math.Min(size.x, drawingAreaRight + 1); + int height = Math.Min(size.y, drawingAreaBottom + 1); int uOrigin = texture.x + (xOrigin - origin.x); int vOrigin = texture.y + (yOrigin - origin.y); From af63b9606c8545db52c061699b993a2bc40bdfb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Cort=C3=A9s?= <bluestorm.android@gmail.com> Date: Mon, 25 Sep 2023 12:20:37 +0200 Subject: [PATCH 62/72] BUS: Hardcode 1F80_1054 to 0000_0805 Just to mess with unirom/openbios/cdrom tests --- ProjectPSX/Core/BUS.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ProjectPSX/Core/BUS.cs b/ProjectPSX/Core/BUS.cs index 1ff2c0c..d21abe6 100644 --- a/ProjectPSX/Core/BUS.cs +++ b/ProjectPSX/Core/BUS.cs @@ -8,7 +8,6 @@ namespace ProjectPSX { public class BUS { - //todo write32/16/8 unification pending .NET 7 IBinaryNumber //Memory private unsafe byte* ramPtr = (byte*)Marshal.AllocHGlobal(2048 * 1024); @@ -62,6 +61,8 @@ public unsafe uint load32(uint address) { } else if (addr < 0x1F80_1050) { return joypad.load(addr); } else if (addr < 0x1F80_1060) { + //HardCode SIO_STAT + if (addr == 0x1F80_1054) return 0x0000_0805; Console.WriteLine($"[BUS] Read Unsupported to SIO address: {addr:x8}"); return load<uint>(addr & 0xF, sio); } else if (addr < 0x1F80_1070) { From 99f4e954634e22bdddbe786f0b0a6ad00c253c79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Cort=C3=A9s?= <bluestorm.android@gmail.com> Date: Mon, 25 Sep 2023 12:26:59 +0200 Subject: [PATCH 63/72] CPU: Slow down pipeline on BUS BIOS access. This is a wild guess on access to BIOS. We gonna need this on the next CDROM refactor as tests actually expect access to BIOS to be real slow. --- ProjectPSX/Core/CPU.cs | 27 ++++++++++++++------------- ProjectPSX/Core/ProjectPSX.cs | 8 +++++--- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/ProjectPSX/Core/CPU.cs b/ProjectPSX/Core/CPU.cs index 48b6de0..ff143fd 100644 --- a/ProjectPSX/Core/CPU.cs +++ b/ProjectPSX/Core/CPU.cs @@ -71,7 +71,6 @@ public struct Instr { private Instr instr; //Debug - private long cycle; //current CPU cycle counter for debug public bool debug = false; public CPU(BUS bus) { @@ -106,8 +105,8 @@ public CPU(BUS bus) { }; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Run() { - fetchDecode(); + public int Run() { + int ticks = fetchDecode(); if (instr.value != 0) { //Skip Nops opcodeMainTable[instr.opcode](this); //Execute } @@ -121,6 +120,8 @@ public void Run() { //TTY(); //bios.verbose(PC_Now, GPR); + + return ticks; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -159,15 +160,9 @@ public void handleInterrupts() { } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void fetchDecode() { + private int fetchDecode() { //Executable address space is limited to ram and bios on psx uint maskedPC = PC & 0x1FFF_FFFF; - uint load; - if (maskedPC < 0x1F00_0000) { - load = bus.LoadFromRam(maskedPC); - } else { - load = bus.LoadFromBios(maskedPC); - } PC_Now = PC; PC = PC_Predictor; @@ -182,12 +177,17 @@ private void fetchDecode() { if ((PC_Now & 0x3) != 0) { COP0_GPR[BADA] = PC_Now; EXCEPTION(this, EX.LOAD_ADRESS_ERROR); - return; + return 1; } #endif - instr.value = load; - //cycle++; + if (maskedPC < 0x1F00_0000) { + instr.value = bus.LoadFromRam(maskedPC); + return 1; + } else { + instr.value = bus.LoadFromBios(maskedPC); + return 20; + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -406,6 +406,7 @@ private static void COP2(CPU cpu) { } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void MFC2(CPU cpu) => delayedLoad(cpu, cpu.instr.rt, cpu.gte.loadData(cpu.instr.rd)); [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/ProjectPSX/Core/ProjectPSX.cs b/ProjectPSX/Core/ProjectPSX.cs index e03d902..22e9d1d 100644 --- a/ProjectPSX/Core/ProjectPSX.cs +++ b/ProjectPSX/Core/ProjectPSX.cs @@ -50,12 +50,14 @@ public ProjectPSX(IHostWindow window, string diskFilename) { public void RunFrame() { //A lame mainloop with a workaround to be able to underclock. + int sync = 0; for (int i = 0; i < SYNC_LOOPS; i++) { - for (int j = 0; j < SYNC_CYCLES; j++) { - cpu.Run(); + while (sync < SYNC_CYCLES) { + sync += cpu.Run(); //cpu.handleInterrupts(); } - bus.tick(SYNC_CYCLES * MIPS_UNDERCLOCK); + sync -= SYNC_CYCLES; + bus.tick(SYNC_CYCLES * MIPS_UNDERCLOCK + 1); cpu.handleInterrupts(); } } From 947a5d96c2d248b00e14d54bd07fe3c63de3ecee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Cort=C3=A9s?= <bluestorm.android@gmail.com> Date: Sat, 30 Sep 2023 19:02:35 +0200 Subject: [PATCH 64/72] SPU: Fix main volume being too low. Turns out it's a volume type register (doh!) and it goes -0x4000 to 0x3FFF that needs to be translated to << 1 --- ProjectPSX/Devices/SPU/SPU.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ProjectPSX/Devices/SPU/SPU.cs b/ProjectPSX/Devices/SPU/SPU.cs index b7d0cf9..c216ccb 100644 --- a/ProjectPSX/Devices/SPU/SPU.cs +++ b/ProjectPSX/Devices/SPU/SPU.cs @@ -841,8 +841,8 @@ public bool tick(int cycles) { captureBufferPos = (captureBufferPos + 2) & 0x3FF; //Clamp sum - sumLeft = (Math.Clamp(sumLeft, -0x8000, 0x7FFF) * mainVolumeLeft) >> 15; - sumRight = (Math.Clamp(sumRight, -0x8000, 0x7FFF) * mainVolumeRight) >> 15; + sumLeft = (Math.Clamp(sumLeft, -0x8000, 0x7FFF) * (mainVolumeLeft << 1)) >> 15; + sumRight = (Math.Clamp(sumRight, -0x8000, 0x7FFF) * (mainVolumeRight << 1)) >> 15; //Add to samples bytes to output array spuOutput[spuOutputPointer++] = (byte)sumLeft; From 636a223e23dff9c89fd697592833fb82de22a08d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Cort=C3=A9s?= <bluestorm.android@gmail.com> Date: Sun, 15 Oct 2023 19:48:47 +0200 Subject: [PATCH 65/72] Project: Bump OpenTK to 4.8.1 --- ProjectPSX.OpenTK/AudioPlayer.cs | 8 +++++++- ProjectPSX.OpenTK/Program.cs | 1 - ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ProjectPSX.OpenTK/AudioPlayer.cs b/ProjectPSX.OpenTK/AudioPlayer.cs index 7022da2..e1bd897 100644 --- a/ProjectPSX.OpenTK/AudioPlayer.cs +++ b/ProjectPSX.OpenTK/AudioPlayer.cs @@ -52,7 +52,7 @@ public unsafe void UpdateAudio(byte[] samples) { queueLength++; } - if (AL.GetSourceState(audioSource) != ALSourceState.Playing) + if (GetSourceState(audioSource) != ALSourceState.Playing) AL.SourcePlay(audioSource); } @@ -62,5 +62,11 @@ public unsafe void UpdateAudio(byte[] samples) { ALC.DestroyContext(audioContext); ALC.CloseDevice(audioDevice); } + + public static ALSourceState GetSourceState(int sid) + { + AL.GetSource(sid, ALGetSourcei.SourceState, out var value); + return (ALSourceState)value; + } } } diff --git a/ProjectPSX.OpenTK/Program.cs b/ProjectPSX.OpenTK/Program.cs index a7bfb6e..86a0f96 100644 --- a/ProjectPSX.OpenTK/Program.cs +++ b/ProjectPSX.OpenTK/Program.cs @@ -11,7 +11,6 @@ static class Program { [STAThread] static void Main() { GameWindowSettings settings = new GameWindowSettings(); - settings.RenderFrequency = 60; settings.UpdateFrequency = 60; NativeWindowSettings nativeWindow = new NativeWindowSettings(); nativeWindow.API = ContextAPI.OpenGL; diff --git a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj index af03732..82b5d64 100644 --- a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj +++ b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj @@ -15,7 +15,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="OpenTK" Version="4.7.5" /> + <PackageReference Include="OpenTK" Version="4.8.1" /> </ItemGroup> <ItemGroup> From 72798c59a4386357a963dd0df53027a9aef4500f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Cort=C3=A9s?= <bluestorm.android@gmail.com> Date: Sun, 15 Oct 2023 19:49:13 +0200 Subject: [PATCH 66/72] Project: Bump naudio to 2.2.1 --- ProjectPSX.WinForms/ProjectPSX.WinForms.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj index 9996dd1..0adbc42 100644 --- a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj +++ b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj @@ -13,7 +13,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="NAudio.WinMM" Version="2.1.0" /> + <PackageReference Include="NAudio.WinMM" Version="2.2.1" /> </ItemGroup> <ItemGroup> From 8879c1c92a649e049875fbfd89de388511b61a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Cort=C3=A9s?= <bluestorm.android@gmail.com> Date: Sun, 5 Nov 2023 17:14:43 +0100 Subject: [PATCH 67/72] SPU: Wrap overflow ramDataTransferInternalAddress on DMA Load Monkey Hero tries to DMA at 0x80000 --- ProjectPSX/Devices/SPU/SPU.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/ProjectPSX/Devices/SPU/SPU.cs b/ProjectPSX/Devices/SPU/SPU.cs index c216ccb..2023912 100644 --- a/ProjectPSX/Devices/SPU/SPU.cs +++ b/ProjectPSX/Devices/SPU/SPU.cs @@ -1027,6 +1027,7 @@ public unsafe Span<uint> processDmaLoad(int size) { //todo trigger interrupt } ramDataTransferAddressInternal += (uint)(size * 4); + ramDataTransferAddressInternal &= 0x7FFFF; return MemoryMarshal.Cast<byte, uint>(dma); } From eb9f7e1915f24bee04e202ad61184e5ef60ca19c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Cort=C3=A9s?= <bluestorm.android@gmail.com> Date: Sat, 18 Nov 2023 10:46:12 +0100 Subject: [PATCH 68/72] Project: Update to .NET 8 It really goes brrrrrrr... --- ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj | 2 +- ProjectPSX.WinForms/ProjectPSX.WinForms.csproj | 2 +- ProjectPSX/ProjectPSX.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj index 82b5d64..ddd7d9c 100644 --- a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj +++ b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net7.0</TargetFramework> + <TargetFramework>net8.0</TargetFramework> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> diff --git a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj index 0adbc42..81d0781 100644 --- a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj +++ b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net7.0-windows</TargetFramework> + <TargetFramework>net8.0-windows7.0</TargetFramework> <UseWindowsForms>true</UseWindowsForms> <DisableWinExeOutputInference>true</DisableWinExeOutputInference> <AllowUnsafeBlocks>True</AllowUnsafeBlocks> diff --git a/ProjectPSX/ProjectPSX.csproj b/ProjectPSX/ProjectPSX.csproj index 2514a86..47a062c 100644 --- a/ProjectPSX/ProjectPSX.csproj +++ b/ProjectPSX/ProjectPSX.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Library</OutputType> - <TargetFramework>net7.0</TargetFramework> + <TargetFramework>net8.0</TargetFramework> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> From d2d7f58a87eea2da3d1afd4925dfc4f8465e311d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Cort=C3=A9s?= <bluestorm.android@gmail.com> Date: Mon, 20 May 2024 18:12:47 +0200 Subject: [PATCH 69/72] VRAM: Remove useless getters Turns up they appear when profiling... --- ProjectPSX/Devices/GPU/VRAM.cs | 4 ++-- ProjectPSX/Devices/GPU/VRAM1555.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ProjectPSX/Devices/GPU/VRAM.cs b/ProjectPSX/Devices/GPU/VRAM.cs index 4b05a6f..5ec4ebb 100644 --- a/ProjectPSX/Devices/GPU/VRAM.cs +++ b/ProjectPSX/Devices/GPU/VRAM.cs @@ -3,11 +3,11 @@ namespace ProjectPSX { public class VRAM { - public int[] Bits { get; private set; } + public int[] Bits; public const int Height = 512; public const int Width = 1024; - protected GCHandle BitsHandle { get; private set; } + protected GCHandle BitsHandle; public VRAM() { Bits = new int[Width * Height]; diff --git a/ProjectPSX/Devices/GPU/VRAM1555.cs b/ProjectPSX/Devices/GPU/VRAM1555.cs index a1e4780..17f6c3e 100644 --- a/ProjectPSX/Devices/GPU/VRAM1555.cs +++ b/ProjectPSX/Devices/GPU/VRAM1555.cs @@ -3,11 +3,11 @@ namespace ProjectPSX { public class VRAM1555 { - public ushort[] Bits { get; private set; } + public ushort[] Bits; public const int Height = 512; public const int Width = 1024; - protected GCHandle BitsHandle { get; private set; } + protected GCHandle BitsHandle; public VRAM1555() { Bits = new ushort[Width * Height]; From dce289f164d3bc46309025990bbd209061ea72a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Cort=C3=A9s?= <bluestorm.android@gmail.com> Date: Mon, 20 May 2024 18:56:29 +0200 Subject: [PATCH 70/72] GPU: Only process E1 if it actually changed --- ProjectPSX/Devices/GPU/GPU.cs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index 9cd30e8..9250673 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -116,6 +116,8 @@ private struct Color { private bool isTexturedRectangleXFlipped; private bool isTexturedRectangleYFlipped; + private uint drawModeBits; + private uint textureWindowBits = 0xFFFF_FFFF; private int preMaskX; private int preMaskY; @@ -199,12 +201,7 @@ public bool tick(int cycles) { public uint loadGPUSTAT() { uint GPUSTAT = 0; - GPUSTAT |= textureXBase; - GPUSTAT |= (uint)textureYBase << 4; - GPUSTAT |= (uint)transparencyMode << 5; - GPUSTAT |= (uint)textureDepth << 7; - GPUSTAT |= (uint)(isDithered ? 1 : 0) << 9; - GPUSTAT |= (uint)(isDrawingToDisplayAllowed ? 1 : 0) << 10; + GPUSTAT |= drawModeBits & 0x7FF; GPUSTAT |= (uint)maskWhileDrawing << 11; GPUSTAT |= (uint)(checkMaskBeforeDraw ? 1 : 0) << 12; GPUSTAT |= (uint)(isInterlaceField ? 1 : 0) << 13; @@ -268,7 +265,7 @@ public void writeGP0(uint value) { [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void processDma(Span<uint> dma) { if (mode == Mode.COMMAND) { - DecodeGP0Command(dma); + DecodeGP0Command(dma); } else { for (int i = 0; i < dma.Length; i++) { WriteToVRAM(dma[i]); @@ -494,11 +491,7 @@ public void GP0_RenderPolygon(Span<uint> buffer) { uint texpage = textureData >> 16; //SET GLOBAL GPU E1 - textureXBase = (byte)(texpage & 0xF); - textureYBase = (byte)((texpage >> 4) & 0x1); - transparencyMode = (byte)((texpage >> 5) & 0x3); - textureDepth = (byte)((texpage >> 7) & 0x3); - isTextureDisabled = isTextureDisabledAllowed && ((texpage >> 11) & 0x1) != 0; + GP0_E1_SetDrawMode(texpage); primitive.depth = textureDepth; primitive.textureBase.x = (short)(textureXBase << 6); @@ -986,6 +979,12 @@ private static int orient2d(Point2D a, Point2D b, Point2D c) { } private void GP0_E1_SetDrawMode(uint val) { + uint bits = val & 0xFF_FFFF; + + if (bits == drawModeBits) return; + + drawModeBits = bits; + textureXBase = (byte)(val & 0xF); textureYBase = (byte)((val >> 4) & 0x1); transparencyMode = (byte)((val >> 5) & 0x3); @@ -996,7 +995,7 @@ private void GP0_E1_SetDrawMode(uint val) { isTexturedRectangleXFlipped = ((val >> 12) & 0x1) != 0; isTexturedRectangleYFlipped = ((val >> 13) & 0x1) != 0; - //Console.WriteLine("[GPU] [GP0] DrawMode "); + //Console.WriteLine("[GPU] [GP0] DrawMode " + val.ToString("x8")); } private void GP0_E2_SetTextureWindow(uint val) { From 50130eab6bf73d7509d464b6009a794e7f78c5de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Cort=C3=A9s?= <bluestorm.android@gmail.com> Date: Mon, 20 May 2024 21:25:05 +0200 Subject: [PATCH 71/72] GPU: Avoid calling unneded GP1_e6 e7 e8 --- ProjectPSX/Devices/GPU/GPU.cs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index 9250673..6676915 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -116,7 +116,10 @@ private struct Color { private bool isTexturedRectangleXFlipped; private bool isTexturedRectangleYFlipped; - private uint drawModeBits; + private uint drawModeBits = 0xFFFF_FFFF; + private uint displayModeBits = 0xFFFF_FFFF; + uint displayVerticalRange = 0xFFFF_FFFF; + uint displayHorizontalRange = 0xFFFF_FFFF; private uint textureWindowBits = 0xFFFF_FFFF; private int preMaskX; @@ -1101,6 +1104,12 @@ private void GP1_05_DisplayVRAMStart(uint value) { } private void GP1_06_DisplayHorizontalRange(uint value) { + uint bits = value & 0xFF_FFFF; + + if (bits == displayHorizontalRange) return; + + displayHorizontalRange = bits; + displayX1 = (ushort)(value & 0xFFF); displayX2 = (ushort)((value >> 12) & 0xFFF); @@ -1108,6 +1117,12 @@ private void GP1_06_DisplayHorizontalRange(uint value) { } private void GP1_07_DisplayVerticalRange(uint value) { + uint bits = value & 0xFF_FFFF; + + if (bits == displayVerticalRange) return; + + displayVerticalRange = bits; + displayY1 = (ushort)(value & 0x3FF); displayY2 = (ushort)((value >> 10) & 0x3FF); @@ -1115,6 +1130,12 @@ private void GP1_07_DisplayVerticalRange(uint value) { } private void GP1_08_DisplayMode(uint value) { + uint bits = value & 0xFF_FFFF; + + if (bits == displayModeBits) return; + + displayModeBits = bits; + horizontalResolution1 = (byte)(value & 0x3); isVerticalResolution480 = (value & 0x4) != 0; isPal = (value & 0x8) != 0; From 35e314fea52fbcf3c41d75add614ba80e79c0a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Cort=C3=A9s?= <bluestorm.android@gmail.com> Date: Fri, 15 Nov 2024 18:06:23 +0100 Subject: [PATCH 72/72] Project: Update to .NET 9 --- ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj | 2 +- ProjectPSX.WinForms/ProjectPSX.WinForms.csproj | 2 +- ProjectPSX/ProjectPSX.csproj | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj index ddd7d9c..610a82b 100644 --- a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj +++ b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net8.0</TargetFramework> + <TargetFramework>net9.0</TargetFramework> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> diff --git a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj index 81d0781..c5115d3 100644 --- a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj +++ b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net8.0-windows7.0</TargetFramework> + <TargetFramework>net9.0-windows7.0</TargetFramework> <UseWindowsForms>true</UseWindowsForms> <DisableWinExeOutputInference>true</DisableWinExeOutputInference> <AllowUnsafeBlocks>True</AllowUnsafeBlocks> diff --git a/ProjectPSX/ProjectPSX.csproj b/ProjectPSX/ProjectPSX.csproj index 47a062c..c7918ea 100644 --- a/ProjectPSX/ProjectPSX.csproj +++ b/ProjectPSX/ProjectPSX.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Library</OutputType> - <TargetFramework>net8.0</TargetFramework> + <TargetFramework>net9.0</TargetFramework> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> @@ -10,7 +10,7 @@ </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> - <DefineConstants>TRACE</DefineConstants> + <DefineConstants>TRACE</DefineConstants> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup>