From c2343145b92d01241ae4d1630b24bfe67643c85e Mon Sep 17 00:00:00 2001 From: SynesthesiaDev_ Date: Thu, 30 Jul 2026 20:52:35 +0200 Subject: [PATCH 1/4] update dependencies --- Synesthesia.AssetGenerators/AssetGenerator.cs | 2 +- Synesthesia.Demo/Demo.cs | 51 +---------------- Synesthesia.Engine/Animations/Animation.cs | 2 +- .../Animations/AnimationSequence.cs | 3 +- Synesthesia.Engine/Animations/Animator.cs | 2 +- Synesthesia.Engine/Animations/Transform.cs | 2 +- .../Two/Barebones/BarebonesTextbox.cs | 2 +- .../Two/Debug/EngineDebugOverlay.cs | 4 +- .../Components/Two/Debug/StatisticsPanel.cs | 2 +- .../Extensions/SDL3Extensions.cs | 4 +- Synesthesia.Engine/Graphics/Textures/Font.cs | 10 ++-- .../Graphics/Textures/FontAtlas.cs | 15 +++-- .../Graphics/Textures/GlyphInfo.cs | 12 ++-- .../Graphics/Textures/TextureAtlas.cs | 14 ++--- .../Graphics/Textures/TextureData.cs | 12 ++-- .../Graphics/Textures/TextureRegion.cs | 10 ++-- Synesthesia.Engine/Graphics/Two/Box2d.cs | 2 +- Synesthesia.Engine/Graphics/Two/Circle2d.cs | 2 +- .../Graphics/Two/CompositeDrawable2d.cs | 2 +- .../Two/Container/FillFlowContainer2d.cs | 2 +- .../Two/Container/ScrollableContainer.cs | 2 +- Synesthesia.Engine/Graphics/Two/Drawable2d.cs | 2 +- .../Graphics/Two/Text/Text2d.cs | 2 +- Synesthesia.Engine/Graphics/Vertex2d.cs | 2 +- .../Platform/Host/SDL3WindowHost.cs | 4 +- .../Platform/Render/OpenGlRenderer.cs | 57 ++++++++++++++----- .../Resources/Stores/AssemblyResourceStore.cs | 2 +- .../Stores/FileSystemResourceStore.cs | 3 +- Synesthesia.Engine/Synesthesia.Engine.csproj | 14 +++-- .../Threading/Threads/RenderThread.cs | 2 + Synesthesia.Engine/Timing/FrameInfo.cs | 2 +- Synesthesia.Engine/Timing/Scheduler.cs | 4 +- .../Timing/ThreadRunningClock.cs | 4 +- Synesthesia.Engine/Util/Bindables/Bindable.cs | 2 +- .../Util/Bindables/BindableDouble.cs | 3 +- .../Util/Bindables/BindableFloat.cs | 2 +- .../Util/Bindables/BindableProxy.cs | 5 +- 37 files changed, 124 insertions(+), 143 deletions(-) diff --git a/Synesthesia.AssetGenerators/AssetGenerator.cs b/Synesthesia.AssetGenerators/AssetGenerator.cs index 373bc3c..1e8be79 100644 --- a/Synesthesia.AssetGenerators/AssetGenerator.cs +++ b/Synesthesia.AssetGenerators/AssetGenerator.cs @@ -4,7 +4,7 @@ using Synesthesia.Engine.Graphics.Textures; using Synesthesia.Engine.Platform.Host; using Synesthesia.Engine.Resources; -using SynesthesiaUtil; +using Synesthesia.Utils; namespace Synesthesia.AssetGenerators; diff --git a/Synesthesia.Demo/Demo.cs b/Synesthesia.Demo/Demo.cs index 8ae220e..c7cb816 100644 --- a/Synesthesia.Demo/Demo.cs +++ b/Synesthesia.Demo/Demo.cs @@ -1,8 +1,4 @@ -using System.Numerics; -using Synesthesia.Engine; -using Synesthesia.Engine.Components.Two.Default; -using Synesthesia.Engine.Graphics.Layout; -using Synesthesia.Engine.Graphics.Two.Container; +using Synesthesia.Engine; using Synesthesia.Engine.Platform.Host; using Synesthesia.Engine.Util.Bindables; @@ -10,10 +6,6 @@ namespace Synesthesia.Demo; internal static class Demo { - private static DefaultButton? button; - private static DefaultTextbox? textbox; - private static DefaultToggle? toggle; - [STAThread] private static void Main(string[] args) { @@ -23,46 +15,7 @@ private static void Main(string[] args) game.OnInitialized.Subscribe(_ => { - game.DrawableScene2D.Children = - [ - new FillFlowContainer2D - { - AutoSizeAxes = Axes.Both, - Direction = Direction.Vertical, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Spacing = 10, - Children = - [ - button = new DefaultButton - { - Size = new Vector2(140, 50), - Text = "Testing", - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - ButtonStyle = DefaultButton.Style.Tertiary, - }, - - toggle = new DefaultToggle - { - Size = new Vector2(65, 24), - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Checked = toggled - }, - - textbox = new DefaultTextbox - { - Size = new Vector2(200, 40), - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - } - ] - }, - ]; - - textbox.Text.Value = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - textbox.IsPassword.BindTo(toggled); + game.DrawableScene2D.Children = []; }); game.Run(); diff --git a/Synesthesia.Engine/Animations/Animation.cs b/Synesthesia.Engine/Animations/Animation.cs index 1e2f679..05ea2f7 100644 --- a/Synesthesia.Engine/Animations/Animation.cs +++ b/Synesthesia.Engine/Animations/Animation.cs @@ -1,6 +1,6 @@ using Synesthesia.Engine.Animations.Easings; using Synesthesia.Engine.Graphics.Two; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Animations; diff --git a/Synesthesia.Engine/Animations/AnimationSequence.cs b/Synesthesia.Engine/Animations/AnimationSequence.cs index b05d282..6bd6b3f 100644 --- a/Synesthesia.Engine/Animations/AnimationSequence.cs +++ b/Synesthesia.Engine/Animations/AnimationSequence.cs @@ -1,4 +1,5 @@ -using SynesthesiaUtil.Extensions; + +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Animations; diff --git a/Synesthesia.Engine/Animations/Animator.cs b/Synesthesia.Engine/Animations/Animator.cs index 6da0992..de040d6 100644 --- a/Synesthesia.Engine/Animations/Animator.cs +++ b/Synesthesia.Engine/Animations/Animator.cs @@ -1,6 +1,6 @@ using Synesthesia.Engine.Timing; using Synesthesia.Engine.Util.Statistics; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Animations; diff --git a/Synesthesia.Engine/Animations/Transform.cs b/Synesthesia.Engine/Animations/Transform.cs index fe1a653..0270759 100644 --- a/Synesthesia.Engine/Animations/Transform.cs +++ b/Synesthesia.Engine/Animations/Transform.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using Synesthesia.Engine.Animations.Easings; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Animations; diff --git a/Synesthesia.Engine/Components/Two/Barebones/BarebonesTextbox.cs b/Synesthesia.Engine/Components/Two/Barebones/BarebonesTextbox.cs index c006fb7..248d5c9 100644 --- a/Synesthesia.Engine/Components/Two/Barebones/BarebonesTextbox.cs +++ b/Synesthesia.Engine/Components/Two/Barebones/BarebonesTextbox.cs @@ -18,7 +18,7 @@ using Synesthesia.Engine.Util; using Synesthesia.Engine.Util.Bindables; using Synesthesia.Engine.Util.Pooling; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Components.Two.Barebones; diff --git a/Synesthesia.Engine/Components/Two/Debug/EngineDebugOverlay.cs b/Synesthesia.Engine/Components/Two/Debug/EngineDebugOverlay.cs index 7c68f6f..45abb62 100644 --- a/Synesthesia.Engine/Components/Two/Debug/EngineDebugOverlay.cs +++ b/Synesthesia.Engine/Components/Two/Debug/EngineDebugOverlay.cs @@ -3,14 +3,14 @@ using System.Numerics; using Synesthesia.Engine.Animations.Easings; +using Synesthesia.Engine.Extensions; using Synesthesia.Engine.Graphics.Layout; using Synesthesia.Engine.Graphics.Two; using Synesthesia.Engine.Graphics.Two.Container; using Synesthesia.Engine.Input; using Synesthesia.Engine.Input.ActionBindings; using Synesthesia.Engine.Timing; -using SynesthesiaUtil; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils; namespace Synesthesia.Engine.Components.Two.Debug; diff --git a/Synesthesia.Engine/Components/Two/Debug/StatisticsPanel.cs b/Synesthesia.Engine/Components/Two/Debug/StatisticsPanel.cs index 87232da..5c67a19 100644 --- a/Synesthesia.Engine/Components/Two/Debug/StatisticsPanel.cs +++ b/Synesthesia.Engine/Components/Two/Debug/StatisticsPanel.cs @@ -14,7 +14,7 @@ using Synesthesia.Engine.Timing; using Synesthesia.Engine.Util; using Synesthesia.Engine.Util.Statistics; -using SynesthesiaUtil; +using Synesthesia.Utils; namespace Synesthesia.Engine.Components.Two.Debug; diff --git a/Synesthesia.Engine/Extensions/SDL3Extensions.cs b/Synesthesia.Engine/Extensions/SDL3Extensions.cs index ba15456..6947768 100644 --- a/Synesthesia.Engine/Extensions/SDL3Extensions.cs +++ b/Synesthesia.Engine/Extensions/SDL3Extensions.cs @@ -8,8 +8,8 @@ using Synesthesia.Engine.Logging; using Synesthesia.Engine.Platform; using Synesthesia.Engine.Util.Exceptions; -using SynesthesiaUtil; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils; +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Extensions; diff --git a/Synesthesia.Engine/Graphics/Textures/Font.cs b/Synesthesia.Engine/Graphics/Textures/Font.cs index dae90cb..a0fdd70 100644 --- a/Synesthesia.Engine/Graphics/Textures/Font.cs +++ b/Synesthesia.Engine/Graphics/Textures/Font.cs @@ -11,12 +11,10 @@ public class Font(int size, FontAtlas atlas) : IDisposable public readonly int Size = size; public readonly FontAtlas Atlas = atlas; - public static readonly IBinaryCodec BINARY_CODEC = BinaryCodec.Of - ( - BinaryCodec.INT, f => f.Size, - FontAtlas.BINARY_CODEC, f => f.Atlas, - (size, atlas) => new Font(size, atlas) - ); + public static readonly IBinaryCodec BINARY_CODEC = BinaryCodecs.For() + .Field(BinaryCodecs.INT, f => f.Size) + .Field(FontAtlas.BINARY_CODEC, f => f.Atlas) + .Build((size, atlas) => new Font(size, atlas)); public override string ToString() => $"Font(Size={Size}, Atlas={Atlas})"; diff --git a/Synesthesia.Engine/Graphics/Textures/FontAtlas.cs b/Synesthesia.Engine/Graphics/Textures/FontAtlas.cs index e435177..d269da6 100644 --- a/Synesthesia.Engine/Graphics/Textures/FontAtlas.cs +++ b/Synesthesia.Engine/Graphics/Textures/FontAtlas.cs @@ -29,14 +29,13 @@ public class FontAtlas : IDisposable public override string ToString() => $"FontAtlas(TextureAtlas={TextureAtlas}, LineHeight={LineHeight}, Ascent={Ascent}, Descent={Descent}, Glyphs={Glyphs.Count})"; - public static readonly IBinaryCodec BINARY_CODEC = BinaryCodec.Of - ( - TextureAtlas.BINARY_CODEC, f => f.TextureAtlas, - BinaryCodec.INT.MapTo(GlyphInfo.BINARY_CODEC), f => f.Glyphs, - BinaryCodec.FLOAT, f => f.LineHeight, - BinaryCodec.FLOAT, f => f.Ascent, - BinaryCodec.FLOAT, f => f.Descent, - (atlas, glyphs, lineHeight, ascent, descent) => new FontAtlas + public static readonly IBinaryCodec BINARY_CODEC = BinaryCodecs.For() + .Field(TextureAtlas.BINARY_CODEC, f => f.TextureAtlas) + .Field(BinaryCodecs.INT.MapTo(GlyphInfo.BINARY_CODEC), f => f.Glyphs) + .Field(BinaryCodecs.FLOAT, f => f.LineHeight) + .Field(BinaryCodecs.FLOAT, f => f.Ascent) + .Field(BinaryCodecs.FLOAT, f => f.Descent) + .Build((atlas, glyphs, lineHeight, ascent, descent) => new FontAtlas { TextureAtlas = atlas, Glyphs = glyphs, diff --git a/Synesthesia.Engine/Graphics/Textures/GlyphInfo.cs b/Synesthesia.Engine/Graphics/Textures/GlyphInfo.cs index 50a2e21..3312449 100644 --- a/Synesthesia.Engine/Graphics/Textures/GlyphInfo.cs +++ b/Synesthesia.Engine/Graphics/Textures/GlyphInfo.cs @@ -15,11 +15,9 @@ public readonly struct GlyphInfo(int regionHandle, Vector2 bearing, float advanc public readonly Vector2 Bearing = bearing; public readonly float Advance = advance; - public static readonly IBinaryCodec BINARY_CODEC = BinaryCodec.Of - ( - BinaryCodec.INT, g => g.RegionHandle, - ExtraCodecs.VECTOR_2, g => g.Bearing, - BinaryCodec.FLOAT, g => g.Advance, - (handle, bearing, advance) => new GlyphInfo(handle, bearing, advance) - ); + public static readonly IBinaryCodec BINARY_CODEC = BinaryCodecs.For() + .Field(BinaryCodecs.INT, g => g.RegionHandle) + .Field(ExtraCodecs.VECTOR_2, g => g.Bearing) + .Field(BinaryCodecs.FLOAT, g => g.Advance) + .Build((handle, bearing, advance) => new GlyphInfo(handle, bearing, advance)); } diff --git a/Synesthesia.Engine/Graphics/Textures/TextureAtlas.cs b/Synesthesia.Engine/Graphics/Textures/TextureAtlas.cs index 357360b..b81ebbe 100644 --- a/Synesthesia.Engine/Graphics/Textures/TextureAtlas.cs +++ b/Synesthesia.Engine/Graphics/Textures/TextureAtlas.cs @@ -30,14 +30,12 @@ public TextureAtlas(int width, int height, Texture texture, Dictionary Texture.IsUploaded; - public static readonly IBinaryCodec BINARY_CODEC = BinaryCodec.Of - ( - BinaryCodec.INT, a => a.Width, - BinaryCodec.INT, a => a.Height, - TextureData.BINARY_CODEC.Transform(texture => texture.TextureData, textureData => new Texture(textureData, true)), a => a.Texture, - BinaryCodec.INT.MapTo(TextureRegion.BINARY_CODEC), a => a.TextureRegions, - (width, height, texture, areas) => new TextureAtlas(width, height, texture, areas) - ); + public static readonly IBinaryCodec BINARY_CODEC = BinaryCodecs.For() + .Field(BinaryCodecs.INT, a => a.Width) + .Field(BinaryCodecs.INT, a => a.Height) + .Field(TextureData.BINARY_CODEC.Transform(texture => texture.TextureData, textureData => new Texture(textureData, true)), a => a.Texture) + .Field(BinaryCodecs.INT.MapTo(TextureRegion.BINARY_CODEC), a => a.TextureRegions) + .Build((width, height, texture, areas) => new TextureAtlas(width, height, texture, areas)); public TextureRegion GetRegion(int handle) => GetRegionOrNull(handle) ?? throw new InvalidOperationException($"No texture in atlas with handle {handle}"); diff --git a/Synesthesia.Engine/Graphics/Textures/TextureData.cs b/Synesthesia.Engine/Graphics/Textures/TextureData.cs index d666bba..8d18572 100644 --- a/Synesthesia.Engine/Graphics/Textures/TextureData.cs +++ b/Synesthesia.Engine/Graphics/Textures/TextureData.cs @@ -13,12 +13,12 @@ public class TextureData(int width, int height, byte[] data, PixelFormat pixelFo public byte[] Data { get; set; } = data; public PixelFormat PixelFormat { get; set; } = pixelFormat; - public static readonly IBinaryCodec BINARY_CODEC = BinaryCodec.Of( - BinaryCodec.INT, d => d.Width, - BinaryCodec.INT, d => d.Height, - BinaryCodec.BYTE_ARRAY, d => d.Data, - BinaryCodec.Enum(), d => d.PixelFormat, - (w, h, data, format) => new TextureData(w, h, data, format)); + public static readonly IBinaryCodec BINARY_CODEC = BinaryCodecs.For() + .Field(BinaryCodecs.INT, d => d.Width) + .Field(BinaryCodecs.INT, d => d.Height) + .Field(BinaryCodecs.BYTE_ARRAY, d => d.Data) + .Field(BinaryCodecs.Enum(), d => d.PixelFormat) + .Build((w, h, data, format) => new TextureData(w, h, data, format)); public override string ToString() => $"TextureData(Width={Width}, Height={Height}, Data={Data.Length} PixelFormat={PixelFormat})"; } diff --git a/Synesthesia.Engine/Graphics/Textures/TextureRegion.cs b/Synesthesia.Engine/Graphics/Textures/TextureRegion.cs index 7216d07..3842634 100644 --- a/Synesthesia.Engine/Graphics/Textures/TextureRegion.cs +++ b/Synesthesia.Engine/Graphics/Textures/TextureRegion.cs @@ -15,10 +15,8 @@ public readonly struct TextureRegion(RectangleF uvRect, Vector2 size) public readonly RectangleF UvRect = uvRect; public readonly Vector2 Size = size; - public static readonly IBinaryCodec BINARY_CODEC = BinaryCodec.Of - ( - ExtraCodecs.RECTANGLE_F, r => r.UvRect, - ExtraCodecs.VECTOR_2, r => r.Size, - (uv, size) => new TextureRegion(uv, size) - ); + public static readonly IBinaryCodec BINARY_CODEC = BinaryCodecs.For() + .Field(ExtraCodecs.RECTANGLE_F, r => r.UvRect) + .Field(ExtraCodecs.VECTOR_2, r => r.Size) + .Build((uv, size) => new TextureRegion(uv, size)); } diff --git a/Synesthesia.Engine/Graphics/Two/Box2d.cs b/Synesthesia.Engine/Graphics/Two/Box2d.cs index 3bd01ae..c1b5bc7 100644 --- a/Synesthesia.Engine/Graphics/Two/Box2d.cs +++ b/Synesthesia.Engine/Graphics/Two/Box2d.cs @@ -9,7 +9,7 @@ using Synesthesia.Engine.Graphics.Layout; using Synesthesia.Engine.Graphics.Textures; using Synesthesia.Engine.Platform.Render; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Graphics.Two; diff --git a/Synesthesia.Engine/Graphics/Two/Circle2d.cs b/Synesthesia.Engine/Graphics/Two/Circle2d.cs index 66ca5d0..491d313 100644 --- a/Synesthesia.Engine/Graphics/Two/Circle2d.cs +++ b/Synesthesia.Engine/Graphics/Two/Circle2d.cs @@ -5,7 +5,7 @@ using Synesthesia.Engine.Dependency; using Synesthesia.Engine.Graphics.Layout; using Synesthesia.Engine.Platform.Render; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Graphics.Two; diff --git a/Synesthesia.Engine/Graphics/Two/CompositeDrawable2d.cs b/Synesthesia.Engine/Graphics/Two/CompositeDrawable2d.cs index 7389dad..af78422 100644 --- a/Synesthesia.Engine/Graphics/Two/CompositeDrawable2d.cs +++ b/Synesthesia.Engine/Graphics/Two/CompositeDrawable2d.cs @@ -13,7 +13,7 @@ using Synesthesia.Engine.Platform.Render; using Synesthesia.Engine.Timing; using Synesthesia.Engine.Util.Pooling; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Graphics.Two; diff --git a/Synesthesia.Engine/Graphics/Two/Container/FillFlowContainer2d.cs b/Synesthesia.Engine/Graphics/Two/Container/FillFlowContainer2d.cs index 3f78d63..7caddd3 100644 --- a/Synesthesia.Engine/Graphics/Two/Container/FillFlowContainer2d.cs +++ b/Synesthesia.Engine/Graphics/Two/Container/FillFlowContainer2d.cs @@ -4,7 +4,7 @@ using System.Numerics; using Synesthesia.Engine.Graphics.Layout; using Synesthesia.Engine.Util; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Graphics.Two.Container; diff --git a/Synesthesia.Engine/Graphics/Two/Container/ScrollableContainer.cs b/Synesthesia.Engine/Graphics/Two/Container/ScrollableContainer.cs index 92d443b..e728268 100644 --- a/Synesthesia.Engine/Graphics/Two/Container/ScrollableContainer.cs +++ b/Synesthesia.Engine/Graphics/Two/Container/ScrollableContainer.cs @@ -7,7 +7,7 @@ using Synesthesia.Engine.Graphics.Layout; using Synesthesia.Engine.Timing; using Synesthesia.Engine.Util.Bindables; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Graphics.Two.Container; diff --git a/Synesthesia.Engine/Graphics/Two/Drawable2d.cs b/Synesthesia.Engine/Graphics/Two/Drawable2d.cs index d49a33d..7ae6ead 100644 --- a/Synesthesia.Engine/Graphics/Two/Drawable2d.cs +++ b/Synesthesia.Engine/Graphics/Two/Drawable2d.cs @@ -10,7 +10,7 @@ using Synesthesia.Engine.Timing; using Synesthesia.Engine.Util; using Synesthesia.Engine.Util.Statistics; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Graphics.Two; diff --git a/Synesthesia.Engine/Graphics/Two/Text/Text2d.cs b/Synesthesia.Engine/Graphics/Two/Text/Text2d.cs index 0f6cda1..7d5fad2 100644 --- a/Synesthesia.Engine/Graphics/Two/Text/Text2d.cs +++ b/Synesthesia.Engine/Graphics/Two/Text/Text2d.cs @@ -9,7 +9,7 @@ using Synesthesia.Engine.Graphics.Textures; using Synesthesia.Engine.Platform.Render; using Synesthesia.Engine.Resources; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Graphics.Two.Text; diff --git a/Synesthesia.Engine/Graphics/Vertex2d.cs b/Synesthesia.Engine/Graphics/Vertex2d.cs index 4c31e48..04924de 100644 --- a/Synesthesia.Engine/Graphics/Vertex2d.cs +++ b/Synesthesia.Engine/Graphics/Vertex2d.cs @@ -4,7 +4,7 @@ using System.Numerics; using System.Runtime.InteropServices; using Silk.NET.OpenGL; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Graphics; diff --git a/Synesthesia.Engine/Platform/Host/SDL3WindowHost.cs b/Synesthesia.Engine/Platform/Host/SDL3WindowHost.cs index 6fee1d7..270f808 100644 --- a/Synesthesia.Engine/Platform/Host/SDL3WindowHost.cs +++ b/Synesthesia.Engine/Platform/Host/SDL3WindowHost.cs @@ -17,8 +17,8 @@ using Synesthesia.Engine.Platform.Render; using Synesthesia.Engine.Util.Bindables; using Synesthesia.Engine.Util.Future; -using SynesthesiaUtil; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils; +using Synesthesia.Utils.Extensions; using static SDL3.SDL; namespace Synesthesia.Engine.Platform.Host; diff --git a/Synesthesia.Engine/Platform/Render/OpenGlRenderer.cs b/Synesthesia.Engine/Platform/Render/OpenGlRenderer.cs index f84ab97..d78fd42 100644 --- a/Synesthesia.Engine/Platform/Render/OpenGlRenderer.cs +++ b/Synesthesia.Engine/Platform/Render/OpenGlRenderer.cs @@ -13,7 +13,7 @@ using Synesthesia.Engine.Threading; using Synesthesia.Engine.Util; using Synesthesia.Engine.Util.Statistics; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils.Extensions; using Shader = Synesthesia.Engine.Graphics.Shader; using Texture = Synesthesia.Engine.Graphics.Textures.Texture; @@ -70,6 +70,9 @@ public GL OpenGL private Matrix4x4 projectionMatrix; + public Matrix4x4 View3D { get; private set; } = Matrix4x4.Identity; + public Matrix4x4 Projection3D { get; private set; } = Matrix4x4.Identity; + public static readonly ConcurrentQueue TEXTURE_UPLOAD_QUEUE = new ConcurrentQueue(); // Cache so we don't query with string every frame. That's expensive on gc allocations!! @@ -284,10 +287,10 @@ public void BindTexture(Texture? texture) public void Resize(int width, int height) { EnsureInitialized(); - pushViewport(); + pushViewport2D(); } - private void pushViewport() + private void pushViewport2D() { SDL.GetWindowSizeInPixels(Surface.WindowHandle, out int w, out int h); BackBufferWidth = w; @@ -304,11 +307,39 @@ public void EnsureInitialized() if (!openGlInitialized) throw new InvalidOperationException("OpenGL is not initialized yet"); } - public void BeginDrawing() + public void BeginDrawing3D(Matrix4x4 view, Matrix4x4 projection) { EnsureInitialized(); + OpenGL.Enable(EnableCap.DepthTest); + OpenGL.DepthFunc(DepthFunction.Less); + OpenGL.Enable(EnableCap.CullFace); + OpenGL.CullFace(TriangleFace.Back); + OpenGL.FrontFace(FrontFaceDirection.Ccw); + + View3D = view; + Projection3D = projection; + } + + public void EndDrawing3D() + { + OpenGL.Disable(EnableCap.DepthTest); + OpenGL.Disable(EnableCap.CullFace); + } + + public void BeginDrawing2D() + { stencilDepthStack = 0; + pushViewport2D(); + + LoadIdentity(); + updateShaderMatrix(); + DrawStatistics.Reset(); + } + + public void BeginDrawing() + { + EnsureInitialized(); ClearBufferMask mask = ClearBufferMask.None; @@ -328,27 +359,24 @@ public void BeginDrawing() TEXTURE_UPLOAD_QUEUE.TryDequeue(out var texture); texture?.Upload(OpenGL); } - - pushViewport(); - - LoadIdentity(); - updateShaderMatrix(); - DrawStatistics.Reset(); } public void EndDrawing() { EnsureInitialized(); - VertexBatch2D.Flush(); - Surface.SwapBuffers(); - BindTexture(null); - ClearFlags = default_clear_flags; } + + public void EndDrawing2D() + { + EnsureInitialized(); + VertexBatch2D.Flush(); + } + public void PushMatrix() { EnsureInitialized(); @@ -455,7 +483,6 @@ public void BeginStencil() if (stencilDepthStack == 0) { OpenGL.Enable(GLEnum.StencilTest); - // OpenGL.Clear(ClearBufferMask.StencilBufferBit); } stencilDepthStack++; diff --git a/Synesthesia.Engine/Resources/Stores/AssemblyResourceStore.cs b/Synesthesia.Engine/Resources/Stores/AssemblyResourceStore.cs index f1f6044..45223ea 100644 --- a/Synesthesia.Engine/Resources/Stores/AssemblyResourceStore.cs +++ b/Synesthesia.Engine/Resources/Stores/AssemblyResourceStore.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Reflection; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Resources.Stores; diff --git a/Synesthesia.Engine/Resources/Stores/FileSystemResourceStore.cs b/Synesthesia.Engine/Resources/Stores/FileSystemResourceStore.cs index 73ce52a..ff4d952 100644 --- a/Synesthesia.Engine/Resources/Stores/FileSystemResourceStore.cs +++ b/Synesthesia.Engine/Resources/Stores/FileSystemResourceStore.cs @@ -1,7 +1,8 @@ // Copyright (c) 2026 SynesthesiaDev . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using SynesthesiaUtil.Extensions; + +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Resources.Stores; diff --git a/Synesthesia.Engine/Synesthesia.Engine.csproj b/Synesthesia.Engine/Synesthesia.Engine.csproj index 32def9a..1f12bd5 100644 --- a/Synesthesia.Engine/Synesthesia.Engine.csproj +++ b/Synesthesia.Engine/Synesthesia.Engine.csproj @@ -30,7 +30,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all @@ -42,8 +42,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + @@ -51,8 +51,8 @@ - - + + @@ -76,4 +76,8 @@ + + + + diff --git a/Synesthesia.Engine/Threading/Threads/RenderThread.cs b/Synesthesia.Engine/Threading/Threads/RenderThread.cs index 895abd8..c14ca6b 100644 --- a/Synesthesia.Engine/Threading/Threads/RenderThread.cs +++ b/Synesthesia.Engine/Threading/Threads/RenderThread.cs @@ -57,9 +57,11 @@ protected override void ProcessFrame(FrameInfo frameInfo) var gameContainer = game.GetInternalGameContainer(); Renderer.BeginDrawing(); + Renderer.BeginDrawing2D(); gameContainer.OnDraw(); + Renderer.EndDrawing2D(); Renderer.EndDrawing(); if (isFirstSwap) { diff --git a/Synesthesia.Engine/Timing/FrameInfo.cs b/Synesthesia.Engine/Timing/FrameInfo.cs index cffd38d..9c2deba 100644 --- a/Synesthesia.Engine/Timing/FrameInfo.cs +++ b/Synesthesia.Engine/Timing/FrameInfo.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; using Synesthesia.Engine.Threading; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Timing; diff --git a/Synesthesia.Engine/Timing/Scheduler.cs b/Synesthesia.Engine/Timing/Scheduler.cs index 930e335..f461d8a 100644 --- a/Synesthesia.Engine/Timing/Scheduler.cs +++ b/Synesthesia.Engine/Timing/Scheduler.cs @@ -6,8 +6,8 @@ using Synesthesia.Engine.Dependency; using Synesthesia.Engine.Threading.Threads; using Synesthesia.Engine.Util.Statistics; -using SynesthesiaUtil.Extensions; -using SynesthesiaUtil.Types; +using Synesthesia.Utils.Extensions; +using Synesthesia.Utils.Types; namespace Synesthesia.Engine.Timing; diff --git a/Synesthesia.Engine/Timing/ThreadRunningClock.cs b/Synesthesia.Engine/Timing/ThreadRunningClock.cs index 4da50f0..798cb96 100644 --- a/Synesthesia.Engine/Timing/ThreadRunningClock.cs +++ b/Synesthesia.Engine/Timing/ThreadRunningClock.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; -using SynesthesiaUtil; +using Synesthesia.Utils; namespace Synesthesia.Engine.Timing; @@ -10,7 +10,7 @@ public class ThreadRunningClock : FramedClock, IDisposable { private const double spin_threshold = 1.0; - private readonly INativeSleep? nativeSleep = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? new WindowsSleep() : new UnixSleep(); + private readonly INativeSleep? nativeSleep = RuntimeInfo.OperatingSystem == RuntimeInfo.Platform.Windows ? new WindowsSleep() : new UnixSleep(); public double MaximumUpdateHz { diff --git a/Synesthesia.Engine/Util/Bindables/Bindable.cs b/Synesthesia.Engine/Util/Bindables/Bindable.cs index 39d1abf..fb773ec 100644 --- a/Synesthesia.Engine/Util/Bindables/Bindable.cs +++ b/Synesthesia.Engine/Util/Bindables/Bindable.cs @@ -1,6 +1,6 @@ using Synesthesia.Engine.Util.Pooling; using Synesthesia.Engine.Util.Statistics; -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Util.Bindables; diff --git a/Synesthesia.Engine/Util/Bindables/BindableDouble.cs b/Synesthesia.Engine/Util/Bindables/BindableDouble.cs index ca7faad..23af6d5 100644 --- a/Synesthesia.Engine/Util/Bindables/BindableDouble.cs +++ b/Synesthesia.Engine/Util/Bindables/BindableDouble.cs @@ -1,7 +1,8 @@ // Copyright (c) 2026 SynesthesiaDev . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using SynesthesiaUtil.Extensions; + +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Util.Bindables; diff --git a/Synesthesia.Engine/Util/Bindables/BindableFloat.cs b/Synesthesia.Engine/Util/Bindables/BindableFloat.cs index 8e484e9..9bb8ab8 100644 --- a/Synesthesia.Engine/Util/Bindables/BindableFloat.cs +++ b/Synesthesia.Engine/Util/Bindables/BindableFloat.cs @@ -1,7 +1,7 @@ // Copyright (c) 2026 SynesthesiaDev . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using SynesthesiaUtil.Extensions; +using Synesthesia.Utils.Extensions; namespace Synesthesia.Engine.Util.Bindables; diff --git a/Synesthesia.Engine/Util/Bindables/BindableProxy.cs b/Synesthesia.Engine/Util/Bindables/BindableProxy.cs index 5653467..9f6479f 100644 --- a/Synesthesia.Engine/Util/Bindables/BindableProxy.cs +++ b/Synesthesia.Engine/Util/Bindables/BindableProxy.cs @@ -2,7 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using Synesthesia.Engine.Events; -using SynesthesiaUtil.Types; +using Synesthesia.Utils.Types; +using System.Runtime.InteropServices; namespace Synesthesia.Engine.Util.Bindables; @@ -22,7 +23,7 @@ public void Dispose() { foreach (var (dispatcher, subscriberList) in eventDispatchers) { - foreach (var subscriber in subscriberList) + foreach (ref IEventSubscriber subscriber in CollectionsMarshal.AsSpan(subscriberList)) { dispatcher.Unsubscribe(subscriber); } From 29774ad84471dfd15f5d6302b395698d01b085fb Mon Sep 17 00:00:00 2001 From: SynesthesiaDev_ Date: Thu, 30 Jul 2026 22:24:08 +0200 Subject: [PATCH 2/4] Split renderer into `GraphicsDevice`, `Renderer2D` and `Renderer3D` --- .../Components/Two/Debug/StatisticsPanel.cs | 3 +- Synesthesia.Engine/Game.cs | 5 +- .../Graphics/Textures/Texture.cs | 2 +- Synesthesia.Engine/Graphics/Two/Box2d.cs | 2 +- Synesthesia.Engine/Graphics/Two/Circle2d.cs | 2 +- .../Graphics/Two/CompositeDrawable2d.cs | 23 +- .../Graphics/Two/Text/Text2d.cs | 2 +- Synesthesia.Engine/Graphics/VertexBatch.cs | 6 +- .../Platform/Host/IWindowHost.cs | 2 +- .../Platform/Host/SDL3WindowHost.cs | 10 +- .../Platform/Render/GraphicsDevice.cs | 281 +++++ .../Platform/Render/IRenderer.cs | 18 + .../Platform/Render/OpenGlRenderer.cs | 1091 ++++++++--------- .../Platform/Render/Renderer2D.cs | 239 ++++ .../Threading/Threads/RenderThread.cs | 19 +- .../Threading/Threads/UpdateThread.cs | 2 +- .../Util/Statistics/DrawStatistics.cs | 3 +- 17 files changed, 1128 insertions(+), 582 deletions(-) create mode 100644 Synesthesia.Engine/Platform/Render/GraphicsDevice.cs create mode 100644 Synesthesia.Engine/Platform/Render/IRenderer.cs create mode 100644 Synesthesia.Engine/Platform/Render/Renderer2D.cs diff --git a/Synesthesia.Engine/Components/Two/Debug/StatisticsPanel.cs b/Synesthesia.Engine/Components/Two/Debug/StatisticsPanel.cs index 5c67a19..ec2f785 100644 --- a/Synesthesia.Engine/Components/Two/Debug/StatisticsPanel.cs +++ b/Synesthesia.Engine/Components/Two/Debug/StatisticsPanel.cs @@ -30,7 +30,8 @@ public class StatisticsPanel : EngineDebugElement new EngineStatisticLine(EngineStatistics.Type.Drawables), new DrawStatisticLine(DrawStatistics.Type.Invalidations), new Spacer(), - new DrawStatisticLine(DrawStatistics.Type.DrawCalls), + new DrawStatisticLine(DrawStatistics.Type.DrawCalls2D), + new DrawStatisticLine(DrawStatistics.Type.DrawCalls3D), new DrawStatisticLine(DrawStatistics.Type.TextureBinds), new DrawStatisticLine(DrawStatistics.Type.ShaderBinds), new DrawStatisticLine(DrawStatistics.Type.TextureUploadQueue), diff --git a/Synesthesia.Engine/Game.cs b/Synesthesia.Engine/Game.cs index 5530b0c..c5e2fe4 100644 --- a/Synesthesia.Engine/Game.cs +++ b/Synesthesia.Engine/Game.cs @@ -168,9 +168,10 @@ public void Run() if (initialized) throw new InvalidOperationException("Game is already initialized"); WindowHost.Initialize(); - renderThread = new RenderThread(WindowHost.Renderer); + renderThread = new RenderThread(WindowHost.GraphicsDevice); - DependencyContainer.AddSingleton(renderThread.Renderer); + DependencyContainer.AddSingleton(renderThread.GraphicsDevice); + DependencyContainer.AddSingleton(renderThread.GraphicsDevice.Renderer2D); DependencyContainer.AddSingleton(renderThread); WindowHost.Surface.ReleaseOwnership(); diff --git a/Synesthesia.Engine/Graphics/Textures/Texture.cs b/Synesthesia.Engine/Graphics/Textures/Texture.cs index 67235d8..7d62450 100644 --- a/Synesthesia.Engine/Graphics/Textures/Texture.cs +++ b/Synesthesia.Engine/Graphics/Textures/Texture.cs @@ -39,7 +39,7 @@ public Texture(TextureData textureData, bool uploadImmediately) public void EnqueueUpload() { - OpenGlRenderer.TEXTURE_UPLOAD_QUEUE.Enqueue(this); + GraphicsDevice.TEXTURE_UPLOAD_QUEUE.Enqueue(this); UploadQueued = true; } diff --git a/Synesthesia.Engine/Graphics/Two/Box2d.cs b/Synesthesia.Engine/Graphics/Two/Box2d.cs index c1b5bc7..2912c21 100644 --- a/Synesthesia.Engine/Graphics/Two/Box2d.cs +++ b/Synesthesia.Engine/Graphics/Two/Box2d.cs @@ -16,7 +16,7 @@ namespace Synesthesia.Engine.Graphics.Two; public class Box2D : Drawable2D { [Singleton] - private OpenGlRenderer renderer = null!; + private Renderer2D renderer = null!; private RectangleF uvCoords = new(0, 0, 1, 1); private Vector2 drawSize; diff --git a/Synesthesia.Engine/Graphics/Two/Circle2d.cs b/Synesthesia.Engine/Graphics/Two/Circle2d.cs index 491d313..86a11db 100644 --- a/Synesthesia.Engine/Graphics/Two/Circle2d.cs +++ b/Synesthesia.Engine/Graphics/Two/Circle2d.cs @@ -12,7 +12,7 @@ namespace Synesthesia.Engine.Graphics.Two; public class Circle2D : Drawable2D { [Singleton] - private OpenGlRenderer renderer = null!; + private Renderer2D renderer = null!; public Color Color { diff --git a/Synesthesia.Engine/Graphics/Two/CompositeDrawable2d.cs b/Synesthesia.Engine/Graphics/Two/CompositeDrawable2d.cs index af78422..a252c7e 100644 --- a/Synesthesia.Engine/Graphics/Two/CompositeDrawable2d.cs +++ b/Synesthesia.Engine/Graphics/Two/CompositeDrawable2d.cs @@ -25,7 +25,10 @@ public class CompositeDrawable2D : Drawable2D protected internal List InternalChildren = []; [Singleton] - private OpenGlRenderer renderer = null!; + private GraphicsDevice graphicsDevice = null!; + + [Singleton] + private Renderer2D renderer = null!; public float CornerRadius { @@ -179,15 +182,15 @@ protected override void OnDraw2d() if (Masking) { - renderer.VertexBatch2D.Flush(); + graphicsDevice.FlushAllVertexBuffers(); - renderer.BeginStencil(); - renderer.BeginStencilMask(); + graphicsDevice.BeginStencil(); + graphicsDevice.BeginStencilMask(); drawStencilQuad(); - renderer.VertexBatch2D.Flush(); - renderer.EndStencilMask(); + graphicsDevice.FlushAllVertexBuffers(); + graphicsDevice.EndStencilMask(); } using (snapshot) @@ -200,13 +203,13 @@ protected override void OnDraw2d() if (Masking) { - renderer.VertexBatch2D.Flush(); + renderer.FlushVertexBatch(); - renderer.BeginStencilRestore(); + graphicsDevice.BeginStencilRestore(); drawStencilQuad(); - renderer.VertexBatch2D.Flush(); + renderer.FlushVertexBatch(); - renderer.EndStencil(); + graphicsDevice.EndStencil(); } if (BorderThickness > 0) diff --git a/Synesthesia.Engine/Graphics/Two/Text/Text2d.cs b/Synesthesia.Engine/Graphics/Two/Text/Text2d.cs index 7d5fad2..221d141 100644 --- a/Synesthesia.Engine/Graphics/Two/Text/Text2d.cs +++ b/Synesthesia.Engine/Graphics/Two/Text/Text2d.cs @@ -18,7 +18,7 @@ public class Text2D : Drawable2D private const float spread = 8f; [Singleton] - private OpenGlRenderer renderer = null!; + private Renderer2D renderer = null!; [Singleton] private IResourceStore fontResourceStore = null!; diff --git a/Synesthesia.Engine/Graphics/VertexBatch.cs b/Synesthesia.Engine/Graphics/VertexBatch.cs index 913003d..a32aff1 100644 --- a/Synesthesia.Engine/Graphics/VertexBatch.cs +++ b/Synesthesia.Engine/Graphics/VertexBatch.cs @@ -14,13 +14,15 @@ public class VertexBatch : IDisposable where T : unmanaged private readonly uint vao, vbo, ebo; private readonly T[] vertexArray; private readonly int maxVertices; + private readonly bool is2D; private int vertexIndex; - public VertexBatch(GL gl, int maxQuads = 1000) + public VertexBatch(GL gl, bool is2D, int maxQuads = 1000) { this.gl = gl; maxVertices = maxQuads * 4; vertexArray = new T[maxVertices]; + this.is2D = is2D; gl.GenVertexArrays(1, out vao); gl.BindVertexArray(vao); @@ -105,7 +107,7 @@ public void Flush() gl.BindVertexArray(vao); gl.DrawElements(PrimitiveType.Triangles, (uint)(vertexIndex / 4 * 6), DrawElementsType.UnsignedInt, (void*)0); - DrawStatistics.Increment(DrawStatistics.Type.DrawCalls); + DrawStatistics.Increment(is2D ? DrawStatistics.Type.DrawCalls2D : DrawStatistics.Type.DrawCalls3D); vertexIndex = 0; } diff --git a/Synesthesia.Engine/Platform/Host/IWindowHost.cs b/Synesthesia.Engine/Platform/Host/IWindowHost.cs index ec5f035..bd00381 100644 --- a/Synesthesia.Engine/Platform/Host/IWindowHost.cs +++ b/Synesthesia.Engine/Platform/Host/IWindowHost.cs @@ -26,7 +26,7 @@ public interface IWindowHost : IDisposable OpenGLSurface Surface { get; } - OpenGlRenderer Renderer { get; } + GraphicsDevice GraphicsDevice { get; } bool WindowExists { get; } diff --git a/Synesthesia.Engine/Platform/Host/SDL3WindowHost.cs b/Synesthesia.Engine/Platform/Host/SDL3WindowHost.cs index 270f808..6b417eb 100644 --- a/Synesthesia.Engine/Platform/Host/SDL3WindowHost.cs +++ b/Synesthesia.Engine/Platform/Host/SDL3WindowHost.cs @@ -74,7 +74,7 @@ public class SDL3WindowHost : IWindowHost /// /// Manages everything related to rendering/drawing /// - public OpenGlRenderer Renderer { get; private set; } = null!; + public GraphicsDevice GraphicsDevice { get; private set; } = null!; public bool WindowExists { get; private set; } @@ -216,14 +216,14 @@ public void Initialize() Surface.ClaimOwnership(); StartTextInput(Surface.WindowHandle); - Renderer = new OpenGlRenderer + GraphicsDevice = new GraphicsDevice { Surface = Surface, }; WindowState.OnValueChange(e => updateWindowState(e.NewValue), ignoresSource: windowStateEventSource); - Renderer.Initialize(); + GraphicsDevice.Initialize(); var driver = TabletDriver.Create(); driver.DeviceReported += handleTabletDeviceReport; @@ -380,8 +380,8 @@ private void fetchCurrentWindowState() WindowState.Set(Platform.WindowState.Normal, windowStateEventSource); } - GetWindowSizeInPixels(handle, out int w, out int h); - Renderer.Resize(w, h); + // GetWindowSizeInPixels(handle, out int w, out int h); + GraphicsDevice.PushViewport(); } private void updateWindowState(WindowState windowState) diff --git a/Synesthesia.Engine/Platform/Render/GraphicsDevice.cs b/Synesthesia.Engine/Platform/Render/GraphicsDevice.cs new file mode 100644 index 0000000..89a8b69 --- /dev/null +++ b/Synesthesia.Engine/Platform/Render/GraphicsDevice.cs @@ -0,0 +1,281 @@ +// Copyright (c) 2026 SynesthesiaDev . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using SDL3; +using Silk.NET.OpenGL; +using Synesthesia.Engine.Graphics.Shaders; +using Synesthesia.Engine.Logging; +using Synesthesia.Engine.Threading; +using Synesthesia.Engine.Util.Statistics; +using Synesthesia.Utils.Extensions; +using Shader = Synesthesia.Engine.Graphics.Shader; +using Texture = Synesthesia.Engine.Graphics.Textures.Texture; + +namespace Synesthesia.Engine.Platform.Render; + +public class GraphicsDevice +{ + public const ClearFlags DEFAULT_CLEAR_FLAGS = ClearFlags.ColorBuffer | ClearFlags.DepthBuffer | ClearFlags.StencilBuffer; + + private const string shader_uniform_texture = "u_texture"; + private const string shader_uniform_use_texture = "u_useTexture"; + + public required OpenGLSurface Surface { get; init; } + + public int BackBufferWidth { get; private set; } + public int BackBufferHeight { get; private set; } + public ClearFlags ClearFlags = DEFAULT_CLEAR_FLAGS; + private int stencilDepthStack; + + public Shader CurrentShader { get; private set; } = null!; + public Shader DefaultShader = null!; + public Shader StencilShader = null!; + + public int TextureShaderLocation; + public int UseTextureShaderLocation; + + public Renderer2D Renderer2D = null!; + //Renderer3D + + public Texture? CurrentTexture { get; private set; } + + public static readonly ConcurrentQueue TEXTURE_UPLOAD_QUEUE = new ConcurrentQueue(); + + private bool openGlInitialized; + + public void Initialize() + { + if (openGlInitialized) throw new InvalidOperationException("OpenGL is already initialized"); + var gl = GL.GetApi(name => + { + var ptr = OpenGLSurface.GetProcAddress(name); + return ptr; + }); + + OpenGL = gl ?? throw new InvalidOperationException("Silk.NET could not bind to OpenGL"); + + BackBufferHeight = Surface.BackBufferHeight; + BackBufferWidth = Surface.BackBufferWidth; + + openGlInitialized = true; + Renderer2D = new Renderer2D(this); + + PushViewport(); + + var version = OpenGL.GetStringS(GLEnum.Version); + var shadingLanguageVersion = OpenGL.GetStringS(GLEnum.ShadingLanguageVersion); + var vendor = OpenGL.GetStringS(GLEnum.Vendor); + var renderer = OpenGL.GetStringS(GLEnum.Renderer); + + Logger.EmptyLine(); + Logger.Debug("OpenGL Initialized", Logger.Platform); + Logger.Debug($"- Version: {version}", Logger.Platform); + Logger.Debug($"- Vendor: {vendor}", Logger.Platform); + Logger.Debug($"- Renderer {renderer}", Logger.Platform); + Logger.Debug($"- GLSL: {shadingLanguageVersion}", Logger.Platform); + } + + public GL OpenGL + { + get + { + EnsureInitialized(); + return field; + } + + private set; + } = null!; + + public bool CanDraw => BackBufferWidth > 0 && BackBufferHeight > 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EnsureInitialized() + { + if (!openGlInitialized) throw new InvalidOperationException("OpenGL is not initialized yet"); + } + + public void CompileDefaultShaders() + { + DefaultShader = new Shader(OpenGL, ShaderSources.DefaultVertex, ShaderSources.DefaultFragment); + StencilShader = new Shader(OpenGL, ShaderSources.DefaultVertex, ShaderSources.StencilFragment); + BindShader(DefaultShader); + } + + public void FlushAllVertexBuffers() + { + Renderer2D.FlushVertexBatch(); + } + + public void BindShader(Shader shader) + { + ThreadSafety.AssertRunningOnRenderThread(); + + if (CurrentShader == shader) return; + + // Flush any pending vertices BEFORE swapping shaders + if (openGlInitialized) + { + Renderer2D.VertexBatch.Flush(); + } + + CurrentShader = shader; + shader.Use(); + + cacheShaderUniformLocations(); + Renderer2D.UpdateShaderMatrix(); + + if (CurrentTexture != null) + { + CurrentShader.SetInt(TextureShaderLocation, 0); + CurrentShader.SetInt(UseTextureShaderLocation, 1); + } + else + { + CurrentShader.SetInt(UseTextureShaderLocation, 0); + } + } + + public void UnbindShader() + { + ThreadSafety.AssertRunningOnRenderThread(); + BindShader(DefaultShader); + + Renderer2D.UpdateShaderMatrix(); + } + + private void cacheShaderUniformLocations() + { + TextureShaderLocation = CurrentShader.GetUniformLocation(shader_uniform_texture); + UseTextureShaderLocation = CurrentShader.GetUniformLocation(shader_uniform_use_texture); + + Renderer2D.CacheUniformLocations(); + } + + public void BindTexture(Texture? texture) + { + ThreadSafety.AssertRunningOnRenderThread(); + if (CurrentTexture == texture) return; + + Renderer2D.FlushVertexBatch(); + + CurrentTexture = texture; + if (texture != null && texture.Bind(OpenGL)) + { + CurrentShader.SetInt(TextureShaderLocation, 0); + CurrentShader.SetInt(UseTextureShaderLocation, 1); + } + else + { + OpenGL.BindTexture(TextureTarget.Texture2D, 0); + CurrentShader.SetInt(UseTextureShaderLocation, 0); + } + } + + public void PushViewport() + { + EnsureInitialized(); + + SDL.GetWindowSizeInPixels(Surface.WindowHandle, out int w, out int h); + BackBufferWidth = w; + BackBufferHeight = h; + + OpenGL.Viewport(0, 0, (uint)w, (uint)h); + } + + public void BeginDrawing() + { + EnsureInitialized(); + + DrawStatistics.Reset(); + stencilDepthStack = 0; + ClearBufferMask mask = ClearBufferMask.None; + + if (ClearFlags.HasFlagFast(ClearFlags.ColorBuffer)) + mask |= ClearBufferMask.ColorBufferBit; + if (ClearFlags.HasFlagFast(ClearFlags.DepthBuffer)) + mask |= ClearBufferMask.DepthBufferBit; + if (ClearFlags.HasFlagFast(ClearFlags.StencilBuffer)) + mask |= ClearBufferMask.StencilBufferBit; + + if (mask != ClearBufferMask.None) OpenGL.Clear(mask); + + DrawStatistics.Set(DrawStatistics.Type.TextureUploadQueue, TEXTURE_UPLOAD_QUEUE.Count); + + while (!TEXTURE_UPLOAD_QUEUE.IsEmpty) + { + TEXTURE_UPLOAD_QUEUE.TryDequeue(out var texture); + texture?.Upload(OpenGL); + } + } + + public void EndDrawing() + { + EnsureInitialized(); + + Surface.SwapBuffers(); + BindTexture(null); + ClearFlags = DEFAULT_CLEAR_FLAGS; + } + + public void BeginStencil() + { + if (stencilDepthStack == 0) + { + OpenGL.Enable(GLEnum.StencilTest); + } + + stencilDepthStack++; + } + + public void BeginStencilMask() + { + OpenGL.Enable(EnableCap.Multisample); + OpenGL.ColorMask(false, false, false, false); + + OpenGL.StencilFunc(StencilFunction.Equal, stencilDepthStack - 1, 0xFF); + OpenGL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Incr); + + BindShader(StencilShader); + } + + public void EndStencilMask() + { + OpenGL.ColorMask(true, true, true, true); + + OpenGL.StencilFunc(StencilFunction.Equal, stencilDepthStack, 0xFF); + OpenGL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Keep); + + UnbindShader(); + } + + public void EndStencil() + { + stencilDepthStack--; + + if (stencilDepthStack == 0) + { + OpenGL.Disable(EnableCap.StencilTest); + } + else + { + OpenGL.StencilFunc(StencilFunction.Equal, stencilDepthStack, 0xFF); + OpenGL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Keep); + } + + OpenGL.ColorMask(true, true, true, true); + UnbindShader(); + } + + public void BeginStencilRestore() + { + OpenGL.Enable(EnableCap.Multisample); + OpenGL.ColorMask(false, false, false, false); + + OpenGL.StencilFunc(StencilFunction.Equal, stencilDepthStack, 0xFF); + OpenGL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Decr); + + BindShader(StencilShader); + } +} diff --git a/Synesthesia.Engine/Platform/Render/IRenderer.cs b/Synesthesia.Engine/Platform/Render/IRenderer.cs new file mode 100644 index 0000000..597193b --- /dev/null +++ b/Synesthesia.Engine/Platform/Render/IRenderer.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2026 SynesthesiaDev . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Synesthesia.Engine.Graphics; + +namespace Synesthesia.Engine.Platform.Render; + +public interface IRenderer where T : unmanaged +{ + void BeginDrawing(); + void EndDrawing(); + void FlushVertexBatch(); + + void UpdateShaderMatrix(); + void CacheUniformLocations(); + + VertexBatch VertexBatch { get; } +} diff --git a/Synesthesia.Engine/Platform/Render/OpenGlRenderer.cs b/Synesthesia.Engine/Platform/Render/OpenGlRenderer.cs index d78fd42..0268e40 100644 --- a/Synesthesia.Engine/Platform/Render/OpenGlRenderer.cs +++ b/Synesthesia.Engine/Platform/Render/OpenGlRenderer.cs @@ -1,546 +1,545 @@ -// Copyright (c) 2026 SynesthesiaDev . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Collections.Concurrent; -using System.Drawing; -using System.Numerics; -using System.Runtime.CompilerServices; -using SDL3; -using Silk.NET.OpenGL; -using Synesthesia.Engine.Graphics; -using Synesthesia.Engine.Graphics.Shaders; -using Synesthesia.Engine.Logging; -using Synesthesia.Engine.Threading; -using Synesthesia.Engine.Util; -using Synesthesia.Engine.Util.Statistics; -using Synesthesia.Utils.Extensions; -using Shader = Synesthesia.Engine.Graphics.Shader; -using Texture = Synesthesia.Engine.Graphics.Textures.Texture; - -namespace Synesthesia.Engine.Platform.Render; - -public sealed class OpenGlRenderer : IDisposable -{ - private const ClearFlags default_clear_flags = ClearFlags.ColorBuffer | ClearFlags.DepthBuffer | ClearFlags.StencilBuffer; - - private const string shader_uniform_texture = "u_texture"; - private const string shader_uniform_use_texture = "u_useTexture"; - private const string shader_uniform_transform_matrix = "u_transform"; - public required OpenGLSurface Surface { get; init; } - - private bool openGlInitialized; - - public int BackBufferWidth { get; private set; } - - public int BackBufferHeight { get; private set; } - - public ClearFlags ClearFlags = default_clear_flags; - - public Shader DefaultShader = null!; - public Shader StencilShader = null!; - - public VertexBatch VertexBatch2D = null!; - - public GL OpenGL - { - get - { - EnsureInitialized(); - return field; - } - - private set; - } = null!; - - public bool CanDraw => BackBufferWidth > 0 && BackBufferHeight > 0; - - private readonly Stack matrixStack = new Stack(); - - private readonly Stack inverseMatrixStack = new(); - - public int StackDepth => matrixStack.Count; - - public Shader CurrentShader { get; private set; } = null!; - - public Texture? CurrentTexture { get; private set; } - - public Matrix4x4 Matrix { get; private set; } = Matrix4x4.Identity; - - public Matrix4x4 InverseMatrix { get; private set; } = Matrix4x4.Identity; - - private Matrix4x4 projectionMatrix; - - public Matrix4x4 View3D { get; private set; } = Matrix4x4.Identity; - public Matrix4x4 Projection3D { get; private set; } = Matrix4x4.Identity; - - public static readonly ConcurrentQueue TEXTURE_UPLOAD_QUEUE = new ConcurrentQueue(); - - // Cache so we don't query with string every frame. That's expensive on gc allocations!! - private int textureShaderLocation; - private int useTextureShaderLocation; - private int transformMatrixShaderLocation; - private int stencilDepthStack; - - public void Initialize() - { - if (openGlInitialized) throw new InvalidOperationException("OpenGL is already initialized"); - - var gl = GL.GetApi(name => - { - var ptr = OpenGLSurface.GetProcAddress(name); - return ptr; - }); - - OpenGL = gl ?? throw new InvalidOperationException("Silk.NET could not bind to OpenGL"); - - BackBufferHeight = Surface.BackBufferHeight; - BackBufferWidth = Surface.BackBufferWidth; - - openGlInitialized = true; - Resize(BackBufferWidth, BackBufferHeight); - - var version = OpenGL.GetStringS(GLEnum.Version); - var shadingLanguageVersion = OpenGL.GetStringS(GLEnum.ShadingLanguageVersion); - var vendor = OpenGL.GetStringS(GLEnum.Vendor); - var renderer = OpenGL.GetStringS(GLEnum.Renderer); - - VertexBatch2D = new VertexBatch(OpenGL); - - Logger.EmptyLine(); - Logger.Debug("OpenGL Initialized", Logger.Platform); - Logger.Debug($"- Version: {version}", Logger.Platform); - Logger.Debug($"- Vendor: {vendor}", Logger.Platform); - Logger.Debug($"- Renderer {renderer}", Logger.Platform); - Logger.Debug($"- GLSL: {shadingLanguageVersion}", Logger.Platform); - } - - public void DrawQuad - ( - DrawMatrix drawMatrix, - Vector2 position, - Vector2 size, - uint packedColor, - float alpha, - float borderThickness, - bool borderHasSingleColor, - Matrix4x4 borderColor, - float cornerRadius, - RectangleF? textureCoord = null, - Texture? texture = null, - VertexMode vertexMode = VertexMode.Shape - ) - { - if (texture is { IsUploaded: false }) return; - - if (texture != CurrentTexture) - { - BindTexture(texture); - } - - var v0 = position; - var v1 = position with { Y = position.Y + size.Y }; - var v2 = position + size; - var v3 = position with { X = position.X + size.X }; - - v0 = Vector2.Transform(v0, drawMatrix.Matrix); - v1 = Vector2.Transform(v1, drawMatrix.Matrix); - v2 = Vector2.Transform(v2, drawMatrix.Matrix); - v3 = Vector2.Transform(v3, drawMatrix.Matrix); - - var tex = textureCoord ?? new Rectangle(0, 0, 1, 1); - - VertexBatch2D.PushVertex(new Vertex2D( - position: v0, - texCoord: new Vector2(tex.Left, tex.Top), - size: size, - color: packedColor, - alpha: alpha, - radius: cornerRadius, - localUv: new Vector2(0, 0), - mode: vertexMode, - borderThickness: borderThickness, - hasSingleColor: borderHasSingleColor, - borderColor: borderColor - )); - - VertexBatch2D.PushVertex(new Vertex2D( - position: v1, - texCoord: new Vector2(tex.Left, tex.Bottom), - size: size, - color: packedColor, - alpha: alpha, - radius: cornerRadius, - localUv: new Vector2(0, 1), - mode: vertexMode, - borderThickness: borderThickness, - hasSingleColor: borderHasSingleColor, - borderColor: borderColor - )); - - VertexBatch2D.PushVertex(new Vertex2D( - position: v2, - texCoord: new Vector2(tex.Right, tex.Bottom), - size: size, - color: packedColor, - alpha: alpha, - radius: cornerRadius, - localUv: new Vector2(1, 1), - mode: vertexMode, - borderThickness: borderThickness, - hasSingleColor: borderHasSingleColor, - borderColor: borderColor - )); - - VertexBatch2D.PushVertex(new Vertex2D( - position: v3, - texCoord: new Vector2(tex.Right, tex.Top), - size: size, - color: packedColor, - alpha: alpha, - radius: cornerRadius, - localUv: new Vector2(1, 0), - mode: vertexMode, - borderThickness: borderThickness, - hasSingleColor: borderHasSingleColor, - borderColor: borderColor - )); - } - - public void CompileDefaultShaders() - { - DefaultShader = new Shader(OpenGL, ShaderSources.DefaultVertex, ShaderSources.DefaultFragment); - StencilShader = new Shader(OpenGL, ShaderSources.DefaultVertex, ShaderSources.StencilFragment); - BindShader(DefaultShader); - } - - public void BindShader(Shader shader) - { - ThreadSafety.AssertRunningOnRenderThread(); - - if (CurrentShader == shader) return; - // Flush any pending vertices BEFORE swapping shaders - if (openGlInitialized) - VertexBatch2D.Flush(); - - CurrentShader = shader; - shader.Use(); - updateShaderMatrix(); - cacheShaderUniformLocations(); - - if (CurrentTexture != null) - { - CurrentShader.SetInt(textureShaderLocation, 0); - CurrentShader.SetInt(useTextureShaderLocation, 1); - } - else - { - CurrentShader.SetInt(useTextureShaderLocation, 0); - } - } - - public void UnbindShader() - { - ThreadSafety.AssertRunningOnRenderThread(); - BindShader(DefaultShader); - updateShaderMatrix(); - } - - private void cacheShaderUniformLocations() - { - textureShaderLocation = CurrentShader.GetUniformLocation(shader_uniform_texture); - useTextureShaderLocation = CurrentShader.GetUniformLocation(shader_uniform_use_texture); - transformMatrixShaderLocation = CurrentShader.GetUniformLocation(shader_uniform_transform_matrix); - } - - public void BindTexture(Texture? texture) - { - ThreadSafety.AssertRunningOnRenderThread(); - if (CurrentTexture == texture) return; - - VertexBatch2D.Flush(); - CurrentTexture = texture; - if (texture != null && texture.Bind(OpenGL)) - { - CurrentShader.SetInt(textureShaderLocation, 0); - CurrentShader.SetInt(useTextureShaderLocation, 1); - } - else - { - OpenGL.BindTexture(TextureTarget.Texture2D, 0); - CurrentShader.SetInt(useTextureShaderLocation, 0); - } - - // - // if (texture is { IsUploaded: true }) - // { - // texture.Bind(OpenGL); - // CurrentShader.SetInt(textureShaderLocation, 0); - // CurrentShader.SetInt(useTextureShaderLocation, 1); - // } - // else - // { - // OpenGL.BindTexture(TextureTarget.Texture2D, 0); - // CurrentShader.SetInt(useTextureShaderLocation, 0); - // } - } - - public void Resize(int width, int height) - { - EnsureInitialized(); - pushViewport2D(); - } - - private void pushViewport2D() - { - SDL.GetWindowSizeInPixels(Surface.WindowHandle, out int w, out int h); - BackBufferWidth = w; - BackBufferHeight = h; - - projectionMatrix = Matrix4x4.CreateOrthographicOffCenter(0, w, h, 0, -1, 1); - - OpenGL.Viewport(0, 0, (uint)w, (uint)h); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void EnsureInitialized() - { - if (!openGlInitialized) throw new InvalidOperationException("OpenGL is not initialized yet"); - } - - public void BeginDrawing3D(Matrix4x4 view, Matrix4x4 projection) - { - EnsureInitialized(); - - OpenGL.Enable(EnableCap.DepthTest); - OpenGL.DepthFunc(DepthFunction.Less); - OpenGL.Enable(EnableCap.CullFace); - OpenGL.CullFace(TriangleFace.Back); - OpenGL.FrontFace(FrontFaceDirection.Ccw); - - View3D = view; - Projection3D = projection; - } - - public void EndDrawing3D() - { - OpenGL.Disable(EnableCap.DepthTest); - OpenGL.Disable(EnableCap.CullFace); - } - - public void BeginDrawing2D() - { - stencilDepthStack = 0; - pushViewport2D(); - - LoadIdentity(); - updateShaderMatrix(); - DrawStatistics.Reset(); - } - - public void BeginDrawing() - { - EnsureInitialized(); - - ClearBufferMask mask = ClearBufferMask.None; - - if (ClearFlags.HasFlagFast(ClearFlags.ColorBuffer)) - mask |= ClearBufferMask.ColorBufferBit; - if (ClearFlags.HasFlagFast(ClearFlags.DepthBuffer)) - mask |= ClearBufferMask.DepthBufferBit; - if (ClearFlags.HasFlagFast(ClearFlags.StencilBuffer)) - mask |= ClearBufferMask.StencilBufferBit; - - if (mask != ClearBufferMask.None) OpenGL.Clear(mask); - - DrawStatistics.Set(DrawStatistics.Type.TextureUploadQueue, TEXTURE_UPLOAD_QUEUE.Count); - - while (!TEXTURE_UPLOAD_QUEUE.IsEmpty) - { - TEXTURE_UPLOAD_QUEUE.TryDequeue(out var texture); - texture?.Upload(OpenGL); - } - } - - public void EndDrawing() - { - EnsureInitialized(); - - Surface.SwapBuffers(); - BindTexture(null); - ClearFlags = default_clear_flags; - } - - - public void EndDrawing2D() - { - EnsureInitialized(); - VertexBatch2D.Flush(); - } - - public void PushMatrix() - { - EnsureInitialized(); - matrixStack.Push(Matrix); - inverseMatrixStack.Push(InverseMatrix); - } - - public void PopMatrix() - { - EnsureInitialized(); - - if (matrixStack.Count == 0) throw new InvalidOperationException("Matrix stack is empty"); - - Matrix = matrixStack.Pop(); - InverseMatrix = inverseMatrixStack.Pop(); - - updateShaderMatrix(); - } - - public void Translate(float x, float y, float z) - { - EnsureInitialized(); - - Matrix = Matrix4x4.CreateTranslation(x, y, z) * Matrix; - InverseMatrix *= Matrix4x4.CreateTranslation(-x, -y, -z); - - updateShaderMatrix(); - } - - public void Scale(float x, float y, float z) - { - EnsureInitialized(); - Matrix = Matrix4x4.CreateScale(x, y, z) * Matrix; - InverseMatrix *= Matrix4x4.CreateScale(1 / x, 1 / y, 1 / z); - - updateShaderMatrix(); - } - - public void Rotate(float degrees, float x, float y, float z) - { - EnsureInitialized(); - - var rads = degrees.ToRads(); - var axis = Vector3.Normalize(new Vector3(x, y, z)); - - Matrix = Matrix4x4.CreateFromAxisAngle(axis, rads) * Matrix; - InverseMatrix *= Matrix4x4.CreateFromAxisAngle(axis, -rads); - - updateShaderMatrix(); - } - - public void LoadIdentity() - { - EnsureInitialized(); - Matrix = Matrix4x4.Identity; - InverseMatrix = Matrix4x4.Identity; - updateShaderMatrix(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector2 ScreenToLocal(Vector2 screenPos) - { - return Vector2.Transform(screenPos, InverseMatrix); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector2 ScreenToLocalDirection(Vector2 screenDelta) - { - return Vector2.TransformNormal(screenDelta, InverseMatrix); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ContainsPoint(Vector2 screenPos, Vector2 size) - { - var localPos = ScreenToLocal(screenPos); - return localPos.X >= 0 && localPos.X <= size.X && - localPos.Y >= 0 && localPos.Y <= size.Y; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ContainsPoint(Vector2 screenPos, Vector2 offset, Vector2 size) - { - var localPos = ScreenToLocal(screenPos); - return localPos.X >= offset.X && localPos.X <= offset.X + size.X && - localPos.Y >= offset.Y && localPos.Y <= offset.Y + size.Y; - } - - private void updateShaderMatrix() - { - CurrentShader.SetMatrix4(transformMatrixShaderLocation, Matrix * projectionMatrix); - } - - public void RotateAround(Vector2 pivot, float degrees) - { - EnsureInitialized(); - - Translate(-pivot.X, -pivot.Y, 0); - Rotate(degrees, 0, 0, 1); - Translate(pivot.X, pivot.Y, 0); - } - - public void BeginStencil() - { - if (stencilDepthStack == 0) - { - OpenGL.Enable(GLEnum.StencilTest); - } - - stencilDepthStack++; - } - - public void BeginStencilMask() - { - OpenGL.Enable(EnableCap.Multisample); - OpenGL.ColorMask(false, false, false, false); - - OpenGL.StencilFunc(StencilFunction.Equal, stencilDepthStack - 1, 0xFF); - OpenGL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Incr); - - BindShader(StencilShader); - } - - public void EndStencilMask() - { - OpenGL.ColorMask(true, true, true, true); - - OpenGL.StencilFunc(StencilFunction.Equal, stencilDepthStack, 0xFF); - OpenGL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Keep); - - UnbindShader(); - } - - public void EndStencil() - { - stencilDepthStack--; - - if (stencilDepthStack == 0) - { - OpenGL.Disable(EnableCap.StencilTest); - } - else - { - OpenGL.StencilFunc(StencilFunction.Equal, stencilDepthStack, 0xFF); - OpenGL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Keep); - } - - OpenGL.ColorMask(true, true, true, true); - UnbindShader(); - } - - public void BeginStencilRestore() - { - OpenGL.Enable(EnableCap.Multisample); - OpenGL.ColorMask(false, false, false, false); - - OpenGL.StencilFunc(StencilFunction.Equal, stencilDepthStack, 0xFF); - OpenGL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Decr); - - BindShader(StencilShader); - } - - public void Dispose() - { - openGlInitialized = false; - OpenGL.Dispose(); - } -} +// // Copyright (c) 2026 SynesthesiaDev . Licensed under the MIT Licence. +// // See the LICENCE file in the repository root for full licence text. +// +// using System.Collections.Concurrent; +// using System.Drawing; +// using System.Numerics; +// using System.Runtime.CompilerServices; +// using SDL3; +// using Silk.NET.OpenGL; +// using Synesthesia.Engine.Graphics; +// using Synesthesia.Engine.Graphics.Shaders; +// using Synesthesia.Engine.Logging; +// using Synesthesia.Engine.Threading; +// using Synesthesia.Engine.Util; +// using Synesthesia.Engine.Util.Statistics; +// using Synesthesia.Utils.Extensions; +// using Shader = Synesthesia.Engine.Graphics.Shader; +// using Texture = Synesthesia.Engine.Graphics.Textures.Texture; +// +// namespace Synesthesia.Engine.Platform.Render; +// +// public sealed class OpenGlRenderer : IDisposable +// { +// private const ClearFlags default_clear_flags = ClearFlags.ColorBuffer | ClearFlags.DepthBuffer | ClearFlags.StencilBuffer; +// +// private const string shader_uniform_texture = "u_texture"; +// private const string shader_uniform_use_texture = "u_useTexture"; +// private const string shader_uniform_transform_matrix = "u_transform"; +// public required OpenGLSurface Surface { get; init; } +// +// private bool openGlInitialized; +// +// public int BackBufferWidth { get; private set; } +// +// public int BackBufferHeight { get; private set; } +// +// public ClearFlags ClearFlags = default_clear_flags; +// +// public Shader DefaultShader = null!; +// public Shader StencilShader = null!; +// +// public VertexBatch VertexBatch2D = null!; +// +// public GL OpenGL +// { +// get +// { +// EnsureInitialized(); +// return field; +// } +// +// private set; +// } = null!; +// +// public bool CanDraw => BackBufferWidth > 0 && BackBufferHeight > 0; +// +// private readonly Stack matrixStack = new Stack(); +// +// private readonly Stack inverseMatrixStack = new(); +// +// public int StackDepth => matrixStack.Count; +// +// public Shader CurrentShader { get; private set; } = null!; +// +// public Texture? CurrentTexture { get; private set; } +// +// public Matrix4x4 Matrix { get; private set; } = Matrix4x4.Identity; +// +// public Matrix4x4 InverseMatrix { get; private set; } = Matrix4x4.Identity; +// +// private Matrix4x4 projectionMatrix; +// +// public Matrix4x4 View3D { get; private set; } = Matrix4x4.Identity; +// public Matrix4x4 Projection3D { get; private set; } = Matrix4x4.Identity; +// +// public static readonly ConcurrentQueue TEXTURE_UPLOAD_QUEUE = new ConcurrentQueue(); +// +// // Cache so we don't query with string every frame. That's expensive on gc allocations!! +// private int textureShaderLocation; +// private int useTextureShaderLocation; +// private int transformMatrixShaderLocation; +// private int stencilDepthStack; +// +// public void Initialize() +// { +// if (openGlInitialized) throw new InvalidOperationException("OpenGL is already initialized"); +// +// var gl = GL.GetApi(name => +// { +// var ptr = OpenGLSurface.GetProcAddress(name); +// return ptr; +// }); +// +// OpenGL = gl ?? throw new InvalidOperationException("Silk.NET could not bind to OpenGL"); +// +// BackBufferHeight = Surface.BackBufferHeight; +// BackBufferWidth = Surface.BackBufferWidth; +// +// openGlInitialized = true; +// Resize(BackBufferWidth, BackBufferHeight); +// +// var version = OpenGL.GetStringS(GLEnum.Version); +// var shadingLanguageVersion = OpenGL.GetStringS(GLEnum.ShadingLanguageVersion); +// var vendor = OpenGL.GetStringS(GLEnum.Vendor); +// var renderer = OpenGL.GetStringS(GLEnum.Renderer); +// +// VertexBatch2D = new VertexBatch(OpenGL); +// +// Logger.EmptyLine(); +// Logger.Debug("OpenGL Initialized", Logger.Platform); +// Logger.Debug($"- Version: {version}", Logger.Platform); +// Logger.Debug($"- Vendor: {vendor}", Logger.Platform); +// Logger.Debug($"- Renderer {renderer}", Logger.Platform); +// Logger.Debug($"- GLSL: {shadingLanguageVersion}", Logger.Platform); +// } +// +// public void DrawQuad +// ( +// DrawMatrix drawMatrix, +// Vector2 position, +// Vector2 size, +// uint packedColor, +// float alpha, +// float borderThickness, +// bool borderHasSingleColor, +// Matrix4x4 borderColor, +// float cornerRadius, +// RectangleF? textureCoord = null, +// Texture? texture = null, +// VertexMode vertexMode = VertexMode.Shape +// ) +// { +// if (texture is { IsUploaded: false }) return; +// +// if (texture != CurrentTexture) +// { +// BindTexture(texture); +// } +// +// var v0 = position; +// var v1 = position with { Y = position.Y + size.Y }; +// var v2 = position + size; +// var v3 = position with { X = position.X + size.X }; +// +// v0 = Vector2.Transform(v0, drawMatrix.Matrix); +// v1 = Vector2.Transform(v1, drawMatrix.Matrix); +// v2 = Vector2.Transform(v2, drawMatrix.Matrix); +// v3 = Vector2.Transform(v3, drawMatrix.Matrix); +// +// var tex = textureCoord ?? new Rectangle(0, 0, 1, 1); +// +// VertexBatch2D.PushVertex(new Vertex2D( +// position: v0, +// texCoord: new Vector2(tex.Left, tex.Top), +// size: size, +// color: packedColor, +// alpha: alpha, +// radius: cornerRadius, +// localUv: new Vector2(0, 0), +// mode: vertexMode, +// borderThickness: borderThickness, +// hasSingleColor: borderHasSingleColor, +// borderColor: borderColor +// )); +// +// VertexBatch2D.PushVertex(new Vertex2D( +// position: v1, +// texCoord: new Vector2(tex.Left, tex.Bottom), +// size: size, +// color: packedColor, +// alpha: alpha, +// radius: cornerRadius, +// localUv: new Vector2(0, 1), +// mode: vertexMode, +// borderThickness: borderThickness, +// hasSingleColor: borderHasSingleColor, +// borderColor: borderColor +// )); +// +// VertexBatch2D.PushVertex(new Vertex2D( +// position: v2, +// texCoord: new Vector2(tex.Right, tex.Bottom), +// size: size, +// color: packedColor, +// alpha: alpha, +// radius: cornerRadius, +// localUv: new Vector2(1, 1), +// mode: vertexMode, +// borderThickness: borderThickness, +// hasSingleColor: borderHasSingleColor, +// borderColor: borderColor +// )); +// +// VertexBatch2D.PushVertex(new Vertex2D( +// position: v3, +// texCoord: new Vector2(tex.Right, tex.Top), +// size: size, +// color: packedColor, +// alpha: alpha, +// radius: cornerRadius, +// localUv: new Vector2(1, 0), +// mode: vertexMode, +// borderThickness: borderThickness, +// hasSingleColor: borderHasSingleColor, +// borderColor: borderColor +// )); +// } +// +// public void CompileDefaultShaders() +// { +// DefaultShader = new Shader(OpenGL, ShaderSources.DefaultVertex, ShaderSources.DefaultFragment); +// StencilShader = new Shader(OpenGL, ShaderSources.DefaultVertex, ShaderSources.StencilFragment); +// BindShader(DefaultShader); +// } +// +// public void BindShader(Shader shader) +// { +// ThreadSafety.AssertRunningOnRenderThread(); +// +// if (CurrentShader == shader) return; +// // Flush any pending vertices BEFORE swapping shaders +// if (openGlInitialized) +// VertexBatch2D.Flush(); +// +// CurrentShader = shader; +// shader.Use(); +// updateShaderMatrix(); +// cacheShaderUniformLocations(); +// +// if (CurrentTexture != null) +// { +// CurrentShader.SetInt(textureShaderLocation, 0); +// CurrentShader.SetInt(useTextureShaderLocation, 1); +// } +// else +// { +// CurrentShader.SetInt(useTextureShaderLocation, 0); +// } +// } +// +// public void UnbindShader() +// { +// ThreadSafety.AssertRunningOnRenderThread(); +// BindShader(DefaultShader); +// updateShaderMatrix(); +// } +// +// private void cacheShaderUniformLocations() +// { +// textureShaderLocation = CurrentShader.GetUniformLocation(shader_uniform_texture); +// useTextureShaderLocation = CurrentShader.GetUniformLocation(shader_uniform_use_texture); +// transformMatrixShaderLocation = CurrentShader.GetUniformLocation(shader_uniform_transform_matrix); +// } +// +// public void BindTexture(Texture? texture) +// { +// ThreadSafety.AssertRunningOnRenderThread(); +// if (CurrentTexture == texture) return; +// +// VertexBatch2D.Flush(); +// CurrentTexture = texture; +// if (texture != null && texture.Bind(OpenGL)) +// { +// CurrentShader.SetInt(textureShaderLocation, 0); +// CurrentShader.SetInt(useTextureShaderLocation, 1); +// } +// else +// { +// OpenGL.BindTexture(TextureTarget.Texture2D, 0); +// CurrentShader.SetInt(useTextureShaderLocation, 0); +// } +// +// // +// // if (texture is { IsUploaded: true }) +// // { +// // texture.Bind(OpenGL); +// // CurrentShader.SetInt(textureShaderLocation, 0); +// // CurrentShader.SetInt(useTextureShaderLocation, 1); +// // } +// // else +// // { +// // OpenGL.BindTexture(TextureTarget.Texture2D, 0); +// // CurrentShader.SetInt(useTextureShaderLocation, 0); +// // } +// } +// +// public void Resize(int width, int height) +// { +// EnsureInitialized(); +// pushViewport2D(); +// } +// +// private void pushViewport2D() +// { +// SDL.GetWindowSizeInPixels(Surface.WindowHandle, out int w, out int h); +// BackBufferWidth = w; +// BackBufferHeight = h; +// +// projectionMatrix = Matrix4x4.CreateOrthographicOffCenter(0, w, h, 0, -1, 1); +// +// OpenGL.Viewport(0, 0, (uint)w, (uint)h); +// } +// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public void EnsureInitialized() +// { +// if (!openGlInitialized) throw new InvalidOperationException("OpenGL is not initialized yet"); +// } +// +// public void BeginDrawing3D(Matrix4x4 view, Matrix4x4 projection) +// { +// EnsureInitialized(); +// +// OpenGL.Enable(EnableCap.DepthTest); +// OpenGL.DepthFunc(DepthFunction.Less); +// OpenGL.Enable(EnableCap.CullFace); +// OpenGL.CullFace(TriangleFace.Back); +// OpenGL.FrontFace(FrontFaceDirection.Ccw); +// +// View3D = view; +// Projection3D = projection; +// } +// +// public void EndDrawing3D() +// { +// OpenGL.Disable(EnableCap.DepthTest); +// OpenGL.Disable(EnableCap.CullFace); +// } +// +// public void BeginDrawing2D() +// { +// stencilDepthStack = 0; +// pushViewport2D(); +// +// LoadIdentity(); +// updateShaderMatrix(); +// DrawStatistics.Reset(); +// } +// +// public void BeginDrawing() +// { +// EnsureInitialized(); +// +// ClearBufferMask mask = ClearBufferMask.None; +// +// if (ClearFlags.HasFlagFast(ClearFlags.ColorBuffer)) +// mask |= ClearBufferMask.ColorBufferBit; +// if (ClearFlags.HasFlagFast(ClearFlags.DepthBuffer)) +// mask |= ClearBufferMask.DepthBufferBit; +// if (ClearFlags.HasFlagFast(ClearFlags.StencilBuffer)) +// mask |= ClearBufferMask.StencilBufferBit; +// +// if (mask != ClearBufferMask.None) OpenGL.Clear(mask); +// +// DrawStatistics.Set(DrawStatistics.Type.TextureUploadQueue, TEXTURE_UPLOAD_QUEUE.Count); +// +// while (!TEXTURE_UPLOAD_QUEUE.IsEmpty) +// { +// TEXTURE_UPLOAD_QUEUE.TryDequeue(out var texture); +// texture?.Upload(OpenGL); +// } +// } +// +// public void EndDrawing() +// { +// EnsureInitialized(); +// +// Surface.SwapBuffers(); +// BindTexture(null); +// ClearFlags = default_clear_flags; +// } +// +// public void EndDrawing2D() +// { +// EnsureInitialized(); +// VertexBatch2D.Flush(); +// } +// +// public void PushMatrix() +// { +// EnsureInitialized(); +// matrixStack.Push(Matrix); +// inverseMatrixStack.Push(InverseMatrix); +// } +// +// public void PopMatrix() +// { +// EnsureInitialized(); +// +// if (matrixStack.Count == 0) throw new InvalidOperationException("Matrix stack is empty"); +// +// Matrix = matrixStack.Pop(); +// InverseMatrix = inverseMatrixStack.Pop(); +// +// updateShaderMatrix(); +// } +// +// public void Translate(float x, float y, float z) +// { +// EnsureInitialized(); +// +// Matrix = Matrix4x4.CreateTranslation(x, y, z) * Matrix; +// InverseMatrix *= Matrix4x4.CreateTranslation(-x, -y, -z); +// +// updateShaderMatrix(); +// } +// +// public void Scale(float x, float y, float z) +// { +// EnsureInitialized(); +// Matrix = Matrix4x4.CreateScale(x, y, z) * Matrix; +// InverseMatrix *= Matrix4x4.CreateScale(1 / x, 1 / y, 1 / z); +// +// updateShaderMatrix(); +// } +// +// public void Rotate(float degrees, float x, float y, float z) +// { +// EnsureInitialized(); +// +// var rads = degrees.ToRads(); +// var axis = Vector3.Normalize(new Vector3(x, y, z)); +// +// Matrix = Matrix4x4.CreateFromAxisAngle(axis, rads) * Matrix; +// InverseMatrix *= Matrix4x4.CreateFromAxisAngle(axis, -rads); +// +// updateShaderMatrix(); +// } +// +// public void LoadIdentity() +// { +// EnsureInitialized(); +// Matrix = Matrix4x4.Identity; +// InverseMatrix = Matrix4x4.Identity; +// updateShaderMatrix(); +// } +// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public Vector2 ScreenToLocal(Vector2 screenPos) +// { +// return Vector2.Transform(screenPos, InverseMatrix); +// } +// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public Vector2 ScreenToLocalDirection(Vector2 screenDelta) +// { +// return Vector2.TransformNormal(screenDelta, InverseMatrix); +// } +// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public bool ContainsPoint(Vector2 screenPos, Vector2 size) +// { +// var localPos = ScreenToLocal(screenPos); +// return localPos.X >= 0 && localPos.X <= size.X && +// localPos.Y >= 0 && localPos.Y <= size.Y; +// } +// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public bool ContainsPoint(Vector2 screenPos, Vector2 offset, Vector2 size) +// { +// var localPos = ScreenToLocal(screenPos); +// return localPos.X >= offset.X && localPos.X <= offset.X + size.X && +// localPos.Y >= offset.Y && localPos.Y <= offset.Y + size.Y; +// } +// +// private void updateShaderMatrix() +// { +// CurrentShader.SetMatrix4(transformMatrixShaderLocation, Matrix * projectionMatrix); +// } +// +// public void RotateAround(Vector2 pivot, float degrees) +// { +// EnsureInitialized(); +// +// Translate(-pivot.X, -pivot.Y, 0); +// Rotate(degrees, 0, 0, 1); +// Translate(pivot.X, pivot.Y, 0); +// } +// +// public void BeginStencil() +// { +// if (stencilDepthStack == 0) +// { +// OpenGL.Enable(GLEnum.StencilTest); +// } +// +// stencilDepthStack++; +// } +// +// public void BeginStencilMask() +// { +// OpenGL.Enable(EnableCap.Multisample); +// OpenGL.ColorMask(false, false, false, false); +// +// OpenGL.StencilFunc(StencilFunction.Equal, stencilDepthStack - 1, 0xFF); +// OpenGL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Incr); +// +// BindShader(StencilShader); +// } +// +// public void EndStencilMask() +// { +// OpenGL.ColorMask(true, true, true, true); +// +// OpenGL.StencilFunc(StencilFunction.Equal, stencilDepthStack, 0xFF); +// OpenGL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Keep); +// +// UnbindShader(); +// } +// +// public void EndStencil() +// { +// stencilDepthStack--; +// +// if (stencilDepthStack == 0) +// { +// OpenGL.Disable(EnableCap.StencilTest); +// } +// else +// { +// OpenGL.StencilFunc(StencilFunction.Equal, stencilDepthStack, 0xFF); +// OpenGL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Keep); +// } +// +// OpenGL.ColorMask(true, true, true, true); +// UnbindShader(); +// } +// +// public void BeginStencilRestore() +// { +// OpenGL.Enable(EnableCap.Multisample); +// OpenGL.ColorMask(false, false, false, false); +// +// OpenGL.StencilFunc(StencilFunction.Equal, stencilDepthStack, 0xFF); +// OpenGL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Decr); +// +// BindShader(StencilShader); +// } +// +// public void Dispose() +// { +// openGlInitialized = false; +// OpenGL.Dispose(); +// } +// } diff --git a/Synesthesia.Engine/Platform/Render/Renderer2D.cs b/Synesthesia.Engine/Platform/Render/Renderer2D.cs new file mode 100644 index 0000000..5069f95 --- /dev/null +++ b/Synesthesia.Engine/Platform/Render/Renderer2D.cs @@ -0,0 +1,239 @@ +// Copyright (c) 2026 SynesthesiaDev . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Drawing; +using System.Numerics; +using System.Runtime.CompilerServices; +using Synesthesia.Engine.Graphics; +using Synesthesia.Engine.Graphics.Textures; +using Synesthesia.Engine.Util; + +namespace Synesthesia.Engine.Platform.Render; + +public class Renderer2D(GraphicsDevice graphicsDevice) : IRenderer +{ + private const string shader_uniform_transform_matrix = "u_transform"; + + public readonly GraphicsDevice GraphicsDevice = graphicsDevice; + + private readonly Stack matrixStack = new(); + private readonly Stack inverseMatrixStack = new(); + private Matrix4x4 projectionMatrix; + private int transformMatrixShaderLocation; + + public VertexBatch VertexBatch { get; private set; } = new(graphicsDevice.OpenGL, true); + + public Matrix4x4 Matrix { get; private set; } = Matrix4x4.Identity; + public Matrix4x4 InverseMatrix { get; private set; } = Matrix4x4.Identity; + + public void PushMatrix() + { + GraphicsDevice.EnsureInitialized(); + matrixStack.Push(Matrix); + inverseMatrixStack.Push(InverseMatrix); + } + + public void PopMatrix() + { + GraphicsDevice.EnsureInitialized(); + + if (matrixStack.Count == 0) throw new InvalidOperationException("Matrix stack is empty"); + + Matrix = matrixStack.Pop(); + InverseMatrix = inverseMatrixStack.Pop(); + + UpdateShaderMatrix(); + } + + public void DrawQuad + ( + DrawMatrix drawMatrix, + Vector2 position, + Vector2 size, + uint packedColor, + float alpha, + float borderThickness, + bool borderHasSingleColor, + Matrix4x4 borderColor, + float cornerRadius, + RectangleF? textureCoord = null, + Texture? texture = null, + VertexMode vertexMode = VertexMode.Shape + ) + { + if (texture is { IsUploaded: false }) return; + + var v0 = position; + if (texture != GraphicsDevice.CurrentTexture) + { + GraphicsDevice.BindTexture(texture); + } + + var v1 = position with { Y = position.Y + size.Y }; + var v2 = position + size; + var v3 = position with { X = position.X + size.X }; + + v0 = Vector2.Transform(v0, drawMatrix.Matrix); + v1 = Vector2.Transform(v1, drawMatrix.Matrix); + v2 = Vector2.Transform(v2, drawMatrix.Matrix); + v3 = Vector2.Transform(v3, drawMatrix.Matrix); + + var tex = textureCoord ?? new Rectangle(0, 0, 1, 1); + + VertexBatch.PushVertex(new Vertex2D( + position: v0, + texCoord: new Vector2(tex.Left, tex.Top), + size: size, + color: packedColor, + alpha: alpha, + radius: cornerRadius, + localUv: new Vector2(0, 0), + mode: vertexMode, + borderThickness: borderThickness, + hasSingleColor: borderHasSingleColor, + borderColor: borderColor + )); + + VertexBatch.PushVertex(new Vertex2D( + position: v1, + texCoord: new Vector2(tex.Left, tex.Bottom), + size: size, + color: packedColor, + alpha: alpha, + radius: cornerRadius, + localUv: new Vector2(0, 1), + mode: vertexMode, + borderThickness: borderThickness, + hasSingleColor: borderHasSingleColor, + borderColor: borderColor + )); + + VertexBatch.PushVertex(new Vertex2D( + position: v2, + texCoord: new Vector2(tex.Right, tex.Bottom), + size: size, + color: packedColor, + alpha: alpha, + radius: cornerRadius, + localUv: new Vector2(1, 1), + mode: vertexMode, + borderThickness: borderThickness, + hasSingleColor: borderHasSingleColor, + borderColor: borderColor + )); + + VertexBatch.PushVertex(new Vertex2D( + position: v3, + texCoord: new Vector2(tex.Right, tex.Top), + size: size, + color: packedColor, + alpha: alpha, + radius: cornerRadius, + localUv: new Vector2(1, 0), + mode: vertexMode, + borderThickness: borderThickness, + hasSingleColor: borderHasSingleColor, + borderColor: borderColor + )); + } + + public void Translate(float x, float y, float z) + { + GraphicsDevice.EnsureInitialized(); + + Matrix = Matrix4x4.CreateTranslation(x, y, z) * Matrix; + InverseMatrix *= Matrix4x4.CreateTranslation(-x, -y, -z); + + UpdateShaderMatrix(); + } + + public void Scale(float x, float y, float z) + { + GraphicsDevice.EnsureInitialized(); + Matrix = Matrix4x4.CreateScale(x, y, z) * Matrix; + InverseMatrix *= Matrix4x4.CreateScale(1 / x, 1 / y, 1 / z); + + UpdateShaderMatrix(); + } + + public void Rotate(float degrees, float x, float y, float z) + { + GraphicsDevice.EnsureInitialized(); + + var rads = degrees.ToRads(); + var axis = Vector3.Normalize(new Vector3(x, y, z)); + + Matrix = Matrix4x4.CreateFromAxisAngle(axis, rads) * Matrix; + InverseMatrix *= Matrix4x4.CreateFromAxisAngle(axis, -rads); + + UpdateShaderMatrix(); + } + + public void CacheUniformLocations() + { + transformMatrixShaderLocation = GraphicsDevice.CurrentShader.GetUniformLocation(shader_uniform_transform_matrix); + } + + public void LoadIdentity() + { + GraphicsDevice.EnsureInitialized(); + Matrix = Matrix4x4.Identity; + InverseMatrix = Matrix4x4.Identity; + UpdateShaderMatrix(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector2 ScreenToLocal(Vector2 screenPos) + { + return Vector2.Transform(screenPos, InverseMatrix); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector2 ScreenToLocalDirection(Vector2 screenDelta) + { + return Vector2.TransformNormal(screenDelta, InverseMatrix); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ContainsPoint(Vector2 screenPos, Vector2 size) + { + var localPos = ScreenToLocal(screenPos); + return localPos.X >= 0 && localPos.X <= size.X && + localPos.Y >= 0 && localPos.Y <= size.Y; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ContainsPoint(Vector2 screenPos, Vector2 offset, Vector2 size) + { + var localPos = ScreenToLocal(screenPos); + return localPos.X >= offset.X && localPos.X <= offset.X + size.X && + localPos.Y >= offset.Y && localPos.Y <= offset.Y + size.Y; + } + + public void UpdateShaderMatrix() + { + GraphicsDevice.CurrentShader.SetMatrix4(transformMatrixShaderLocation, Matrix * projectionMatrix); + } + + public void BeginDrawing() + { + GraphicsDevice.PushViewport(); + + projectionMatrix = Matrix4x4.CreateOrthographicOffCenter( + 0, GraphicsDevice.BackBufferWidth, GraphicsDevice.BackBufferHeight, 0, -1, 1); + + LoadIdentity(); + UpdateShaderMatrix(); + } + + public void EndDrawing() + { + GraphicsDevice.EnsureInitialized(); + VertexBatch.Flush(); + } + + public void FlushVertexBatch() + { + VertexBatch.Flush(); + } +} diff --git a/Synesthesia.Engine/Threading/Threads/RenderThread.cs b/Synesthesia.Engine/Threading/Threads/RenderThread.cs index c14ca6b..00bf235 100644 --- a/Synesthesia.Engine/Threading/Threads/RenderThread.cs +++ b/Synesthesia.Engine/Threading/Threads/RenderThread.cs @@ -11,9 +11,9 @@ namespace Synesthesia.Engine.Threading.Threads; -public class RenderThread(OpenGlRenderer renderer) : ThreadRunner +public class RenderThread(GraphicsDevice graphicsDevice) : ThreadRunner { - public OpenGlRenderer Renderer { get; } = renderer; + public GraphicsDevice GraphicsDevice { get; } = graphicsDevice; public override ThreadType Type => ThreadType.Draw; @@ -34,14 +34,14 @@ public class RenderThread(OpenGlRenderer renderer) : ThreadRunner protected override void OnThreadInit() { - Renderer.Surface.ClaimOwnership(); + GraphicsDevice.Surface.ClaimOwnership(); Logger.Verbose("Transferred renderer context ownership to Render Thread", Logger.Platform); hasContextOwnership = true; (textureResourceStore as DeferredResourceStore)?.Unlock(); (fontResourceStore as DeferredResourceStore)?.Unlock(); - Renderer.CompileDefaultShaders(); + GraphicsDevice.CompileDefaultShaders(); fontResourceStore.Get("Synesthesia.Resources.Fonts.Quicksand-Regular.fna"); // cache game.UpdateThread.Schedule(() => @@ -53,16 +53,17 @@ protected override void OnThreadInit() protected override void ProcessFrame(FrameInfo frameInfo) { - if (!Renderer.CanDraw || !hasContextOwnership) return; + if (!GraphicsDevice.CanDraw || !hasContextOwnership) return; var gameContainer = game.GetInternalGameContainer(); - Renderer.BeginDrawing(); - Renderer.BeginDrawing2D(); + GraphicsDevice.BeginDrawing(); + GraphicsDevice.Renderer2D.BeginDrawing(); gameContainer.OnDraw(); - Renderer.EndDrawing2D(); - Renderer.EndDrawing(); + GraphicsDevice.Renderer2D.EndDrawing(); + GraphicsDevice.EndDrawing(); + if (isFirstSwap) { isFirstSwap = false; diff --git a/Synesthesia.Engine/Threading/Threads/UpdateThread.cs b/Synesthesia.Engine/Threading/Threads/UpdateThread.cs index 0f87e1f..8068c48 100644 --- a/Synesthesia.Engine/Threading/Threads/UpdateThread.cs +++ b/Synesthesia.Engine/Threading/Threads/UpdateThread.cs @@ -19,7 +19,7 @@ public class UpdateThread : ThreadRunner private Game game = null!; [Singleton] - private OpenGlRenderer renderer = null!; + private GraphicsDevice renderer = null!; protected override void ProcessFrame(FrameInfo frameInfo) { diff --git a/Synesthesia.Engine/Util/Statistics/DrawStatistics.cs b/Synesthesia.Engine/Util/Statistics/DrawStatistics.cs index 1eb47f8..af66940 100644 --- a/Synesthesia.Engine/Util/Statistics/DrawStatistics.cs +++ b/Synesthesia.Engine/Util/Statistics/DrawStatistics.cs @@ -27,7 +27,8 @@ public enum Type Invalidations, VertexBatchFlushes, VertexBatchOverflows, - DrawCalls, + DrawCalls2D, + DrawCalls3D, TextureBinds, ShaderBinds, UniformUploads, From 87c5717e77ba4d7f880d9b5f2b659ccc9da73fa2 Mon Sep 17 00:00:00 2001 From: SynesthesiaDev_ Date: Thu, 30 Jul 2026 22:35:16 +0200 Subject: [PATCH 3/4] fix audio thread lowering update rate when window is inactive --- Synesthesia.Engine/Threading/Threads/AudioThread.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Synesthesia.Engine/Threading/Threads/AudioThread.cs b/Synesthesia.Engine/Threading/Threads/AudioThread.cs index ea4a3d3..c27a6a1 100644 --- a/Synesthesia.Engine/Threading/Threads/AudioThread.cs +++ b/Synesthesia.Engine/Threading/Threads/AudioThread.cs @@ -19,6 +19,7 @@ protected override void ProcessFrame(FrameInfo frameInfo) protected override void OnThreadInit() { LoadFuture.Complete(this); + InactiveUpdateRate.Value = ActiveUpdateRate.Value; } } From 06949fbeeb2876ac99a83b4808b7682981f15c93 Mon Sep 17 00:00:00 2001 From: SynesthesiaDev_ Date: Sun, 23 Aug 2026 14:54:54 +0200 Subject: [PATCH 4/4] update StbImageSharp --- Synesthesia.Engine/Synesthesia.Engine.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Synesthesia.Engine/Synesthesia.Engine.csproj b/Synesthesia.Engine/Synesthesia.Engine.csproj index 1f12bd5..a51c14d 100644 --- a/Synesthesia.Engine/Synesthesia.Engine.csproj +++ b/Synesthesia.Engine/Synesthesia.Engine.csproj @@ -49,7 +49,7 @@ - +