Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/wasm silk net opengl lab #101

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .github/workflows/dotnet-blazor-app-publish-to-gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ env:

PROJECT_FILE: "src/apps/Highbyte.DotNet6502.App.WASM/Highbyte.DotNet6502.App.WASM.csproj"
PROJECT_GH_PAGES_DIR: "app"
PROJECT_GH_PAGES_DIR_WEBGL: "app2"
BUILD_OUTPUT_WORKING_DIR: "build"
GH_PAGES_WORKING_DIR: "ghpages"
GH_PAGES_BRANCH_NAME: "gh-pages"

jobs:

#Deploy the default version (SkiaSharp) of the Blazor WASM site
deploy:
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -66,3 +69,51 @@ jobs:
folder: ${{ env.GH_PAGES_WORKING_DIR }} # The folder the action should deploy.
#target-folder: app #If not specified, the contents for folder above is copied to the root of the GH pages branch.
single-commit: true

#Deploy WebGL version of the Blazor WASM site
deploy2:
runs-on: ubuntu-latest
steps:
# Checkout the code
- uses: actions/checkout@v4

# Get version from tag
- name: Get version from tag with 'v' prefix
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV

# Install .NET Core SDK
- name: Setup .NET Core
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

- name: Install DotNet workload - WASM tools
run: |
dotnet workload install wasm-tools

# Create GitHub Pages root folder contents
- name: Create GH root folder and add .nojekyll file do disable built-in Jekyll CMS
run: |
mkdir ${{ env.GH_PAGES_WORKING_DIR }}
echo '' > ${{ env.GH_PAGES_WORKING_DIR }}/.nojekyll

# Publish the Blazor WASM site to a build working folder
- name: Publish
# Note: Could not get -p:GHPagesInjectBrotliLoader=true to work. Gets error "Uncaught ReferenceError: Blazor is not defined" from "brotliloader.min.js"
run: dotnet publish ${{ env.PROJECT_FILE }} -p:Version=${{ env.VERSION }} -c:${{ env.CONFIGURATION }} -p:GHPages=true -p:GHPagesBase="/${{ github.event.repository.name }}/${{ env.PROJECT_GH_PAGES_DIR_WEBGL }}/" -p:GHPagesInjectBrotliLoader=false -o:${{ env.BUILD_OUTPUT_WORKING_DIR }}/${{ env.PROJECT_GH_PAGES_DIR }} /p:DefineConstants=SILKNETWASM

# Copy the published Blazor WASM site from wwwroot folder in publish output dir
- name: Copy published wwwroot contents to a subdirectory in GH pages working dir root
run: |
cp -r ${{ env.BUILD_OUTPUT_WORKING_DIR }}/${{ env.PROJECT_GH_PAGES_DIR }}/wwwroot/ ${{ env.GH_PAGES_WORKING_DIR }}/${{ env.PROJECT_GH_PAGES_DIR_WEBGL }}/

# Deploy the Blazor WASM site
- name: Deploy to Github Pages
uses: JamesIves/github-pages-deploy-action@v4
with:
# token: ${{ secrets.ACCESS_TOKEN }} # Use if custom token needed to another repo
branch: ${{ env.GH_PAGES_BRANCH_NAME }} # The branch the action should deploy to.
folder: ${{ env.GH_PAGES_WORKING_DIR }} # The folder the action should deploy.
#target-folder: app #If not specified, the contents for folder above is copied to the root of the GH pages branch.
single-commit: true

