Skip to content

Commit

Permalink
perf(Studio): Use custom-platform-control for SkiaSharp on GTK
Browse files Browse the repository at this point in the history
  • Loading branch information
psyGamer committed Nov 5, 2024
1 parent 6220e22 commit 56e084b
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 51 deletions.
7 changes: 4 additions & 3 deletions Studio/CelesteStudio.GTK/CelesteStudio.GTK.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>latest</LangVersion>
<RuntimeIdentifiers>linux-x64;linux-arm;linux-arm64</RuntimeIdentifiers>
<Nullable>enable</Nullable>

<RestoreSources>$(RestoreSources);https://www.myget.org/F/eto/api/v3/index.json;https://api.nuget.org/v3/index.json</RestoreSources>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' != 'Debug'">
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<DebugType>embedded</DebugType>
</PropertyGroup>

<ItemGroup>
Expand All @@ -20,6 +19,8 @@
<PackageReference Include="Eto.Platform.Gtk" Version="2.8.4-ci-20240710.9879198421" />
<!-- <PackageReference Include="Eto.Platform.Gtk" Version="2.8.3"/>-->

<PackageReference Include="SkiaSharp.Views.Desktop.Common" Version="2.88.8" />
<PackageReference Include="SkiaSharp.Views.Gtk3" Version="2.88.8" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.8" />
</ItemGroup>

Expand Down
6 changes: 5 additions & 1 deletion Studio/CelesteStudio.GTK/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using CelesteStudio.Communication;
using CelesteStudio.Controls;
using Eto.Forms;

