* Push Interop

* GDI Render: UI

* Gdi: Add BltMode

* Winform GDI: Handle y ranges
This is badly handled and X still left
This commit is contained in:
Pedro Cortés 2021-01-24 12:16:01 +01:00 committed by GitHub
parent 8912d88da4
commit 2f79a50d36
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 350 additions and 112 deletions

View file

@ -0,0 +1,8 @@
namespace ProjectPSX
{
internal static class ExternDll
{
public const string Gdi32 = "gdi32.dll";
public const string User32 = "user32.dll";
}
}

View file

@ -0,0 +1,121 @@
using System;
using System.Runtime.InteropServices;
using System.Security;
namespace ProjectPSX.Interop.Gdi32
{
internal enum BitmapCompression : uint
{
BI_RGB = 0,
BI_RLE8 = 1,
BI_RLE4 = 2,
BI_BITFIELDS = 3,
BI_JPEG = 4,
BI_PNG = 5,
}
[StructLayout(LayoutKind.Sequential)]
internal struct BitmapInfoHeader
{
public uint biSize;
public int biWidth;
public int biHeight;
public ushort biPlanes;
public ushort biBitCount;
public BitmapCompression biCompression;
public uint biSizeImage;
public int biXPelsPerMeter;
public int biYPelsPerMeter;
public uint biClrUsed;
public uint biClrImportant;
}
[StructLayout(LayoutKind.Sequential)]
internal struct RgbQuad
{
public byte rgbBlue;
public byte rgbGreen;
public byte rgbRed;
public byte rgbReserved;
}
[StructLayout(LayoutKind.Sequential)]
internal struct BitmapInfo
{
public BitmapInfoHeader bmiHeader;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public uint[] bmiColors;
}
internal enum ColorUsage : uint
{
DIB_RGB_COLORS = 0, /* color table in RGBs */
DIB_PAL_COLORS = 1, /* color table in palette indices */
}
internal enum RasterOp : uint
{
SRCCOPY = 0x00CC0020, // dest = source
SRCPAINT = 0x00EE0086, // dest = source OR dest
SRCAND = 0x008800C6, // dest = source AND dest
SRCINVERT = 0x00660046, // dest = source XOR dest
SRCERASE = 0x00440328, // dest = source AND (NOT dest )
NOTSRCCOPY = 0x00330008, // dest = (NOT source)
NOTSRCERASE = 0x001100A6, // dest = (NOT src) AND (NOT dest
MERGECOPY = 0x00C000CA, // dest = (source AND pattern)
MERGEPAINT = 0x00BB0226, // dest = (NOT source) OR dest
PATCOPY = 0x00F00021, // dest = pattern
PATPAINT = 0x00FB0A09, // dest = DPSnoo
PATINVERT = 0x005A0049, // dest = pattern XOR dest
DSTINVERT = 0x00550009, // dest = (NOT dest)
BLACKNESS = 0x00000042, // dest = BLACK
WHITENESS = 0x00FF0062, // dest = WHITE
NOMIRRORBITMAP = 0x80000000, // Do not Mirror the bitmap in this call
CAPTUREBLT = 0x40000000, // Include layered windows
}
internal enum BltMode : uint {
STRETCH_ANDSCANS = 0x01,
STRETCH_ORSCANS = 0x02,
STRETCH_DELETESCANS = 0x03,
STRETCH_HALFTONE = 0x04,
}
[SuppressUnmanagedCodeSecurity]
internal static class NativeMethods
{
[DllImport(ExternDll.Gdi32)]
internal static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport(ExternDll.Gdi32)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DeleteDC(IntPtr hdc);
[DllImport(ExternDll.Gdi32)]
internal static extern IntPtr CreateDIBSection(IntPtr hdc, [In] in BitmapInfo pbmi, ColorUsage usage,
out IntPtr ppvBits, IntPtr hSection, uint offset);
[DllImport(ExternDll.Gdi32)]
internal static extern IntPtr SelectObject(IntPtr hdc, IntPtr h);
[DllImport(ExternDll.Gdi32)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DeleteObject(IntPtr ho);
[DllImport(ExternDll.Gdi32)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool StretchBlt(IntPtr hdcDest, int xDest, int yDest, int wDest, int hDest,
IntPtr hdcSrc, int xSrc, int ySrc, int wSrc, int hSrc,
RasterOp rop);
[DllImport(ExternDll.Gdi32)]
internal static extern int SetStretchBltMode(IntPtr hdc, BltMode mode);
[DllImport(ExternDll.Gdi32)]
internal static extern int StretchDIBits(IntPtr hdc, int xDest, int yDest, int DestWidth, int DestHeight,
int xSrc, int ySrc, int SrcWidth, int SrcHeight, IntPtr lpBits,
[In] ref BitmapInfo lpbmi, ColorUsage iUsage, RasterOp rop);
}
}

View file

@ -0,0 +1,105 @@
using System;
using System.Runtime.InteropServices;
using ProjectPSX.Interop.Gdi32;
using Gdi32 = ProjectPSX.Interop.Gdi32.NativeMethods;
namespace ProjectPSX
{
internal class GdiBitmap : IDisposable
{
public readonly int BytesPerPixel = 4;
public readonly IntPtr DeviceContext;
public readonly IntPtr BitmapHandle;
public readonly int Width;
public readonly int Height;
public readonly IntPtr BitmapData;
private readonly IntPtr _oldObject;
private bool _disposed = false;
public GdiBitmap(int width, int height)
{
Width = width;
Height = height;
DeviceContext = Gdi32.CreateCompatibleDC(IntPtr.Zero);
var bitmapHeader = new BitmapInfoHeader
{
biSize = (uint)Marshal.SizeOf<BitmapInfoHeader>(),
biWidth = width,
biHeight = -height, // negative, top-down bitmap
biPlanes = 1,
biBitCount = (ushort)(8 * BytesPerPixel),
biCompression = BitmapCompression.BI_RGB,
};
var bitmapInfo = new BitmapInfo
{
bmiHeader = bitmapHeader,
};
BitmapHandle = Gdi32.CreateDIBSection(DeviceContext, in bitmapInfo, ColorUsage.DIB_RGB_COLORS,
out BitmapData, IntPtr.Zero, 0);
_oldObject = Gdi32.SelectObject(DeviceContext, BitmapHandle);
}
public unsafe void DrawPixel(int x, int y, int color) {
int* pixel = (int*)BitmapData;
pixel += x + (y * Width);
*pixel = color;
}
public unsafe int GetPixel(int x, int y) {
int* pixel = (int*)BitmapData;
pixel += x + (y * Width);
return *pixel;
}
~GdiBitmap()
{
Dispose(false);
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// TODO: dispose managed state (managed objects).
}
if (_oldObject != IntPtr.Zero)
{
Gdi32.SelectObject(DeviceContext, _oldObject);
}
if (BitmapHandle != IntPtr.Zero)
{
Gdi32.DeleteObject(BitmapHandle);
}
if (DeviceContext != IntPtr.Zero)
{
Gdi32.DeleteDC(DeviceContext);
}
_disposed = true;
}
}
}
}