6 changes: 6 additions & 0 deletions src/apps/Highbyte.DotNet6502.App.SilkNetNative/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@
SilkNetOpenGlRendererConfig = new C64SilkNetOpenGlRendererConfig()
{
UseFineScrollPerRasterLine = false, // Setting to true may work, depending on how code is written. Full screen scroll may not work (actual screen memory is not rendered in sync with raster line).

ShaderEmbeddedResourceType = typeof(C64SilkNetOpenGlRendererConfig),
VertexShaderPath = "Highbyte.DotNet6502.Impl.SilkNet.Commodore64.Video.C64shader_es.vert",
FragmentShaderPath = "Highbyte.DotNet6502.Impl.SilkNet.Commodore64.Video.C64shader_es.frag",

//UseTestShader = true // Set to true to use a test fragment shader instead of the C64 fragment shader.
}
};
var c64Setup = new C64Setup(loggerFactory, c64HostConfig);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
using Highbyte.DotNet6502.App.WASM.Emulator.SystemSetup;
using Highbyte.DotNet6502.Impl.AspNet;
using Highbyte.DotNet6502.Impl.Skia;
using Highbyte.DotNet6502.Monitor;
using Highbyte.DotNet6502.Systems;

namespace Highbyte.DotNet6502.App.WASM.Skia;
namespace Highbyte.DotNet6502.App.WASM.Emulator;

