Skip to content

Commit

Permalink
D2D emulation: render target API
Browse files Browse the repository at this point in the history
  • Loading branch information
jgcodes2020 committed Nov 30, 2023
1 parent ded9719 commit 0f3993e
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 121 deletions.
2 changes: 1 addition & 1 deletion M64RPFW.Services/Abstractions/SkiaRenderEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ namespace M64RPFW.Services.Abstractions;

public class SkiaRenderEventArgs : EventArgs
{
public SKCanvas Canvas { get; init; }
public ISkiaSurfaceManagerService SkiaSurfaceManager { get; init; }

Check warning on line 7 in M64RPFW.Services/Abstractions/SkiaRenderEventArgs.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'SkiaSurfaceManager' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
}
9 changes: 9 additions & 0 deletions M64RPFW.Services/ISkiaSurfaceManagerService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using SkiaSharp;

namespace M64RPFW.Services;

public interface ISkiaSurfaceManagerService
{
public SKCanvas PrimaryCanvas { get; }
public SKSurface CreateOffscreenBuffer(int width, int height);
}
11 changes: 6 additions & 5 deletions M64RPFW.ViewModels/Scripting/LuaEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public LibFunctionAttribute(string path)

public event Action<bool>? StateChanged;

private SKCanvas? _skCanvas;
private ISkiaSurfaceManagerService? _skiaSurfaceManagerService;

private List<LuaFunction> _viCallbacks = new();
private List<LuaFunction> _inputCallbacks = new();
private List<LuaFunction> _stopCallbacks = new();
Expand Down Expand Up @@ -154,13 +155,13 @@ private void LuaLoadDotnetClasses()
{
const string code =
"""
# Load SkiaSharp
-- Load SkiaSharp
luanet.load_assembly("SkiaSharp")
skiasharp = {}
for k, v in pairs(luanet.SkiaSharp) do
skiasharp[k] = v
end
# disable CLR importing (safety)
-- disable CLR importing (safety)
import = function () end
""";
_lua.DoString(code);
Expand Down Expand Up @@ -257,15 +258,15 @@ private void AtUpdateScreen(object? sender, SkiaRenderEventArgs args)
{
try
{
_skCanvas = args.Canvas;
_skiaSurfaceManagerService = args.SkiaSurfaceManager;
foreach (LuaFunction callback in _updateScreenCallbacks)
{
callback.GuardedCall(_lua);
}
}
finally
{
_skCanvas = null;
_skiaSurfaceManagerService = null;
}
}
if (!_isActive)
Expand Down
109 changes: 84 additions & 25 deletions M64RPFW.ViewModels/Scripting/LuaEnvironment_D2D.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using BitFaster.Caching.Lru;
using M64RPFW.ViewModels.Scripting.Extensions;
using M64RPFW.ViewModels.Scripting.Graphics;
Expand All @@ -13,14 +15,28 @@ public partial class LuaEnvironment
{
private const int CACHE_LIMIT = 100;

private SKCanvas? CurrentCanvas => _renderStack.TryPeek(out var canvas) ?
canvas : _skiaSurfaceManagerService?.PrimaryCanvas;

private int _textAntialiasMode;
private readonly Dictionary<string, SKImage> _imageDict = new();
private readonly Dictionary<string, SKImage> _images = new();
private readonly Dictionary<string, SKSurface> _offscreenSurfaces = new();
private readonly Stack<SKCanvas> _renderStack = new();

private readonly ClassicLru<TextPaintParams, SKPaint> _textPaintCache = new(CACHE_LIMIT);
private readonly ClassicLru<FillPaintParams, SKPaint> _fillPaintCache = new(CACHE_LIMIT);
private readonly ClassicLru<StrokePaintParams, SKPaint> _strokePaintCache = new(CACHE_LIMIT);
private readonly ClassicLru<ImagePaintParams, SKPaint> _imagePaintCache = new(CACHE_LIMIT);

private SKImage? D2DPrivate_FindImage(string key)
{
if (_images.TryGetValue(key, out var image))
return image;
if (_offscreenSurfaces.TryGetValue(key, out var surface))
return surface.Snapshot();
return null;
}

[LibFunction("d2d.fill_rectangle")]
private void D2D_FillRectangle(float x, float y, float right, float bottom, float red, float green, float blue,
float alpha)
Expand All @@ -29,7 +45,7 @@ private void D2D_FillRectangle(float x, float y, float right, float bottom, floa
new FillPaintParams(red, green, blue, alpha),
PaintFactories.MakeFillPaint
);
_skCanvas?.DrawRect(x, y, right - x, bottom - y, paint);
CurrentCanvas?.DrawRect(x, y, right - x, bottom - y, paint);
}

[LibFunction("d2d.draw_rectangle")]
Expand All @@ -40,7 +56,7 @@ private void D2D_DrawRectangle(float x, float y, float right, float bottom, floa
new StrokePaintParams(thickness, red, green, blue, alpha),
PaintFactories.MakeStrokePaint
);
_skCanvas?.DrawRect(x, y, right - x, bottom - y, paint);
CurrentCanvas?.DrawRect(x, y, right - x, bottom - y, paint);
}

