diff --git a/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SilkNetWindow.cs b/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SilkNetWindow.cs index 9cc91488..13e429be 100644 --- a/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SilkNetWindow.cs +++ b/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SilkNetWindow.cs @@ -3,12 +3,19 @@ using Highbyte.DotNet6502.Impl.NAudio.NAudioOpenALProvider; using Highbyte.DotNet6502.Impl.SilkNet; using Highbyte.DotNet6502.Impl.Skia; +using Highbyte.DotNet6502.Impl.Skia.Commodore64.Video.v2; using Highbyte.DotNet6502.Instrumentation; using Highbyte.DotNet6502.Instrumentation.Stats; using Highbyte.DotNet6502.Logging; using Highbyte.DotNet6502.Monitor; using Highbyte.DotNet6502.Systems; +using Highbyte.DotNet6502.Systems.Commodore64.Config; +using Highbyte.DotNet6502.Systems.Commodore64; using Microsoft.Extensions.Logging; +using Silk.NET.SDL; +using Highbyte.DotNet6502.App.SilkNetNative.SystemSetup; +using Highbyte.DotNet6502.Impl.SilkNet.Commodore64.Video; +using AutoMapper.Internal.Mappers; namespace Highbyte.DotNet6502.App.SilkNetNative; @@ -209,6 +216,8 @@ private void SetUninitializedWindow() private void InitRendering() { + _silkNetRenderContextContainer?.Cleanup(); + // Init SkipSharp resources (must be done in OnLoad, otherwise no OpenGL context will exist create by SilkNet.) //_skiaRenderContext = new SkiaRenderContext(s_window.Size.X, s_window.Size.Y, _canvasScale); GRGlGetProcedureAddressDelegate getProcAddress = (name) => @@ -274,6 +283,10 @@ public void Start() if (!_systemList.IsValidConfig(_currentSystemName).Result) throw new DotNet6502Exception("Internal error. Cannot start emulator if current system config is invalid."); + // Force a full GC to free up memory, so it won't risk accumulate memory usage if GC has not run for a while. + var m0 = GC.GetTotalMemory(forceFullCollection: true); + _logger.LogInformation("Allocated memory before starting emulator: " + m0); + // Only create a new instance of SystemRunner if we previously has not started (so resume after pause works). if (EmulatorState == EmulatorState.Uninitialized) _systemRunner = _systemList.BuildSystemRunner(_currentSystemName).Result; diff --git a/src/apps/Highbyte.DotNet6502.App.WASM/Pages/Index.razor.cs b/src/apps/Highbyte.DotNet6502.App.WASM/Pages/Index.razor.cs index ca399392..d198c69f 100644 --- a/src/apps/Highbyte.DotNet6502.App.WASM/Pages/Index.razor.cs +++ b/src/apps/Highbyte.DotNet6502.App.WASM/Pages/Index.razor.cs @@ -564,6 +564,11 @@ public async Task OnStart(MouseEventArgs mouseEventArgs) if (!isOk) return; + // TODO: Is forcing a full GC a good idea in Blazor WASM? + // Force a full GC to free up memory, so it won't risk accumulate memory usage if GC has not run for a while. + var m0 = GC.GetTotalMemory(forceFullCollection: true); + _logger.LogInformation("Allocated memory before starting emulator: " + m0); + InitEmulator(); } @@ -614,7 +619,7 @@ private async Task OnStatsToggle(MouseEventArgs mouseEventArgs) private void OnKeyDown(KeyboardEventArgs e) { - if (_wasmHost == null) + if (_wasmHost == null || _wasmHost.InputHandlerContext == null) return; _wasmHost.InputHandlerContext.KeyDown(e); @@ -624,14 +629,14 @@ private void OnKeyDown(KeyboardEventArgs e) private void OnKeyUp(KeyboardEventArgs e) { - if (_wasmHost == null) + if (_wasmHost == null || _wasmHost.InputHandlerContext == null) return; _wasmHost.InputHandlerContext.KeyUp(e); } private void OnFocus(FocusEventArgs e) { - if (_wasmHost == null) + if (_wasmHost == null || _wasmHost.InputHandlerContext == null) return; _wasmHost.InputHandlerContext.OnFocus(e); } diff --git a/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/v2/C64SkiaRenderer2.cs b/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/v2/C64SkiaRenderer2.cs index 1d622ee8..1ab7dfc7 100644 --- a/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/v2/C64SkiaRenderer2.cs +++ b/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/v2/C64SkiaRenderer2.cs @@ -134,7 +134,7 @@ private enum ShaderLineData : int private ElapsedMillisecondsTimedStatSystem _textAndBitmapScreenStat; private ElapsedMillisecondsTimedStatSystem _spritesStat; private ElapsedMillisecondsTimedStatSystem _lineDataImageStat; - private ElapsedMillisecondsTimedStatSystem _drawCanvasWithShader; + private ElapsedMillisecondsTimedStatSystem _drawCanvasWithShaderStat; public C64SkiaRenderer2() { @@ -159,7 +159,7 @@ public void Init(C64 c64, SkiaRenderContext skiaRenderContext) _textAndBitmapScreenStat = Instrumentations.Add($"{StatsCategory}-Screen", new ElapsedMillisecondsTimedStatSystem(c64)); _spritesStat = Instrumentations.Add($"{StatsCategory}-Sprites", new ElapsedMillisecondsTimedStatSystem(c64)); _lineDataImageStat = Instrumentations.Add($"{StatsCategory}-LineDataImage", new ElapsedMillisecondsTimedStatSystem(c64)); - _drawCanvasWithShader = Instrumentations.Add($"{StatsCategory}-DrawCanvasWithShader", new ElapsedMillisecondsTimedStatSystem(c64)); + _drawCanvasWithShaderStat = Instrumentations.Add($"{StatsCategory}-DrawCanvasWithShader", new ElapsedMillisecondsTimedStatSystem(c64)); } public void Init(ISystem system, IRenderContext renderContext) @@ -488,7 +488,7 @@ private void InitLineDataBitmap(C64 c64) private void WriteBitmapToCanvas(SKBitmap bitmap, SKBitmap spritesBitmap, SKBitmap lineDataBitmap, SKCanvas canvas, C64 c64) { - _drawCanvasWithShader.Start(); + _drawCanvasWithShaderStat.Start(); // shader uniform values _sKRuntimeEffectUniforms["bg0Color"] = _sKColorToShaderColorMap[(uint)_bg0DrawColorActual]; _sKRuntimeEffectUniforms["bg1Color"] = _sKColorToShaderColorMap[(uint)_bg1DrawColor]; @@ -523,12 +523,12 @@ private void WriteBitmapToCanvas(SKBitmap bitmap, SKBitmap spritesBitmap, SKBitm // Shader uniform texture sampling values // Convert bitmap (that one have written the C64 screen to) to shader texture - var shaderTexture = bitmap.ToShader(); + using var shaderTexture = bitmap.ToShader(); // Convert shader bitmap (that one have written the C64 sprites to) to shader texture - var spritesTexture = spritesBitmap.ToShader(); + using var spritesTexture = spritesBitmap.ToShader(); // Convert other bitmaps to shader texture - var lineDataBitmapShaderTexture = lineDataBitmap.ToShader(); + using var lineDataBitmapShaderTexture = lineDataBitmap.ToShader(); _sKRuntimeEffectChildren["bitmap_texture"] = shaderTexture; _sKRuntimeEffectChildren["sprites_texture"] = spritesTexture; @@ -539,7 +539,7 @@ private void WriteBitmapToCanvas(SKBitmap bitmap, SKBitmap spritesBitmap, SKBitm canvas.DrawRect(0, 0, bitmap.Width, bitmap.Height, _shaderPaint); - _drawCanvasWithShader.Stop(); + _drawCanvasWithShaderStat.Stop(); } private void DrawBorderAndScreenToBitmapBackedByPixelArray(C64 c64, uint[] pixelArray) diff --git a/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/v2/C64SkiaRenderer2b.cs b/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/v2/C64SkiaRenderer2b.cs index 635767d5..f74803e5 100644 --- a/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/v2/C64SkiaRenderer2b.cs +++ b/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/v2/C64SkiaRenderer2b.cs @@ -127,7 +127,7 @@ private enum ShaderLineData : int private const string StatsCategory = "SkiaSharp-Custom"; private ElapsedMillisecondsTimedStatSystem _spritesStat; private ElapsedMillisecondsTimedStatSystem _lineDataImageStat; - private ElapsedMillisecondsTimedStatSystem _drawCanvasWithShader; + private ElapsedMillisecondsTimedStatSystem _drawCanvasWithShaderStat; // Keep track of C64 data that should update each new line @@ -276,7 +276,7 @@ public void Init(C64 c64, SkiaRenderContext skiaRenderContext) Instrumentations.Clear(); _spritesStat = Instrumentations.Add($"{StatsCategory}-Sprites", new ElapsedMillisecondsTimedStatSystem(c64)); _lineDataImageStat = Instrumentations.Add($"{StatsCategory}-LineDataImage", new ElapsedMillisecondsTimedStatSystem(c64)); - _drawCanvasWithShader = Instrumentations.Add($"{StatsCategory}-DrawCanvasWithShader", new ElapsedMillisecondsTimedStatSystem(c64)); + _drawCanvasWithShaderStat = Instrumentations.Add($"{StatsCategory}-DrawCanvasWithShader", new ElapsedMillisecondsTimedStatSystem(c64)); } public void Init(ISystem system, IRenderContext renderContext) @@ -549,7 +549,7 @@ private void InitLineDataBitmap(C64 c64) private void WriteBitmapToCanvas(SKBitmap backgroundAndBorderBitmap, SKBitmap foregroundBitmap, SKBitmap lineDataBitmap, SKCanvas canvas, C64 c64) { - _drawCanvasWithShader.Start(); + _drawCanvasWithShaderStat.Start(); // shader uniform values _sKRuntimeEffectUniforms["transparentColor"] = _sKColorToShaderColorMap[(uint)_transparentColor]; @@ -579,11 +579,11 @@ private void WriteBitmapToCanvas(SKBitmap backgroundAndBorderBitmap, SKBitmap fo // Shader uniform texture sampling values // Convert bitmaps to shader textures - var backgroundAndBorderTexture = backgroundAndBorderBitmap.ToShader(); - var foregroundTexture = foregroundBitmap.ToShader(); + using var backgroundAndBorderTexture = backgroundAndBorderBitmap.ToShader(); + using var foregroundTexture = foregroundBitmap.ToShader(); // Convert "data" bitmaps to shader texture - var lineDataBitmapShaderTexture = lineDataBitmap.ToShader(); + using var lineDataBitmapShaderTexture = lineDataBitmap.ToShader(); _sKRuntimeEffectChildren["background_and_border_texture"] = backgroundAndBorderTexture; _sKRuntimeEffectChildren["foreground_texture"] = foregroundTexture; @@ -594,7 +594,7 @@ private void WriteBitmapToCanvas(SKBitmap backgroundAndBorderBitmap, SKBitmap fo canvas.DrawRect(0, 0, backgroundAndBorderBitmap.Width, backgroundAndBorderBitmap.Height, _shaderPaint); - _drawCanvasWithShader.Stop(); + _drawCanvasWithShaderStat.Stop(); } diff --git a/src/libraries/Highbyte.DotNet6502.Impl.Skia/SkiaRenderContext.cs b/src/libraries/Highbyte.DotNet6502.Impl.Skia/SkiaRenderContext.cs index 3752a034..dfacdefc 100644 --- a/src/libraries/Highbyte.DotNet6502.Impl.Skia/SkiaRenderContext.cs +++ b/src/libraries/Highbyte.DotNet6502.Impl.Skia/SkiaRenderContext.cs @@ -1,4 +1,5 @@ using Highbyte.DotNet6502.Systems; +using SkiaSharp; namespace Highbyte.DotNet6502.Impl.Skia; @@ -16,6 +17,8 @@ public class SkiaRenderContext : IRenderContext private GRContext? _grContext; private readonly Func? _getGrContextExternal; + private GRGlInterface? _glInterface; + private SKCanvas GetCanvasInternal() { if (_canvas != null) @@ -44,10 +47,11 @@ public SkiaRenderContext(GRGlGetProcedureAddressDelegate getProcAddress, int siz { // Create the SkiaSharp context //var glInterface = GRGlInterface.Create(); - var glInterface = GRGlInterface.Create(name => getProcAddress(name)); - glInterface.Validate(); + + _glInterface = GRGlInterface.Create(name => getProcAddress(name)); + _glInterface.Validate(); var grContextOptions = new GRContextOptions { }; - _grContext = GRContext.CreateGl(glInterface, grContextOptions); + _grContext = GRContext.CreateGl(_glInterface, grContextOptions); if (_grContext == null) throw new DotNet6502Exception("Cannot create OpenGL context."); @@ -77,8 +81,11 @@ public void Cleanup() _canvas = null; _renderSurface?.Dispose(); _renderSurface = null; + _renderTarget?.Dispose(); + _renderTarget = null; _grContext?.Dispose(); _grContext = null; + _glInterface?.Dispose(); + _glInterface = null; } - }