public class EmulatorConfig
{
public const int DEFAULT_CANVAS_WINDOW_WIDTH = 640;
public const int DEFAULT_CANVAS_WINDOW_HEIGHT = 400;

public required string DefaultEmulator { get; set; }
public RendererType Renderer { get; set; } = RendererType.SkiaSharp;

public string DefaultEmulator { get; set; }
public double DefaultDrawScale { get; set; }
public double CurrentDrawScale { get; set; }
public required MonitorConfig Monitor { get; set; }

public Dictionary<string, IHostSystemConfig> HostSystemConfigs = new();

public EmulatorConfig()

Check warning on line 22 in src/apps/Highbyte.DotNet6502.App.WASM/Emulator/EmulatorConfig.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Non-nullable property 'DefaultEmulator' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
{
DefaultDrawScale = 2.0;
CurrentDrawScale = DefaultDrawScale;
}

public void Validate(SystemList<SkiaRenderContext, AspNetInputHandlerContext, WASMAudioHandlerContext> systemList)
public void Validate(SystemList<WASMRenderContextContainer, AspNetInputHandlerContext, WASMAudioHandlerContext> systemList)
{
if (!systemList.Systems.Contains(DefaultEmulator))
throw new DotNet6502Exception($"Setting {nameof(DefaultEmulator)} value {DefaultEmulator} is not supported. Valid values are: {string.Join(',', systemList.Systems)}");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
#if SILKNETWASM

using Highbyte.DotNet6502.Impl.AspNet;
using Highbyte.DotNet6502.Impl.SilkNet;
using Highbyte.DotNet6502.Systems;
using Silk.NET.Core.Loader;
using Silk.NET.Core.Native;
using Silk.NET.Input.Sdl;
using Silk.NET.Maths;
using Silk.NET.OpenGL;
using Silk.NET.Windowing;
using Silk.NET.Windowing.Sdl;
using Toolbelt.Blazor.Gamepad;

namespace Highbyte.DotNet6502.App.WASM.Emulator.SilkNet;


public class SilkNetWasmHost : WasmHostBase
{
private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory;

private readonly IJSRuntime _jsRuntime;

[System.Runtime.InteropServices.DllImport("SDL", EntryPoint = "SDL_GetPlatform", CallingConvention = (System.Runtime.InteropServices.CallingConvention)2)]
static extern unsafe byte* I_SDL_GetPlatform();
[System.Runtime.InteropServices.UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
static unsafe byte* S_SDL_GetPlatform() => I_SDL_GetPlatform();
private static IView s_window;
private int _currentCanvasWidth;
private int _currentCanvasHeight;

public SilkNetWasmHost(
IJSRuntime jsRuntime,
SystemList<WASMRenderContextContainer, AspNetInputHandlerContext, WASMAudioHandlerContext> systemList,
Action<string> updateStats,
Action<string> updateDebug,
Func<bool, Task> setMonitorState,
EmulatorConfig emulatorConfig,
Func<Task> toggleDebugStatsState,
ILoggerFactory loggerFactory,
GamepadList gamepadList,
float initialMasterVolume = 50.0f
) : base(
jsRuntime,
systemList,
updateStats,
updateDebug,
setMonitorState,
emulatorConfig,
toggleDebugStatsState,
loggerFactory,
gamepadList,
initialMasterVolume)
{
_logger = loggerFactory.CreateLogger(typeof(SilkNetWasmHost).Name);
_jsRuntime = jsRuntime;
_loggerFactory = loggerFactory;
}

protected async override void OnInit()
{
// Init and start Silk.NET render loop
var canvasId = "canvas";
await _jsRuntime.InvokeVoidAsync("setCanvas", typeof(SilkNetWasmHost).Assembly.GetName().Name, $"{typeof(SilkNetWasmHost).FullName}.CanvasDropped", canvasId);
unsafe
{
Console.WriteLine(SilkMarshal.PtrToString((nint)I_SDL_GetPlatform()));
delegate* unmanaged[Cdecl]<byte*> gp = &S_SDL_GetPlatform;
if (gp == null)
{
throw new("what");
}
}
SearchPathContainer.Platform = UnderlyingPlatform.Browser;

s_window = GetSilkNetWindow();

Console.WriteLine("Before setting window event handlers");
s_window.Load += OnSilkNetLoad;
s_window.Render += OnSilkNetRender;
s_window.Update += OnSilkNetUpdate;
s_window.Resize += OnSilkNetResize;
s_window.FramebufferResize += OnSilkNetFramebufferResize;
s_window.Closing += OnSilkNetClosing;

Console.WriteLine("Before window run");
s_window.Run();
Console.WriteLine("After window run");

}

[JSInvokable("Highbyte.DotNet6502.App.WASM.Emulator.SilkNet.SilkNetWasmHost.CanvasDropped")]
public static void CanvasDropped() =>
Silk.NET.Windowing.Window.CanvasDropped(s_window);

private IView GetSilkNetWindow()
{
Console.WriteLine("Start GetSilkNetWindow");
#if SILKNETWASM //NOTE: not having this WASM check changes nothing, but it's here for clarity
// Register SDL windowing and Input manually
// Silk.NET doesn't have all the reflection facilities to find them automatically on WASM
Console.WriteLine("Before Window registration");
SdlWindowing.RegisterPlatform();
Console.WriteLine("Before Input registration");
SdlInput.RegisterPlatform();
#endif

var opts = ViewOptions.Default;
opts.FramesPerSecond = 90;
opts.UpdatesPerSecond = 90;
opts.API = GraphicsAPI.Default;

#if WASM
//On WASM, we should be using OpenGLES 3.0
opts.API = new GraphicsAPI(ContextAPI.OpenGLES, new APIVersion(3, 0));
#endif

opts.VSync = false;
Console.WriteLine("Before window creation");
Console.WriteLine(Silk.NET.Windowing.Window.Platforms.Count);

IView window;
if (Silk.NET.Windowing.Window.IsViewOnly)
{
Console.WriteLine("View only");
window = Silk.NET.Windowing.Window.GetView(opts);
}
else
{
Console.WriteLine("Not view only");
window = Silk.NET.Windowing.Window.Create(new(opts));
}
Console.WriteLine($"After window creation");
return window;
}

protected void OnSilkNetLoad()
{
RenderContextContainer = new WASMRenderContextContainer(
null,
new SilkNetOpenGlRenderContext(s_window, (float)_emulatorConfig.CurrentDrawScale));
InputHandlerContext = new AspNetInputHandlerContext(_loggerFactory, _gamepadList);
AudioHandlerContext = new WASMAudioHandlerContext(_audioContext, _jsRuntime, _initialMasterVolume);

_systemList.InitContext(() => RenderContextContainer, () => InputHandlerContext, () => AudioHandlerContext);
}

/// <summary>
/// Runs on every Render Frame event.
///
/// Use this method to render the world.
///
/// This method is called at a RenderFrequency set in the GameWindowSettings object.
/// </summary>
/// <param name="args"></param>
protected void OnSilkNetRender(double deltaTime)
{
Render();
}

/// <summary>
/// Runs on every Update Frame event.
///
/// Use this method to run logic.
///
/// </summary>
/// <param name=""></param>
protected void OnSilkNetUpdate(double deltaTime)
{
if (EmulatorState != EmulatorState.Running)
return;
EmulatorRunOneFrame();
}

protected void OnSilkNetClosing()
{
Cleanup();
}

private void OnSilkNetResize(Vector2D<int> size)
{
_logger.LogInformation($"OnSilkNetResize: {size}");
}

private void OnSilkNetFramebufferResize(Vector2D<int> size)
{
_logger.LogInformation($"OnSilkNetFramebufferResize: {size}");

//RenderContextContainer.SilkNetOpenGlRenderContext.Gl.Viewport(size);

//// Temporary hack for Silk.NET viewport size. Parameter size seems to be 1,1 when canvas is hidden...
//Vector2D<int> sizeWorkaround;
//if (size.X <= 1 && size.Y <= 1)
//{
// if (SystemRunner != null)
// {
// var screen = SystemRunner.System.Screen;
// var scale = 2.0f; // TODO: Get scale from UI.
// sizeWorkaround = new Vector2D<int>((int)(screen.VisibleWidth * scale), (int)(screen.VisibleHeight * scale));
// }
// else
// {
// sizeWorkaround = new Vector2D<int>(836, 470);
// //sizeWorkaround = new Vector2D<int>(_currentCanvasWidth, _currentCanvasHeight);
// }

//}
//else
//{
// sizeWorkaround = size;
//}

//RenderContextContainer.SilkNetOpenGlRenderContext.Gl.Viewport(sizeWorkaround);
}

/// <summary>
/// Canvas size change event triggered by the GUI.
/// </summary>
/// <param name="width"></param>
/// <param name="height"></param>
public override void OnAfterUpdateCanvasSize(int width, int height)
{
_logger.LogInformation($"OnAfterUpdateCanvasSize: {width} {height}");

// Hack: remember last time canvas size was changed to use in OnSilkNetFramebufferResize
_currentCanvasWidth = width;
_currentCanvasHeight = height;

//Vector2D<int> size = new(width, height);
//RenderContextContainer.SilkNetOpenGlRenderContext.Gl.Viewport(size);
}


protected override void OnBeforeRender()
{
}

protected override void OnAfterPause()
{
}

protected override async Task OnAfterStop()
{
// Workaround for Silk.NET WASM canvas that doesn't seem to work if it was invisible first.
// Also, make sure to resize canvas to 0,0 when stopped (done in general code at Index.razor.cs?)
RenderContextContainer.SilkNetOpenGlRenderContext.Gl.Clear((uint)ClearBufferMask.ColorBufferBit);
await _jsRuntime!.InvokeVoidAsync("triggerResize"); // hack to make sure canvas is refreshed
}

protected override async Task OnAfterStart()
{
// Workaround for Silk.NET WASM canvas that doesn't seem to work if it was invisible first.
// Make sure to resize back canvas to original size when started (done in general code at Index.razor.cs?)
await _jsRuntime!.InvokeVoidAsync("triggerResize"); // hack to make sure canvas is refreshed
}

protected override void OnAfterCleanup()
{

//// Clear canvas
//_renderContext.SkiaRenderContext.GetCanvas().Clear();

//// Clean up Skia resources
//_renderContext.SkiaRenderContext?.Cleanup();

//// Clean up input handler resources
//InputHandlerContext?.Cleanup();

//// Stop any playing audio
//_systemRunner.AudioHandler.StopPlaying();
//// Clean up audio resources
////AudioHandlerContext?.Cleanup();
}
}

#endif
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Diagnostics;

namespace Highbyte.DotNet6502.App.WASM.Skia;
namespace Highbyte.DotNet6502.App.WASM.Emulator.Skia;

public class PeriodicAsyncTimer
{
Expand Down
Loading
Loading