From 9adb9816876c43f0e9df26457a110d0d5acfb0d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20Engstr=C3=B6m?= Date: Sat, 26 Oct 2024 14:48:20 +0200 Subject: [PATCH] Updated to Skia instead of Canvas through JavaScript --- .../Components/EmulatorComponent.razor | 8 +- .../Components/EmulatorComponent.razor.cs | 266 ++++++++++-------- Platforms/ZXBox.Blazor/ZXBox.Blazor.csproj | 7 +- Platforms/ZXBox.Blazor/wwwroot/index.html | 42 +-- 4 files changed, 179 insertions(+), 144 deletions(-) diff --git a/Platforms/ZXBox.Blazor/Components/EmulatorComponent.razor b/Platforms/ZXBox.Blazor/Components/EmulatorComponent.razor index 54a8928..ca111bc 100644 --- a/Platforms/ZXBox.Blazor/Components/EmulatorComponent.razor +++ b/Platforms/ZXBox.Blazor/Components/EmulatorComponent.razor @@ -1,4 +1,5 @@ -@using ZXBox.Blazor.Pages +@using SkiaSharp.Views.Blazor +@using ZXBox.Blazor.Pages @using ZXBox.Snapshot @using System.Timers @using Toolbelt.Blazor.Gamepad; @@ -6,7 +7,10 @@ @inject Toolbelt.Blazor.Gamepad.GamepadList GamePadList; @inherits EmulatorComponentModel - +@* *@ + + + @if (gameLoop.Enabled) {
diff --git a/Platforms/ZXBox.Blazor/Components/EmulatorComponent.razor.cs b/Platforms/ZXBox.Blazor/Components/EmulatorComponent.razor.cs index 059b977..38a5849 100644 --- a/Platforms/ZXBox.Blazor/Components/EmulatorComponent.razor.cs +++ b/Platforms/ZXBox.Blazor/Components/EmulatorComponent.razor.cs @@ -2,6 +2,8 @@ using Microsoft.AspNetCore.Components.Forms; using Microsoft.JSInterop; using Microsoft.JSInterop.WebAssembly; +using SkiaSharp.Views.Blazor; +using SkiaSharp; using System; using System.Collections.Generic; using System.Diagnostics; @@ -11,71 +13,72 @@ using System.Threading.Tasks; using System.Timers; using ZXBox.Core.Hardware.Input; -using ZXBox.Hardware.Input; -using ZXBox.Hardware.Input.Joystick; +using ZXBox.Hardware.Input; +using ZXBox.Hardware.Input.Joystick; using ZXBox.Hardware.Output; using ZXBox.Snapshot; -namespace ZXBox.Blazor.Pages -{ - public partial class EmulatorComponentModel : ComponentBase, IAsyncDisposable - { - private ZXSpectrum speccy; - public System.Timers.Timer gameLoop; - int flashcounter = 16; - bool flash = false; - JavaScriptKeyboard Keyboard = new(); - Kempston kempston; - Beeper beeper; - public TapePlayer tapePlayer; - - [Inject] - Toolbelt.Blazor.Gamepad.GamepadList GamePadList { get; set; } - - [Inject] - protected HttpClient Http { get; set; } - [Inject] - protected IJSInProcessRuntime JSRuntime { get; set; } - public EmulatorComponentModel() - { - gameLoop = new System.Timers.Timer(20); - gameLoop.Elapsed += GameLoop_Elapsed; - } - - public ZXSpectrum GetZXSpectrum(RomEnum rom) - { - return new ZXSpectrum(true, true, 20, 20, 20, rom); - } - - public void StartZXSpectrum(RomEnum rom) - { - speccy = GetZXSpectrum(rom); +namespace ZXBox.Blazor.Pages +{ + public partial class EmulatorComponentModel : ComponentBase, IAsyncDisposable + { + private ZXSpectrum speccy; + public System.Timers.Timer gameLoop; + int flashcounter = 16; + bool flash = false; + JavaScriptKeyboard Keyboard = new(); + Kempston kempston; + Beeper beeper; + public TapePlayer tapePlayer; + public SKCanvasView _canvasView; + + [Inject] + Toolbelt.Blazor.Gamepad.GamepadList GamePadList { get; set; } + + [Inject] + protected HttpClient Http { get; set; } + [Inject] + protected IJSInProcessRuntime JSRuntime { get; set; } + public EmulatorComponentModel() + { + gameLoop = new System.Timers.Timer(20); + gameLoop.Elapsed += GameLoop_Elapsed; + } + + public ZXSpectrum GetZXSpectrum(RomEnum rom) + { + return new ZXSpectrum(true, true, 20, 20, 20, rom); + } + + public void StartZXSpectrum(RomEnum rom) + { + speccy = GetZXSpectrum(rom); speccy.InputHardware.Add(Keyboard); - kempston = new Kempston(); - speccy.InputHardware.Add(kempston); - //48000 samples per second, 50 frames per second (20ms per frame) Mono - beeper = new Beeper(0, 127, 48000 / 50, 1); - speccy.OutputHardware.Add(beeper); - tapePlayer = new(beeper); - speccy.InputHardware.Add(tapePlayer); - mono = JSRuntime as WebAssemblyJSRuntime; - speccy.Reset(); - gameLoop.Start(); - } - - public string TapeName { get; set; } - public async Task HandleFileSelected(InputFileChangeEventArgs args) - { + kempston = new Kempston(); + speccy.InputHardware.Add(kempston); + //48000 samples per second, 50 frames per second (20ms per frame) Mono + beeper = new Beeper(0, 127, 48000 / 50, 1); + speccy.OutputHardware.Add(beeper); + tapePlayer = new(beeper); + speccy.InputHardware.Add(tapePlayer); + mono = JSRuntime as WebAssemblyJSRuntime; + speccy.Reset(); + gameLoop.Start(); + } + + public string TapeName { get; set; } + public async Task HandleFileSelected(InputFileChangeEventArgs args) + { if (args.File.Name.ToLower().EndsWith(".tap")) { //Load the tape var file = args.File; var ms = new MemoryStream(); - await file.OpenReadStream().CopyToAsync(ms); - tapePlayer.LoadTape(ms.ToArray()); - TapeName = Path.GetFileNameWithoutExtension(args.File.Name); - } + await file.OpenReadStream().CopyToAsync(ms); + tapePlayer.LoadTape(ms.ToArray()); + TapeName = Path.GetFileNameWithoutExtension(args.File.Name); + } else { var file = args.File; @@ -87,33 +90,33 @@ public async Task HandleFileSelected(InputFileChangeEventArgs args) var bytes = ms.ToArray(); handler.LoadSnapshot(bytes, speccy); } - } - [Inject] - HttpClient httpClient { get; set; } - public string Instructions = ""; - public async Task LoadGame(string filename, string instructions) - { - var ms = new MemoryStream(); - var handler = FileFormatFactory.GetSnapShotHandler(filename); - var stream = await httpClient.GetStreamAsync("Roms/" + filename + ".json"); - await stream.CopyToAsync(ms); - var bytes = ms.ToArray(); - handler.LoadSnapshot(bytes, speccy); - Instructions = instructions; - } - - private async void GameLoop_Elapsed(object sender, ElapsedEventArgs e) - { + } + [Inject] + HttpClient httpClient { get; set; } + public string Instructions = ""; + public async Task LoadGame(string filename, string instructions) + { + var ms = new MemoryStream(); + var handler = FileFormatFactory.GetSnapShotHandler(filename); + var stream = await httpClient.GetStreamAsync("Roms/" + filename + ".json"); + await stream.CopyToAsync(ms); + var bytes = ms.ToArray(); + handler.LoadSnapshot(bytes, speccy); + Instructions = instructions; + } + + private async void GameLoop_Elapsed(object sender, ElapsedEventArgs e) + { Stopwatch sw = new Stopwatch(); //Get gamepads kempston.Gamepads = await GamePadList.GetGamepadsAsync(); //Run JavaScriptInterop to find the currently pressed buttons - Keyboard.KeyBuffer = await JSRuntime.InvokeAsync>("getKeyStatus"); - sw.Start(); + Keyboard.KeyBuffer = await JSRuntime.InvokeAsync>("getKeyStatus"); + sw.Start(); speccy.DoIntructions(69888); - beeper.GenerateSound(); + beeper.GenerateSound(); await BufferSound(); Paint(); @@ -123,59 +126,86 @@ private async void GameLoop_Elapsed(object sender, ElapsedEventArgs e) TapeStopped = false; PercentLoaded = ((Convert.ToDouble(tapePlayer.CurrentTstate) / Convert.ToDouble(tapePlayer.TotalTstates)) * 100); await InvokeAsync(() => StateHasChanged()); - } + } if (!TapeStopped && !tapePlayer.IsPlaying) { TapeStopped = true; await InvokeAsync(() => StateHasChanged()); - } + } } bool TapeStopped = false; GCHandle gchsound; IntPtr pinnedsound; WebAssemblyJSRuntime mono; byte[] soundbytes; - protected async Task BufferSound() - { - soundbytes = beeper.GetSoundBuffer(); - gchsound = GCHandle.Alloc(soundbytes, GCHandleType.Pinned); - pinnedsound = gchsound.AddrOfPinnedObject(); - mono.InvokeUnmarshalled("addAudioBuffer", pinnedsound); - gchsound.Free(); - } - - public double PercentLoaded = 0; - protected async override void OnAfterRender(bool firstRender) - { - if (firstRender) - { - await JSRuntime.InvokeAsync("InitCanvas"); - } - base.OnAfterRender(firstRender); - } - + protected async Task BufferSound() + { + soundbytes = beeper.GetSoundBuffer(); + gchsound = GCHandle.Alloc(soundbytes, GCHandleType.Pinned); + pinnedsound = gchsound.AddrOfPinnedObject(); + mono.InvokeUnmarshalled("addAudioBuffer", pinnedsound); + gchsound.Free(); + } + + public double PercentLoaded = 0; + protected async override void OnAfterRender(bool firstRender) + { + + base.OnAfterRender(firstRender); + } + GCHandle gchscreen; - IntPtr pinnedscreen; - //uint[] screen = new uint[68672]; //Height * width (256+20+20)*(192+20+20) - public async void Paint() - { - if (flashcounter == 0) - { - flashcounter = 16; - flash = !flash; - } - else - { - flashcounter--; - } - - var screen = speccy.GetScreenInUint(flash); - - //Allocate memory - gchscreen = GCHandle.Alloc(screen, GCHandleType.Pinned); - pinnedscreen = gchscreen.AddrOfPinnedObject(); - mono.InvokeUnmarshalled("PaintCanvas", pinnedscreen); - gchscreen.Free(); + IntPtr pinnedscreen; + + uint[] screen = new uint[68672]; //Height * width (256+20+20)*(192+20+20) + public async void Paint() + { + if (flashcounter == 0) + { + flashcounter = 16; + flash = !flash; + } + else + { + flashcounter--; + } + + screen = speccy.GetScreenInUint(flash); + + ////Allocate memory + //gchscreen = GCHandle.Alloc(screen, GCHandleType.Pinned); + //pinnedscreen = gchscreen.AddrOfPinnedObject(); + //mono.InvokeUnmarshalled("PaintCanvas", pinnedscreen); + //gchscreen.Free(); + + _canvasView?.Invalidate(); + } + + SKBitmap bitmap = new SKBitmap(296, 232); + + //SKPaint paint = new SKPaint + //{ + // FilterQuality = SKFilterQuality.High, // High-quality filter for smoother rendering + // IsAntialias = true // Additional anti-aliasing if necessary + //}; + public void OnPaintSurface(SKPaintSurfaceEventArgs e) + { + + var canvas = e.Surface.Canvas; + unsafe + { + var ptr = (uint*)bitmap.GetPixels().ToPointer(); + + fixed (uint* srcPtr = screen) + { + // Use Buffer.MemoryCopy for fast memory copying + Buffer.MemoryCopy(srcPtr, ptr, screen.Length * sizeof(uint), screen.Length * sizeof(uint)); + } + } + + // Draw the bitmap onto the canvas + canvas.DrawBitmap(bitmap, new SKRect(0, 0, e.Info.Width, e.Info.Height)); + } public ValueTask DisposeAsync() @@ -184,5 +214,5 @@ public ValueTask DisposeAsync() gameLoop.Dispose(); return ValueTask.CompletedTask; } - } + } } \ No newline at end of file diff --git a/Platforms/ZXBox.Blazor/ZXBox.Blazor.csproj b/Platforms/ZXBox.Blazor/ZXBox.Blazor.csproj index a8f89fc..1bec7b1 100644 --- a/Platforms/ZXBox.Blazor/ZXBox.Blazor.csproj +++ b/Platforms/ZXBox.Blazor/ZXBox.Blazor.csproj @@ -1,9 +1,9 @@ - + net8.0 - false - + true + true @@ -13,6 +13,7 @@ + diff --git a/Platforms/ZXBox.Blazor/wwwroot/index.html b/Platforms/ZXBox.Blazor/wwwroot/index.html index 390f7ca..86a0ef6 100644 --- a/Platforms/ZXBox.Blazor/wwwroot/index.html +++ b/Platforms/ZXBox.Blazor/wwwroot/index.html @@ -136,27 +136,27 @@ return pressedKeys; } - var canvas; - var context; - var imageData; - window.InitCanvas = function InitCanvas() { - - canvas = document.getElementById('emulatorCanvas'); - context = canvas.getContext('2d'); - context.mozImageSmoothingEnabled = false; - context.webkitImageSmoothingEnabled = false; - context.msImageSmoothingEnabled = false; - context.imageSmoothingEnabled = false; - imageData = context.createImageData(256 + 20 + 20, 192 + 20 + 20); - return true; - } - - window.PaintCanvas = function PaintCanvas(dataPtr) { - imageData.data.set(Uint8ClampedArray.from(Module.HEAPU8.subarray(dataPtr, dataPtr + imageData.data.length))); - context.putImageData(imageData, 0, 0); - context.drawImage(canvas, 0, 0, canvas.width, canvas.height); - return true; - } + // var canvas; + // var context; + // var imageData; + // window.InitCanvas = function InitCanvas() { + + // canvas = document.getElementById('emulatorCanvas'); + // context = canvas.getContext('2d'); + // context.mozImageSmoothingEnabled = false; + // context.webkitImageSmoothingEnabled = false; + // context.msImageSmoothingEnabled = false; + // context.imageSmoothingEnabled = false; + // imageData = context.createImageData(256 + 20 + 20, 192 + 20 + 20); + // return true; + // } + + // window.PaintCanvas = function PaintCanvas(dataPtr) { + // imageData.data.set(Uint8ClampedArray.from(Module.HEAPU8.subarray(dataPtr, dataPtr + imageData.data.length))); + // context.putImageData(imageData, 0, 0); + // context.drawImage(canvas, 0, 0, canvas.width, canvas.height); + // return true; + // }
Loading...