View file

@ -0,0 +1,28 @@
using System;
using User32 = ProjectPSX.Interop.User32.NativeMethods;
namespace ProjectPSX
{
internal readonly ref struct GdiDeviceContext
{
private readonly IntPtr _handle;
private readonly IntPtr _hdc;
public GdiDeviceContext(IntPtr handle)
{
_handle = handle;
_hdc = User32.GetDC(handle);
}
public void Dispose()
{
User32.ReleaseDC(_handle, _hdc);
}
public static implicit operator IntPtr(GdiDeviceContext dc)
{
return dc._hdc;
}
}
}

View file

@ -0,0 +1,33 @@
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Security;
namespace ProjectPSX.Interop.User32
{
[StructLayout(LayoutKind.Sequential)]
internal struct Message
{
public IntPtr hWnd;
public uint msg;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public Point p;
}
[SuppressUnmanagedCodeSecurity]
internal static class NativeMethods
{
[DllImport(ExternDll.User32)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin,
uint messageFilterMax, uint flags);
[DllImport(ExternDll.User32)]
internal static extern IntPtr GetDC(IntPtr hWnd);
[DllImport(ExternDll.User32)]
internal static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
}
}

View file

@ -7,6 +7,10 @@
<DisableWinExeOutputInference>true</DisableWinExeOutputInference> <DisableWinExeOutputInference>true</DisableWinExeOutputInference>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NAudio" Version="1.10.0" /> <PackageReference Include="NAudio" Version="1.10.0" />
</ItemGroup> </ItemGroup>

View file