[LibFunction("d2d.fill_ellipse")]
Expand All @@ -51,7 +67,7 @@ private void D2D_FillEllipse(float x, float y, float radiusX, float radiusY, flo
new FillPaintParams(red, green, blue, alpha),
PaintFactories.MakeFillPaint
);
_skCanvas?.DrawOval(x, y, radiusX, radiusY, paint);
CurrentCanvas?.DrawOval(x, y, radiusX, radiusY, paint);
}

[LibFunction("d2d.draw_ellipse")]
Expand All @@ -64,7 +80,7 @@ private void D2D_DrawEllipse(float x, float y, float radiusX, float radiusY, flo
new StrokePaintParams(thickness, red, green, blue, alpha),
PaintFactories.MakeStrokePaint
);
_skCanvas?.DrawRect(x, y, radiusX, radiusY, paint);
CurrentCanvas?.DrawRect(x, y, radiusX, radiusY, paint);
}

[LibFunction("d2d.draw_line")]
Expand All @@ -75,7 +91,7 @@ private void D2D_DrawLine(float x0, float y0, float x1, float y1, float red, flo
new StrokePaintParams(thickness, red, green, blue, alpha),
PaintFactories.MakeStrokePaint
);
_skCanvas?.DrawLine(x0, y0, x1, y1, paint);
CurrentCanvas?.DrawLine(x0, y0, x1, y1, paint);
}