namespace CelesteStudio.GTK;
Expand All @@ -8,7 +9,10 @@ public static class Program {
[STAThread]
public static void Main(string[] args) {
try {
new Application(Eto.Platforms.Gtk).Run(new Studio(args, _ => {}));
var platform = new Eto.GtkSharp.Platform();
platform.Add<SkiaDrawable.IHandler>(() => new SkiaDrawableHandler());

new Application(platform).Run(new Studio(args, _ => {}));
} catch (Exception ex) {
Console.Error.WriteLine(ex);
ErrorLog.Write(ex);
Expand Down
71 changes: 71 additions & 0 deletions Studio/CelesteStudio.GTK/SkiaDrawableHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using Cairo;
using CelesteStudio.Controls;
using Eto.GtkSharp.Forms;
using SkiaSharp;
using SkiaSharp.Views.Gtk;
using System;

namespace CelesteStudio.GTK;

public class SkiaDrawableHandler : GtkPanel<Gtk.EventBox, SkiaDrawable, SkiaDrawable.ICallback>, SkiaDrawable.IHandler {
private Gtk.Box content = null!;

public void Create() {
Control = new SkiaEventBox(Widget);
Control.Events |= Gdk.EventMask.ExposureMask;
Control.CanFocus = false;
Control.CanDefault = true;
Control.Events |= Gdk.EventMask.ButtonPressMask;

content = new Gtk.Box(Gtk.Orientation.Vertical, 0);
Control.Add(content);
}

private class SkiaEventBox(SkiaDrawable drawable) : Gtk.EventBox {
private SKBitmap? bitmap;
private SKSurface? surface;
private ImageSurface? imageSurface;

protected override bool OnDrawn(Context cr) {
if (base.OnDrawn(cr)) {
return true;
}

int drawWidth = drawable.DrawWidth, drawHeight = drawable.DrawHeight;
if (drawWidth != bitmap?.Width || drawHeight != bitmap?.Height) {
var colorType = SKImageInfo.PlatformColorType;

bitmap?.Dispose();
bitmap = new SKBitmap(drawWidth, drawHeight, colorType, SKAlphaType.Premul);
IntPtr pixels = bitmap.GetPixels();

surface?.Dispose();
surface = SKSurface.Create(new SKImageInfo(bitmap.Info.Width, bitmap.Info.Height, colorType, SKAlphaType.Premul), pixels, bitmap.Info.RowBytes);
surface.Canvas.Flush();

imageSurface?.Dispose();
imageSurface = new ImageSurface(pixels, Format.Argb32, bitmap.Width, bitmap.Height, bitmap.Width * 4);
}

using (new SKAutoCanvasRestore(surface!.Canvas, true)) {
drawable.Draw(surface);
}

imageSurface!.MarkDirty();
cr.SetSourceSurface(imageSurface, drawable.DrawX, drawable.DrawY);
cr.Paint();

return false;
}
}

public bool CanFocus {
get => Control.CanFocus;
set => Control.CanFocus = value;
}

protected override void SetContainerContent(Gtk.Widget containerContent)
{
content.Add(containerContent);
}
}
95 changes: 64 additions & 31 deletions Studio/CelesteStudio/Controls/SkiaDrawable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,79 @@

namespace CelesteStudio.Controls;

public abstract class SkiaDrawable : Drawable {
private readonly SKColorType colorType = Platform.Instance.IsWinForms || Platform.Instance.IsWpf ? SKColorType.Bgra8888 : SKColorType.Rgba8888;
[Handler(typeof(IHandler))]
public class SkiaDrawable : Panel {
private static readonly object callback = new Callback();

private Bitmap? image = null;
private SKImageInfo imageInfo = SKImageInfo.Empty;
protected override object GetCallback() => callback;
protected new IHandler Handler => (IHandler)base.Handler;

protected virtual int DrawX => 0;
protected virtual int DrawY => 0;
protected virtual int DrawWidth => Width;
protected virtual int DrawHeight => Height;
public SkiaDrawable() {
Handler.Create();
Initialize();
}

// private readonly SKColorType colorType = Platform.Instance.IsWinForms || Platform.Instance.IsWpf ? SKColorType.Bgra8888 : SKColorType.Rgba8888;

protected abstract void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo info);
// private Bitmap? image = null;
// private SKImageInfo imageInfo = SKImageInfo.Empty;

protected override void OnPaint(PaintEventArgs e)
{
try {
int drawWidth = DrawWidth, drawHeight = DrawHeight;
public virtual int DrawX => 0;
public virtual int DrawY => 0;
public virtual int DrawWidth => Width;
public virtual int DrawHeight => Height;

if (drawWidth <= 0 || drawHeight <= 0) {
return;
}
public bool CanFocus {
get => Handler.CanFocus;
set => Handler.CanFocus = value;
}

if (drawWidth != image?.Size.Width || drawHeight != image?.Size.Height)
{
image?.Dispose();
image = new Bitmap(new Size(drawWidth, drawHeight), PixelFormat.Format32bppRgba);
imageInfo = new SKImageInfo(drawWidth, drawHeight, colorType, SKAlphaType.Unpremul);
}

using var bmp = image.Lock();
using var surface = SKSurface.Create(imageInfo, bmp.Data, bmp.ScanWidth);
public virtual void Draw(SKSurface surface) { }

Draw(e, surface, imageInfo);
// protected virtual void OnPaint(PaintEventArgs e)
// {
// try {
// int drawWidth = DrawWidth, drawHeight = DrawHeight;
//
// if (drawWidth <= 0 || drawHeight <= 0) {
// return;
// }
//
// if (drawWidth != image?.Size.Width || drawHeight != image?.Size.Height)
// {
// image?.Dispose();
// image = new Bitmap(new Size(drawWidth, drawHeight), PixelFormat.Format32bppRgba);
// imageInfo = new SKImageInfo(drawWidth, drawHeight, colorType, SKAlphaType.Unpremul);
// }
//
// using var bmp = image.Lock();
// using var surface = SKSurface.Create(imageInfo, bmp.Data, bmp.ScanWidth);
//
// Draw(e, surface, imageInfo);
//
// e.Graphics.DrawImage(image, DrawX, DrawY);
// }
// catch (Exception ex)
// {
// Console.WriteLine(ex);
// e.Graphics.DrawText(Fonts.Monospace(12.0f), Colors.Red, PointF.Empty, ex.ToString());
// }
// }

e.Graphics.DrawImage(image, DrawX, DrawY);
}
catch (Exception ex)
{
Console.WriteLine(ex);
e.Graphics.DrawText(Fonts.Monospace(12.0f), Colors.Red, PointF.Empty, ex.ToString());
protected new class Callback : Control.Callback, ICallback {
public void Draw(SkiaDrawable widget, SKSurface surface) {
using (widget.Platform.Context)
widget.Draw(surface);
}
}

[AutoInitialize(false)]
public new interface IHandler : Panel.IHandler {
void Create();
bool CanFocus { get; set; }
}
public new interface ICallback : Control.ICallback {
void Draw(SkiaDrawable widget, SKSurface surface);
}
}
2 changes: 1 addition & 1 deletion Studio/CelesteStudio/Dialog/ChangelogDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ private class ContentDrawable(string title, Dictionary<string, List<string>> cat
? new SKColorF(1.0f - SystemColors.ControlText.R, 1.0f - SystemColors.ControlText.G, 1.0f - SystemColors.ControlText.B)
: SystemColors.ControlText.ToSkia();

protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo info) {
public override void Draw(SKSurface surface) {
surface.Canvas.Clear();

var textColor = TextColor;
Expand Down
4 changes: 1 addition & 3 deletions Studio/CelesteStudio/Dialog/FontDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public FontPreview() {
Settings.ThemeChanged += () => BackgroundColor = Settings.Instance.Theme.Background;
}

protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo imageInfo) {
public override void Draw(SKSurface surface) {
surface.Canvas.Clear();

if (highlighter == null)
Expand All @@ -47,8 +47,6 @@ protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo im
" 145,R,D",
];

e.Graphics.AntiAlias = true;

float yPos = 0.0f;
float maxWidth = 0.0f;
foreach (var line in previewText) {
Expand Down
10 changes: 5 additions & 5 deletions Studio/CelesteStudio/Editing/Editor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3619,12 +3619,12 @@ private void UpdateMouseCursor(PointF location, Keys modifiers) {

#region Drawing

protected override int DrawX => scrollablePosition.X;
protected override int DrawY => scrollablePosition.Y;
protected override int DrawWidth => scrollable.Width;
protected override int DrawHeight => scrollable.Height;
public override int DrawX => scrollablePosition.X;
public override int DrawY => scrollablePosition.Y;
public override int DrawWidth => scrollable.Width;
public override int DrawHeight => scrollable.Height;

protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo imageInfo) {
public override void Draw(SKSurface surface) {
var canvas = surface.Canvas;
canvas.Clear();
canvas.Translate(-scrollablePosition.X, -scrollablePosition.Y);
Expand Down
2 changes: 1 addition & 1 deletion Studio/CelesteStudio/Editing/GameInfoPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace CelesteStudio.Editing;

public sealed class GameInfo : Panel {
private sealed class SubpixelIndicator : SkiaDrawable {
protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo imageInfo) {
public override void Draw(SKSurface surface) {
surface.Canvas.Clear();

var remainder = CommunicationWrapper.PlayerPositionRemainder;
Expand Down
12 changes: 6 additions & 6 deletions Studio/CelesteStudio/Editing/PopupMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ public ContentDrawable(PopupMenu menu) {
menu.Scroll += (_, _) => Invalidate();
}

protected override int DrawX => menu.ScrollPosition.X;
protected override int DrawY => menu.ScrollPosition.Y;
protected override int DrawWidth => menu.Width;
protected override int DrawHeight => menu.Height;
public override int DrawX => menu.ScrollPosition.X;
public override int DrawY => menu.ScrollPosition.Y;
public override int DrawWidth => menu.Width;
public override int DrawHeight => menu.Height;

protected override void Draw(PaintEventArgs e, SKSurface surface, SKImageInfo imageInfo) {
public override void Draw(SKSurface surface) {
surface.Canvas.Clear();
surface.Canvas.Translate(-menu.ScrollPosition.X, -menu.ScrollPosition.Y);

Expand Down Expand Up @@ -298,7 +298,7 @@ public bool HandleKeyDown(KeyEventArgs e, bool useTabComplete) {
if (!Visible) {
return false;
}

if (e.Key == Keys.Escape) {
Visible = false;
return true;
Expand Down
1 change: 1 addition & 0 deletions Studio/CelesteStudio/Studio.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using System.Reflection;
using CelesteStudio.Communication;
using CelesteStudio.Controls;
using CelesteStudio.Data;
using CelesteStudio.Dialog;
using CelesteStudio.Editing;
Expand Down

0 comments on commit 56e084b

Please sign in to comment.