@ -1,59 +0,0 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace ProjectPSX {
public class Display : IDisposable {
public Bitmap Bitmap { get; set; }
public Int32[] Bits { get; private set; }
public bool Disposed { get; private set; }
public int Height;
public int Width;
protected GCHandle BitsHandle { get; private set; }
public Display(int width, int height) {
Height = height;
Width = width;
Bits = new Int32[Width * Height];
BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned);
Bitmap = new Bitmap(Width, Height, Width * 4, PixelFormat.Format32bppRgb, BitsHandle.AddrOfPinnedObject());
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetPixel(int x, int y, int color) {
int index = x + (y * Width);
Bits[index] = color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetPixelRGB888(int x, int y) {
int index = x + (y * Width);
return Bits[index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ushort GetPixelBGR555(int x, int y) {
int index = x + (y * Width);
int color = Bits[index];
byte m = (byte)((color & 0xFF000000) >> 24);
byte r = (byte)((color & 0x00FF0000) >> 16 + 3);
byte g = (byte)((color & 0x0000FF00) >> 8 + 3);
byte b = (byte)((color & 0x000000FF) >> 3);
return (ushort)(m << 15 | b << 10 | g << 5 | r);
}
public void Dispose() {
if (Disposed) return;
Disposed = true;
Bitmap.Dispose();
BitsHandle.Free();
}
}
}

View file

@ -4,12 +4,11 @@ namespace ProjectPSX.Util {
public class DoubleBufferedPanel : Panel { public class DoubleBufferedPanel : Panel {
public DoubleBufferedPanel() { public DoubleBufferedPanel() {
this.DoubleBuffered = true; SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.BackgroundImageLayout = ImageLayout.Stretch; SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); SetStyle(ControlStyles.UserPaint, false);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
//this.SetStyle(ControlStyles.UserPaint, true); UpdateStyles();
this.UpdateStyles();
} }
} }
} }

View file

@ -11,20 +11,26 @@ using System.Timers;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Diagnostics; using System.Diagnostics;
using System.Threading; using System.Threading;
using ProjectPSX.Interop.Gdi32;
using Gdi32 = ProjectPSX.Interop.Gdi32.NativeMethods;
using System.Runtime.InteropServices;
namespace ProjectPSX { namespace ProjectPSX {
public class Window : Form, IHostWindow { public class Window : Form, IHostWindow {
const int PSX_MHZ = 33868800; private const int PSX_MHZ = 33868800;
const int SYNC_CYCLES = 100; private const int SYNC_CYCLES = 100;
const int MIPS_UNDERCLOCK = 2; private const int MIPS_UNDERCLOCK = 2;
private const int cyclesPerFrame = PSX_MHZ / 60;
private const int syncLoops = (cyclesPerFrame / (SYNC_CYCLES * MIPS_UNDERCLOCK)) + 1;
private const int cycles = syncLoops * SYNC_CYCLES;
private Size vramSize = new Size(1024, 512); private Size vramSize = new Size(1024, 512);
private Size _640x480 = new Size(640, 480); private Size _640x480 = new Size(640, 480);
private readonly DoubleBufferedPanel screen = new DoubleBufferedPanel(); private readonly DoubleBufferedPanel screen = new DoubleBufferedPanel();
private Display display = new Display(640, 480); private GdiBitmap display = new GdiBitmap(1024, 512);
private Display vramViewer = new Display(1024, 512);
private ProjectPSX psx; private ProjectPSX psx;
private int fps; private int fps;
@ -57,7 +63,6 @@ namespace ProjectPSX {
FormBorderStyle = FormBorderStyle.FixedDialog; FormBorderStyle = FormBorderStyle.FixedDialog;
KeyUp += new KeyEventHandler(vramViewerToggle); KeyUp += new KeyEventHandler(vramViewerToggle);
screen.BackgroundImage = display.Bitmap;// TESTING
screen.Size = _640x480; screen.Size = _640x480;
screen.Margin = new Padding(0); screen.Margin = new Padding(0);
screen.MouseDoubleClick += new MouseEventHandler(toggleDebug); screen.MouseDoubleClick += new MouseEventHandler(toggleDebug);
@ -104,8 +109,7 @@ namespace ProjectPSX {
if (cla.Any(s => s.EndsWith(".bin") || s.EndsWith(".cue"))) { if (cla.Any(s => s.EndsWith(".bin") || s.EndsWith(".cue"))) {
String filename = cla.First(s => s.EndsWith(".bin") || s.EndsWith(".cue")); String filename = cla.First(s => s.EndsWith(".bin") || s.EndsWith(".cue"));
return filename; return filename;
} } else {
else {
//Show the user a dialog so they can pick the bin they want to load. //Show the user a dialog so they can pick the bin they want to load.
var fileDialog = new OpenFileDialog(); var fileDialog = new OpenFileDialog();
fileDialog.Filter = "BIN/CUE files (*.bin, *.cue)|*.bin;*.cue"; fileDialog.Filter = "BIN/CUE files (*.bin, *.cue)|*.bin;*.cue";
@ -134,15 +138,20 @@ namespace ProjectPSX {
psx.JoyPadDown(button.Value); psx.JoyPadDown(button.Value);
} }
private void toggleDebug(object sender, MouseEventArgs e) { private void toggleDebug(object sender, MouseEventArgs e) => psx.toggleDebug();
psx.toggleDebug();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Render(int[] vram) { public void Render(int[] vram) {
//Console.WriteLine($"x1 {displayX1} x2 {displayX2} y1 {displayY1} y2 {displayY2}");
int horizontalEnd = horizontalRes;
int verticalEnd = verticalRes;
if (isVramViewer) { if (isVramViewer) {
Buffer.BlockCopy(vram, 0, display.Bits, 0, 0x200000); horizontalEnd = 1024;
verticalEnd = 512;
Marshal.Copy(vram, 0, display.BitmapData, 0x80000);
} else if (is24BitDepth) { } else if (is24BitDepth) {
blit24bpp(vram); blit24bpp(vram);
} else { } else {
@ -150,16 +159,18 @@ namespace ProjectPSX {
} }
fps++; fps++;
screen.Invalidate();
using var deviceContext = new GdiDeviceContext(screen.Handle);
Gdi32.StretchBlt(deviceContext, 0, 0, screen.Width, screen.Height,
display.DeviceContext, 0, 0, horizontalEnd, verticalEnd,
RasterOp.SRCCOPY);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void blit24bpp(int[] vramBits) { private void blit24bpp(int[] vramBits) {
int range = (240 - (displayY2 - displayY1)) / 2; int yRangeOffset = (240 - (displayY2 - displayY1)) >> (verticalRes == 480 ? 0 : 1);
if (yRangeOffset < 0) yRangeOffset = 0;
int yRangeOffset;
if (range < 0) yRangeOffset = 0;
else yRangeOffset = range;
for (int y = yRangeOffset; y < verticalRes - yRangeOffset; y++) { for (int y = yRangeOffset; y < verticalRes - yRangeOffset; y++) {
int offset = 0; int offset = 0;
@ -185,29 +196,27 @@ namespace ProjectPSX {
int p0rgb24bpp = p0R << 16 | p0G << 8 | p0B; int p0rgb24bpp = p0R << 16 | p0G << 8 | p0B;
int p1rgb24bpp = p1R << 16 | p1G << 8 | p1B; int p1rgb24bpp = p1R << 16 | p1G << 8 | p1B;
display.Bits[x + (y * horizontalRes)] = p0rgb24bpp; display.DrawPixel(x, y, p0rgb24bpp);
display.Bits[x + 1 + (y * horizontalRes)] = p1rgb24bpp; display.DrawPixel(x + 1, y, p1rgb24bpp);
} }
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void blit16bpp(int[] vramBits) { private void blit16bpp(int[] vramBits) {
//Console.WriteLine($"x1 {displayX1} x2 {displayX2} y1 {displayY1} y2 {displayY2}"); //Console.WriteLine($"x1 {displayX1} x2 {displayX2} y1 {displayY1} y2 {displayY2}");
//Console.WriteLine($"Display Height {display.Height} Width {display.Width}"); //Console.WriteLine($"Display Height {display.Height} Width {display.Width}");
int range = (240 - (displayY2 - displayY1)) / 2; int yRangeOffset = (240 - (displayY2 - displayY1)) >> (verticalRes == 480 ? 0 : 1);
if (yRangeOffset < 0) yRangeOffset = 0;
int yRangeOffset;
if (range < 0) yRangeOffset = 0;
else yRangeOffset = range;
for (int y = yRangeOffset; y < verticalRes - yRangeOffset; y++) { for (int y = yRangeOffset; y < verticalRes - yRangeOffset; y++) {
for (int x = 0; x < display.Width; x++) { for (int x = 0; x < horizontalRes; x++) {
int pixel = vramBits[(x + displayVRAMXStart) + ((y - yRangeOffset + displayVRAMYStart) * 1024)]; int pixel = vramBits[(x + displayVRAMXStart) + ((y - yRangeOffset + displayVRAMYStart) * 1024)];
display.Bits[x + (y * horizontalRes)] = pixel; display.DrawPixel(x, y, pixel);
//Console.WriteLine(y + " " + x);
} }
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -233,19 +242,13 @@ namespace ProjectPSX {
this.horizontalRes = horizontalRes; this.horizontalRes = horizontalRes;
this.verticalRes = verticalRes; this.verticalRes = verticalRes;
clearDisplay();
//Console.WriteLine($"setDisplayMode {horizontalRes} {verticalRes} {is24BitDepth}"); //Console.WriteLine($"setDisplayMode {horizontalRes} {verticalRes} {is24BitDepth}");
if (!isVramViewer) {
display = new Display(horizontalRes, verticalRes);
screen.BackgroundImage = display.Bitmap;
}
} }
} }
public void SetVRAMStart(ushort displayVRAMXStart, ushort displayVRAMYStart) { public void SetVRAMStart(ushort displayVRAMXStart, ushort displayVRAMYStart) {
//if (isVramViewer) return;
this.displayVRAMXStart = displayVRAMXStart; this.displayVRAMXStart = displayVRAMXStart;
this.displayVRAMYStart = displayVRAMYStart; this.displayVRAMYStart = displayVRAMYStart;
@ -253,8 +256,6 @@ namespace ProjectPSX {
} }
public void SetVerticalRange(ushort displayY1, ushort displayY2) { public void SetVerticalRange(ushort displayY1, ushort displayY2) {
//if (isVramViewer) return;
this.displayY1 = displayY1; this.displayY1 = displayY1;
this.displayY2 = displayY2; this.displayY2 = displayY2;
@ -262,28 +263,29 @@ namespace ProjectPSX {
} }
public void SetHorizontalRange(ushort displayX1, ushort displayX2) { public void SetHorizontalRange(ushort displayX1, ushort displayX2) {
//if (isVramViewer) return;
this.displayX1 = displayX1; this.displayX1 = displayX1;
this.displayX2 = displayX2; this.displayX2 = displayX2;
//Console.WriteLine($"Horizontal Range {displayX1} {displayX2}"); //Console.WriteLine($"Horizontal Range {displayX1} {displayX2}");
} }
private void vramViewerToggle(object sender, KeyEventArgs e) { //this is very buggy but its only for debug purposes maybe disable it when unneded? private void vramViewerToggle(object sender, KeyEventArgs e) {
if(e.KeyCode == Keys.Tab) { if(e.KeyCode == Keys.Tab) {
if (!isVramViewer) { if (!isVramViewer) {
display = vramViewer;
screen.Size = vramSize; screen.Size = vramSize;
} else { } else {
display = new Display(horizontalRes, verticalRes);
screen.Size = _640x480; screen.Size = _640x480;
} }
isVramViewer = !isVramViewer; isVramViewer = !isVramViewer;
screen.BackgroundImage = display.Bitmap; clearDisplay();
} }
} }
private unsafe void clearDisplay() {
Span<uint> span = new Span<uint>(display.BitmapData.ToPointer(), 0x80000);
span.Clear();
}
public void Play(byte[] samples) { public void Play(byte[] samples) {
buffer.AddSamples(samples, 0, samples.Length); buffer.AddSamples(samples, 0, samples.Length);
@ -294,7 +296,7 @@ namespace ProjectPSX {
} }
private void OnTimedEvent(object sender, ElapsedEventArgs e) { private void OnTimedEvent(object sender, ElapsedEventArgs e) {
Text = $"ProjectPSX | Cpu Speed {(int)((float)cpuCyclesCounter / (PSX_MHZ / MIPS_UNDERCLOCK) * SYNC_CYCLES)}% | Vps {GetVPS()}"; Text = $"ProjectPSX | Cpu {(int)((float)cpuCyclesCounter / (PSX_MHZ / MIPS_UNDERCLOCK) * SYNC_CYCLES)}% | Vps {GetVPS()}";
cpuCyclesCounter = 0; cpuCyclesCounter = 0;
} }
@ -309,9 +311,6 @@ namespace ProjectPSX {
try { try {
while (true) { while (true) {
psx.RunFrame(); psx.RunFrame();
int cyclesPerFrame = PSX_MHZ / 60;
int syncLoops = (cyclesPerFrame / (SYNC_CYCLES * MIPS_UNDERCLOCK)) + 1;
int cycles = syncLoops * SYNC_CYCLES;
cpuCyclesCounter += cycles; cpuCyclesCounter += cycles;
} }
} catch (Exception e) { } catch (Exception e) {