Expand All @@ -90,7 +106,7 @@ private void D2D_DrawText(float x, float y, float right, float bottom, float red
float alpha, string text, string fontName, float fontSize, int fontWeight, int fontStyle,
int horizontalAlignment, int verticalAlignment, int options)
{
if (_skCanvas == null)
if (CurrentCanvas == null)
return;

var paint = _textPaintCache.GetOrAdd(new TextPaintParams(
Expand Down Expand Up @@ -129,14 +145,14 @@ private void D2D_DrawText(float x, float y, float right, float bottom, float red

if (doClip)
{
_skCanvas.Save();
_skCanvas.ClipRect(new SKRect(x, y, right, bottom));
_skCanvas.DrawText(blob, x, yPos, paint);
_skCanvas.Restore();
CurrentCanvas.Save();
CurrentCanvas.ClipRect(new SKRect(x, y, right, bottom));
CurrentCanvas.DrawText(blob, x, yPos, paint);
CurrentCanvas.Restore();
}
else
{
_skCanvas.DrawText(blob, x, yPos, paint);
CurrentCanvas.DrawText(blob, x, yPos, paint);
}
}

Expand All @@ -155,17 +171,17 @@ private LuaTable D2D_GetTextSize(string text, string fontName, float fontSize, f
[LibFunction("d2d.push_clip")]
private void D2D_PushClip(float x, float y, float right, float bottom)
{
if (_skCanvas == null)
if (CurrentCanvas == null)
return;

_skCanvas.Save();
_skCanvas.ClipRect(SKRect.Create(x, y, right - x, bottom - y));
CurrentCanvas.Save();
CurrentCanvas.ClipRect(SKRect.Create(x, y, right - x, bottom - y));
}

[LibFunction("d2d.pop_clip")]
private void PopClip()
{
_skCanvas?.Restore();
CurrentCanvas?.Restore();
}

[LibFunction("d2d.fill_rounded_rectangle")]
Expand All @@ -177,7 +193,7 @@ private void D2D_FillRoundedRectangle(float x, float y, float right, float botto
new FillPaintParams(red, green, blue, alpha),
PaintFactories.MakeFillPaint
);
_skCanvas?.DrawRoundRect(x, y, right - x, bottom - y, radiusX, radiusY, paint);
CurrentCanvas?.DrawRoundRect(x, y, right - x, bottom - y, radiusX, radiusY, paint);
}

[LibFunction("d2d.draw_rounded_rectangle")]
Expand All @@ -189,7 +205,7 @@ private void D2D_DrawRoundedRectangle(float x, float y, float right, float botto
new StrokePaintParams(thickness, red, green, blue, alpha),
PaintFactories.MakeStrokePaint
);
_skCanvas?.DrawRoundRect(x, y, right - x, bottom - y, radiusX, radiusY, paint);
CurrentCanvas?.DrawRoundRect(x, y, right - x, bottom - y, radiusX, radiusY, paint);
}

[LibFunction("d2d.gdip_fillpolygona")]
Expand Down Expand Up @@ -217,23 +233,23 @@ private void D2D_GdiPlusFillPolygonA(LuaTable pointsTable, byte alpha, byte red,
PaintFactories.MakeFillPaint
);

_skCanvas?.DrawPath(path, paint);
CurrentCanvas?.DrawPath(path, paint);
}


[LibFunction("d2d.load_image")]
private void D2D_LoadImage(string path, string identifier)
{
if (_imageDict.ContainsKey(identifier))
if (_images.ContainsKey(identifier))
throw new InvalidOperationException($"{identifier} already exists");
var image = SKImage.FromEncodedData(path);
_imageDict.Add(identifier, image);
_images.Add(identifier, image);
}

[LibFunction("d2d.free_image")]
private void D2D_FreeImage(string identifier)
{
if (!_imageDict.Remove(identifier, out var image))
if (!_images.Remove(identifier, out var image))
return;

image.Dispose();
Expand All @@ -244,13 +260,14 @@ private void D2D_DrawImage(float destinationX, float destinationY, float destina
float sourceY, float sourceRight, float sourceBottom,
string identifier, float opacity, int interpolation)
{
if (!_imageDict.TryGetValue(identifier, out var image))
SKImage? image;
if ((image = D2DPrivate_FindImage(identifier)) == null)
throw new ArgumentException("Identifier does not exist");
var paint = _imagePaintCache.GetOrAdd(
new ImagePaintParams(interpolation, opacity),
PaintFactories.MakeImagePaint
);
_skCanvas?.DrawImage(image,
CurrentCanvas?.DrawImage(image,
source: SKRect.Create(sourceX, sourceY, sourceRight - sourceX, sourceBottom - sourceY),
dest: SKRect.Create(destinationX, destinationY, destinationRight - destinationX, destinationBottom - destinationY),
paint
Expand All @@ -260,12 +277,54 @@ private void D2D_DrawImage(float destinationX, float destinationY, float destina
[LibFunction("d2d.get_image_info")]
private LuaTable D2D_GetImageInfo(string identifier)
{
if (!_imageDict.TryGetValue(identifier, out var image))
SKImage? image;
if ((image = D2DPrivate_FindImage(identifier)) == null)
throw new ArgumentException("Identifier does not exist");

var table = _lua.NewUnnamedTable();
table["width"] = image.Width;
table["height"] = image.Height;
return table;
}

[LibFunction("d2d.create_render_target")]
private string? D2D_CreateRenderTarget(int width, int height)
{
var surface = _skiaSurfaceManagerService?.CreateOffscreenBuffer(width, height);
if (surface == null)
return null;

string key;
do
{
key = $"__sksurface_{Guid.NewGuid():N}";
} while (!_offscreenSurfaces.TryAdd(key, surface));

return key;
}

[LibFunction("d2d.destroy_render_target")]
private void D2D_DestroyRenderTarget(string key)
{
if (_offscreenSurfaces.Remove(key, out var surface))
surface.Dispose();
}

[LibFunction("d2d.begin_render_target")]
private void D2D_BeginRenderTarget(string key)
{
if (!_offscreenSurfaces.TryGetValue(key, out var surface))
return;
var canvas = surface.Canvas;
canvas.Clear(SKColors.Transparent);
_renderStack.Push(canvas);
}

[LibFunction("d2d.end_render_target")]
private void D2D_EndRenderTarget(string key)
{
if (!_renderStack.TryPop(out var canvas))
return;
canvas.Flush();
}
}
10 changes: 8 additions & 2 deletions M64RPFW.ViewModels/Scripting/LuaEnvironment_Skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ namespace M64RPFW.ViewModels.Scripting;
public partial class LuaEnvironment
{
[LibFunction("skiasharp.get_canvas()")]
private SKCanvas? SkiaGetCanvas()
private SKCanvas? SkiaSharp_GetCanvas()
{
return _skCanvas;
return CurrentCanvas;
}

[LibFunction("skiasharp.create_offscreen_surface()")]
private SKSurface? SkiaSharp_CreateOffscreenSurface(int width, int height)
{
return _skiaSurfaceManagerService?.CreateOffscreenBuffer(width, height);
}
}
9 changes: 7 additions & 2 deletions M64RPFW.Views.Avalonia/Controls/OpenGL/WindowedGlControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

namespace M64RPFW.Views.Avalonia.Controls.OpenGL;

public unsafe class WindowedGlControl : NativeControlHost, IOpenGLContextService, IFrameCaptureService
public unsafe class WindowedGlControl : NativeControlHost, IOpenGLContextService, IFrameCaptureService, ISkiaSurfaceManagerService
{

internal SDL.Window* _sdlWin;
Expand Down Expand Up @@ -268,7 +268,7 @@ private void SkiaRenderImpl()
_grContext.ResetContext();
SkiaRender(this, new SkiaRenderEventArgs
{
Canvas = _skSurface!.Canvas
SkiaSurfaceManager = this
});


Expand All @@ -285,4 +285,9 @@ private void SkiaQuit()
}

#endregion
public SKCanvas PrimaryCanvas => _skSurface!.Canvas;
public SKSurface CreateOffscreenBuffer(int width, int height)
{
return SKSurface.Create(_grContext, false, new SKImageInfo(width, height));
}
}
Loading

0 comments on commit 0f3993e

Please sign in to comment.