diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 2d46b7c..b4c5d8f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,5 +1,7 @@ - 11.3.12-cibuild0004211-alpha + [12.1.1-cibuild0004449-alpha] + true + true \ No newline at end of file diff --git a/src/DrawiEngine.Browser/BrowserDrawingEngine.cs b/src/DrawiEngine.Browser/BrowserDrawingEngine.cs index 3b10e54..f559414 100644 --- a/src/DrawiEngine.Browser/BrowserDrawingEngine.cs +++ b/src/DrawiEngine.Browser/BrowserDrawingEngine.cs @@ -1,7 +1,7 @@ using Drawie.JSInterop; using Drawie.RenderApi.WebGl; using Drawie.Skia; -using Drawie.Windowing.Browser; +using Drawie.Host.Browser; namespace DrawiEngine.Browser; diff --git a/src/DrawiEngine.Browser/DrawiEngine.Browser.csproj b/src/DrawiEngine.Browser/DrawiEngine.Browser.csproj index ce76d37..b0f1193 100644 --- a/src/DrawiEngine.Browser/DrawiEngine.Browser.csproj +++ b/src/DrawiEngine.Browser/DrawiEngine.Browser.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable @@ -9,7 +9,7 @@ - + diff --git a/src/DrawiEngine.Desktop/DesktopDrawingEngine.cs b/src/DrawiEngine.Desktop/DesktopDrawingEngine.cs index 58fed86..2c855b0 100644 --- a/src/DrawiEngine.Desktop/DesktopDrawingEngine.cs +++ b/src/DrawiEngine.Desktop/DesktopDrawingEngine.cs @@ -8,9 +8,9 @@ namespace DrawiEngine.Desktop; public static class DesktopDrawingEngine { - public static DrawingEngine CreateDefaultDesktop() + public static DrawingEngine CreateDefaultDesktop(bool preferVulkan = true) { - IRenderApi renderApi = new VulkanRenderApi(); + IRenderApi renderApi = preferVulkan ? new VulkanRenderApi() : new OpenGlRenderApi(); if (OperatingSystem.IsMacOS()) { diff --git a/src/DrawiEngine.Desktop/DrawiEngine.Desktop.csproj b/src/DrawiEngine.Desktop/DrawiEngine.Desktop.csproj index 9bd2407..f81c29c 100644 --- a/src/DrawiEngine.Desktop/DrawiEngine.Desktop.csproj +++ b/src/DrawiEngine.Desktop/DrawiEngine.Desktop.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable @@ -9,7 +9,7 @@ - + diff --git a/src/DrawiEngine/DrawiEngine.csproj b/src/DrawiEngine/DrawiEngine.csproj index b131288..fb05b47 100644 --- a/src/DrawiEngine/DrawiEngine.csproj +++ b/src/DrawiEngine/DrawiEngine.csproj @@ -1,13 +1,13 @@  - net8.0 + net10.0 enable enable - + diff --git a/src/DrawiEngine/DrawieApp.cs b/src/DrawiEngine/DrawieApp.cs index 1777b8f..bd6cf27 100644 --- a/src/DrawiEngine/DrawieApp.cs +++ b/src/DrawiEngine/DrawieApp.cs @@ -1,5 +1,5 @@ using Drawie.Backend.Core.Bridge; -using Drawie.Windowing; +using Drawie.Host; namespace DrawiEngine; @@ -17,11 +17,11 @@ public void Initialize(DrawingEngine engine) Engine = engine; } - public abstract IWindow CreateMainWindow(); + public abstract IHost CreateMainWindow(); public void Run() { - if (DrawingBackendApi.HasBackend) + if (DrawingBackendApi.Initialized) { OnInitialize(); } diff --git a/src/DrawiEngine/DrawieRenderingDispatcher.cs b/src/DrawiEngine/DrawieRenderingDispatcher.cs index 77391da..62c0f40 100644 --- a/src/DrawiEngine/DrawieRenderingDispatcher.cs +++ b/src/DrawiEngine/DrawieRenderingDispatcher.cs @@ -4,7 +4,40 @@ namespace DrawiEngine; public class DrawieRenderingDispatcher : IRenderingDispatcher { - public Action Invoke { get; } = action => action(); + private bool renderApiReady = false; + + private List queuedActions = new List(); + + public Action Invoke { get; } + + public DrawieRenderingDispatcher() + { + Invoke = OnInvoke; + } + + private void OnInvoke(Action action) + { + if (renderApiReady) + { + action(); + } + else + { + queuedActions.Add(action); + } + } + + void IRenderingDispatcher.RenderApiReady() + { + renderApiReady = true; + + foreach (var action in queuedActions) + { + action(); + } + + queuedActions.Clear(); + } public async Task InvokeAsync(Func func) { @@ -32,4 +65,4 @@ public class EmptyDisposable : IDisposable public void Dispose() { } -} +} \ No newline at end of file diff --git a/src/DrawiEngine/DrawingEngine.cs b/src/DrawiEngine/DrawingEngine.cs index 35a88d0..eefcefa 100644 --- a/src/DrawiEngine/DrawingEngine.cs +++ b/src/DrawiEngine/DrawingEngine.cs @@ -1,7 +1,7 @@ using Drawie.Backend.Core; using Drawie.Backend.Core.Bridge; using Drawie.RenderApi; -using Drawie.Windowing; +using Drawie.Host; namespace DrawiEngine; @@ -32,14 +32,14 @@ public void RunWithApp(DrawieApp app) Console.WriteLine($"\t- DrawingBackend: {DrawingBackend}"); app.Initialize(this); - IWindow window = app.CreateMainWindow(); + IHost host = app.CreateMainWindow(); - window.Initialize(); + host.Initialize(); DrawingBackendApi.InitializeBackend(RenderApi); app.Run(); - window.Show(); + host.Show(); } public void Run() @@ -55,5 +55,6 @@ public void Run() public async ValueTask Dispose() { await DrawingBackend.DisposeAsync(); + RenderApi.Dispose(); } } diff --git a/src/Drawie.AvaloniaInterop/Drawie.AvaloniaInterop.csproj b/src/Drawie.AvaloniaInterop/Drawie.AvaloniaInterop.csproj index 3baba41..2155765 100644 --- a/src/Drawie.AvaloniaInterop/Drawie.AvaloniaInterop.csproj +++ b/src/Drawie.AvaloniaInterop/Drawie.AvaloniaInterop.csproj @@ -1,7 +1,7 @@  WinExe - net8.0 + net10.0 enable true app.manifest @@ -11,12 +11,12 @@ - - - - + + + + - + diff --git a/src/Drawie.AvaloniaInterop/MainWindow.axaml.cs b/src/Drawie.AvaloniaInterop/MainWindow.axaml.cs index 2a987e8..08e2423 100644 --- a/src/Drawie.AvaloniaInterop/MainWindow.axaml.cs +++ b/src/Drawie.AvaloniaInterop/MainWindow.axaml.cs @@ -23,15 +23,15 @@ public MainWindow() protected override void OnLoaded(RoutedEventArgs e) { - Texture texture = new Texture(new VecI(128, 128)); + NativeTexture nativeTexture = new NativeTexture(new VecI(128, 128)); using Paint paint = new Paint(); paint.Color = Colors.Red; - texture.DrawingSurface.Canvas.DrawRect(0, 0, 128, 128, paint); + nativeTexture.DrawingSurface.Canvas.DrawRect(0, 0, 128, 128, paint); paint.Color = Colors.Blue; - texture.DrawingSurface.Canvas.DrawCircle(64, 64, 64, paint); + nativeTexture.DrawingSurface.Canvas.DrawCircle(64, 64, 64, paint); - DrawieControl.Texture = texture; + DrawieControl.NativeTexture = nativeTexture; base.OnLoaded(e); } @@ -45,7 +45,7 @@ public override void Render(DrawingContext context) byte green = (byte)(Math.Sin(time / 1000.0 + 2) * 127 + 128); byte blue = (byte)(Math.Sin(time / 1000.0 + 4) * 127 + 128); - DrawieControl.Texture?.DrawingSurface.Canvas.DrawRect(0, 0, 128, 128, new Paint() + DrawieControl.NativeTexture?.DrawingSurface.Canvas.DrawRect(0, 0, 128, 128, new Paint() { Color = new Color(red, green, blue, 255), Style = PaintStyle.StrokeAndFill @@ -53,7 +53,7 @@ public override void Render(DrawingContext context) // test transparency - DrawieControl.Texture?.DrawingSurface.Canvas.DrawCircle(64, 64, 64, new Paint() + DrawieControl.NativeTexture?.DrawingSurface.Canvas.DrawCircle(64, 64, 64, new Paint() { Color = new Color(255, 255, 255, 128), Style = PaintStyle.Fill diff --git a/src/Drawie.AvaloniaInterop/Program.cs b/src/Drawie.AvaloniaInterop/Program.cs index bc78b0c..aeba86a 100644 --- a/src/Drawie.AvaloniaInterop/Program.cs +++ b/src/Drawie.AvaloniaInterop/Program.cs @@ -2,6 +2,7 @@ using System; using System.Threading.Tasks; using Avalonia.Logging; +using Avalonia.OpenGL.Egl; using Avalonia.Vulkan; using Drawie.Interop.Avalonia.Vulkan; using Drawie.Interop.VulkanAvalonia; @@ -32,7 +33,7 @@ public static AppBuilder BuildAvaloniaApp() Win32RenderingMode.Vulkan }, }) - .With(new X11PlatformOptions() { RenderingMode = new[] { X11RenderingMode.Vulkan, X11RenderingMode.Glx } }) + .With(new X11PlatformOptions() { RenderingMode = new[] { X11RenderingMode.Egl, X11RenderingMode.Glx } }) .WithDrawie() .LogToTrace(LogEventLevel.Debug, "Vulkan"); } \ No newline at end of file diff --git a/src/Drawie.Backend.Core/Bridge/DrawingBackendApi.cs b/src/Drawie.Backend.Core/Bridge/DrawingBackendApi.cs index dccf998..a78ce3b 100644 --- a/src/Drawie.Backend.Core/Bridge/DrawingBackendApi.cs +++ b/src/Drawie.Backend.Core/Bridge/DrawingBackendApi.cs @@ -22,6 +22,7 @@ public static IDrawingBackend Current } public static bool HasBackend => _current != null; + public static bool Initialized { get; private set; } public static void SetupBackend(IDrawingBackend backend, IRenderingDispatcher dispatcher) { @@ -32,8 +33,6 @@ public static void SetupBackend(IDrawingBackend backend, IRenderingDispatcher di _current = backend; _current.RenderingDispatcher = dispatcher; - - OnBackendInitialized?.Invoke(); } public static void InitializeBackend(IRenderApi renderApi) @@ -44,6 +43,9 @@ public static void InitializeBackend(IRenderApi renderApi) } _current.Setup(renderApi); + _current.RenderingDispatcher.RenderApiReady(); + OnBackendInitialized?.Invoke(); + Initialized = true; } } } diff --git a/src/Drawie.Backend.Core/Bridge/IDrawingBackend.cs b/src/Drawie.Backend.Core/Bridge/IDrawingBackend.cs index e874dd8..e3f5cc4 100644 --- a/src/Drawie.Backend.Core/Bridge/IDrawingBackend.cs +++ b/src/Drawie.Backend.Core/Bridge/IDrawingBackend.cs @@ -3,11 +3,13 @@ using Drawie.Backend.Core.Surfaces; using Drawie.Numerics; using Drawie.RenderApi; +using Drawie.RenderApi.Abstraction.Textures; namespace Drawie.Backend.Core.Bridge { public interface IDrawingBackend : IAsyncDisposable { + public IRenderApi ActiveRenderApi { get; } public void Setup(IRenderApi renderApi); public IColorImplementation ColorImplementation { get; } public IImageImplementation ImageImplementation { get; } @@ -31,8 +33,9 @@ public interface IDrawingBackend : IAsyncDisposable public IPictureImplementation PictureImplementation { get; } public IBlenderImplementation BlenderImplementation { get; } public IMeshImplementation MeshImplementation { get; } - public DrawingSurface CreateRenderSurface(VecI size, ITexture renderTexture, SurfaceOrigin origin); + public DrawingSurface? CreateRenderSurface(VecI size, ITexture renderTexture, SurfaceOrigin origin); public int GetNativeInstancesTotalCount(); public void Flush(); + void ResetContext(); } } diff --git a/src/Drawie.Backend.Core/Bridge/NativeObjectsImpl/IPaintImplementation.cs b/src/Drawie.Backend.Core/Bridge/NativeObjectsImpl/IPaintImplementation.cs index 5465156..5f0dd3a 100644 --- a/src/Drawie.Backend.Core/Bridge/NativeObjectsImpl/IPaintImplementation.cs +++ b/src/Drawie.Backend.Core/Bridge/NativeObjectsImpl/IPaintImplementation.cs @@ -15,8 +15,6 @@ public interface IPaintImplementation public void SetColor(Paint paint, Color value); public BlendMode GetBlendMode(Paint paint); public void SetBlendMode(Paint paint, BlendMode value); - public FilterQuality GetFilterQuality(Paint paint); - public void SetFilterQuality(Paint paint, FilterQuality value); public bool GetIsAntiAliased(Paint paint); public void SetIsAntiAliased(Paint paint, bool value); public PaintStyle GetStyle(Paint paint); diff --git a/src/Drawie.Backend.Core/Bridge/Operations/IImageImplementation.cs b/src/Drawie.Backend.Core/Bridge/Operations/IImageImplementation.cs index 056b7bb..1f4b0de 100644 --- a/src/Drawie.Backend.Core/Bridge/Operations/IImageImplementation.cs +++ b/src/Drawie.Backend.Core/Bridge/Operations/IImageImplementation.cs @@ -29,5 +29,9 @@ public Shader ToShader(IntPtr objectPointer, TileMode tileX, TileMode tileY, Sam Matrix3X3 localMatrix); public Shader ToRawShader(IntPtr objectPointer); public Shader? ToShader(IntPtr objectPointer, TileMode clamp, TileMode tileMode, Matrix3X3 fillMatrixValue); + public uint GetUniqueId(IntPtr objectPointer); + /* + public ulong? GetTextureId(IntPtr objectPointer); + */ } } diff --git a/src/Drawie.Backend.Core/Bridge/Operations/ISurfaceImplementation.cs b/src/Drawie.Backend.Core/Bridge/Operations/ISurfaceImplementation.cs index 996f8e8..49dcfd4 100644 --- a/src/Drawie.Backend.Core/Bridge/Operations/ISurfaceImplementation.cs +++ b/src/Drawie.Backend.Core/Bridge/Operations/ISurfaceImplementation.cs @@ -21,5 +21,7 @@ public interface ISurfaceImplementation public RectI GetDeviceClipBounds(IntPtr drawingSurface); public void Unmanage(DrawingSurface surface); public RectD GetLocalClipBounds(IntPtr objectPointer); + INativeSurfaceInfo? GetNativeSurfaceInfo(IntPtr objectPointer); + INativeSurfaceInfo? ToExternallyAccessibleSurface(IntPtr objectPointer); } diff --git a/src/Drawie.Backend.Core/ColorsImpl/Paintables/TexturePaintable.cs b/src/Drawie.Backend.Core/ColorsImpl/Paintables/TexturePaintable.cs index 20cf17d..3f418b0 100644 --- a/src/Drawie.Backend.Core/ColorsImpl/Paintables/TexturePaintable.cs +++ b/src/Drawie.Backend.Core/ColorsImpl/Paintables/TexturePaintable.cs @@ -15,7 +15,7 @@ public class TexturePaintable : Paintable public override RectD LocalBounds => new RectD(0, 0, Image.Size.X, Image.Size.Y); - private Image lastSnapshot; + private Image? lastSnapshot; private bool disposeAfterUse; diff --git a/src/Drawie.Backend.Core/Drawie.Backend.Core.csproj b/src/Drawie.Backend.Core/Drawie.Backend.Core.csproj index be3b2d5..a0f8478 100644 --- a/src/Drawie.Backend.Core/Drawie.Backend.Core.csproj +++ b/src/Drawie.Backend.Core/Drawie.Backend.Core.csproj @@ -1,10 +1,11 @@  - net8.0 + net10.0 enable enable true + diff --git a/src/Drawie.Backend.Core/IRenderable.cs b/src/Drawie.Backend.Core/IRenderable.cs new file mode 100644 index 0000000..3a53eab --- /dev/null +++ b/src/Drawie.Backend.Core/IRenderable.cs @@ -0,0 +1,8 @@ +using Drawie.Backend.Core.Surfaces; + +namespace Drawie.Backend.Core; + +public interface IRenderable +{ + public void Draw(Canvas canvas); +} \ No newline at end of file diff --git a/src/Drawie.Backend.Core/IRenderingDispatcher.cs b/src/Drawie.Backend.Core/IRenderingDispatcher.cs index 0a05624..8f540b1 100644 --- a/src/Drawie.Backend.Core/IRenderingDispatcher.cs +++ b/src/Drawie.Backend.Core/IRenderingDispatcher.cs @@ -3,6 +3,7 @@ public interface IRenderingDispatcher { public Action Invoke { get; } + protected internal void RenderApiReady(); public Task InvokeAsync(Func func); public Task InvokeInBackgroundAsync(Func function); public Task InvokeInBackgroundAsync(Action function); diff --git a/src/Drawie.Backend.Core/Surface.cs b/src/Drawie.Backend.Core/Surface.cs index ca70337..3170452 100644 --- a/src/Drawie.Backend.Core/Surface.cs +++ b/src/Drawie.Backend.Core/Surface.cs @@ -26,7 +26,7 @@ public class Surface : IDisposable, ICloneable, IPixelsMap private Paint drawingPaint = new Paint() { BlendMode = BlendMode.Src }; private Paint nearestNeighborReplacingPaint = - new() { BlendMode = BlendMode.Src, FilterQuality = FilterQuality.None }; + new() { BlendMode = BlendMode.Src }; public ImageInfo ImageInfo { get; } @@ -136,8 +136,6 @@ public Surface Resize(VecI newSize, ResizeMethod resizeMethod) _ => FilterQuality.None }; - paint.FilterQuality = filterQuality; - newSurface.DrawingSurface.Canvas.DrawImage(image, new RectD(0, 0, newSize.X, newSize.Y), paint); return newSurface; } diff --git a/src/Drawie.Backend.Core/Surfaces/DrawingSurface.cs b/src/Drawie.Backend.Core/Surfaces/DrawingSurface.cs index ebc67d1..2c0ea51 100644 --- a/src/Drawie.Backend.Core/Surfaces/DrawingSurface.cs +++ b/src/Drawie.Backend.Core/Surfaces/DrawingSurface.cs @@ -2,10 +2,11 @@ using Drawie.Backend.Core.Surfaces.ImageData; using Drawie.Backend.Core.Surfaces.PaintImpl; using Drawie.Numerics; +using Drawie.RenderApi.Abstraction.RenderTargets; namespace Drawie.Backend.Core.Surfaces { - public class DrawingSurface : NativeObject, IPixelsMap + public class DrawingSurface : NativeObject, IPixelsMap, IRenderTarget { public override object Native => DrawingBackendApi.Current.SurfaceImplementation.GetNativeSurface(ObjectPointer); @@ -18,6 +19,9 @@ public class DrawingSurface : NativeObject, IPixelsMap public RectD LocalClipBounds => DrawingBackendApi.Current.SurfaceImplementation.GetLocalClipBounds(ObjectPointer); + VecI IRenderTarget.Size => DeviceClipBounds.Size; + public ulong SurfaceId => DrawingBackendApi.Current.SurfaceImplementation.GetNativeSurfaceInfo(ObjectPointer).SurfaceId; + public bool IsDisposed => isDisposed || Canvas.IsDisposed; public event SurfaceChangedEventHandler? Changed; @@ -45,7 +49,7 @@ public Image Snapshot() { return DrawingBackendApi.Current.ImageImplementation.Snapshot(this); } - + public Image Snapshot(RectI bounds) { return DrawingBackendApi.Current.ImageImplementation.Snapshot(this, bounds); diff --git a/src/Drawie.Backend.Core/Surfaces/INativeSurfaceInfo.cs b/src/Drawie.Backend.Core/Surfaces/INativeSurfaceInfo.cs new file mode 100644 index 0000000..df6b41a --- /dev/null +++ b/src/Drawie.Backend.Core/Surfaces/INativeSurfaceInfo.cs @@ -0,0 +1,6 @@ +namespace Drawie.Backend.Core.Surfaces; + +public interface INativeSurfaceInfo +{ + public ulong SurfaceId { get; } +} \ No newline at end of file diff --git a/src/Drawie.Backend.Core/Surfaces/ImageData/Image.cs b/src/Drawie.Backend.Core/Surfaces/ImageData/Image.cs index 378f8ca..4f47b74 100644 --- a/src/Drawie.Backend.Core/Surfaces/ImageData/Image.cs +++ b/src/Drawie.Backend.Core/Surfaces/ImageData/Image.cs @@ -2,6 +2,8 @@ using Drawie.Backend.Core.Numerics; using Drawie.Backend.Core.Shaders; using Drawie.Numerics; +using Drawie.RenderApi; +using Drawie.RenderApi.Abstraction.Textures; namespace Drawie.Backend.Core.Surfaces.ImageData { @@ -25,6 +27,9 @@ public class Image : NativeObject, ICloneable, IPixelsMap public VecI Size => new VecI(Width, Height); public bool IsDisposed => isDisposed; + /* + public ulong TextureId => DrawingBackendApi.Current.ImageImplementation.GetTextureId(ObjectPointer).Value; + */ private bool isDisposed; @@ -100,5 +105,6 @@ public Shader ToRawShader() { return DrawingBackendApi.Current.ImageImplementation.ToShader(ObjectPointer, clamp, tileMode, fillMatrixValue); } + } } diff --git a/src/Drawie.Backend.Core/Surfaces/PaintImpl/Paint.cs b/src/Drawie.Backend.Core/Surfaces/PaintImpl/Paint.cs index 41a49db..ec136c9 100644 --- a/src/Drawie.Backend.Core/Surfaces/PaintImpl/Paint.cs +++ b/src/Drawie.Backend.Core/Surfaces/PaintImpl/Paint.cs @@ -47,12 +47,6 @@ public StrokeJoin StrokeJoin set => DrawingBackendApi.Current.PaintImplementation.SetStrokeJoin(this, value); } - public FilterQuality FilterQuality - { - get => DrawingBackendApi.Current.PaintImplementation.GetFilterQuality(this); - set => DrawingBackendApi.Current.PaintImplementation.SetFilterQuality(this, value); - } - public bool IsAntiAliased { get => DrawingBackendApi.Current.PaintImplementation.GetIsAntiAliased(this); diff --git a/src/Drawie.Backend.Core/Surfaces/Pixmap.cs b/src/Drawie.Backend.Core/Surfaces/Pixmap.cs index ac4ce80..9dcbf69 100644 --- a/src/Drawie.Backend.Core/Surfaces/Pixmap.cs +++ b/src/Drawie.Backend.Core/Surfaces/Pixmap.cs @@ -9,15 +9,10 @@ public class Pixmap : NativeObject { public override object Native => DrawingBackendApi.Current.PixmapImplementation.GetNativePixmap(ObjectPointer); - internal Pixmap(IntPtr objPtr) : base(objPtr) + public Pixmap(IntPtr objPtr) : base(objPtr) { } - public static Pixmap InternalCreateFromExistingPointer(IntPtr objPointer) - { - return new Pixmap(objPointer); - } - public Pixmap(ImageInfo imgInfo, IntPtr dataPtr) : base(dataPtr) { ObjectPointer = DrawingBackendApi.Current.PixmapImplementation.Construct(dataPtr, imgInfo); diff --git a/src/Drawie.Backend.Core/Text/RichText.cs b/src/Drawie.Backend.Core/Text/RichText.cs index 26ecd2b..df11431 100644 --- a/src/Drawie.Backend.Core/Text/RichText.cs +++ b/src/Drawie.Backend.Core/Text/RichText.cs @@ -36,7 +36,7 @@ public RichText(string text, double maxWidth = double.MaxValue) FormattedText = text.Replace('\n', ' '); Lines = text.Split('\n'); } - + public void Paint(Canvas canvas, VecD position, Font font, Paint paint, VectorPath? onPath, VecD? pathOffset = null) { if (pathOffset == null) diff --git a/src/Drawie.Backend.Core/Texture.cs b/src/Drawie.Backend.Core/Texture.cs index 7ed123f..caf21fb 100644 --- a/src/Drawie.Backend.Core/Texture.cs +++ b/src/Drawie.Backend.Core/Texture.cs @@ -1,14 +1,14 @@ using Drawie.Backend.Core.Bridge; using Drawie.Backend.Core.ColorsImpl; -using Drawie.Backend.Core.ColorsImpl.Paintables; using Drawie.Backend.Core.Surfaces; using Drawie.Backend.Core.Surfaces.ImageData; using Drawie.Backend.Core.Surfaces.PaintImpl; using Drawie.Numerics; +using Drawie.RenderApi.Abstraction.Textures; namespace Drawie.Backend.Core; -public class Texture : IDisposable, ICloneable, IPixelsMap +public class Texture : IDisposable, ICloneable, IPixelsMap, INativeSurfaceInfo, ILazyExternallyAccessibleTexture { public VecI Size { get; } public DrawingSurface DrawingSurface { get; private set; } @@ -21,6 +21,7 @@ public class Texture : IDisposable, ICloneable, IPixelsMap public ColorSpace ColorSpace { get; } public ImageInfo ImageInfo { get; } + public ulong TextureId => SurfaceId; private DrawingSurface? cpuSurface; private Pixmap? cpuPixmap; @@ -32,7 +33,7 @@ public class Texture : IDisposable, ICloneable, IPixelsMap private HashSet lockDisposes = new(); private Paint nearestNeighborReplacingPaint = - new() { BlendMode = BlendMode.Src, FilterQuality = FilterQuality.None }; + new() { BlendMode = BlendMode.Src }; public Texture(VecI size) : this(new ImageInfo(size.X, size.Y, ColorType.RgbaF16, AlphaType.Premul, ColorSpace.CreateSrgb()) @@ -86,7 +87,7 @@ public static Texture ForProcessing(VecI size, ColorSpace colorSpace) return tex; } - + public static Texture ForProcessing(Canvas copySizeAndMatrixFrom, ColorSpace colorSpace) { Texture tex = new Texture( @@ -120,11 +121,12 @@ public Texture(ImageInfo imageImageInfo) throw new Exception("Could not create DrawingSurface for Texture."); } } + + DrawingSurface.Changed += DrawingSurfaceOnChanged; } ); ImageInfo = imageImageInfo; - DrawingSurface.Changed += DrawingSurfaceOnChanged; Changed += OnChanged; } @@ -209,8 +211,6 @@ public Texture CreateResized(VecI newSize, ResizeMethod method) _ => FilterQuality.None }; - paint.FilterQuality = filterQuality; - newTexture.DrawingSurface.Canvas.DrawImage(image, new RectD(0, 0, newSize.X, newSize.Y), paint); return newTexture; @@ -245,7 +245,6 @@ public Texture Resize(VecI newSize, FilterQuality quality) using var ctx = EnsureContext(); using Image image = DrawingSurface.Snapshot(); using Paint paint = new(); - paint.FilterQuality = quality; Texture newSurface = new(newSize); newSurface.DrawingSurface.Canvas.DrawImage(image, new RectD(0, 0, newSize.X, newSize.Y), paint); @@ -304,7 +303,7 @@ void IPixelsMap.MarkPixelsChanged() if (isDisposed) throw new ObjectDisposedException("Texture"); - if(cpuSurface == null) + if (cpuSurface == null) return; using var ctx = EnsureContext(); @@ -411,4 +410,17 @@ public void SaveToDesktop() surf.SaveToDesktop(); } #endif + + public void EnsureExternallyAccessible() + { + if (DrawingBackendApi.Current.SurfaceImplementation.GetNativeSurfaceInfo(DrawingSurface.ObjectPointer) == null) + { + DrawingBackendApi.Current.SurfaceImplementation.ToExternallyAccessibleSurface(DrawingSurface.ObjectPointer); + } + } + + public ulong SurfaceId => + DrawingBackendApi.Current.SurfaceImplementation.GetNativeSurfaceInfo(DrawingSurface.ObjectPointer) + ?.SurfaceId ?? DrawingBackendApi.Current.SurfaceImplementation + .ToExternallyAccessibleSurface(DrawingSurface.ObjectPointer)?.SurfaceId ?? throw new Exception("Unable to get native texture info."); } diff --git a/src/Drawie.Backend.Core/Vector/EditableVectorPath.cs b/src/Drawie.Backend.Core/Vector/EditableVectorPath.cs index 6050ae2..b364e1b 100644 --- a/src/Drawie.Backend.Core/Vector/EditableVectorPath.cs +++ b/src/Drawie.Backend.Core/Vector/EditableVectorPath.cs @@ -108,7 +108,7 @@ private void UpdatePathFrom(VectorPath from) { isSubShapeClosed = true; VecF[] verbData = data.points.ToArray(); - if(verbData.Length < 2) + if (verbData.Length < 2) { var newData = new VecF[2]; if (verbData.Length == 1) @@ -119,6 +119,7 @@ private void UpdatePathFrom(VectorPath from) verbData = newData; } + if (currentSubShapePoints[^1].Verb.IsEmptyVerb()) { int lastIndex = currentSubShapePoints.Count - 2; diff --git a/src/Drawie.Backend.Skia/ConversionExtensions.cs b/src/Drawie.Backend.Skia/ConversionExtensions.cs index aa7bd7b..a3b43d1 100644 --- a/src/Drawie.Backend.Skia/ConversionExtensions.cs +++ b/src/Drawie.Backend.Skia/ConversionExtensions.cs @@ -87,9 +87,14 @@ public static ImageInfo ToImageInfo(this SKImageInfo info) ColorSpace? cs = null; if (info.ColorSpace != null) { - cs = new ColorSpace(info.ColorSpace.Handle); var colorSpaceImpl = DrawingBackendApi.Current.ColorSpaceImplementation as SkiaColorSpaceImplementation; - colorSpaceImpl.AddManagedInstance(info.ColorSpace); + IntPtr? existing = colorSpaceImpl.FindManagedInstanceHandle(info.ColorSpace); + if (existing == null) + { + existing = colorSpaceImpl.AddManagedInstance(info.ColorSpace); + } + + cs = new ColorSpace(existing.Value); } return new ImageInfo(info.Width, info.Height, diff --git a/src/Drawie.Backend.Skia/Drawie.Backend.Skia.csproj b/src/Drawie.Backend.Skia/Drawie.Backend.Skia.csproj index 113bada..7243352 100644 --- a/src/Drawie.Backend.Skia/Drawie.Backend.Skia.csproj +++ b/src/Drawie.Backend.Skia/Drawie.Backend.Skia.csproj @@ -1,11 +1,11 @@  - net8.0 + net10.0 enable enable Drawie.Skia - 3.119.2 + 4.151.1 @@ -29,6 +29,7 @@ + diff --git a/src/Drawie.Backend.Skia/Implementations/SKObjectImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SKObjectImplementation.cs index b563f20..a02fac1 100644 --- a/src/Drawie.Backend.Skia/Implementations/SKObjectImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SKObjectImplementation.cs @@ -7,29 +7,36 @@ public abstract class SkObjectImplementation where T : SKObject { public int Count => ManagedInstances.Count; private readonly ConcurrentDictionary ManagedInstances = new ConcurrentDictionary(); + private nuint handleCounter = 1; #if DRAWIE_TRACE protected static Dictionary sources = new(); #endif - internal void AddManagedInstance(T instance) + internal IntPtr AddManagedInstance(T instance) { - if (ManagedInstances.TryAdd(instance.Handle, instance)) + IntPtr handle = GetNextHandle(); + if (ManagedInstances.TryAdd(handle, instance)) { #if DRAWIE_TRACE sources[instance] = Environment.StackTrace; #endif } + else + { + throw new InvalidOperationException( + $"Native handle {instance.Handle} is already registered. " + + $"Existing: {ManagedInstances[instance.Handle]}, " + + $"New: {instance}"); + } + + return handle; } - internal void AddManagedInstance(IntPtr handle, T instance) + protected IntPtr GetNextHandle() { - if (ManagedInstances.TryAdd(handle, instance)) - { -#if DRAWIE_TRACE - sources[instance] = Environment.StackTrace; -#endif - } + handleCounter++; + return (IntPtr)handleCounter; } public bool TryGetInstance(IntPtr objPtr, out T? instance) @@ -49,20 +56,6 @@ public void UnmanageAndDispose(IntPtr objPtr) } } - public void UnmanageAndDispose(T instance) - { - if (ManagedInstances.TryRemove(instance.Handle, out var managedInstance)) - { - if (managedInstance == null) return; - -#if DRAWIE_TRACE - Untrace(managedInstance); -#endif - } - - instance.Dispose(); - } - public void UpdateManagedInstance(IntPtr objPtr, T instance) { if (ManagedInstances.TryRemove(objPtr, out var managedInstance)) @@ -145,5 +138,16 @@ protected static void Trace(T shader) } #endif + public IntPtr? FindManagedInstanceHandle(T native) + { + foreach (var kvp in ManagedInstances) + { + if (EqualityComparer.Default.Equals(kvp.Value, native)) + { + return kvp.Key; + } + } + return null; + } } } diff --git a/src/Drawie.Backend.Skia/Implementations/SkiaBitmapImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SkiaBitmapImplementation.cs index 1f7af43..9d5ec2f 100644 --- a/src/Drawie.Backend.Skia/Implementations/SkiaBitmapImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SkiaBitmapImplementation.cs @@ -26,16 +26,14 @@ public void Dispose(IntPtr objectPointer) public Bitmap Decode(ReadOnlySpan buffer) { SKBitmap skBitmap = SKBitmap.Decode(buffer); - AddManagedInstance(skBitmap); - return new Bitmap(skBitmap.Handle); + return new Bitmap(AddManagedInstance(skBitmap)); } public Bitmap FromImage(IntPtr ptr) { SKImage image = ImageImplementation[ptr]; SKBitmap skBitmap = SKBitmap.FromImage(image); - AddManagedInstance(skBitmap); - return new Bitmap(skBitmap.Handle); + return new Bitmap(AddManagedInstance(skBitmap)); } public VecI GetSize(IntPtr objectPointer) @@ -67,8 +65,8 @@ public IntPtr Construct(ImageInfo info) { SKImageInfo imageInfo = info.ToSkImageInfo(); SKBitmap bitmap = new SKBitmap(imageInfo); - AddManagedInstance(bitmap); - return bitmap.Handle; + return AddManagedInstance(bitmap); +; } public bool InstallPixels(IntPtr objectPointer, ImageInfo info, IntPtr pixels) diff --git a/src/Drawie.Backend.Skia/Implementations/SkiaBlenderImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SkiaBlenderImplementation.cs index bc1d3df..741b33f 100644 --- a/src/Drawie.Backend.Skia/Implementations/SkiaBlenderImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SkiaBlenderImplementation.cs @@ -27,9 +27,7 @@ public IntPtr CreateFromString(string blenderCode, out string? errors) return IntPtr.Zero; } - AddManagedInstance(blender); - - return blender.Handle; + return AddManagedInstance(blender); } public IntPtr CreateFromString(string blenderCode, Uniforms uniforms, out string? errors) @@ -43,9 +41,7 @@ public IntPtr CreateFromString(string blenderCode, Uniforms uniforms, out string SKRuntimeEffectUniforms effectUniforms = SkiaShaderImplementation.UniformsToSkUniforms(uniforms, declaration, effect); SKRuntimeEffectChildren effectChildren = SkiaShaderImplementation.UniformsToSkChildren(uniforms, effect, shaderImpl); var blender = effect.ToBlender(effectUniforms, effectChildren); - AddManagedInstance(blender); - - return blender.Handle; + return AddManagedInstance(blender); } public object GetNativeObject(IntPtr objectPointer) diff --git a/src/Drawie.Backend.Skia/Implementations/SkiaCanvasImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SkiaCanvasImplementation.cs index 60ff8a6..ebea49f 100644 --- a/src/Drawie.Backend.Skia/Implementations/SkiaCanvasImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SkiaCanvasImplementation.cs @@ -23,6 +23,8 @@ public sealed class SkiaCanvasImplementation : SkObjectImplementation, private readonly SkObjectImplementation _fontImpl; private readonly SkObjectImplementation _verticesImpl; + private static SKFont defaultFont = new SKFont(SKTypeface.Default); + public SkiaCanvasImplementation(SkObjectImplementation paintImpl, SkObjectImplementation imageImpl, SkObjectImplementation bitmapImpl, SkObjectImplementation pathImpl, SkObjectImplementation fontImpl, @@ -278,7 +280,7 @@ public void DrawBitmap(IntPtr objPtr, Bitmap bitmap, float x, float y, Paint? pa public void DrawText(IntPtr objPtr, string text, float x, float y, Paint paint) { - this[objPtr].DrawText(text, x, y, _paintImpl[paint.ObjectPointer]); + this[objPtr].DrawText(SKTextBlob.Create(text, defaultFont), x, y, _paintImpl[paint.ObjectPointer]); } public void DrawText(IntPtr objPtr, string text, float x, float y, Font font, Paint paint) @@ -362,14 +364,16 @@ public void DrawVertices(IntPtr objectPointer, Vertices vertices, BlendMode blen if (skSurface != null) { var surfaceImpl = _surfaceImpl; - if (!surfaceImpl.TryGetInstance(skSurface.Handle, out var surface)) + IntPtr? foundHandle = _surfaceImpl.FindManagedInstanceHandle(skSurface); + if (foundHandle == null) { - surfaceImpl.AddManagedInstance(skSurface.Handle, skSurface); - surface = skSurface; + foundHandle = surfaceImpl.AddManagedInstance(skSurface); } - return surface != null ? new DrawingSurface(surface.Handle, canvas) : null; + return new DrawingSurface(foundHandle.Value, canvas); } + + return null; } throw new ObjectDisposedException(nameof(canvas)); diff --git a/src/Drawie.Backend.Skia/Implementations/SkiaColorFilterImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SkiaColorFilterImplementation.cs index 5aba311..993df7a 100644 --- a/src/Drawie.Backend.Skia/Implementations/SkiaColorFilterImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SkiaColorFilterImplementation.cs @@ -19,17 +19,13 @@ public IntPtr CreateBlendMode(Color color, BlendMode blendMode) public IntPtr CreateColorMatrix(float[] matrix) { var skColorFilter = SKColorFilter.CreateColorMatrix(matrix); - AddManagedInstance(skColorFilter); - - return skColorFilter.Handle; + return AddManagedInstance(skColorFilter); } public IntPtr CreateHighContrast(bool grayscale, ContrastInvertMode invert, float contrast) { var skColorFilter = SKColorFilter.CreateHighContrast(grayscale, (SKHighContrastConfigInvertStyle)invert, contrast); - AddManagedInstance(skColorFilter); - - return skColorFilter.Handle; + return AddManagedInstance(skColorFilter); } public IntPtr CreateCompose(ColorFilter outer, ColorFilter inner) @@ -38,9 +34,7 @@ public IntPtr CreateCompose(ColorFilter outer, ColorFilter inner) var skInner = this[inner.ObjectPointer]; var skColorFilter = SKColorFilter.CreateCompose(skOuter, skInner); - AddManagedInstance(skColorFilter); - - return skColorFilter.Handle; + return AddManagedInstance(skColorFilter); } public void Dispose(ColorFilter colorFilter) @@ -56,33 +50,25 @@ public object GetNativeColorFilter(IntPtr objectPointer) public IntPtr CreateLumaColor() { var skColorFilter = SKColorFilter.CreateLumaColor(); - AddManagedInstance(skColorFilter); - - return skColorFilter.Handle; + return AddManagedInstance(skColorFilter); } public IntPtr CreateLighting(Color mul, Color add) { var skColorFilter = SKColorFilter.CreateLighting(mul.ToSKColor(), add.ToSKColor()); - AddManagedInstance(skColorFilter); - - return skColorFilter.Handle; + return AddManagedInstance(skColorFilter); } public IntPtr CreateSrgbToLinearGamma() { var skColorFilter = SKColorFilter.CreateSrgbToLinearGamma(); - AddManagedInstance(skColorFilter); - - return skColorFilter.Handle; + return AddManagedInstance(skColorFilter); } public IntPtr CreateLinearToSrgbGamma() { var skColorFilter = SKColorFilter.CreateLinearToSrgbGamma(); - AddManagedInstance(skColorFilter); - - return skColorFilter.Handle; + return AddManagedInstance(skColorFilter); } } } diff --git a/src/Drawie.Backend.Skia/Implementations/SkiaColorSpaceImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SkiaColorSpaceImplementation.cs index a4e3ad7..4d9eb18 100644 --- a/src/Drawie.Backend.Skia/Implementations/SkiaColorSpaceImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SkiaColorSpaceImplementation.cs @@ -15,22 +15,20 @@ public class SkiaColorSpaceImplementation : SkObjectImplementation public SkiaColorSpaceImplementation() { - _srgbPointer = SKColorSpace.CreateSrgb().Handle; - _srgbLinearPointer = SKColorSpace.CreateSrgbLinear().Handle; + var srgb = SKColorSpace.CreateSrgb(); + var srgbLinear = SKColorSpace.CreateSrgbLinear(); + _srgbPointer = AddManagedInstance(srgb); + _srgbLinearPointer = AddManagedInstance(srgbLinear); } public ColorSpace CreateSrgb() { - SKColorSpace skColorSpace = SKColorSpace.CreateSrgb(); - AddManagedInstance(skColorSpace); - return new ColorSpace(skColorSpace.Handle); + return new ColorSpace(_srgbPointer); } public ColorSpace CreateSrgbLinear() { - SKColorSpace skColorSpace = SKColorSpace.CreateSrgbLinear(); - AddManagedInstance(skColorSpace); - return new ColorSpace(skColorSpace.Handle); + return new ColorSpace(_srgbLinearPointer); } public void Dispose(IntPtr objectPointer) diff --git a/src/Drawie.Backend.Skia/Implementations/SkiaFontImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SkiaFontImplementation.cs index 177f739..23cb659 100644 --- a/src/Drawie.Backend.Skia/Implementations/SkiaFontImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SkiaFontImplementation.cs @@ -12,8 +12,6 @@ public class SkiaFontImplementation : SkObjectImplementation, IFontImple { private readonly SkiaPathImplementation pathImplementation; - private volatile int fontCounter = 0; - public SkiaFontImplementation(SkiaPathImplementation pathImplementation) { this.pathImplementation = pathImplementation; @@ -30,8 +28,7 @@ public VectorPath GetTextPath(IntPtr objectPointer, string text) if (TryGetInstance(objectPointer, out SKFont? font)) { var path = font.GetTextPath(text); - pathImplementation.AddManagedInstance(path); - return new VectorPath(path.Handle); + return new VectorPath(pathImplementation.AddManagedInstance(path)); } throw new InvalidOperationException("Native font object not found"); @@ -47,8 +44,7 @@ public VectorPath GetTextPath(IntPtr objectPointer, string text) } SKFont font = new(typeface, fontSize, scaleX, skewY); - int handle = Interlocked.Increment(ref fontCounter); - AddManagedInstance(handle, font); + IntPtr handle = AddManagedInstance(font); return new Font(handle, new FontFamilyName(typeface.FamilyName)); } @@ -316,8 +312,7 @@ public bool ContainsGlyph(IntPtr objectPointer, int glyphId) public Font CreateDefault(float fontSize) { SKFont font = new(SKTypeface.Default, fontSize); - int handle = Interlocked.Increment(ref fontCounter); - AddManagedInstance(handle, font); + IntPtr handle = AddManagedInstance(font); return new Font(handle, new FontFamilyName(SKTypeface.Default.FamilyName)); } @@ -330,8 +325,7 @@ public Font CreateDefault(float fontSize) } SKFont font = new(typeface); - int handle = Interlocked.Increment(ref fontCounter); - AddManagedInstance(handle, font); + IntPtr handle = AddManagedInstance(font); return new Font(handle, new FontFamilyName(familyName)); } @@ -345,8 +339,7 @@ public Font CreateDefault(float fontSize) } SKFont font = new(typeface); - int handle = Interlocked.Increment(ref fontCounter); - AddManagedInstance(handle, font); + IntPtr handle = AddManagedInstance(font); return new Font(handle, new FontFamilyName(familyName)); } diff --git a/src/Drawie.Backend.Skia/Implementations/SkiaImageFilterImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SkiaImageFilterImplementation.cs index 2e4b3ea..cd0c66c 100644 --- a/src/Drawie.Backend.Skia/Implementations/SkiaImageFilterImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SkiaImageFilterImplementation.cs @@ -30,8 +30,7 @@ public class SkiaImageFilterImplementation : SkObjectImplementation this[objPtr]; @@ -83,8 +79,7 @@ public class SkiaImageFilterImplementation : SkObjectImplementation, IImageIm { private readonly SkObjectImplementation _imgImplementation; private readonly SkiaPixmapImplementation _pixmapImplementation; - private SkObjectImplementation? _surfaceImplementation; + private SkiaSurfaceImplementation _surfaceImplementation; private SkiaShaderImplementation shaderImpl; + /* + private Dictionary textureInfos = new Dictionary(); + */ private Dictionary nonSkiaEncoders = new Dictionary() { @@ -30,7 +37,7 @@ public SkiaImageImplementation(SkObjectImplementation imgDataImplementat shaderImpl = shaderImplementation; } - public void SetSurfaceImplementation(SkObjectImplementation surfaceImplementation) + public void SetSurfaceImplementation(SkiaSurfaceImplementation surfaceImplementation) { _surfaceImplementation = surfaceImplementation; } @@ -40,8 +47,7 @@ public Image Snapshot(DrawingSurface drawingSurface) var surface = _surfaceImplementation![drawingSurface.ObjectPointer]; SKImage snapshot = surface.Snapshot(); - AddManagedInstance(snapshot); - return new Image(snapshot.Handle); + return new Image(AddManagedInstance(snapshot)); } public Image Snapshot(DrawingSurface drawingSurface, RectI bounds) @@ -49,23 +55,132 @@ public Image Snapshot(DrawingSurface drawingSurface, RectI bounds) var surface = _surfaceImplementation![drawingSurface.ObjectPointer]; SKImage snapshot = surface.Snapshot(bounds.ToSkRectI()); + return new Image(AddManagedInstance(snapshot)); + } + + /*public Image? TextureSnapshot(DrawingSurface drawingSurface) + { + var surface = _surfaceImplementation![drawingSurface.ObjectPointer]; + + VecI size = new VecI(surface.Canvas.DeviceClipBounds.Width, surface.Canvas.DeviceClipBounds.Height); + + /* + var fbInfo = DrawingBackendApi.Current.SurfaceImplementation.GetFramebufferInfo(drawingSurface.ObjectPointer); + + if (fbInfo is not SkiaFramebufferInfo skiaFramebufferInfo) + { + return null; + } + + SKImage? snapshot = CreateFromFramebuffer(skiaFramebufferInfo, size, SurfaceOrigin.BottomLeft, out var textureInfo); + if (snapshot == null) return null;#1# + + var nativeTexture =_surfaceImplementation.GraphicsDevice.CreateTexture(new TextureDesc() + { + Width = size.X, + Height = size.Y, + Format = TextureFormat.RGBA8_Unorm + }); + + SKImage snapshot = SnapshotToOwnedTexture(surface, (uint)nativeTexture.TextureId, size.X, size.Y, + SurfaceOrigin.BottomLeft, out var textureInfo); + AddManagedInstance(snapshot); + textureInfos[snapshot.Handle] = textureInfo; return new Image(snapshot.Handle); } + + internal SKImage? SnapshotToOwnedTexture( + SKSurface source, + uint textureId, + int width, + int height, + SurfaceOrigin origin, out SkiaTextureInfo info) + { + const uint GL_TEXTURE_2D = 3553; + const uint GL_RGBA8 = 0x8058; + + var textureInfo = new GRGlTextureInfo( + GL_TEXTURE_2D, + textureId, + GL_RGBA8); + + var backendTexture = new GRBackendTexture( + width, + height, + false, + textureInfo); + + var targetSurface = SKSurface.Create( + _surfaceImplementation.GrContext, + backendTexture, + (GRSurfaceOrigin)origin, + SKColorType.Rgba8888); + + info = new SkiaTextureInfo(backendTexture); + + if (targetSurface == null) + return null; + + using var snapshot = source.Snapshot(); + + targetSurface.Canvas.DrawImage(snapshot, 0, 0, SKSamplingOptions.Default); + + return SKImage.FromTexture( + _surfaceImplementation.GrContext, + backendTexture, + (GRSurfaceOrigin)origin, + SKColorType.Rgba8888, + SKAlphaType.Premul); + }*/ + + internal SKImage? CreateFromFramebuffer(SkiaNativeSurfaceInfo skiaNativeSurfaceInfo, VecI size, SurfaceOrigin surfaceOrigin, out ITexture fbInfo) + { + if (skiaNativeSurfaceInfo.VkImageInfo != null) + { + var imageInfo = skiaNativeSurfaceInfo.VkImageInfo.Value; + var backendRenderTarget = new GRBackendTexture(size.X, size.Y, imageInfo); + var surface = SKImage.FromTexture(_surfaceImplementation.GrContext, backendRenderTarget, + (GRSurfaceOrigin)surfaceOrigin, SKColorType.Rgba8888, SKAlphaType.Premul); + + fbInfo = new SkiaTextureInfo(backendRenderTarget); + return surface; + } + + if (skiaNativeSurfaceInfo.GlFramebufferInfo != null) + { + uint textureId = skiaNativeSurfaceInfo.GlFramebufferInfo.Value.FramebufferObjectId; + + const uint OpenGlTexture2D = 3553; + const uint RGBA8 = 0x8058; + GRBackendTexture backendRenderTarget = + new GRBackendTexture(size.X, size.Y, false, new GRGlTextureInfo(OpenGlTexture2D, textureId, RGBA8)); + + var surface = SKImage.FromTexture(_surfaceImplementation.GrContext, backendRenderTarget, (GRSurfaceOrigin)surfaceOrigin, + SKColorType.Rgba8888, SKAlphaType.Premul); + + fbInfo = new SkiaTextureInfo(backendRenderTarget); + return surface; + } + + throw new ArgumentException("Unsupported texture type."); + } public Image? FromEncodedData(byte[] dataBytes) { SKImage img = SKImage.FromEncodedData(dataBytes); if (img is null) return null; - AddManagedInstance(img); - return new Image(img.Handle); + return new Image(AddManagedInstance(img)); } public void DisposeImage(Image image) { UnmanageAndDispose(image.ObjectPointer); + /* + textureInfos.Remove(image.ObjectPointer); + */ } public Image? FromEncodedData(string path) @@ -73,8 +188,7 @@ public void DisposeImage(Image image) var nativeImg = SKImage.FromEncodedData(path); if (nativeImg is null) return null; - AddManagedInstance(nativeImg); - return new Image(nativeImg.Handle); + return new Image(AddManagedInstance(nativeImg)); } public Image? FromPixelCopy(ImageInfo info, byte[] pixels) @@ -82,8 +196,7 @@ public void DisposeImage(Image image) var nativeImg = SKImage.FromPixelCopy(info.ToSkImageInfo(), pixels); if (nativeImg is null) return null; - AddManagedInstance(nativeImg); - return new Image(nativeImg.Handle); + return new Image(AddManagedInstance(nativeImg)); } public Pixmap PeekPixels(Image image) @@ -107,8 +220,7 @@ public ImgData Encode(Image image) { var native = this[image.ObjectPointer]; var encoded = native.Encode(); - _imgImplementation.AddManagedInstance(encoded); - return new ImgData(encoded.Handle); + return new ImgData(_imgImplementation.AddManagedInstance(encoded)); } public ImgData Encode(Image image, EncodedImageFormat format, int quality) @@ -133,8 +245,7 @@ public ImgData Encode(Image image, EncodedImageFormat format, int quality) encoded = native.Encode((SKEncodedImageFormat)format, quality); } - _imgImplementation.AddManagedInstance(encoded); - return new ImgData(encoded.Handle); + return new ImgData(_imgImplementation.AddManagedInstance(encoded)); } public int GetWidth(IntPtr objectPointer) @@ -152,8 +263,7 @@ public Image Clone(Image image) var native = this[image.ObjectPointer]; var encoded = native.Encode(); var clone = SKImage.FromEncodedData(encoded); - AddManagedInstance(clone); - return new Image(clone.Handle); + return new Image(AddManagedInstance(clone)); } public Pixmap PeekPixels(IntPtr objectPointer) @@ -172,8 +282,7 @@ public ImageInfo GetImageInfo(IntPtr objectPointer) public Shader ToShader(IntPtr objectPointer) { var shader = this[objectPointer].ToShader(); - shaderImpl.AddManagedInstance(shader); - return new Shader(shader.Handle); + return new Shader(shaderImpl.AddManagedInstance(shader)); } @@ -181,15 +290,14 @@ public Shader ToShader(IntPtr objectPointer, TileMode tileX, TileMode tileY, Sam { var shader = this[objectPointer] .ToShader((SKShaderTileMode)tileX, (SKShaderTileMode)tileY, samplingOptions.ToSkSamplingOptions(), localMatrix.ToSkMatrix()); - shaderImpl.AddManagedInstance(shader); - return new Shader(shader.Handle); + + return new Shader(shaderImpl.AddManagedInstance(shader)); } public Shader ToRawShader(IntPtr objectPointer) { var shader = this[objectPointer].ToRawShader(); - shaderImpl.AddManagedInstance(shader); - return new Shader(shader.Handle); + return new Shader(shaderImpl.AddManagedInstance(shader)); } public Shader? ToShader(IntPtr objectPointer, TileMode clamp, TileMode tileMode, Matrix3X3 fillMatrixValue) @@ -199,9 +307,25 @@ public Shader ToRawShader(IntPtr objectPointer) if (shader is null) return null; - shaderImpl.AddManagedInstance(shader); - return new Shader(shader.Handle); + return new Shader(shaderImpl.AddManagedInstance(shader)); + } + + public uint GetUniqueId(IntPtr objectPointer) + { + return this[objectPointer].UniqueId; + } + + /* + public ulong? GetTextureId(IntPtr objectPointer) + { + if (textureInfos.TryGetValue(objectPointer, out var info)) + { + return info.TextureId; + } + + return null; } + */ public object GetNativeImage(IntPtr objectPointer) { diff --git a/src/Drawie.Backend.Skia/Implementations/SkiaImgDataImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SkiaImgDataImplementation.cs index 73a07cf..c65139f 100644 --- a/src/Drawie.Backend.Skia/Implementations/SkiaImgDataImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SkiaImgDataImplementation.cs @@ -32,8 +32,7 @@ public ReadOnlySpan AsSpan(ImgData imgData) public ImgData Create(ReadOnlySpan buffer) { SKData data = SKData.CreateCopy(buffer.ToArray()); - AddManagedInstance(data); - return new ImgData(data.Handle); + return new ImgData(AddManagedInstance(data)); } public object GetNativeImgData(IntPtr objectPointer) diff --git a/src/Drawie.Backend.Skia/Implementations/SkiaMeshImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SkiaMeshImplementation.cs index 83d1644..c9910fa 100644 --- a/src/Drawie.Backend.Skia/Implementations/SkiaMeshImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SkiaMeshImplementation.cs @@ -20,8 +20,7 @@ public IntPtr Create(VertexMode mode, VecF[] points, VecF[] texs, Color[] colors SKColor[] skColors = CastUtility.UnsafeArrayCast(colors); var vertices = SKVertices.CreateCopy((SKVertexMode)mode, skPoints, skTexs, skColors, indices); - AddManagedInstance(vertices); - return vertices.Handle; + return AddManagedInstance(vertices); } public void Dispose(IntPtr verticesPointer) diff --git a/src/Drawie.Backend.Skia/Implementations/SkiaPaintImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SkiaPaintImplementation.cs index f23de1e..655ee9e 100644 --- a/src/Drawie.Backend.Skia/Implementations/SkiaPaintImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SkiaPaintImplementation.cs @@ -30,13 +30,14 @@ public SkiaPaintImplementation(SkiaColorFilterImplementation colorFilterImpl, public IntPtr CreatePaint() { SKPaint skPaint = new SKPaint(); - AddManagedInstance(skPaint); + IntPtr address = AddManagedInstance(skPaint); if (skPaint.ColorFilter != null) { - colorFilterImplementation.AddManagedInstance(skPaint.ColorFilter.Handle, skPaint.ColorFilter); + throw new Exception("Color filter needs managed instance. How did we get here?"); + //colorFilterImplementation.AddManagedInstance(skPaint.ColorFilter.Handle, skPaint.ColorFilter); } - return skPaint.Handle; + return address; } public void Dispose(IntPtr paintObjPointer) @@ -47,9 +48,8 @@ public void Dispose(IntPtr paintObjPointer) public Paint Clone(IntPtr paintObjPointer) { SKPaint clone = this[paintObjPointer].Clone(); - AddManagedInstance(clone); - return new Paint(clone.Handle); + return new Paint(AddManagedInstance(clone)); } public Color GetColor(Paint paint) @@ -76,18 +76,6 @@ public void SetBlendMode(Paint paint, BlendMode value) skPaint.BlendMode = (SKBlendMode)value; } - public FilterQuality GetFilterQuality(Paint paint) - { - SKPaint skPaint = this[paint.ObjectPointer]; - return (FilterQuality)skPaint.FilterQuality; - } - - public void SetFilterQuality(Paint paint, FilterQuality value) - { - SKPaint skPaint = this[paint.ObjectPointer]; - skPaint.FilterQuality = (SKFilterQuality)value; - } - public bool GetIsAntiAliased(Paint paint) { SKPaint skPaint = this[paint.ObjectPointer]; @@ -127,9 +115,10 @@ public void SetStrokeJoin(Paint paint, StrokeJoin value) return null; } - if(!blenderImplementation.TryGetInstance(skPaint.Blender.Handle, out _)) + if(!blenderImplementation.TryGetInstance(paint.Blender.ObjectPointer, out _)) { - blenderImplementation.AddManagedInstance(skPaint.Blender.Handle, skPaint.Blender); + throw new Exception("Blender needs managed instance. How did we get here?"); + //blenderImplementation.AddManagedInstance(skPaint.Blender); } return new Blender(skPaint.Blender.Handle); diff --git a/src/Drawie.Backend.Skia/Implementations/SkiaPathEffectImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SkiaPathEffectImplementation.cs index 177211f..9d76cce 100644 --- a/src/Drawie.Backend.Skia/Implementations/SkiaPathEffectImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SkiaPathEffectImplementation.cs @@ -8,8 +8,8 @@ public class SkiaPathEffectImplementation : SkObjectImplementation public IntPtr CreateDash(float[] intervals, float phase) { SKPathEffect skPathEffect = SKPathEffect.CreateDash(intervals, phase); - AddManagedInstance(skPathEffect); - return skPathEffect.Handle; + return AddManagedInstance(skPathEffect); +; } public void Dispose(IntPtr pathEffectPointer) diff --git a/src/Drawie.Backend.Skia/Implementations/SkiaPathImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SkiaPathImplementation.cs index af9dc41..e3ac9b0 100644 --- a/src/Drawie.Backend.Skia/Implementations/SkiaPathImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SkiaPathImplementation.cs @@ -74,15 +74,13 @@ public int GetPointCount(VectorPath path) public IntPtr Create() { SKPath path = new SKPath(); - AddManagedInstance(path); - return path.Handle; + return AddManagedInstance(path); } public IntPtr Clone(VectorPath other) { SKPath path = new SKPath(this[other.ObjectPointer]); - AddManagedInstance(path); - return path.Handle; + return AddManagedInstance(path); } public RectD GetTightBounds(VectorPath vectorPath) @@ -186,8 +184,8 @@ public VecF GetLastPoint(VectorPath vectorPath) return null; } - AddManagedInstance(skPath); - return new VectorPath(skPath.Handle); + IntPtr handle = AddManagedInstance(skPath); + return new VectorPath(handle); } public VecF[] GetPoints(IntPtr objectPointer) @@ -316,19 +314,16 @@ public VectorPath Op(VectorPath vectorPath, VectorPath ellipsePath, VectorPathOp if (skPath == null) { var emptyPath = new SKPath(); - AddManagedInstance(emptyPath); - return new VectorPath(emptyPath.Handle); + return new VectorPath(AddManagedInstance(emptyPath)); } - AddManagedInstance(skPath); - return new VectorPath(skPath.Handle); + return new VectorPath(AddManagedInstance(skPath)); } public VectorPath Simplify(VectorPath path) { SKPath skPath = this[path.ObjectPointer].Simplify(); - AddManagedInstance(skPath); - return new VectorPath(skPath.Handle); + return new VectorPath(AddManagedInstance(skPath)); } public void Close(VectorPath vectorPath) diff --git a/src/Drawie.Backend.Skia/Implementations/SkiaPictureImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SkiaPictureImplementation.cs index f3ab967..6bb75da 100644 --- a/src/Drawie.Backend.Skia/Implementations/SkiaPictureImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SkiaPictureImplementation.cs @@ -50,9 +50,8 @@ public RectD GetCullRect(Picture picture) var shader = skPicture.ToShader((SKShaderTileMode)tileModeX, (SKShaderTileMode)tileModeY, (SKFilterMode)filterMode, localMatrix.ToSkMatrix(), tile.ToSkRect()); - _shaderImplementation.AddManagedInstance(shader); - return new Shader(shader.Handle); + return new Shader(_shaderImplementation.AddManagedInstance(shader)); } public void Serialize(Picture picture, Stream stream) diff --git a/src/Drawie.Backend.Skia/Implementations/SkiaPixmapImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SkiaPixmapImplementation.cs index d7ce45a..7ec0efa 100644 --- a/src/Drawie.Backend.Skia/Implementations/SkiaPixmapImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SkiaPixmapImplementation.cs @@ -45,8 +45,7 @@ public Span GetPixelSpan(Pixmap pixmap) public IntPtr Construct(IntPtr dataPtr, ImageInfo imgInfo) { SKPixmap pixmap = new SKPixmap(imgInfo.ToSkImageInfo(), dataPtr); - AddManagedInstance(pixmap); - return pixmap.Handle; + return AddManagedInstance(pixmap); } public int GetWidth(Pixmap pixmap) @@ -71,8 +70,7 @@ public object GetNativePixmap(IntPtr objectPointer) public Pixmap CreateFrom(SKPixmap pixmap) { - AddManagedInstance(pixmap); - return Pixmap.InternalCreateFromExistingPointer(pixmap.Handle); + return new Pixmap(AddManagedInstance(pixmap)); } } } diff --git a/src/Drawie.Backend.Skia/Implementations/SkiaRecorderImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SkiaRecorderImplementation.cs index 3872297..c0e78b9 100644 --- a/src/Drawie.Backend.Skia/Implementations/SkiaRecorderImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SkiaRecorderImplementation.cs @@ -41,8 +41,7 @@ public Canvas BeginRecording(DrawingRecorder drawingRecorder, RectD bounds) if (canvas != null) { - _canvasImpl.AddManagedInstance(canvas); - return new Canvas(canvas.Handle); + return new Canvas(_canvasImpl.AddManagedInstance(canvas)); } else { @@ -62,8 +61,7 @@ public Picture EndRecordingImmutable(DrawingRecorder recorder) var skPicture = skRecorder?.EndRecording(); if (skPicture != null) { - _pictureImplementation.AddManagedInstance(skPicture); - return new Picture(skPicture.Handle); + return new Picture(_pictureImplementation.AddManagedInstance(skPicture)); } throw new InvalidOperationException("Failed to end recording on SKPictureRecorder."); @@ -75,7 +73,6 @@ public Picture EndRecordingImmutable(DrawingRecorder recorder) public IntPtr CreateRecorder() { var recorder = new SKPictureRecorder(); - AddManagedInstance(recorder); - return recorder.Handle; + return AddManagedInstance(recorder); } } diff --git a/src/Drawie.Backend.Skia/Implementations/SkiaShaderImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SkiaShaderImplementation.cs index 2cf478d..405f0c3 100644 --- a/src/Drawie.Backend.Skia/Implementations/SkiaShaderImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SkiaShaderImplementation.cs @@ -31,8 +31,7 @@ public void SetBitmapImplementation(SkiaBitmapImplementation bitmapImplementatio public IntPtr CreateShader() { SKShader skShader = SKShader.CreateEmpty(); - AddManagedInstance(skShader); - return skShader.Handle; + return AddManagedInstance(skShader); } public Shader? CreateFromString(string shaderCode, Uniforms uniforms, out string errors) @@ -44,11 +43,11 @@ public IntPtr CreateShader() SKRuntimeEffectUniforms effectUniforms = UniformsToSkUniforms(uniforms, declaration, effect); SKRuntimeEffectChildren effectChildren = UniformsToSkChildren(uniforms, effect); SKShader shader = effect.ToShader(effectUniforms, effectChildren); - AddManagedInstance(shader); - runtimeEffects[shader.Handle] = effect; - declarations[shader.Handle] = declaration; + IntPtr shaderHandle = AddManagedInstance(shader); + runtimeEffects[shaderHandle] = effect; + declarations[shaderHandle] = declaration; - return new Shader(shader.Handle, declaration); + return new Shader(shaderHandle, declaration); } return null; @@ -65,14 +64,14 @@ public IntPtr CreateShader() return null; } - AddManagedInstance(shader); + IntPtr shaderHandle = AddManagedInstance(shader); var declaration = DeclarationsFromEffect(shaderCode, effect); - declarations[shader.Handle] = declaration; + declarations[shaderHandle] = declaration; #if DRAWIE_TRACE Trace(shader); #endif - return new Shader(shader.Handle, declaration); + return new Shader(shaderHandle, declaration); } return null; @@ -89,8 +88,8 @@ public IntPtr CreateShader() if (shader == null) return null; - AddManagedInstance(shader); - return new Shader(shader.Handle); + IntPtr shaderHandle = AddManagedInstance(shader); + return new Shader(shaderHandle); } public Shader? CreateLinearGradient(VecD p1, VecD p2, Color[] colors, float[] offsets) @@ -104,12 +103,12 @@ public IntPtr CreateShader() if (shader == null) return null; - AddManagedInstance(shader); + IntPtr shaderHandle = AddManagedInstance(shader); #if DRAWIE_TRACE Trace(shader); #endif - return new Shader(shader.Handle); + return new Shader(shaderHandle); } public Shader? CreateLinearGradient(VecD p1, VecD p2, Color[] colors, float[] offsets, Matrix3X3 localMatrix) @@ -124,12 +123,12 @@ public IntPtr CreateShader() if (shader == null) return null; - AddManagedInstance(shader); + IntPtr shaderHandle = AddManagedInstance(shader); #if DRAWIE_TRACE Trace(shader); #endif - return new Shader(shader.Handle); + return new Shader(shaderHandle); } public Shader? CreateRadialGradient(VecD center, float radius, Color[] colors, float[] colorPos, @@ -143,13 +142,13 @@ public IntPtr CreateShader() (SKShaderTileMode)tileMode); if (shader == null) return null; - AddManagedInstance(shader); + IntPtr shaderHandle = AddManagedInstance(shader); #if DRAWIE_TRACE Trace(shader); #endif - return new Shader(shader.Handle); + return new Shader(shaderHandle); } public Shader? CreateRadialGradient(VecD center, float radius, Color[] colors) @@ -161,13 +160,13 @@ public IntPtr CreateShader() if (shader == null) return null; - AddManagedInstance(shader); + IntPtr shaderHandle = AddManagedInstance(shader); #if DRAWIE_TRACE Trace(shader); #endif - return new Shader(shader.Handle); + return new Shader(shaderHandle); } public Shader? CreateRadialGradient(VecD center, float radius, Color[] colors, float[] colorPos, @@ -183,13 +182,13 @@ public IntPtr CreateShader() if (shader == null) return null; - AddManagedInstance(shader); + IntPtr shaderHandle = AddManagedInstance(shader); #if DRAWIE_TRACE Trace(shader); #endif - return new Shader(shader.Handle); + return new Shader(shaderHandle); } public Shader? CreateSweepGradient(VecD center, Color[] colors, float[] colorPos, Matrix3X3 localMatrix) @@ -202,13 +201,13 @@ public IntPtr CreateShader() if (shader == null) return null; - AddManagedInstance(shader); + IntPtr shaderHandle = AddManagedInstance(shader); #if DRAWIE_TRACE Trace(shader); #endif - return new Shader(shader.Handle); + return new Shader(shaderHandle); } public Shader? CreateSweepGradient(VecD center, Color[] colors, float[] colorPos, @@ -224,13 +223,13 @@ public IntPtr CreateShader() if (shader == null) return null; - AddManagedInstance(shader); + IntPtr shaderHandle = AddManagedInstance(shader); #if DRAWIE_TRACE Trace(shader); #endif - return new Shader(shader.Handle); + return new Shader(shaderHandle); } public Shader? CreatePerlinNoiseTurbulence(float baseFrequencyX, float baseFrequencyY, int numOctaves, @@ -244,12 +243,12 @@ public IntPtr CreateShader() if (shader == null) return null; - AddManagedInstance(shader); + IntPtr shaderHandle = AddManagedInstance(shader); #if DRAWIE_TRACE Trace(shader); #endif - return new Shader(shader.Handle); + return new Shader(shaderHandle); } public Shader? CreatePerlinFractalNoise(float baseFrequencyX, float baseFrequencyY, int numOctaves, float seed) @@ -265,12 +264,12 @@ public IntPtr CreateShader() if (shader == null) return null; - AddManagedInstance(shader); + IntPtr shaderHandle = AddManagedInstance(shader); #if DRAWIE_TRACE Trace(shader); #endif - return new Shader(shader.Handle); + return new Shader(shaderHandle); } public object GetNativeShader(IntPtr objectPointer) @@ -295,16 +294,16 @@ public Shader WithUpdatedUniforms(IntPtr objectPointer, Uniforms uniforms) runtimeEffects.Remove(objectPointer); var newShader = effect.ToShader(effectUniforms, effectChildren); - AddManagedInstance(newShader); + IntPtr shaderHandle = AddManagedInstance(newShader); - runtimeEffects[newShader.Handle] = effect; - declarations[newShader.Handle] = oldDeclarations; + runtimeEffects[shaderHandle] = effect; + declarations[shaderHandle] = oldDeclarations; #if DRAWIE_TRACE Trace(newShader); #endif - return new Shader(newShader.Handle, oldDeclarations); + return new Shader(shaderHandle, oldDeclarations); } public Shader SetLocalMatrix(IntPtr objectPointer, Matrix3X3 matrix) @@ -314,7 +313,8 @@ public Shader SetLocalMatrix(IntPtr objectPointer, Matrix3X3 matrix) throw new InvalidOperationException("Shader does not exist"); } - return new Shader(shader.WithLocalMatrix(matrix.ToSkMatrix()).Handle); + IntPtr shaderHandle = AddManagedInstance(shader.WithLocalMatrix(matrix.ToSkMatrix())); + return new Shader(shaderHandle); } public Shader? CreateBitmap(Bitmap bitmap, TileMode tileX, TileMode tileY, Matrix3X3 matrix) @@ -329,12 +329,12 @@ public Shader SetLocalMatrix(IntPtr objectPointer, Matrix3X3 matrix) if (shader == null) return null; - AddManagedInstance(shader); + IntPtr shaderHandle = AddManagedInstance(shader); #if DRAWIE_TRACE Trace(shader); #endif - return new Shader(shader.Handle); + return new Shader(shaderHandle); } public Shader? CreateCreate(Image image, TileMode tileX, TileMode tileY, Matrix3X3 matrix) @@ -350,8 +350,8 @@ public Shader SetLocalMatrix(IntPtr objectPointer, Matrix3X3 matrix) if (shader == null) return null; - AddManagedInstance(shader); - return new Shader(shader.Handle); + IntPtr shaderHandle = AddManagedInstance(shader); + return new Shader(shaderHandle); } public UniformDeclaration[]? GetUniformDeclarations(string shaderCode) diff --git a/src/Drawie.Backend.Skia/Implementations/SkiaSurfaceImplementation.cs b/src/Drawie.Backend.Skia/Implementations/SkiaSurfaceImplementation.cs index f2f7165..a04961d 100644 --- a/src/Drawie.Backend.Skia/Implementations/SkiaSurfaceImplementation.cs +++ b/src/Drawie.Backend.Skia/Implementations/SkiaSurfaceImplementation.cs @@ -1,9 +1,13 @@ using System.Diagnostics; +using Drawie.Backend.Core.Bridge; using Drawie.Backend.Core.Bridge.Operations; using Drawie.Backend.Core.Surfaces; using Drawie.Backend.Core.Surfaces.ImageData; using Drawie.Backend.Core.Surfaces.PaintImpl; using Drawie.Numerics; +using Drawie.RenderApi; +using Drawie.RenderApi.Abstraction; +using Drawie.RenderApi.Abstraction.Textures; using SkiaSharp; namespace Drawie.Skia.Implementations @@ -14,15 +18,34 @@ public class SkiaSurfaceImplementation : SkObjectImplementation, ISur private readonly SkiaCanvasImplementation _canvasImplementation; private readonly SkiaPaintImplementation _paintImplementation; + private Dictionary nativeSurfaceInfos = + new Dictionary(); + + private HashSet nativeSurfacesToDispose = new HashSet(); + internal GRContext? GrContext { get; set; } + internal IGraphicsDevice GraphicsDevice { get; set; } - public SkiaSurfaceImplementation(GRContext context, SkiaPixmapImplementation pixmapImplementation, + private readonly SurfaceOrigin defaultSurfaceOrigin; + + public SkiaSurfaceImplementation(SkiaDrawingBackend backendApi, GRContext context, SurfaceOrigin surfaceOrigin, + SkiaPixmapImplementation pixmapImplementation, SkiaCanvasImplementation canvasImplementation, SkiaPaintImplementation paintImplementation) { _pixmapImplementation = pixmapImplementation; _canvasImplementation = canvasImplementation; _paintImplementation = paintImplementation; GrContext = context; + defaultSurfaceOrigin = surfaceOrigin; + backendApi.AfterFlush += () => + { + foreach (var toDispose in nativeSurfacesToDispose) + { + GraphicsDevice.DisposeTexture(toDispose); + } + + nativeSurfacesToDispose.Clear(); + }; } public Pixmap PeekPixels(DrawingSurface drawingSurface) @@ -64,7 +87,7 @@ public void Draw(DrawingSurface drawingSurface, Canvas surfaceToDraw, int x, int { if (isGpuBacked) { - SKSurface? skSurface = CreateSkiaSurface(imageInfo, true); + SKSurface? skSurface = CreateSkiaSurface(imageInfo, true, false); if (skSurface == null) { return null; @@ -85,7 +108,7 @@ public void Draw(DrawingSurface drawingSurface, Canvas surfaceToDraw, int x, int { if (isGpuBacked) { - SKSurface? skSurface = CreateSkiaSurface(imageInfo, true); + SKSurface? skSurface = CreateSkiaSurface(imageInfo, true, false); if (skSurface == null) { return null; @@ -118,23 +141,124 @@ public void Draw(DrawingSurface drawingSurface, Canvas surfaceToDraw, int x, int public DrawingSurface? Create(ImageInfo imageInfo) { - SKSurface? skSurface = CreateSkiaSurface(imageInfo.ToSkImageInfo(), imageInfo.GpuBacked); + SKSurface? skSurface = CreateSkiaSurface(imageInfo.ToSkImageInfo(), imageInfo.GpuBacked, false); return CreateDrawingSurface(skSurface); } - private SKSurface? CreateSkiaSurface(SKImageInfo info, bool gpu) + private SKSurface? CreateSkiaSurface(SKImageInfo info, bool gpu, bool externallyAccessible) { if (!gpu || GrContext == null) { return SKSurface.Create(info); } + if (externallyAccessible) + { + if (GraphicsDevice == null) + { + throw new InvalidOperationException("GraphicsDevice is not initialized."); + } + + var texture = GraphicsDevice.CreateTexture(new TextureDesc() + { + Format = TextureFormat.RGBA8_Unorm, Width = info.Width, Height = info.Height + }); + + var surface = CreateFromNativeTexture(texture, new VecI(info.Width, info.Height), defaultSurfaceOrigin, + false, + out var framebufferInfo); + if (surface == null) return null; + + nativeSurfaceInfos[surface] = framebufferInfo; + return surface; + } + return SKSurface.Create(GrContext, false, info); } + internal SKSurface? CreateFromNativeTexture(ITexture renderTexture, VecI size, SurfaceOrigin surfaceOrigin, + bool asRenderTarget, + out INativeSurfaceInfo fbInfo) + { + if (renderTexture is IVkTexture texture) + { + var imageInfo = new GRVkImageInfo() + { + CurrentQueueFamily = texture.QueueFamily, + Format = texture.ImageFormat, + Image = texture.ImageHandle, + ImageLayout = texture.Layout, + ImageTiling = texture.Tiling, + ImageUsageFlags = texture.UsageFlags, + LevelCount = 1, + SampleCount = 1, + Protected = false, + SharingMode = texture.TargetSharingMode, + }; + + var backendRenderTarget = new GRBackendRenderTarget(size.X, size.Y, imageInfo); + var surface = SKSurface.Create(GrContext, backendRenderTarget, (GRSurfaceOrigin)surfaceOrigin, + SKColorType.Rgba8888, new SKSurfaceProperties(SKPixelGeometry.RgbHorizontal)); + + fbInfo = new SkiaNativeSurfaceInfo(backendRenderTarget, imageInfo); + return surface; + } + + if (renderTexture is IWebGlTexture or IOpenGlTexture) + { + uint textureId = renderTexture switch + { + IWebGlTexture wgl => wgl.TextureId, + IOpenGlTexture ogl => (uint)ogl.TextureId, + _ => throw new ArgumentException("Unsupported texture type.") + }; + + SKSurface? surface; + if (!asRenderTarget) + { + const uint OpenGlTexture2D = 3553; + const uint RGBA8 = 0x8058; + var info = new GRGlTextureInfo(OpenGlTexture2D, textureId, RGBA8); + var backendRenderTarget = new GRBackendTexture(size.X, size.Y, false, info); + + surface = SKSurface.Create(GrContext, backendRenderTarget, (GRSurfaceOrigin)surfaceOrigin, + SKColorType.Rgba8888); + fbInfo = new SkiaNativeSurfaceInfo(backendRenderTarget, info); + } + else + { + GRGlFramebufferInfo grGlFramebufferInfo = + new GRGlFramebufferInfo(textureId, SKColorType.Rgba8888.ToGlSizedFormat()); + GRBackendRenderTarget backendRenderTarget = new GRBackendRenderTarget(size.X, size.Y, 1, 0, + grGlFramebufferInfo); + + surface = SKSurface.Create(GrContext, backendRenderTarget, (GRSurfaceOrigin)surfaceOrigin, + SKColorType.Rgba8888); + + fbInfo = new SkiaNativeSurfaceInfo(backendRenderTarget, grGlFramebufferInfo); + } + + return surface; + } + + throw new ArgumentException("Unsupported texture type."); + } + public void Dispose(DrawingSurface drawingSurface) { + var instance = this.GetInstanceOrDefault(drawingSurface.ObjectPointer); + ulong? surfaceId = null; + if (instance != null) + { + nativeSurfaceInfos.Remove(instance, out var surfaceInfo); + surfaceId = surfaceInfo?.SurfaceId; + } + UnmanageAndDispose(drawingSurface.ObjectPointer); + if (surfaceId != null) + { + nativeSurfacesToDispose.Add(surfaceId.Value); + } } public object GetNativeSurface(IntPtr objectPointer) @@ -153,11 +277,11 @@ public object GetNativeSurface(IntPtr objectPointer) Trace(skSurface); #endif - _canvasImplementation.AddManagedInstance(skSurface.Canvas.Handle, skSurface.Canvas); - Canvas canvas = new Canvas(skSurface.Canvas.Handle); + IntPtr canvasHandle = _canvasImplementation.AddManagedInstance(skSurface.Canvas); + Canvas canvas = new Canvas(canvasHandle); - DrawingSurface surface = new DrawingSurface(skSurface.Handle, canvas); - AddManagedInstance(skSurface); + IntPtr surfaceHandle = AddManagedInstance(skSurface); + DrawingSurface surface = new DrawingSurface(surfaceHandle, canvas); return surface; } @@ -193,5 +317,32 @@ public RectD GetLocalClipBounds(IntPtr objectPointer) SKRect skRect = this[objectPointer].Canvas.LocalClipBounds; return new RectD(skRect.Left, skRect.Top, skRect.Width, skRect.Height); } + + public INativeSurfaceInfo? GetNativeSurfaceInfo(IntPtr objectPointer) + { + return nativeSurfaceInfos.GetValueOrDefault(this[objectPointer]); + } + + public INativeSurfaceInfo? ToExternallyAccessibleSurface(IntPtr objectPointer) + { + var original = this[objectPointer]; + using var snapshot = original.Snapshot(); + + var surface = CreateSkiaSurface(snapshot.Info, true, true); + if (surface == null) + { + return null; + } + + surface.Canvas.DrawImage(snapshot, 0, 0, SKSamplingOptions.Default); + UpdateManagedInstance(objectPointer, surface); + surface.Canvas.Flush(); + return nativeSurfaceInfos[surface]; + } + + public void AddManagedFramebuffer(SKSurface nativeHandle, INativeSurfaceInfo fbInfo) + { + nativeSurfaceInfos.Add(nativeHandle, fbInfo); + } } } diff --git a/src/Drawie.Backend.Skia/SkiaDrawingBackend.cs b/src/Drawie.Backend.Skia/SkiaDrawingBackend.cs index 9c07746..456a64b 100644 --- a/src/Drawie.Backend.Skia/SkiaDrawingBackend.cs +++ b/src/Drawie.Backend.Skia/SkiaDrawingBackend.cs @@ -6,6 +6,8 @@ using Drawie.Backend.Core.Surfaces; using Drawie.Numerics; using Drawie.RenderApi; +using Drawie.RenderApi.Abstraction; +using Drawie.RenderApi.Abstraction.Textures; using Drawie.Skia.Exceptions; using Drawie.Skia.Implementations; using SkiaSharp; @@ -17,7 +19,7 @@ public class SkiaDrawingBackend : IDrawingBackend { private IVulkanRenderApi? vulkanRenderApi; - public GRContext? GraphicsContext + public GRContext? SkiaGraphicsContext { get => _grContext; private set @@ -31,8 +33,10 @@ private set } } + public IGraphicsDevice GraphicsDevice { get; private set; } + public IPathEffectImplementation PathEffectImplementation { get; set; } - public bool IsHardwareAccelerated => GraphicsContext != null; + public bool IsHardwareAccelerated => SkiaGraphicsContext != null; public IRenderingDispatcher RenderingDispatcher { get; set; } @@ -56,11 +60,16 @@ private set public IPictureImplementation PictureImplementation { get; } public IBlenderImplementation BlenderImplementation { get; } public IMeshImplementation MeshImplementation { get; } + + public event Action AfterFlush; private GRContext _grContext; public SkiaDrawingBackend() { + SKColorSpace.CreateSrgb(); + SKColorSpace.CreateSrgbLinear(); + ColorImplementation = new SkiaColorImplementation(); SkiaImgDataImplementation dataImpl = new SkiaImgDataImplementation(); @@ -118,8 +127,8 @@ public SkiaDrawingBackend() SkiaCanvasImplementation canvasImpl = new SkiaCanvasImplementation(paintImpl, imgImpl, bitmapImpl, pathImpl, fontImpl, meshImplementation); - SurfaceImplementation = new SkiaSurfaceImplementation(GraphicsContext, pixmapImpl, canvasImpl, paintImpl); - + SurfaceImplementation = new SkiaSurfaceImplementation(this, SkiaGraphicsContext, SurfaceOrigin.BottomLeft, + pixmapImpl, canvasImpl, paintImpl); canvasImpl.SetSurfaceImplementation(SurfaceImplementation); imgImpl.SetSurfaceImplementation(SurfaceImplementation); @@ -132,8 +141,14 @@ public SkiaDrawingBackend() CanvasImplementation = canvasImpl; } + public IRenderApi ActiveRenderApi { get; private set; } + public void Setup(IRenderApi renderApi) { + ActiveRenderApi = renderApi; + GraphicsDevice = renderApi.GraphicsDevice; + SurfaceImplementation.GraphicsDevice = renderApi.GraphicsDevice; + if (renderApi is IVulkanRenderApi vulkanRenderApi) { SetupVulkan(vulkanRenderApi.VulkanContext); @@ -162,18 +177,16 @@ public void Setup(IRenderApi renderApi) private void SetupOpenGl(IOpenGlContext openGlContext) { GRGlInterface glInterface = GRGlInterface.CreateOpenGl(openGlContext.GetGlInterface); - GraphicsContext = GRContext.CreateGl(glInterface); - SurfaceImplementation.GrContext = GraphicsContext; + SkiaGraphicsContext = GRContext.CreateGl(glInterface); + SurfaceImplementation.GrContext = SkiaGraphicsContext; } private void SetupAngleOpenGl(IOpenGlContext openGlContext) { - GRGlInterface glInterface = GRGlInterface.CreateAngle(openGlContext.GetGlInterface); - GraphicsContext = GRContext.CreateGl(glInterface, new GRContextOptions() - { - AvoidStencilBuffers = true - }); - SurfaceImplementation.GrContext = GraphicsContext; + GRGlInterface glInterface = GRGlInterface.Create(openGlContext.GetGlInterface); + SkiaGraphicsContext = + GRContext.CreateGl(glInterface, new GRContextOptions() { AvoidStencilBuffers = true }); + SurfaceImplementation.GrContext = SkiaGraphicsContext; } private void SetupWebGl(IWebGlContext webGlContext) @@ -181,8 +194,8 @@ private void SetupWebGl(IWebGlContext webGlContext) try { GRGlInterface glInterface = GRGlInterface.CreateWebGl(webGlContext.GetGlInterface); - GraphicsContext = GRContext.CreateGl(glInterface); - SurfaceImplementation.GrContext = GraphicsContext; + SkiaGraphicsContext = GRContext.CreateGl(glInterface); + SurfaceImplementation.GrContext = SkiaGraphicsContext; } catch (Exception e) { @@ -191,49 +204,18 @@ private void SetupWebGl(IWebGlContext webGlContext) } } - public DrawingSurface CreateRenderSurface(VecI size, ITexture renderTexture, SurfaceOrigin surfaceOrigin) + public DrawingSurface? CreateRenderSurface(VecI size, ITexture renderTexture, SurfaceOrigin surfaceOrigin) { - if (renderTexture is IVkTexture texture) - { - var imageInfo = new GRVkImageInfo() - { - CurrentQueueFamily = texture.QueueFamily, - Format = texture.ImageFormat, - Image = texture.ImageHandle, - ImageLayout = texture.Layout, - ImageTiling = texture.Tiling, - ImageUsageFlags = texture.UsageFlags, - LevelCount = 1, - SampleCount = 1, - Protected = false, - SharingMode = texture.TargetSharingMode, - }; - - var surface = SKSurface.Create(GraphicsContext, new GRBackendRenderTarget(size.X, size.Y, 1, imageInfo), - (GRSurfaceOrigin)surfaceOrigin, SKColorType.Rgba8888, - new SKSurfaceProperties(SKPixelGeometry.RgbHorizontal)); - - return DrawingSurface.FromNative(surface); - } - else if (renderTexture is IWebGlTexture or IOpenGlTexture) + var native = + SurfaceImplementation.CreateFromNativeTexture(renderTexture, size, surfaceOrigin, true, out var fbInfo); + if (native == null) { - uint textureId = renderTexture switch - { - IWebGlTexture wgl => wgl.TextureId, - IOpenGlTexture ogl => ogl.TextureId, - _ => throw new ArgumentException("Unsupported texture type.") - }; - - GRBackendRenderTarget backendRenderTarget = new GRBackendRenderTarget(size.X, size.Y, 1, 0, - new GRGlFramebufferInfo(textureId, SKColorType.Rgba8888.ToGlSizedFormat())); - - var surface = SKSurface.Create(GraphicsContext, backendRenderTarget, (GRSurfaceOrigin)surfaceOrigin, - SKColorType.Rgba8888); - - return DrawingSurface.FromNative(surface); + return null; } - throw new ArgumentException("Unsupported texture type."); + SurfaceImplementation.AddManagedInstance(native); + SurfaceImplementation.AddManagedFramebuffer(native, fbInfo); + return DrawingSurface.FromNative(native); } private void SetupVulkan(IVulkanContext vulkanContext) @@ -248,18 +230,25 @@ private void SetupVulkan(IVulkanContext vulkanContext) GetProcedureAddress = vulkanContext.GetProcedureAddress, }; - GraphicsContext = GRContext.CreateVulkan(vkBackendContext); - SurfaceImplementation.GrContext = GraphicsContext; + SkiaGraphicsContext = GRContext.CreateVulkan(vkBackendContext); + SurfaceImplementation.GrContext = SkiaGraphicsContext; } public void Flush() { - GraphicsContext?.Flush(); + SkiaGraphicsContext?.Flush(); + AfterFlush?.Invoke(); + } + + public void ResetContext() + { + SkiaGraphicsContext?.ResetContext(); } public override string ToString() { - return "Skia"; + var version = typeof(SKCanvas).Assembly.GetName().Version; + return "Skia " + version; } public async ValueTask DisposeAsync() diff --git a/src/Drawie.Backend.Skia/SkiaNativeSurfaceInfo.cs b/src/Drawie.Backend.Skia/SkiaNativeSurfaceInfo.cs new file mode 100644 index 0000000..cab0bc6 --- /dev/null +++ b/src/Drawie.Backend.Skia/SkiaNativeSurfaceInfo.cs @@ -0,0 +1,35 @@ +using Drawie.Backend.Core.Surfaces; +using SkiaSharp; + +namespace Drawie.Skia; + +public class SkiaNativeSurfaceInfo : INativeSurfaceInfo +{ + public GRVkImageInfo? VkImageInfo { get; } + public GRGlFramebufferInfo? GlFramebufferInfo { get; } + public GRGlTextureInfo? GlTextureInfo { get; } + + private GRBackendTexture texture; + private GRBackendRenderTarget target; + + public SkiaNativeSurfaceInfo(GRBackendRenderTarget target, GRVkImageInfo imageInfo) + { + this.target = target; + VkImageInfo = imageInfo; + } + + public SkiaNativeSurfaceInfo(GRBackendRenderTarget backendRenderTarget, GRGlFramebufferInfo grGlFramebufferInfo) + { + this.target = backendRenderTarget; + GlFramebufferInfo = grGlFramebufferInfo; + } + + public SkiaNativeSurfaceInfo(GRBackendTexture backendRenderTarget, GRGlTextureInfo grGlFramebufferInfo) + { + GlTextureInfo = grGlFramebufferInfo; + texture = backendRenderTarget; + } + + + public ulong SurfaceId => VkImageInfo?.Image ?? target?.GetGlFramebufferInfo().FramebufferObjectId ?? GlTextureInfo.Value.Id; +} \ No newline at end of file diff --git a/src/Drawie.Backend.Skia/SkiaTextureInfo.cs b/src/Drawie.Backend.Skia/SkiaTextureInfo.cs new file mode 100644 index 0000000..6f96aff --- /dev/null +++ b/src/Drawie.Backend.Skia/SkiaTextureInfo.cs @@ -0,0 +1,18 @@ +using Drawie.Backend.Core.Surfaces; +using Drawie.RenderApi.Abstraction.Textures; +using SkiaSharp; + +namespace Drawie.Skia; + +public class SkiaTextureInfo : ITexture +{ + public GRBackendTexture Target => target; + private GRBackendTexture target; + + public SkiaTextureInfo(GRBackendTexture target) + { + this.target = target; + } + + public ulong TextureId => target.GetGlTextureInfo().Id; +} \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Core/Camera.cs b/src/Drawie.Backend.Vertie/Core/Camera.cs new file mode 100644 index 0000000..319ffa9 --- /dev/null +++ b/src/Drawie.Backend.Vertie/Core/Camera.cs @@ -0,0 +1,76 @@ +using System.Numerics; +using Drawie.Backend.Vertie.Helpers; +using Drawie.Numerics; + +namespace Drawie.Backend.Vertie.Core; + +public class Camera +{ + public Vector3 Position { get; set; } + public Vector3 Forward { get; private set; } + public Vector3 Up { get; private set; } + public Vector3 Right { get; private set; } + public float AspectRatio { get; set; } + + public float Yaw { get; set; } = -90f; + public float Pitch { get; set; } + public Frustum Frustum { get; private set; } + + private float _zoom = 45f; + public float Zoom + { + get => _zoom; + set => _zoom = Math.Clamp(Zoom - value, 1f, 45f); + } + + public Matrix4x4 ViewMatrix => Matrix4x4.CreateLookAt(Position, (Position + Forward), Up); + + public Matrix4x4 ProjectionMatrix => + Matrix4x4.CreatePerspectiveFieldOfView(MathEx.DegreesToRadians * Zoom, AspectRatio, 0.1f, 100f); + + public Quaternion Rotation => Quaternion.CreateFromYawPitchRoll(-MathEx.DegreesToRadians * Yaw, + MathEx.DegreesToRadians * Pitch, 0f); + + public Camera(Vector3 position, Vector3 forward, Vector3 up, float aspectRatio) + { + Position = position; + Forward = forward; + Up = up; + AspectRatio = aspectRatio; + SetDirection(0, 0); + Frustum = new Frustum(this, MathEx.DegreesToRadians * Zoom, 0.1f, 100f); + } + + public void RecalculateFrustum() + { + Frustum = new Frustum(this, MathEx.DegreesToRadians * Zoom, 0.1f, 100f); + } + + public void SetDirection(float xOffset, float yOffset) + { + Yaw += xOffset; + Pitch -= yOffset; + + Pitch = Math.Clamp(Pitch, -89f, 89f); + + var cameraDirection = Vector3.Zero; + cameraDirection.X = MathF.Cos(MathEx.DegreesToRadians * Yaw) * MathF.Cos(MathEx.DegreesToRadians * Pitch); + cameraDirection.Y = MathF.Sin(MathEx.DegreesToRadians * Pitch); + cameraDirection.Z = MathF.Sin(MathEx.DegreesToRadians * Yaw) * MathF.Cos(MathEx.DegreesToRadians * Pitch); + cameraDirection = Vector3.Normalize(cameraDirection); + + Forward = cameraDirection; + Right = Vector3.Normalize(Vector3.Cross(Forward, Vector3.UnitY)); + Up = Vector3.Normalize(Vector3.Cross(Right, cameraDirection)); + } + + public void LookAt(Vector3 target) + { + Forward = Vector3.Normalize(target - Position); + Right = Vector3.Normalize(Vector3.Cross(Forward, Vector3.UnitY)); + Up = Vector3.Normalize(Vector3.Cross(Right, Forward)); + + Yaw = MathF.Atan2(Forward.Z, Forward.X) * (180f / MathF.PI); + Pitch = MathF.Asin(Forward.Y) * (180f / MathF.PI); + } +} diff --git a/src/Drawie.Backend.Vertie/Core/Frustum.cs b/src/Drawie.Backend.Vertie/Core/Frustum.cs new file mode 100644 index 0000000..bd9bf1b --- /dev/null +++ b/src/Drawie.Backend.Vertie/Core/Frustum.cs @@ -0,0 +1,33 @@ +using System.Numerics; +using Drawie.Numerics; + +namespace Drawie.Backend.Vertie.Core; + +public struct Frustum +{ + public Plane Near { get; private set; } + public Plane Far { get; private set; } + public Plane Left { get; private set; } + public Plane Right { get; private set; } + public Plane Top { get; private set; } + public Plane Bottom { get; private set; } + + public Frustum(Camera camera, float fovY, float zNear, float zFar) : this() + { + Recalculate(camera, fovY, zNear, zFar); + } + + public void Recalculate(Camera camera, float fovY, float zNear, float zFar) + { + float halfVerticalSize = zFar * (float)Math.Tan(fovY / 2); + float halfHorizontalSize = halfVerticalSize * camera.AspectRatio; + Vector3 frontMultFar = zFar * camera.Forward; + + Near = new Plane(camera.Position + zNear * camera.Forward, camera.Forward); + Far = new Plane(camera.Position + frontMultFar, -camera.Forward); + Right = new Plane(camera.Position, Vector3.Cross(camera.Up, frontMultFar + camera.Right * halfHorizontalSize)); + Left = new Plane(camera.Position, Vector3.Cross(frontMultFar - camera.Right * halfHorizontalSize, camera.Up)); + Bottom = new Plane(camera.Position, Vector3.Cross(camera.Right, frontMultFar - camera.Up * halfVerticalSize)); + Top = new Plane(camera.Position, Vector3.Cross(frontMultFar + camera.Up * halfVerticalSize, camera.Right)); + } +} \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Core/Mesh.cs b/src/Drawie.Backend.Vertie/Core/Mesh.cs new file mode 100644 index 0000000..9098e10 --- /dev/null +++ b/src/Drawie.Backend.Vertie/Core/Mesh.cs @@ -0,0 +1,66 @@ +using System.Numerics; +using Drawie.Backend.Vertie.Rendering; +using Drawie.RenderApi.Abstraction; +using Drawie.RenderApi.Abstraction.Buffers; + +namespace Drawie.Backend.Vertie.Core; + +public class Mesh +{ + public Transform Transform { get; } = new Transform(); + public IReadOnlyList Vertices => vertices; + public IReadOnlyList Normals => normals; + public IReadOnlyList TexCoords => texCoords; + public IReadOnlyList Indicies => indicies; + public int IndexCount => Indicies.Count; + public MaterialInstance Material { get; } + + internal bool BuffersInitialized { get; private set; } = false; + public IBufferGroup Buffers { get; private set; } + + private Vector3[] vertices; + private Vector3[] normals; + private Vector2[] texCoords; + private uint[] indicies; + + public Mesh(Vector3[] vertices, uint[] indicies, Vector3[] normals, Vector2[] texCoords, Material material) + { + this.vertices = vertices; + this.indicies = indicies; + this.normals = normals; + this.texCoords = texCoords; + Material = new MaterialInstance(material); + } + + internal void GenerateBuffers(IGraphicsDevice device) + { + Buffers = device.CreateBufferGroup(); + // TODO: Fragile api, CreateBuffer is assumed to be created in buffer Open func (vao is bound there) + Buffers.Open(list => + { + list.Buffers.AddRange( + device.CreateBuffer(BufferUsage.Vertex, CreateVertexData()), + device.CreateBuffer(BufferUsage.Index, indicies)); + } + ); + + BuffersInitialized = true; + } + + private float[] CreateVertexData() + { + float[] vertData = new float[vertices.Length * 8]; + for (int i = 0; i < vertices.Length; i++) + { + vertData[i * 8 + 0] = vertices[i].X; + vertData[i * 8 + 1] = vertices[i].Y; + vertData[i * 8 + 2] = vertices[i].Z; + vertData[i * 8 + 3] = normals.ElementAtOrDefault(i).X; + vertData[i * 8 + 4] = normals.ElementAtOrDefault(i).Y; + vertData[i * 8 + 5] = normals.ElementAtOrDefault(i).Z; + vertData[i * 8 + 6] = texCoords.ElementAtOrDefault(i).X; + vertData[i * 8 + 7] = texCoords.ElementAtOrDefault(i).Y; + } + return vertData; + } +} \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Core/Plane.cs b/src/Drawie.Backend.Vertie/Core/Plane.cs new file mode 100644 index 0000000..30967c7 --- /dev/null +++ b/src/Drawie.Backend.Vertie/Core/Plane.cs @@ -0,0 +1,21 @@ +using System.Numerics; +using Drawie.Numerics; + +namespace Drawie.Backend.Vertie.Core; + +public struct Plane +{ + public Vector3 Normal { get; set; } + public Vector3 Point { get; set; } + + public float GetSignedDistance(Vector3 point) + { + return Vector3.Dot(Normal, point - Point); + } + + public Plane(Vector3 point, Vector3 normal) + { + Normal = Vector3.Normalize(normal); + Point = point; + } +} \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Core/RenderOptions.cs b/src/Drawie.Backend.Vertie/Core/RenderOptions.cs new file mode 100644 index 0000000..a61f63e --- /dev/null +++ b/src/Drawie.Backend.Vertie/Core/RenderOptions.cs @@ -0,0 +1,16 @@ +namespace Drawie.Backend.Vertie.Core; + +public struct RenderOptions +{ + public RenderMode RenderMode { get; set; } + public MsaaSamples MsaaSamples { get; set; } +} + +public enum MsaaSamples +{ + None = 0, + X2 = 2, + X4 = 4, + X8 = 8, + X16 = 16, +} \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Core/Scene.cs b/src/Drawie.Backend.Vertie/Core/Scene.cs new file mode 100644 index 0000000..9622c75 --- /dev/null +++ b/src/Drawie.Backend.Vertie/Core/Scene.cs @@ -0,0 +1,72 @@ +using System.Numerics; +using Assimp; +using Drawie.Backend.Core; +using Drawie.Backend.Vertie.Rendering; +using Material = Drawie.Backend.Vertie.Rendering.Material; + +namespace Drawie.Backend.Vertie.Core; + +public class Scene +{ + public List Meshes { get; } = new List(); + + + public Scene(string path, Texture texture) + { + AssimpContext ctx = new AssimpContext(); + var scene = ctx.ImportFile(path, PostProcessPreset.TargetRealTimeMaximumQuality); + if (!scene.HasMeshes) return; + + Rendering.Material[] materials = new Material[scene.Meshes.Count]; + for (var index = 0; index < scene.Materials.Count; index++) + { + var sceneMaterial = scene.Materials[index]; + materials[index] = new Material(sceneMaterial.Name, + [BuiltInShaders.BasicVertexShader, BuiltInShaders.UnlitFragmentShader]); + } + + foreach (var mesh in scene.Meshes) + { + var vertices = mesh!.Vertices.Select(x => new Vector3(x.X, x.Y, x.Z)).ToArray(); + var indicies = mesh.GetUnsignedIndices().ToArray(); + var normals = mesh.Normals.Select(x => new Vector3(x.X, x.Y, x.Z)).ToArray(); + var texCoords = mesh.TextureCoordinateChannels[0].Select(x => new Vector2(x.X, x.Y)).ToArray(); + Meshes.Add(new Mesh(vertices, indicies, normals, texCoords, materials[mesh.MaterialIndex])); + } + } + + public Scene(string path, string assetsRoot = "") + { + AssimpContext ctx = new AssimpContext(); + var scene = ctx.ImportFile(path, PostProcessPreset.TargetRealTimeMaximumQuality); + if (!scene.HasMeshes) return; + + Rendering.Material[] materials = new Material[scene.Meshes.Count]; + for (var index = 0; index < scene.Materials.Count; index++) + { + var sceneMaterial = scene.Materials[index]; + materials[index] = new Material(sceneMaterial.Name, + [BuiltInShaders.BasicVertexShader, BuiltInShaders.UnlitFragmentShader]); + string texPath = Path.Combine(assetsRoot, + Path.GetFileName(sceneMaterial.TextureDiffuse.FilePath?.Replace("\\", "/") ?? "")); + + //TODO It's a temp solution + if (!sceneMaterial.HasTextureDiffuse) + { + texPath = Path.Combine(assetsRoot, "diffuse.png"); + if(!File.Exists(texPath)) continue; + } + + materials[index].AddTexture(Texture.Load(texPath)); + } + + foreach (var mesh in scene.Meshes) + { + var vertices = mesh!.Vertices.Select(x => new Vector3(x.X, x.Y, x.Z)).ToArray(); + var indicies = mesh.GetUnsignedIndices().ToArray(); + var normals = mesh.Normals.Select(x => new Vector3(x.X, x.Y, x.Z)).ToArray(); + var texCoords = mesh.TextureCoordinateChannels[0].Select(x => new Vector2(x.X, x.Y)).ToArray(); + Meshes.Add(new Mesh(vertices, indicies, normals, texCoords, materials[mesh.MaterialIndex])); + } + } +} diff --git a/src/Drawie.Backend.Vertie/Core/Transform.cs b/src/Drawie.Backend.Vertie/Core/Transform.cs new file mode 100644 index 0000000..42e0d01 --- /dev/null +++ b/src/Drawie.Backend.Vertie/Core/Transform.cs @@ -0,0 +1,73 @@ +using System.Numerics; + +namespace Drawie.Backend.Vertie.Core; + +public class Transform +{ + private Vector3 _position = Vector3.Zero; + private float _scale = 1f; + private Quaternion _rotation = Quaternion.Identity; + private Matrix4x4 _cachedMatrix = Matrix4x4.Identity; + + private bool _isDirty = true; + + public Vector3 Position + { + get => _position; + set + { + _position = value; + _isDirty = true; + } + } + + public Vector3 Right + { + get => Vector3.Transform(Vector3.UnitX, _rotation); + } + + public Vector3 Up + { + get => Vector3.Transform(Vector3.UnitY, _rotation); + } + + public Vector3 Forward + { + get => Vector3.Transform(Vector3.UnitZ, _rotation); + } + + public float Scale + { + get => _scale; + set + { + _scale = value; + _isDirty = true; + } + } + + public Quaternion Rotation + { + get => _rotation; + set + { + _rotation = value; + _isDirty = true; + } + } + + public Matrix4x4 ViewMatrix + { + get + { + if (_isDirty) + { + _cachedMatrix = Matrix4x4.Identity * Matrix4x4.CreateFromQuaternion(Rotation) * Matrix4x4.CreateScale(Scale) * + Matrix4x4.CreateTranslation(Position); + _isDirty = false; + } + + return _cachedMatrix; + } + } +} \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Directory.Build.props b/src/Drawie.Backend.Vertie/Directory.Build.props new file mode 100644 index 0000000..da53da9 --- /dev/null +++ b/src/Drawie.Backend.Vertie/Directory.Build.props @@ -0,0 +1,11 @@ + + + $(ProjectDir)Shaders + false + + + + + + + \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Directory.Build.targets b/src/Drawie.Backend.Vertie/Directory.Build.targets new file mode 100644 index 0000000..3ebeb07 --- /dev/null +++ b/src/Drawie.Backend.Vertie/Directory.Build.targets @@ -0,0 +1,45 @@ + + + $(IntermediateOutputPath)CompiledShaders + + + + + + + + + + + + + + + + + + + + + $(AssemblyName).BuiltInShaders.%(CompiledShaderResources.Filename)%(CompiledShaderResources.Extension) + + + + + + \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Drawie.Backend.Vertie.csproj b/src/Drawie.Backend.Vertie/Drawie.Backend.Vertie.csproj new file mode 100644 index 0000000..8538b65 --- /dev/null +++ b/src/Drawie.Backend.Vertie/Drawie.Backend.Vertie.csproj @@ -0,0 +1,25 @@ + + + + net10.0 + enable + enable + Drawie.Backend.Vertie + true + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Helpers/MathEx.cs b/src/Drawie.Backend.Vertie/Helpers/MathEx.cs new file mode 100644 index 0000000..0f8ccbe --- /dev/null +++ b/src/Drawie.Backend.Vertie/Helpers/MathEx.cs @@ -0,0 +1,7 @@ +namespace Drawie.Backend.Vertie.Helpers; + +public static class MathEx +{ + public const float DegreesToRadians = 0.017453292f; + public const float RadiansToDegrees = 57.2957795f; +} \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Helpers/NumericsExtensions.cs b/src/Drawie.Backend.Vertie/Helpers/NumericsExtensions.cs new file mode 100644 index 0000000..975650a --- /dev/null +++ b/src/Drawie.Backend.Vertie/Helpers/NumericsExtensions.cs @@ -0,0 +1,12 @@ +using System.Numerics; +using Drawie.Numerics; + +namespace Drawie.Backend.Vertie.Helpers; + +public static class NumericsExtensions +{ + public static Vector3 ToVector3(this Vec3D vec) + { + return new Vector3((float)vec.X, (float)vec.Y, (float)vec.Z); + } +} \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Helpers/ShaderLoader.cs b/src/Drawie.Backend.Vertie/Helpers/ShaderLoader.cs new file mode 100644 index 0000000..83669e4 --- /dev/null +++ b/src/Drawie.Backend.Vertie/Helpers/ShaderLoader.cs @@ -0,0 +1,51 @@ +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using Drawie.Backend.Shaders.Common; +using Drawie.Backend.Vertie.Rendering; + +namespace Drawie.Backend.Vertie.Helpers; + +public static class ShaderLoader +{ + public static Shader? LoadShader(string name) + { + using var shaderStream = ReadFromAssemblyStream(name + ".shader"); + using var reflectionStream = ReadFromAssemblyStream(name + ".reflection.json"); + + using var memoryStream = new MemoryStream(); + shaderStream.CopyTo(memoryStream); + byte[] shaderBytes = memoryStream.ToArray(); + + using StreamReader reader = new StreamReader(reflectionStream); + string reflectionJson = reader.ReadToEnd(); + + if (string.IsNullOrEmpty(reflectionJson)) return null; + + ShaderReflection? reflection = JsonSerializer.Deserialize(reflectionJson, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + TypeInfoResolver = new ShaderReflectionContext() + }); + + return new Shader(shaderBytes, reflection); + } + + private static Stream ReadFromAssemblyStream(string name) + { + Stream? stream = null; + try + { + stream = Assembly.GetExecutingAssembly() + .GetManifestResourceStream("Drawie.Backend.Vertie.BuiltInShaders." + name) + ?? throw new InvalidOperationException("Shader not found"); + return stream; + } + catch + { + stream?.Dispose(); + throw; + } + } +} \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Helpers/TextureFramebufferExtensions.cs b/src/Drawie.Backend.Vertie/Helpers/TextureFramebufferExtensions.cs new file mode 100644 index 0000000..53e5986 --- /dev/null +++ b/src/Drawie.Backend.Vertie/Helpers/TextureFramebufferExtensions.cs @@ -0,0 +1,167 @@ +using Drawie.Backend.Core.Bridge; +using Drawie.Backend.Core.Surfaces; +using Drawie.Backend.Vertie.Core; +using Drawie.Backend.Vertie.Rendering; +using Drawie.Numerics; +using Drawie.RenderApi.Abstraction; +using Drawie.RenderApi.Abstraction.CommandRecording; +using Drawie.RenderApi.Abstraction.Pipeline; +using Drawie.RenderApi.Abstraction.RenderTargets; +using Drawie.RenderApi.Abstraction.Shaders; +using Drawie.RenderApi.Abstraction.Textures; +using Drawie.Rendering; + +namespace Drawie.Backend.Vertie.Helpers; + +public static class TextureFramebufferExtensions +{ + private static Dictionary cachedSamplers = new Dictionary(); + + private static Dictionary cachedShaderPrograms = + new Dictionary(); + + private static IRenderTarget? cachedRenderTarget; + + private static ICommandList? cmdList; + private static Dictionary preparedTextures = new Dictionary(); + + public static void DrawScene(this TextureFramebuffer fb, Scene scene, Camera camera, + RenderOptions options = default) + { + IGraphicsDevice device = DrawingBackendApi.Current.ActiveRenderApi.GraphicsDevice; + + fb.Canvas?.Flush(); + DrawScene(fb, scene, camera, device, options); + } + + public static void DrawScene(this DrawingSurface fb, Scene scene, Camera camera, + RenderOptions options = default) + { + IGraphicsDevice device = DrawingBackendApi.Current.ActiveRenderApi.GraphicsDevice; + foreach (var mesh in scene.Meshes) + { + foreach (var texture in mesh.Material.Textures) + { + if (texture is ILazyExternallyAccessibleTexture extTexture) + { + extTexture.EnsureExternallyAccessible(); + } + } + } + + fb.Canvas?.Flush(); + DrawScene(fb, scene, camera, device, options); + } + + public static void DrawScene(IRenderTarget fb, Scene scene, Camera camera, + IGraphicsDevice device, + RenderOptions options) + { + if (cachedRenderTarget == null || cachedRenderTarget.Size != fb.Size) + { + (cachedRenderTarget as IDisposable)?.Dispose(); + + cachedRenderTarget = device.CreateRenderTarget(new TextureDesc() + { + Width = fb.Size.X, + Height = fb.Size.Y, + Format = TextureFormat.RGBA8_Unorm, + Depth = DepthFormat.Depth24Stencil8, + Samples = (int)options.MsaaSamples + }); + } + + preparedTextures.Clear(); + var program = GetOrCreateShaderProgram(device, scene); + + var pipeline = device.CreatePipeline(new PipelineDesc() + { + Depth = new DepthDesc() + { + Enabled = true, + DepthCompare = DepthCompareType.Less, + Format = DepthFormat.Depth24Stencil8 + }, + Rasterizer = new RasterizerDesc() + { + RenderMode = options.RenderMode, + Samples = (int)options.MsaaSamples + }, + ShaderProgram = program, + Viewport = new RectI(0, 0, fb.Size.X, fb.Size.Y), + }); + + cmdList = device.CreateCommandList(); + + foreach (var mesh in scene.Meshes) + { + foreach (var texture in mesh.Material.Textures) + { + if (preparedTextures.ContainsKey(texture.TextureId)) continue; + preparedTextures.Add(texture.TextureId, cmdList.PrepareTexture(texture)); + } + } + + cmdList.SetPipeline(pipeline); + + cmdList.BeginRenderPass(cachedRenderTarget); + cmdList.BindPipeline(); + + foreach (var mesh in scene.Meshes) + { + if (!mesh.BuffersInitialized) + { + mesh.GenerateBuffers(device); + } + + cmdList.SetBuffers(mesh.Buffers); + + var material = mesh.Material; + material.Use(camera); + material.PrepareForObject(mesh.Transform); + + List texturesForMaterial = new List(); + List samplers = new List(); + foreach (var materialTexture in material.Textures) + { + if (preparedTextures.TryGetValue(materialTexture.TextureId, out var texture)) + { + texturesForMaterial.Add(texture); + var sampler = cachedSamplers.GetValueOrDefault(materialTexture); + if (sampler == null) + { + sampler = device.CreateSampler(new SamplerDesc()); + cachedSamplers[materialTexture] = sampler; + } + + samplers.Add(sampler); + } + } + + cmdList.UpdateUniforms(material.Properties.Values.ToList(), texturesForMaterial, samplers); + cmdList.DrawIndexed(mesh.IndexCount); + } + + var recordedRenderPass = cmdList.EndRenderPass(fb); + + device.Submit(recordedRenderPass); + + DrawingBackendApi.Current.ResetContext(); + } + + private static IShaderProgram GetOrCreateShaderProgram(IGraphicsDevice graphicsDevice, Scene scene) + { + if (cachedShaderPrograms.ContainsKey(scene)) + { + return cachedShaderPrograms[scene]; + } + + var program = + graphicsDevice.CreateShaderProgram(new ShaderProgramDesc( + scene.Meshes.SelectMany(x => x.Material.Original.Shaders) + .Select(x => new ShaderDesc(x.EntryName, x.ShaderBytes, x.ShaderType)))); + cachedShaderPrograms.Add(scene, program); + + return program; + } +} diff --git a/src/Drawie.Backend.Vertie/Rendering/BuiltInShaders.cs b/src/Drawie.Backend.Vertie/Rendering/BuiltInShaders.cs new file mode 100644 index 0000000..8ebe14e --- /dev/null +++ b/src/Drawie.Backend.Vertie/Rendering/BuiltInShaders.cs @@ -0,0 +1,15 @@ +using Drawie.Backend.Vertie.Helpers; + +namespace Drawie.Backend.Vertie.Rendering; + +public static class BuiltInShaders +{ + static BuiltInShaders() + { + BasicVertexShader = ShaderLoader.LoadShader("BasicVertex"); + UnlitFragmentShader = ShaderLoader.LoadShader("Unlit"); + } + + public static Shader BasicVertexShader { get; private set; } + public static Shader UnlitFragmentShader { get; private set; } +} \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Rendering/Material.cs b/src/Drawie.Backend.Vertie/Rendering/Material.cs new file mode 100644 index 0000000..b12e301 --- /dev/null +++ b/src/Drawie.Backend.Vertie/Rendering/Material.cs @@ -0,0 +1,66 @@ +using System.Numerics; +using Drawie.Backend.Vertie.Core; +using Drawie.RenderApi; +using Drawie.RenderApi.Abstraction.Shaders; +using Drawie.RenderApi.Abstraction.Textures; + +namespace Drawie.Backend.Vertie.Rendering; + +public class Material +{ + public string Name { get; set; } + public Shader[] Shaders { get; set; } + public Dictionary Properties { get; set; } = new(); + public List Textures { get; set; } = new(); + + private int _textureCount; + + public Material(string name, Shader[] shaders) + { + Name = name; + Shaders = shaders; + + Properties.Add("Transform", + new PropertyGroupDefinition("Transform") + .AddProperty("uModel", Matrix4x4.Identity) + .AddProperty("uView", Matrix4x4.Identity) + .AddProperty("uProjection", Matrix4x4.Identity)); + + foreach (var propertyGroup in Properties) + { + foreach (var shader in shaders) + { + if (shader.HasUniformBlock(propertyGroup.Key)) + { + propertyGroup.Value.ShaderLayout = shader.GetLayoutFor(propertyGroup.Key); + } + } + } + } + + public void AddTexture(ITexture texture) + { + Textures.Add(texture); + } + + /* + public void UpdateShader() + { + foreach (var property in Properties) + { + ApplyToShader(property.Value); + } + } + + + private void ApplyToShader(UniformBlock prop) + { + foreach (var shader in Shaders) + { + if (shader.HasUniformBlock(prop.Name)) + { + shader.SetUniformBlock(prop); + } + } + }*/ +} \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Rendering/MaterialInstance.cs b/src/Drawie.Backend.Vertie/Rendering/MaterialInstance.cs new file mode 100644 index 0000000..ed2b0e7 --- /dev/null +++ b/src/Drawie.Backend.Vertie/Rendering/MaterialInstance.cs @@ -0,0 +1,50 @@ +using Drawie.Backend.Vertie.Core; +using Drawie.RenderApi.Abstraction.Shaders; +using Drawie.RenderApi.Abstraction.Textures; + +namespace Drawie.Backend.Vertie.Rendering; + +public class MaterialInstance +{ + public Material Original { get; } + + public Guid InstanceId { get; } = Guid.NewGuid(); + + public Dictionary Properties { get; set; } + public List Textures { get; } + + public MaterialInstance(Material from) + { + Original = from; + Properties = CreateFromDefinitions(from.Properties); + Textures = new List(Original.Textures); + } + + public void Use(Camera camera) + { + Properties["Transform"].SetProperty("uView", camera.ViewMatrix); + Properties["Transform"].SetProperty("uProjection", camera.ProjectionMatrix); + } + + public void PrepareForObject(Transform transform) + { + Properties["Transform"].SetProperty("uModel", transform.ViewMatrix); + } + + private Dictionary CreateFromDefinitions(Dictionary fromProperties) + { + var dict = new Dictionary(); + foreach (var propertyGroupDefinition in fromProperties) + { + var block = new UniformBlock(propertyGroupDefinition.Key) { ShaderLayout = propertyGroupDefinition.Value.ShaderLayout }; + foreach (var prop in propertyGroupDefinition.Value.Properties) + { + block.AddProperty(new ShaderProperty(prop.Name) { ObjValue = prop.DefaultValue, Type = prop.ValueType }); + } + + dict.Add(propertyGroupDefinition.Key, block); + } + + return dict; + } +} \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Rendering/PropertyDefinition.cs b/src/Drawie.Backend.Vertie/Rendering/PropertyDefinition.cs new file mode 100644 index 0000000..34c8891 --- /dev/null +++ b/src/Drawie.Backend.Vertie/Rendering/PropertyDefinition.cs @@ -0,0 +1,40 @@ +using System.Numerics; +using System.Reflection.Metadata; +using Drawie.RenderApi.Abstraction.Shaders; + +namespace Drawie.Backend.Vertie.Rendering; + +public class PropertyGroupDefinition +{ + public string Name { get; } + public IReadOnlyList Properties => properties; + public UniformBlockLayout ShaderLayout { get; set; } + + private List properties = new List(); + + public PropertyGroupDefinition(string name) + { + Name = name; + properties = new List(); + } + + public PropertyGroupDefinition AddProperty(string name, T? defaultValue) + { + properties.Add(new PropertyDefinition(name, typeof(T), defaultValue)); + return this; + } +} + +public struct PropertyDefinition +{ + public string Name { get; } + public Type ValueType { get; } + public object? DefaultValue { get; } + + public PropertyDefinition(string name, Type valueType, object? defaultValue) + { + Name = name; + ValueType = valueType; + DefaultValue = defaultValue; + } +} \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Rendering/Shader.cs b/src/Drawie.Backend.Vertie/Rendering/Shader.cs new file mode 100644 index 0000000..302bcbe --- /dev/null +++ b/src/Drawie.Backend.Vertie/Rendering/Shader.cs @@ -0,0 +1,52 @@ +using Drawie.Backend.Shaders.Common; +using Drawie.RenderApi.Abstraction.Shaders; + +namespace Drawie.Backend.Vertie.Rendering; + +public class Shader(byte[] shaderBytes, ShaderReflection reflection) +{ + public byte[] ShaderBytes { get; } = shaderBytes; + + public ShaderType ShaderType { get; } = reflection.EntryPoints.FirstOrDefault()?.Type ?? + throw new ArgumentException( + "Shader type not found in the compiled shader."); + + public string EntryName { get; } = reflection.EntryPoints.FirstOrDefault()?.Name ?? + throw new ArgumentException( + "Shader entry point not found in the compiled shader."); + + public ShaderReflection Reflection { get; } = reflection; + + public bool HasUniformBlock(string name) + { + return Reflection.Parameters.Any(x => x.Name == name); + } + + public UniformBlockLayout GetLayoutFor(string propertyGroupKey) + { + var elementVarLayout = Reflection.Parameters.FirstOrDefault(x => x.Name == propertyGroupKey); + + if (elementVarLayout?.Var == null) + { + throw new ArgumentException($"Uniform block binding '{propertyGroupKey}' not found in the compiled shader."); + } + + List propLayouts = GetPropertyLayouts(elementVarLayout.Var); + + return new UniformBlockLayout() + { + Index = elementVarLayout.Index, + Size = elementVarLayout.Size, + UniformProperties = propLayouts + }; + } + + private List GetPropertyLayouts(ShaderVar elementVar) + { + var fields = elementVar.Fields; + + if (fields == null) return new List(); + + return fields.ToList(); + } +} \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Shaders/BasicVertex.slang b/src/Drawie.Backend.Vertie/Shaders/BasicVertex.slang new file mode 100644 index 0000000..46a3771 --- /dev/null +++ b/src/Drawie.Backend.Vertie/Shaders/BasicVertex.slang @@ -0,0 +1,52 @@ +struct VertexInput +{ + float3 vPos : POSITION; + float3 vNormal : NORMAL; + float2 vTexCoords : TEXCOORD0; +}; + +struct VertexOutput +{ + float4 position : SV_Position; + float3 fNormal : TEXCOORD0; + float3 fPos : TEXCOORD1; + float2 fTexCoords : TEXCOORD2; +}; + +[[vk::binding(0, 0)]] +cbuffer Transform +{ + float4x4 uModel; + float4x4 uView; + float4x4 uProjection; +}; + +[shader("vertex")] +VertexOutput VSMain(VertexInput input) +{ + VertexOutput output; + + output.position = mul( + mul( + mul(uProjection, uView), + uModel + ), + float4(input.vPos, 1.0) + ); + + output.fPos = mul( + uModel, + float4(input.vPos, 1.0) + ).xyz; + + float3x3 model3x3 = float3x3(uModel); + + output.fNormal = mul( + float3x3(uModel), + input.vNormal + ); + + output.fTexCoords = input.vTexCoords; + + return output; +} \ No newline at end of file diff --git a/src/Drawie.Backend.Vertie/Shaders/Unlit.slang b/src/Drawie.Backend.Vertie/Shaders/Unlit.slang new file mode 100644 index 0000000..171eb35 --- /dev/null +++ b/src/Drawie.Backend.Vertie/Shaders/Unlit.slang @@ -0,0 +1,15 @@ +struct FragmentInput +{ + float3 fNormal : TEXCOORD0; + float3 fPos : TEXCOORD1; + float2 fTexCoords : TEXCOORD2; +}; + +[[vk::binding(1, 0)]] +Sampler2D uTexture; + +[shader("fragment")] +float4 PSMain(FragmentInput input) : SV_Target +{ + return uTexture.Sample(input.fTexCoords); +} \ No newline at end of file diff --git a/src/Drawie.Windowing.Browser/BrowserInterop.cs b/src/Drawie.Host.Browser/BrowserInterop.cs similarity index 96% rename from src/Drawie.Windowing.Browser/BrowserInterop.cs rename to src/Drawie.Host.Browser/BrowserInterop.cs index 349b833..56e813f 100644 --- a/src/Drawie.Windowing.Browser/BrowserInterop.cs +++ b/src/Drawie.Host.Browser/BrowserInterop.cs @@ -1,8 +1,8 @@ using Drawie.Numerics; -using Drawie.Windowing.Input; +using Drawie.Host.Input; using JSRuntime = Drawie.JSInterop.JSRuntime; -namespace Drawie.Windowing.Browser; +namespace Drawie.Host.Browser; public partial class BrowserInterop { diff --git a/src/Drawie.Host.Browser/BrowserWindow.cs b/src/Drawie.Host.Browser/BrowserWindow.cs new file mode 100644 index 0000000..872cbb1 --- /dev/null +++ b/src/Drawie.Host.Browser/BrowserWindow.cs @@ -0,0 +1,188 @@ +using Drawie.Backend.Core; +using Drawie.Backend.Core.Bridge; +using Drawie.Host; +using Drawie.Host.Browser.Input; +using Drawie.JSInterop; +using Drawie.Numerics; +using Drawie.RenderApi; +using Drawie.RenderApi.Web.Common; +using Drawie.Rendering; +using Drawie.Host.Input; + +namespace Drawie.Host.Browser; + +public class BrowserWindow(IHostViewRenderApi hostViewRenderApi) : IHost +{ + public string Name + { + get => BrowserInterop.GetTitle(); + set => BrowserInterop.SetTitle(value); + } + + public VecI Size + { + get => UsableWindowSize; + } + + public VecI UsableWindowSize => BrowserInterop.GetWindowSize(); + + public IHostViewRenderApi RenderApi { get; set; } = hostViewRenderApi; + + public InputController InputController { get; private set; } + + public bool ShowOnTop + { + get => false; + set { } + } + + public bool IsVisible + { + get => true; + set + { + throw new NotSupportedException("Browser windows cannot be hidden."); + } + } + + public event Action? Update; + public event Action? Render; + public event Action? Resize; + + public object Native => canvas; + public event Action? Loaded; + + private Texture renderTexture; + private VecI? pendingResize = null; + private HtmlCanvas canvas; + + private List layers = new List(); + private List renderStack = new List(); + private List renderContentOrder = new List(); + + public void Initialize() + { + JSRuntime.InterceptGLObject(); + var canvasObject = JSRuntime.CreateElement(); + canvas = canvasObject; + RenderApi.CreateInstance(canvasObject, UsableWindowSize); + RenderApi.FramebufferResized += FramebufferResized; + + InputController = new InputController(new [] { new BrowserKeyboard() }, [new BrowserPointer()], null); + + renderStack.Add(new RenderOrder("Init", _ => { })); + renderStack.Add(new RenderOrder("RenderContent", RenderContent)); + renderStack.Add(new RenderOrder("RenderApi", RenderApi.Render)); + + renderContentOrder.Add(new RenderContentOrder("Init", (_, _) => {})); + renderContentOrder.Add(new RenderContentOrder("RenderContent", OnRenderContentDefault)); + } + + + public void AddLayer(ILayer layer) + { + layers.Add(layer); + } + + public void SubscribeToRender(string name, string renderAfter, Action render) + { + var foundRenderAfter = renderStack.FindIndex(r => r.Name == renderAfter); + if (foundRenderAfter != -1) + { + renderStack.Insert(foundRenderAfter + 1, new RenderOrder(name, render)); + } + } + + public void SubscribeToRenderContent(string name, string renderAfter, Action render) + { + var foundRenderAfter = renderContentOrder.FindIndex(r => r.Name == renderAfter); + if (foundRenderAfter != -1) + { + renderContentOrder.Insert(foundRenderAfter + 1, new RenderContentOrder(name, render)); + } + } + + private void FramebufferResized() + { + pendingResize = UsableWindowSize; + } + + public void Show() + { + renderTexture = CreateRenderTexture(); + OnRender(0); + BrowserInterop.SubscribeWindowResize(OnWindowResized); + Loaded?.Invoke(); + + foreach (var layer in layers) + { + layer.Initialize(this); + } + } + + private void OnRender(double dt) + { + double deltaTime = dt / 1000.0; + Update?.Invoke(deltaTime); + if (pendingResize.HasValue) + { + if (pendingResize.Value != renderTexture.Size) + { + var newRenderTexture = CreateRenderTexture(); + + var oldTexture = renderTexture; + renderTexture = newRenderTexture; + + oldTexture.Dispose(); + } + + pendingResize = null; + } + + foreach (var layer in renderStack) + { + layer.Render(dt); + } + + BrowserInterop.RequestAnimationFrame(OnRender); + } + + private void RenderContent(double dt) + { + RenderApi.PrepareTextureToWrite(); + RenderingContext ctx = new RenderingContext(RenderApi.GraphicsContext); + var renderingScope = ctx.Open(); + var fbo = ctx.Edit(renderTexture); + fbo.Clear(); + + foreach (var layer in renderContentOrder) + { + layer.Render(fbo, dt); + } + + fbo.Dispose(); + renderingScope.Dispose(); + ctx.Dispose(); + } + + + private void OnRenderContentDefault(TextureFramebuffer fbo, double dt) + { + Render?.Invoke(fbo, dt); + } + + public void Close() + { + } + + private void OnWindowResized(int width, int height) + { + RenderApi?.UpdateFramebufferSize(width, height); + Resize?.Invoke(new VecI(width, height)); + } + + private Texture CreateRenderTexture() + { + return new Texture(NativeTexture.FromExisting(DrawingBackendApi.Current.CreateRenderSurface(UsableWindowSize, RenderApi.RenderTexture, SurfaceOrigin.BottomLeft))); + } +} diff --git a/src/Drawie.Windowing.Browser/BrowserWindowingPlatform.cs b/src/Drawie.Host.Browser/BrowserWindowingPlatform.cs similarity index 76% rename from src/Drawie.Windowing.Browser/BrowserWindowingPlatform.cs rename to src/Drawie.Host.Browser/BrowserWindowingPlatform.cs index dac2969..576cf4b 100644 --- a/src/Drawie.Windowing.Browser/BrowserWindowingPlatform.cs +++ b/src/Drawie.Host.Browser/BrowserWindowingPlatform.cs @@ -1,19 +1,19 @@ using Drawie.Numerics; using Drawie.RenderApi; -namespace Drawie.Windowing.Browser; +namespace Drawie.Host.Browser; public class BrowserWindowingPlatform(IRenderApi renderApi) : IWindowingPlatform { public BrowserWindow Window { get; private set; } public IRenderApi RenderApi { get; } = renderApi; - IReadOnlyCollection IWindowingPlatform.Windows => new IWindow[] { Window }; - public IWindow CreateWindow(string name) + IReadOnlyCollection IWindowingPlatform.Windows => new IHost[] { Window }; + public IHost CreateWindow(string name) { return CreateWindow(name, VecI.Zero); } - public IWindow CreateWindow(string name, VecI size) + public IHost CreateWindow(string name, VecI size) { if (Window != null) { diff --git a/src/Drawie.Windowing.Browser/Drawie.Windowing.Browser.csproj b/src/Drawie.Host.Browser/Drawie.Host.Browser.csproj similarity index 66% rename from src/Drawie.Windowing.Browser/Drawie.Windowing.Browser.csproj rename to src/Drawie.Host.Browser/Drawie.Host.Browser.csproj index dbfc715..e96a4fa 100644 --- a/src/Drawie.Windowing.Browser/Drawie.Windowing.Browser.csproj +++ b/src/Drawie.Host.Browser/Drawie.Host.Browser.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable true @@ -9,7 +9,8 @@ - + + diff --git a/src/Drawie.Host.Browser/Input/BrowserCursor.cs b/src/Drawie.Host.Browser/Input/BrowserCursor.cs new file mode 100644 index 0000000..8d37a71 --- /dev/null +++ b/src/Drawie.Host.Browser/Input/BrowserCursor.cs @@ -0,0 +1,8 @@ +using Drawie.Host.Input; + +namespace Drawie.Host.Browser.Input; + +public class BrowserCursor : ICursor +{ + public CursorState State { get; set; } +} \ No newline at end of file diff --git a/src/Drawie.Windowing.Browser/Input/BrowserKeyboard.cs b/src/Drawie.Host.Browser/Input/BrowserKeyboard.cs similarity index 95% rename from src/Drawie.Windowing.Browser/Input/BrowserKeyboard.cs rename to src/Drawie.Host.Browser/Input/BrowserKeyboard.cs index 9fdd375..7023c39 100644 --- a/src/Drawie.Windowing.Browser/Input/BrowserKeyboard.cs +++ b/src/Drawie.Host.Browser/Input/BrowserKeyboard.cs @@ -1,7 +1,7 @@ using Drawie.JSInterop; -using Drawie.Windowing.Input; +using Drawie.Host.Input; -namespace Drawie.Windowing.Browser.Input; +namespace Drawie.Host.Browser.Input; public class BrowserKeyboard : IKeyboard { diff --git a/src/Drawie.Host.Browser/Input/BrowserPointer.cs b/src/Drawie.Host.Browser/Input/BrowserPointer.cs new file mode 100644 index 0000000..512953a --- /dev/null +++ b/src/Drawie.Host.Browser/Input/BrowserPointer.cs @@ -0,0 +1,21 @@ +using Drawie.Host.Input; +using Drawie.Numerics; + +namespace Drawie.Host.Browser.Input; + +public class BrowserPointer : IPointer +{ + public event PointerPress? PointerPressed; + public event PointerRelease? PointerReleased; + public event PointerMove? PointerMoved; + public event PointerClick? PointerClicked; + public event PointerDoubleClick? PointerDoubleClicked; + public event PointerScroll? PointerScrolled; + public VecD Position { get; } + public ICursor Cursor { get; } = new BrowserCursor(); + + public bool IsButtonPressed(PointerButton button) + { + return false; + } +} \ No newline at end of file diff --git a/src/Drawie.Windowing.Browser/Properties/AssemblyInfo.cs b/src/Drawie.Host.Browser/Properties/AssemblyInfo.cs similarity index 100% rename from src/Drawie.Windowing.Browser/Properties/AssemblyInfo.cs rename to src/Drawie.Host.Browser/Properties/AssemblyInfo.cs diff --git a/src/Drawie.Windowing.Glfw/Drawie.Windowing.Glfw.csproj b/src/Drawie.Host.Glfw/Drawie.Host.Glfw.csproj similarity index 62% rename from src/Drawie.Windowing.Glfw/Drawie.Windowing.Glfw.csproj rename to src/Drawie.Host.Glfw/Drawie.Host.Glfw.csproj index 3aa63e5..4297cd9 100644 --- a/src/Drawie.Windowing.Glfw/Drawie.Windowing.Glfw.csproj +++ b/src/Drawie.Host.Glfw/Drawie.Host.Glfw.csproj @@ -1,23 +1,23 @@  - net8.0 + net10.0 enable enable - Drawie.Silk + Drawie.Host.Glfw true - - - + + + - + diff --git a/src/Drawie.Windowing.Glfw/Extensions/VectorExtensions.cs b/src/Drawie.Host.Glfw/Extensions/VectorExtensions.cs similarity index 100% rename from src/Drawie.Windowing.Glfw/Extensions/VectorExtensions.cs rename to src/Drawie.Host.Glfw/Extensions/VectorExtensions.cs diff --git a/src/Drawie.Windowing.Glfw/GlfwWindow.cs b/src/Drawie.Host.Glfw/GlfwHost.cs similarity index 51% rename from src/Drawie.Windowing.Glfw/GlfwWindow.cs rename to src/Drawie.Host.Glfw/GlfwHost.cs index f4ba9c6..1b7bbba 100644 --- a/src/Drawie.Windowing.Glfw/GlfwWindow.cs +++ b/src/Drawie.Host.Glfw/GlfwHost.cs @@ -2,19 +2,23 @@ using Drawie.Backend.Core.Bridge; using Drawie.Numerics; using Drawie.RenderApi; +using Drawie.RenderApi.Abstraction.Textures; +using Drawie.Rendering; using Drawie.Silk.Extensions; using Drawie.Silk.Input; using Drawie.Skia; -using Drawie.Windowing.Input; +using Drawie.Host; +using Drawie.Host.Input; using Silk.NET.Input; using Silk.NET.Maths; using Silk.NET.Windowing; using SkiaSharp; -using IKeyboard = Drawie.Windowing.Input.IKeyboard; +using IKeyboard = Drawie.Host.Input.IKeyboard; +using IWindow = Silk.NET.Windowing.IWindow; namespace Drawie.Silk; -public class GlfwWindow : Drawie.Windowing.IWindow +public class GlfwHost : Drawie.Host.IHost { private IWindow? window; private bool isRunning; @@ -46,47 +50,67 @@ public bool IsVisible } } - public IWindowRenderApi RenderApi { get; set; } + public IHostViewRenderApi RenderApi { get; set; } public InputController InputController { get; private set; } public bool ShowOnTop { get => window?.TopMost ?? false; - set - { - if (window != null) window.TopMost = value; - } + set => window?.TopMost = value; } + public object Native => window; + public event Action? Loaded; + public event Action Update; - public event Action Render; + public event Action Render; + public event Action? Resize; private SKSurface? surface; private Texture renderTexture; - private GRContext context; + private ITexture nativeTexture; private bool initialized; - public GlfwWindow(string name, VecI size, IWindowRenderApi renderApi) + private List layers = new List(); + private List renderStack = new List(); + private List renderContentStack = new List(); + + public GlfwHost(string name, VecI size, IHostViewRenderApi renderApi) { window = Window.Create(WindowOptions.Default with { Title = name, Size = size.ToVector2DInt(), - API = renderApi is IVulkanWindowRenderApi ? GraphicsAPI.DefaultVulkan : GraphicsAPI.Default + API = renderApi is IVulkanHostViewRenderApi ? GraphicsAPI.DefaultVulkan : GraphicsAPI.Default, + Samples = 4 }); + + window.Load += () => Loaded?.Invoke(); RenderApi = renderApi; + + renderStack.Add(new RenderOrder("Init", _ => { })); + renderStack.Add(new RenderOrder("Render", RenderContent)); + renderStack.Add(new RenderOrder("RenderApi", RenderApi.Render)); + + renderContentStack.Add(new RenderContentOrder("Init", (_, _) => { })); + renderContentStack.Add(new RenderContentOrder("RenderContent", DefaultRenderContent)); + } + + private void DefaultRenderContent(TextureFramebuffer fbo, double dt) + { + Render?.Invoke(fbo, dt); } + public void Initialize() { if (initialized) return; window.Initialize(); - InitInput(); - if (RenderApi is IVulkanWindowRenderApi) + if (RenderApi is IVulkanHostViewRenderApi) { if (window.VkSurface == null) { @@ -96,7 +120,7 @@ public void Initialize() GlfwVulkanContextInfo info = new GlfwVulkanContextInfo(window.VkSurface!); RenderApi.CreateInstance(info, window.Size.ToVecI()); } - else if (RenderApi is IOpenGlWindowRenderApi) + else if (RenderApi is IOpenGlHostViewRenderApi) { RenderApi.CreateInstance(window.GLContext, window.Size.ToVecI()); } @@ -105,12 +129,28 @@ public void Initialize() RenderApi.CreateInstance(window.Native, window.Size.ToVecI()); } + for (int i = 0; i < layers.Count; i++) + { + if (!layers[i].IsRenderApiSupported(RenderApi)) + { + Console.WriteLine($"Layer {layers[i]} is not supported on this render api. Skipping..."); + layers.RemoveAt(i); + i--; + } + } + + foreach (var layer in layers) + { + layer.Initialize(this); + } + initialized = true; } private void InitInput() { var input = window.CreateInput(); + GlfwKeyboard[] keyboards = new GlfwKeyboard[input.Keyboards.Count]; for (var i = 0; i < input.Keyboards.Count; i++) { @@ -127,7 +167,7 @@ private void InitInput() pointers[i] = new GlfwPointer(pointer); } - InputController = new InputController(keyboards, pointers); + InputController = new InputController(keyboards, pointers, input); } public void Show() @@ -145,7 +185,6 @@ public void Show() CreateRenderTarget(window.FramebufferSize.ToVecI(), RenderApi.RenderTexture); window.Render += OnRender; - window.Render += RenderApi.Render; window.Update += OnUpdate; isRunning = true; @@ -156,7 +195,6 @@ public void Show() private void RenderApiOnFramebufferResized() { renderTexture.Dispose(); - renderTexture = null!; surface = null!; CreateRenderTarget(window!.FramebufferSize.ToVecI(), RenderApi.RenderTexture); @@ -164,14 +202,16 @@ private void RenderApiOnFramebufferResized() private void CreateRenderTarget(VecI size, ITexture nativeRenderTexture) { - var drawingSurface = - DrawingBackendApi.Current.CreateRenderSurface(size, nativeRenderTexture, SurfaceOrigin.TopLeft); - renderTexture = Texture.FromExisting(drawingSurface); + nativeTexture = nativeRenderTexture; + renderTexture = Texture.FromExisting(DrawingBackendApi.Current.CreateRenderSurface(size, + nativeRenderTexture, + RenderApi is IVulkanHostViewRenderApi ? SurfaceOrigin.TopLeft : SurfaceOrigin.BottomLeft)); } private void WindowOnFramebufferResize(Vector2D newSize) { RenderApi.UpdateFramebufferSize(newSize.X, newSize.Y); + Resize?.Invoke(newSize.ToVecI()); } private void OnUpdate(double dt) @@ -180,21 +220,66 @@ private void OnUpdate(double dt) } private void OnRender(double dt) + { + foreach (var layer in renderStack) + { + layer.Render(dt); + } + } + + private void RenderContent(double dt) { RenderApi.PrepareTextureToWrite(); - renderTexture.DrawingSurface?.Canvas.Clear(); - Render?.Invoke(renderTexture, dt); - renderTexture.DrawingSurface?.Flush(); + RenderingContext ctx = new RenderingContext(RenderApi.GraphicsContext); + using var renderingScope = ctx.Open(); + using var fbo = ctx.Edit(renderTexture); + fbo.Clear(); + foreach (var layer in renderContentStack) + { + layer.Render(fbo, dt); + } + + DrawingBackendApi.Current.Flush(); } public void Close() { window.Update -= OnUpdate; window.Render -= OnRender; - renderTexture.Dispose(); RenderApi.DestroyInstance(); window?.Close(); window?.Dispose(); } -} + + public void AddLayer(ILayer layer) + { + layers.Add(layer); + } + + public void SubscribeToRender(string name, string renderAfter, Action render) + { + var foundRenderAfter = renderStack.FindIndex(r => r.Name == renderAfter); + if (foundRenderAfter != -1) + { + renderStack.Insert(foundRenderAfter + 1, new RenderOrder(name, render)); + } + else + { + renderStack.Add(new RenderOrder(name, render)); + } + } + + public void SubscribeToRenderContent(string name, string renderAfter, Action render) + { + var foundRenderAfter = renderContentStack.FindIndex(r => r.Name == renderAfter); + if (foundRenderAfter != -1) + { + renderContentStack.Insert(foundRenderAfter + 1, new RenderContentOrder(name, render)); + } + else + { + renderContentStack.Add(new RenderContentOrder(name, render)); + } + } +} \ No newline at end of file diff --git a/src/Drawie.Windowing.Glfw/GlfwVulkanContextInfo.cs b/src/Drawie.Host.Glfw/GlfwVulkanContextInfo.cs similarity index 100% rename from src/Drawie.Windowing.Glfw/GlfwVulkanContextInfo.cs rename to src/Drawie.Host.Glfw/GlfwVulkanContextInfo.cs diff --git a/src/Drawie.Host.Glfw/GlfwWindowingPlatform.cs b/src/Drawie.Host.Glfw/GlfwWindowingPlatform.cs new file mode 100644 index 0000000..6c6fcc7 --- /dev/null +++ b/src/Drawie.Host.Glfw/GlfwWindowingPlatform.cs @@ -0,0 +1,37 @@ +using Drawie.Numerics; +using Drawie.RenderApi; +using Drawie.Host; +using Drawie.Host.Input; +using Silk.NET.Input; + +namespace Drawie.Silk; + +public class GlfwWindowingPlatform : IWindowingPlatform +{ + private readonly List _windows = new(); + + public IReadOnlyCollection Windows => _windows; + public IRenderApi RenderApi { get; } + + public GlfwWindowingPlatform(IRenderApi renderApi) + { + RenderApi = renderApi; + } + + public IHost CreateWindow(string name) + { + return CreateWindow(name, VecI.Zero); + } + + public IHost CreateWindow(string name, VecI size) + { + GlfwHost host = new(name, size, RenderApi.CreateWindowRenderApi()); + _windows.Add(host); + return host; + } + + public override string ToString() + { + return "Glfw"; + } +} \ No newline at end of file diff --git a/src/Drawie.Host.Glfw/Input/GlfwCursor.cs b/src/Drawie.Host.Glfw/Input/GlfwCursor.cs new file mode 100644 index 0000000..d4ef37e --- /dev/null +++ b/src/Drawie.Host.Glfw/Input/GlfwCursor.cs @@ -0,0 +1,31 @@ +using Drawie.Host.Input; +using Silk.NET.Input; +using ICursor = Drawie.Host.Input.ICursor; + +namespace Drawie.Silk.Input; + +public class GlfwCursor : ICursor +{ + public global::Silk.NET.Input.ICursor SilkCursor { get; } + + public GlfwCursor(global::Silk.NET.Input.ICursor silkMouseCursor) + { + SilkCursor = silkMouseCursor; + } + + public CursorState State + { + get => ToCursorState(SilkCursor.CursorMode); + set => SilkCursor.CursorMode = ToCursorMode(value); + } + + private CursorMode ToCursorMode(CursorState value) + { + return (CursorMode)value; + } + + private CursorState ToCursorState(CursorMode silkCursorCursorMode) + { + return (CursorState)silkCursorCursorMode; + } +} \ No newline at end of file diff --git a/src/Drawie.Windowing.Glfw/Input/GlfwKeyboard.cs b/src/Drawie.Host.Glfw/Input/GlfwKeyboard.cs similarity index 69% rename from src/Drawie.Windowing.Glfw/Input/GlfwKeyboard.cs rename to src/Drawie.Host.Glfw/Input/GlfwKeyboard.cs index 568a0b3..629a829 100644 --- a/src/Drawie.Windowing.Glfw/Input/GlfwKeyboard.cs +++ b/src/Drawie.Host.Glfw/Input/GlfwKeyboard.cs @@ -1,11 +1,11 @@ -using Drawie.Windowing.Input; +using Drawie.Host.Input; using Silk.NET.Input; using IKeyboard = Silk.NET.Input.IKeyboard; using Key = Silk.NET.Input.Key; namespace Drawie.Silk.Input; -public class GlfwKeyboard : Drawie.Windowing.Input.IKeyboard +public class GlfwKeyboard : Drawie.Host.Input.IKeyboard { public event KeyPress? KeyPressed; @@ -19,10 +19,10 @@ public GlfwKeyboard(IKeyboard silkKeyboard) private void OnKeyDown(IKeyboard keyboard, Key key, int keyCode) { - KeyPressed?.Invoke(this, (Drawie.Windowing.Input.Key) key, keyCode); + KeyPressed?.Invoke(this, (Drawie.Host.Input.Key) key, keyCode); } - public bool IsKeyPressed(Windowing.Input.Key key) + public bool IsKeyPressed(Host.Input.Key key) { return silkKeyboard.IsKeyPressed((Key)key); } diff --git a/src/Drawie.Windowing.Glfw/Input/GlfwPointer.cs b/src/Drawie.Host.Glfw/Input/GlfwPointer.cs similarity index 90% rename from src/Drawie.Windowing.Glfw/Input/GlfwPointer.cs rename to src/Drawie.Host.Glfw/Input/GlfwPointer.cs index 759bf6d..52d4c33 100644 --- a/src/Drawie.Windowing.Glfw/Input/GlfwPointer.cs +++ b/src/Drawie.Host.Glfw/Input/GlfwPointer.cs @@ -1,11 +1,12 @@ using System.Numerics; using Drawie.Numerics; -using Drawie.Windowing.Input; +using Drawie.Host.Input; using Silk.NET.Input; +using ICursor = Drawie.Host.Input.ICursor; namespace Drawie.Silk.Input; -public class GlfwPointer : Drawie.Windowing.Input.IPointer +public class GlfwPointer : Drawie.Host.Input.IPointer { public IMouse SilkMouse { get; } public event PointerPress? PointerPressed; @@ -15,6 +16,7 @@ public class GlfwPointer : Drawie.Windowing.Input.IPointer public event PointerDoubleClick? PointerDoubleClicked; public event PointerScroll? PointerScrolled; public VecD Position => new VecD(SilkMouse.Position.X, SilkMouse.Position.Y); + public ICursor Cursor { get; } public GlfwPointer(IMouse silkMouse) { @@ -25,6 +27,7 @@ public GlfwPointer(IMouse silkMouse) silkMouse.Click += OnMouseClick; silkMouse.DoubleClick += OnMouseDoubleClick; silkMouse.Scroll += OnMouseScroll; + Cursor = new GlfwCursor(silkMouse.Cursor); } private void OnMouseDown(IMouse mouse, MouseButton button) diff --git a/src/Drawie.Windowing/Drawie.Windowing.csproj b/src/Drawie.Host/Drawie.Host.csproj similarity index 77% rename from src/Drawie.Windowing/Drawie.Windowing.csproj rename to src/Drawie.Host/Drawie.Host.csproj index b0dcde8..98bd6d0 100644 --- a/src/Drawie.Windowing/Drawie.Windowing.csproj +++ b/src/Drawie.Host/Drawie.Host.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable @@ -10,6 +10,7 @@ + diff --git a/src/Drawie.Host/IHost.cs b/src/Drawie.Host/IHost.cs new file mode 100644 index 0000000..5850af9 --- /dev/null +++ b/src/Drawie.Host/IHost.cs @@ -0,0 +1,33 @@ +using Drawie.Backend.Core; +using Drawie.Host.Input; +using Drawie.Numerics; +using Drawie.RenderApi; +using Drawie.Rendering; + +namespace Drawie.Host; + +public interface IHost +{ + public string Name { get; set; } + public VecI Size { get; } + + public IHostViewRenderApi RenderApi { get; set; } + + public InputController InputController { get; } + public bool ShowOnTop { get; set; } + + public bool IsVisible { get; set; } + + public event Action Update; + public event Action Render; + public event Action Resize; + + public void Initialize(); + public void Show(); + public void Close(); + public void AddLayer(ILayer layer); + public object Native { get; } + public event Action Loaded; + public void SubscribeToRender(string name, string renderAfter, Action render); + public void SubscribeToRenderContent(string name, string renderAfter, Action render); +} diff --git a/src/Drawie.Host/IHostPlatform.cs b/src/Drawie.Host/IHostPlatform.cs new file mode 100644 index 0000000..5d63573 --- /dev/null +++ b/src/Drawie.Host/IHostPlatform.cs @@ -0,0 +1,13 @@ +using Drawie.Numerics; +using Drawie.RenderApi; +using Drawie.Host.Input; + +namespace Drawie.Host; + +public interface IWindowingPlatform +{ + public IRenderApi RenderApi { get; } + public IReadOnlyCollection Windows { get; } + public IHost CreateWindow(string name); + public IHost CreateWindow(string name, VecI size); +} \ No newline at end of file diff --git a/src/Drawie.Host/ILayer.cs b/src/Drawie.Host/ILayer.cs new file mode 100644 index 0000000..a5a5025 --- /dev/null +++ b/src/Drawie.Host/ILayer.cs @@ -0,0 +1,10 @@ +using Drawie.RenderApi; +using Drawie.Rendering; + +namespace Drawie.Host; + +public interface ILayer +{ + public bool IsRenderApiSupported(IHostViewRenderApi api); + void Initialize(IHost host); +} \ No newline at end of file diff --git a/src/Drawie.Windowing/Input/IKeyboard.cs b/src/Drawie.Host/Input/IKeyboard.cs similarity index 83% rename from src/Drawie.Windowing/Input/IKeyboard.cs rename to src/Drawie.Host/Input/IKeyboard.cs index 9695dad..42d76a3 100644 --- a/src/Drawie.Windowing/Input/IKeyboard.cs +++ b/src/Drawie.Host/Input/IKeyboard.cs @@ -1,4 +1,4 @@ -namespace Drawie.Windowing.Input; +namespace Drawie.Host.Input; public delegate void KeyPress(IKeyboard keyboard, Key key, int keyCode); public interface IKeyboard diff --git a/src/Drawie.Windowing/Input/IPointer.cs b/src/Drawie.Host/Input/IPointer.cs similarity index 91% rename from src/Drawie.Windowing/Input/IPointer.cs rename to src/Drawie.Host/Input/IPointer.cs index b0f452e..4a5b913 100644 --- a/src/Drawie.Windowing/Input/IPointer.cs +++ b/src/Drawie.Host/Input/IPointer.cs @@ -1,6 +1,6 @@ using Drawie.Numerics; -namespace Drawie.Windowing.Input; +namespace Drawie.Host.Input; public delegate void PointerPress(IPointer pointer, PointerButton button, VecD position); public delegate void PointerRelease(IPointer pointer, PointerButton button, VecD position); @@ -18,10 +18,24 @@ public interface IPointer public event PointerDoubleClick PointerDoubleClicked; public event PointerScroll PointerScrolled; public VecD Position { get; } + ICursor Cursor { get; } public bool IsButtonPressed(PointerButton button); } +public interface ICursor +{ + public CursorState State { get; set; } +} + +public enum CursorState +{ + Normal, + Hidden, + Disabled, + Raw +} + public enum PointerButton { /// diff --git a/src/Drawie.Windowing/Input/InputController.cs b/src/Drawie.Host/Input/InputController.cs similarity index 53% rename from src/Drawie.Windowing/Input/InputController.cs rename to src/Drawie.Host/Input/InputController.cs index 55bfe89..ec26555 100644 --- a/src/Drawie.Windowing/Input/InputController.cs +++ b/src/Drawie.Host/Input/InputController.cs @@ -1,16 +1,19 @@ -namespace Drawie.Windowing.Input; +namespace Drawie.Host.Input; public class InputController { public IKeyboard? PrimaryKeyboard => Keyboards.FirstOrDefault(); public IPointer? PrimaryPointer => Pointers.FirstOrDefault(); - public IReadOnlyCollection Keyboards { get; } + public IReadOnlyList Keyboards { get; } - public IReadOnlyCollection Pointers { get; } + public IReadOnlyList Pointers { get; } - public InputController(IEnumerable keyboards, IEnumerable pointers) + public object NativeInputController { get; } + + public InputController(IEnumerable keyboards, IEnumerable pointers, object nativeInputController) { Keyboards = keyboards.ToList().AsReadOnly(); Pointers = pointers.ToList().AsReadOnly(); + NativeInputController = nativeInputController; } } \ No newline at end of file diff --git a/src/Drawie.Windowing/Input/Key.cs b/src/Drawie.Host/Input/Key.cs similarity index 99% rename from src/Drawie.Windowing/Input/Key.cs rename to src/Drawie.Host/Input/Key.cs index 8a3396e..4479fef 100644 --- a/src/Drawie.Windowing/Input/Key.cs +++ b/src/Drawie.Host/Input/Key.cs @@ -1,4 +1,4 @@ -namespace Drawie.Windowing.Input; +namespace Drawie.Host.Input; // Taken from https://github.com/dotnet/Silk.NET/blob/14ee3f16a1c1b7c5f561c307b956f769c5e89474/src/Input/Silk.NET.Input.Common/Enums/Key.cs // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. diff --git a/src/Drawie.Windowing/Input/Modifiers.cs b/src/Drawie.Host/Input/Modifiers.cs similarity index 74% rename from src/Drawie.Windowing/Input/Modifiers.cs rename to src/Drawie.Host/Input/Modifiers.cs index f2830c8..a77f714 100644 --- a/src/Drawie.Windowing/Input/Modifiers.cs +++ b/src/Drawie.Host/Input/Modifiers.cs @@ -1,4 +1,4 @@ -namespace Drawie.Windowing.Input; +namespace Drawie.Host.Input; [Flags] public enum Modifiers diff --git a/src/Drawie.Host/RenderOrder.cs b/src/Drawie.Host/RenderOrder.cs new file mode 100644 index 0000000..507cbfe --- /dev/null +++ b/src/Drawie.Host/RenderOrder.cs @@ -0,0 +1,27 @@ +using Drawie.Rendering; + +namespace Drawie.Host; + +public class RenderOrder +{ + public string Name { get; } + public Action Render { get; } + + public RenderOrder(string name, Action render) + { + Name = name; + Render = render; + } +} + +public class RenderContentOrder +{ + public string Name { get; } + public Action Render { get; } + + public RenderContentOrder(string name, Action render) + { + Name = name; + Render = render; + } +} diff --git a/src/Drawie.Interop.Avalonia.Core/AvaloniaRenderingDispatcher.cs b/src/Drawie.Interop.Avalonia.Core/AvaloniaRenderingDispatcher.cs index 24080d0..69df860 100644 --- a/src/Drawie.Interop.Avalonia.Core/AvaloniaRenderingDispatcher.cs +++ b/src/Drawie.Interop.Avalonia.Core/AvaloniaRenderingDispatcher.cs @@ -24,6 +24,11 @@ public class AvaloniaRenderingDispatcher : IRenderingDispatcher }); }; + public void RenderApiReady() + { + + } + public async Task InvokeAsync(Func func) { return await Dispatcher.UIThread.InvokeAsync(() => diff --git a/src/Drawie.Interop.Avalonia.Core/Controls/DrawieTextureControl.cs b/src/Drawie.Interop.Avalonia.Core/Controls/DrawieTextureControl.cs index be45a7a..e2512b5 100644 --- a/src/Drawie.Interop.Avalonia.Core/Controls/DrawieTextureControl.cs +++ b/src/Drawie.Interop.Avalonia.Core/Controls/DrawieTextureControl.cs @@ -55,9 +55,9 @@ static DrawieTextureControl() { x.QueueNextFrame(); if (e.OldValue is Texture oldTexture && x.RepaintOnChanged) - oldTexture.Changed -= x.Texture_Changed; + oldTexture.Changed -= x.TextureChanged; if (e.NewValue is Texture newTexture && x.RepaintOnChanged) - newTexture.Changed += x.Texture_Changed; + newTexture.Changed += x.TextureChanged; }); SamplingOptionsProperty.Changed.AddClassHandler((x,e) => x.QueueNextFrame()); StretchProperty.Changed.AddClassHandler((x,e) => x.QueueNextFrame()); @@ -67,12 +67,12 @@ static DrawieTextureControl() { x.QueueNextFrame(); if(x.Texture != null) - x.Texture.Changed += x.Texture_Changed; + x.Texture.Changed += x.TextureChanged; } else { if(x.Texture != null) - x.Texture.Changed -= x.Texture_Changed; + x.Texture.Changed -= x.TextureChanged; } }); } @@ -118,7 +118,7 @@ protected override Size ArrangeOverride(Size finalSize) return new Size(); } - private void Texture_Changed(RectD? changedRect) + private void TextureChanged(RectD? changedRect) { QueueNextFrame(); } diff --git a/src/Drawie.Interop.Avalonia.Core/Controls/InteropControl.cs b/src/Drawie.Interop.Avalonia.Core/Controls/InteropControl.cs index 7703bf6..708c612 100644 --- a/src/Drawie.Interop.Avalonia.Core/Controls/InteropControl.cs +++ b/src/Drawie.Interop.Avalonia.Core/Controls/InteropControl.cs @@ -124,7 +124,7 @@ public override void Render(DrawingContext context) void UpdateFrame() { updateQueued = false; - var root = this.GetVisualRoot(); + var root = this.VisualRoot; if (root == null) { return; diff --git a/src/Drawie.Interop.Avalonia.Core/Drawie.Interop.Avalonia.Core.csproj b/src/Drawie.Interop.Avalonia.Core/Drawie.Interop.Avalonia.Core.csproj index ae66f4e..9ac5a11 100644 --- a/src/Drawie.Interop.Avalonia.Core/Drawie.Interop.Avalonia.Core.csproj +++ b/src/Drawie.Interop.Avalonia.Core/Drawie.Interop.Avalonia.Core.csproj @@ -1,14 +1,14 @@  - net8.0 + net10.0 enable enable Drawie.Interop.Avalonia.Core - + diff --git a/src/Drawie.Interop.Avalonia.Core/RenderApiResources.cs b/src/Drawie.Interop.Avalonia.Core/RenderApiResources.cs index 5730cfb..90d3410 100644 --- a/src/Drawie.Interop.Avalonia.Core/RenderApiResources.cs +++ b/src/Drawie.Interop.Avalonia.Core/RenderApiResources.cs @@ -1,6 +1,7 @@ using Avalonia; using Avalonia.Rendering.Composition; using Drawie.RenderApi; +using Drawie.RenderApi.Abstraction.Textures; namespace Drawie.Interop.Avalonia.Core; diff --git a/src/Drawie.Interop.Avalonia.OpenGl/Drawie.Interop.Avalonia.OpenGl.csproj b/src/Drawie.Interop.Avalonia.OpenGl/Drawie.Interop.Avalonia.OpenGl.csproj index 358fb47..5eca195 100644 --- a/src/Drawie.Interop.Avalonia.OpenGl/Drawie.Interop.Avalonia.OpenGl.csproj +++ b/src/Drawie.Interop.Avalonia.OpenGl/Drawie.Interop.Avalonia.OpenGl.csproj @@ -1,9 +1,11 @@  - net8.0 + net10.0 enable enable + true + true diff --git a/src/Drawie.Interop.Avalonia.OpenGl/OpenGlInteropContext.cs b/src/Drawie.Interop.Avalonia.OpenGl/OpenGlInteropContext.cs index 3f28125..0e77dae 100644 --- a/src/Drawie.Interop.Avalonia.OpenGl/OpenGlInteropContext.cs +++ b/src/Drawie.Interop.Avalonia.OpenGl/OpenGlInteropContext.cs @@ -10,10 +10,13 @@ namespace Drawie.Interop.Avalonia.OpenGl; public class OpenGlInteropContext : IOpenGlContext, IDrawieInteropContext { public bool IsGlViaAngle { get; } + public static OpenGlInteropContext? Current { get; private set; } public IGlContext Context { get; } + private Dictionary managedTextures = new Dictionary(); + public OpenGlInteropContext(IGlContext context, bool isGlViaAngle) { Context = context; @@ -37,6 +40,23 @@ public RenderApiResources CreateResources(CompositionDrawingSurface surface, ICo return new OpenGlRenderApiResources(surface, interop); } + public void AddManagedTexture(IOpenGlTexture texture) + { + managedTextures[texture.TextureId] = texture; + } + + public IOpenGlTexture? GetManagedTexture(ulong textureId) + { + managedTextures.TryGetValue(textureId, out var texture); + return texture; + + } + + public void RemoveManagedTexture(ulong textureId) + { + managedTextures.Remove(textureId); + } + public GpuDiagnostics GetGpuDiagnostics() { Dictionary details = new Dictionary(); diff --git a/src/Drawie.Interop.Avalonia.OpenGl/OpenGlRenderApiResources.cs b/src/Drawie.Interop.Avalonia.OpenGl/OpenGlRenderApiResources.cs index f5c6548..37277e4 100644 --- a/src/Drawie.Interop.Avalonia.OpenGl/OpenGlRenderApiResources.cs +++ b/src/Drawie.Interop.Avalonia.OpenGl/OpenGlRenderApiResources.cs @@ -4,6 +4,7 @@ using Drawie.Backend.Core.Bridge; using Drawie.Interop.Avalonia.Core; using Drawie.RenderApi; +using Drawie.RenderApi.Abstraction.Textures; using Drawie.RenderApi.OpenGL; using Silk.NET.OpenGL; @@ -42,7 +43,7 @@ public OpenGlRenderApiResources(CompositionDrawingSurface surface, ICompositionG fbo = Context.GlInterface.GenFramebuffer(); } - fboTexture = new OpenGlTexture((uint)fbo, null); + fboTexture = new OpenGlTexture((uint)fbo, GL.GetApi(s => Context.GlInterface.GetProcAddress(s)), 0, 0); } public override async ValueTask DisposeAsync() diff --git a/src/Drawie.Interop.Avalonia.OpenGl/OpenGlSwapchain.cs b/src/Drawie.Interop.Avalonia.OpenGl/OpenGlSwapchain.cs index 6b2b690..01bba27 100644 --- a/src/Drawie.Interop.Avalonia.OpenGl/OpenGlSwapchain.cs +++ b/src/Drawie.Interop.Avalonia.OpenGl/OpenGlSwapchain.cs @@ -88,7 +88,7 @@ public async ValueTask DisposeAsync() _texture.Dispose(); } - public uint TextureId => (uint)_texture.TextureId; + public ulong TextureId => (ulong)_texture.TextureId; public int InternalFormat => _texture.InternalFormat; public PixelSize Size => new(_texture.Properties.Width, _texture.Properties.Height); public Task? LastPresent => _lastPresent; @@ -142,7 +142,7 @@ public async ValueTask DisposeAsync() _texture.Dispose(); } - public uint TextureId => (uint)_texture.TextureId; + public ulong TextureId => (ulong)_texture.TextureId; public int InternalFormat => _texture.InternalFormat; public PixelSize Size => _texture.Size; public Task? LastPresent { get; private set; } diff --git a/src/Drawie.Interop.Avalonia.Vulkan/AvaloniaInteropContextInfo.cs b/src/Drawie.Interop.Avalonia.Vulkan/AvaloniaInteropContextInfo.cs index cac8e1a..db308fc 100644 --- a/src/Drawie.Interop.Avalonia.Vulkan/AvaloniaInteropContextInfo.cs +++ b/src/Drawie.Interop.Avalonia.Vulkan/AvaloniaInteropContextInfo.cs @@ -1,4 +1,5 @@ -using Avalonia.Vulkan; +using System.Runtime.InteropServices; +using Avalonia.Vulkan; using Drawie.RenderApi; namespace Drawie.Interop.Avalonia.Vulkan; @@ -14,7 +15,10 @@ public string[] GetInstanceExtensions() "VK_KHR_external_semaphore_capabilities", "VK_EXT_debug_utils" }; - + + if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + enabledExtensions.Add("VK_KHR_portability_enumeration"); + return enabledExtensions.ToArray(); } @@ -24,4 +28,4 @@ public ulong GetSurfaceHandle(IntPtr instanceHandle) } public bool HasSurface => false; -} \ No newline at end of file +} diff --git a/src/Drawie.Interop.Avalonia.Vulkan/D3DMemoryHelper.cs b/src/Drawie.Interop.Avalonia.Vulkan/D3DMemoryHelper.cs new file mode 100644 index 0000000..ccfa675 --- /dev/null +++ b/src/Drawie.Interop.Avalonia.Vulkan/D3DMemoryHelper.cs @@ -0,0 +1,96 @@ +using Avalonia; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D11; +using Silk.NET.DXGI; +using VulkanFormat = Silk.NET.Vulkan.Format; +using static Silk.NET.Core.Native.SilkMarshal; + +namespace Drawie.Interop.Avalonia.Vulkan; + +public class D3DMemoryHelper +{ + private const int DxgiErrorNotFound = unchecked((int)0x887A0002); + + public static unsafe ComPtr CreateDeviceByLuid(Luid luid) + { + using var dxgi = new DXGI(DXGI.CreateDefaultContext(["DXGI.dll"])); + using var d3d11 = new D3D11(D3D11.CreateDefaultContext(["d3d11.dll"])); + using var factory = dxgi.CreateDXGIFactory1(); + using var adapter = GetAdapterByLuid(factory, luid); + + const int featureLevelCount = 8; + var featureLevels = stackalloc D3DFeatureLevel[featureLevelCount] + { + D3DFeatureLevel.Level121, + D3DFeatureLevel.Level120, + D3DFeatureLevel.Level111, + D3DFeatureLevel.Level110, + D3DFeatureLevel.Level100, + D3DFeatureLevel.Level93, + D3DFeatureLevel.Level92, + D3DFeatureLevel.Level91 + }; + + ComPtr device = default; + ComPtr context = default; + D3DFeatureLevel actualFeatureLevel; + ThrowHResult(d3d11.CreateDevice( + adapter, + D3DDriverType.Unknown, + IntPtr.Zero, + 0u, + featureLevels, + featureLevelCount, + D3D11.SdkVersion, + device.GetAddressOf(), + &actualFeatureLevel, + context.GetAddressOf())); + + return device; + } + + private static unsafe ComPtr GetAdapterByLuid(ComPtr factory, Luid luid) + { + var index = 0u; + ComPtr adapter = default; + + while (factory.EnumAdapters(index, adapter.GetAddressOf()) != DxgiErrorNotFound) + { + AdapterDesc adapterDesc; + if (adapter.GetDesc(&adapterDesc) == 0 & AreLuidsEqual(adapterDesc.AdapterLuid, luid)) + return adapter; + + adapter.Dispose(); + ++index; + } + + throw new ArgumentException("Device with the corresponding LUID not found"); + } + + public static unsafe ComPtr CreateMemoryHandle(ComPtr device, PixelSize size, VulkanFormat format) + { + if (format != VulkanFormat.R8G8B8A8Unorm) + throw new ArgumentException("Not supported format"); + + ComPtr texture = default; + var textureDesc = new Texture2DDesc + { + Format = Format.FormatR8G8B8A8Unorm, + Width = (uint)size.Width, + Height = (uint)size.Height, + ArraySize = 1, + MipLevels = 1, + SampleDesc = new SampleDesc(1, 0), + Usage = Usage.Default, + BindFlags = (uint)(BindFlag.RenderTarget | BindFlag.ShaderResource), + CPUAccessFlags = 0, + MiscFlags = (uint)(ResourceMiscFlag.SharedKeyedmutex | ResourceMiscFlag.SharedNthandle) + }; + ThrowHResult(device.CreateTexture2D(&textureDesc, (SubresourceData*)null, texture.GetAddressOf())); + + return texture; + } + + private static bool AreLuidsEqual(Luid x, Luid y) + => x.Low == y.Low && x.High == y.High; +} diff --git a/src/Drawie.Interop.Avalonia.Vulkan/Drawie.Interop.Avalonia.Vulkan.csproj b/src/Drawie.Interop.Avalonia.Vulkan/Drawie.Interop.Avalonia.Vulkan.csproj index b9367bc..b6591dc 100644 --- a/src/Drawie.Interop.Avalonia.Vulkan/Drawie.Interop.Avalonia.Vulkan.csproj +++ b/src/Drawie.Interop.Avalonia.Vulkan/Drawie.Interop.Avalonia.Vulkan.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable true @@ -16,13 +16,8 @@ - - - ..\Drawie.AvaloniaInterop\bin\Debug\net8.0\Avalonia.Vulkan.dll - - - + diff --git a/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanCommandBuffer.cs b/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanCommandBuffer.cs index 658a9b6..98a58fc 100644 --- a/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanCommandBuffer.cs +++ b/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanCommandBuffer.cs @@ -108,19 +108,19 @@ internal unsafe VulkanCommandBuffer(Silk.NET.Vulkan.Vk api, Device device, Queue var fenceCreateInfo = new FenceCreateInfo() { - SType = StructureType.FenceCreateInfo, - Flags = FenceCreateFlags.SignaledBit + SType = StructureType.FenceCreateInfo, Flags = FenceCreateFlags.SignaledBit }; - api.CreateFence(device, fenceCreateInfo, null, out _fence); + api.CreateFence(device, in fenceCreateInfo, null, out _fence); } public unsafe void Dispose() { - _api.WaitForFences(_device, 1, _fence, true, ulong.MaxValue); + _api.WaitForFences(_device, 1, in _fence, true, ulong.MaxValue); lock (_commandBufferPool._lock) { - _api.FreeCommandBuffers(_device, _commandBufferPool._commandPool, 1, InternalHandle); + var handle = InternalHandle; + _api.FreeCommandBuffers(_device, _commandBufferPool._commandPool, 1, in handle); } _api.DestroyFence(_device, _fence, null); @@ -134,11 +134,10 @@ public void BeginRecording() var beginInfo = new CommandBufferBeginInfo { - SType = StructureType.CommandBufferBeginInfo, - Flags = CommandBufferUsageFlags.OneTimeSubmitBit + SType = StructureType.CommandBufferBeginInfo, Flags = CommandBufferUsageFlags.OneTimeSubmitBit }; - _api.BeginCommandBuffer(InternalHandle, beginInfo); + _api.BeginCommandBuffer(InternalHandle, in beginInfo); } } @@ -169,7 +168,8 @@ public unsafe void Submit( ReadOnlySpan waitDstStageMask = default, ReadOnlySpan signalSemaphores = default, Fence? fence = null, - KeyedMutexSubmitInfo? keyedMutex = null) + KeyedMutexSubmitInfo? keyedMutex = null, + IntPtr pNext = default) { EndRecording(); @@ -191,7 +191,8 @@ public unsafe void Submit( PReleaseKeys = &releaseKey, PAcquireSyncs = &devMem, PReleaseSyncs = &devMem, - PAcquireTimeouts = &timeout + PAcquireTimeouts = &timeout, + PNext = (void*)pNext }; fixed (Semaphore* pWaitSemaphores = waitSemaphores, pSignalSemaphores = signalSemaphores) @@ -201,7 +202,7 @@ public unsafe void Submit( var commandBuffer = InternalHandle; var submitInfo = new SubmitInfo { - PNext = keyedMutex != null ? &mutex : null, + PNext = keyedMutex != null ? &mutex : (void*)pNext, SType = StructureType.SubmitInfo, WaitSemaphoreCount = waitSemaphores != null ? (uint)waitSemaphores.Length : 0, PWaitSemaphores = pWaitSemaphores, @@ -212,13 +213,14 @@ public unsafe void Submit( PSignalSemaphores = pSignalSemaphores, }; - _api.ResetFences(_device, 1, fence.Value); + var fenceValue = fence.Value; + _api.ResetFences(_device, 1, in fenceValue); - _api.QueueSubmit(_queue, 1, submitInfo, fence.Value); + _api.QueueSubmit(_queue, 1, in submitInfo, fenceValue); } } _commandBufferPool.DisposeCommandBuffer(this); } } -} \ No newline at end of file +} diff --git a/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanContent.cs b/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanContent.cs index cc238f5..4c42d8a 100644 --- a/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanContent.cs +++ b/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanContent.cs @@ -1,5 +1,6 @@ using Avalonia; using Drawie.Numerics; +using Drawie.RenderApi.Abstraction.Textures; using Drawie.RenderApi.Vulkan.Buffers; using Silk.NET.Vulkan; @@ -30,8 +31,7 @@ public void Render(VulkanImage image) var commandBuffer = context.Pool.CreateCommandBuffer(); commandBuffer.BeginRecording(); - texture.TransitionLayoutTo(commandBuffer.InternalHandle, ImageLayout.ColorAttachmentOptimal, - ImageLayout.TransferSrcOptimal); + texture.ColorAttachment.TransitionLayout(ImageLayout.TransferSrcOptimal, commandBuffer.InternalHandle); image.TransitionLayout(commandBuffer.InternalHandle, ImageLayout.TransferDstOptimal, AccessFlags.TransferWriteBit); @@ -63,17 +63,15 @@ public void Render(VulkanImage image) ImageLayout.TransferSrcOptimal, image.InternalHandle, ImageLayout.TransferDstOptimal, 1, srcBlitRegion, Filter.Linear); + texture.ColorAttachment.TransitionLayout(ImageLayout.ColorAttachmentOptimal, commandBuffer.InternalHandle); commandBuffer.Submit(); - - texture.TransitionLayoutTo((uint)ImageLayout.TransferSrcOptimal, - (uint)ImageLayout.ColorAttachmentOptimal); } public void CreateTextureImage(VecI size) { texture = new VulkanTexture(context.Api!, context.LogicalDevice.Device, context.PhysicalDevice, context.Pool.CommandPool, - context.GraphicsQueue, context.GraphicsQueueFamilyIndex, size); + context.GraphicsQueue, context.GraphicsQueueFamilyIndex, new TextureDesc { Width = size.X, Height = size.Y, Depth = DepthFormat.NoDepth, Samples = 1, Format = TextureFormat.RGBA8_Unorm}); } public void Dispose() diff --git a/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanImage.cs b/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanImage.cs index 2865a6b..d83d5d2 100644 --- a/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanImage.cs +++ b/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanImage.cs @@ -2,8 +2,13 @@ using Avalonia; using Avalonia.Platform; using Drawie.RenderApi.Vulkan.Extensions; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D11; +using Silk.NET.DXGI; using Silk.NET.Vulkan; +using Silk.NET.Vulkan.Extensions.EXT; using Silk.NET.Vulkan.Extensions.KHR; +using Format = Silk.NET.Vulkan.Format; namespace Drawie.Interop.Avalonia.Vulkan.Vk; @@ -30,12 +35,15 @@ public class VulkanImage : IDisposable public uint UsageFlags => (uint)_imageUsageFlags; public ulong MemoryHandle => _imageMemory.Handle; public DeviceMemory DeviceMemory => _imageMemory; + private ComPtr _d3dTexture2D; public uint MipLevels { get; } public Silk.NET.Vulkan.Vk Api { get; } public PixelSize Size { get; } public ulong MemorySize { get; } public uint CurrentLayout => (uint)_currentLayout; + private bool hasIOSurface; + public unsafe VulkanImage(VulkanInteropContext vk, uint format, PixelSize size, bool exportable, IReadOnlyList supportedHandleTypes) { @@ -66,9 +74,20 @@ public unsafe VulkanImage(VulkanInteropContext vk, uint format, PixelSize size, SType = StructureType.ExternalMemoryImageCreateInfo, HandleTypes = handleType }; + var ioSurfaceCreateInfo = new ExportMetalObjectCreateInfoEXT + { + SType = StructureType.ExportMetalObjectCreateInfoExt, + ExportObjectType = ExportMetalObjectTypeFlagsEXT.IosurfaceBitExt + }; + + hasIOSurface = exportable && RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + var imageCreateInfo = new ImageCreateInfo { - PNext = exportable ? &externalMemoryCreateInfo : null, + PNext = + exportable + ? RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? &ioSurfaceCreateInfo : &externalMemoryCreateInfo + : null, SType = StructureType.ImageCreateInfo, ImageType = ImageType.Type2D, Format = Format, @@ -89,87 +108,108 @@ public unsafe VulkanImage(VulkanInteropContext vk, uint format, PixelSize size, .CreateImage(_device, imageCreateInfo, null, out var image).ThrowOnError("Failed to create image"); InternalHandle = image; - Api.GetImageMemoryRequirements(_device, InternalHandle, - out var memoryRequirements); - - var dedicatedAllocation = new MemoryDedicatedAllocateInfoKHR + if (!exportable || !RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - SType = StructureType.MemoryDedicatedAllocateInfoKhr, Image = image - }; + Api.GetImageMemoryRequirements(_device, InternalHandle, + out var memoryRequirements); - var fdExport = new ExportMemoryAllocateInfo - { - HandleTypes = handleType, SType = StructureType.ExportMemoryAllocateInfo, PNext = &dedicatedAllocation - }; + var dedicatedAllocation = new MemoryDedicatedAllocateInfoKHR + { + SType = StructureType.MemoryDedicatedAllocateInfoKhr, Image = image + }; - ImportMemoryWin32HandleInfoKHR handleImport = default; - /*if (handleType == ExternalMemoryHandleTypeFlags.D3D11TextureBit && exportable) - { - var d3dDevice = vk.D3DDevice ?? throw new NotSupportedException("Vulkan D3DDevice wasn't created"); - _d3dTexture2D = D3DMemoryHelper.CreateMemoryHandle(d3dDevice, size, Format); - using var dxgi = _d3dTexture2D.QueryInterface(); + var fdExport = new ExportMemoryAllocateInfo + { + HandleTypes = handleType, + SType = StructureType.ExportMemoryAllocateInfo, + PNext = &dedicatedAllocation + }; - handleImport = new ImportMemoryWin32HandleInfoKHR + ImportMemoryWin32HandleInfoKHR handleImport = default; + if (handleType == ExternalMemoryHandleTypeFlags.D3D11TextureBit && exportable) + { + if (vk.D3DDevice.Handle == null) + throw new NotSupportedException("Vulkan D3DDevice wasn't created"); + _d3dTexture2D = D3DMemoryHelper.CreateMemoryHandle(vk.D3DDevice, size, Format); + + handleImport = new ImportMemoryWin32HandleInfoKHR + { + PNext = &dedicatedAllocation, + SType = StructureType.ImportMemoryWin32HandleInfoKhr, + HandleType = ExternalMemoryHandleTypeFlags.D3D11TextureBit, + Handle = CreateDxgiSharedHandle() + }; + } + + var memoryAllocateInfo = new MemoryAllocateInfo { - PNext = &dedicatedAllocation, - SType = StructureType.ImportMemoryWin32HandleInfoKhr, - HandleType = ExternalMemoryHandleTypeFlags.D3D11TextureBit, - Handle = dxgi.CreateSharedHandle(null, SharedResourceFlags.Read | SharedResourceFlags.Write), + PNext = + exportable ? handleImport.Handle != IntPtr.Zero ? &handleImport : &fdExport : null, + SType = StructureType.MemoryAllocateInfo, + AllocationSize = memoryRequirements.Size, + MemoryTypeIndex = (uint)VulkanMemoryHelper.FindSuitableMemoryTypeIndex( + Api, + _physicalDevice, + memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit) }; - }*/ - var memoryAllocateInfo = new MemoryAllocateInfo - { - PNext = - exportable ? handleImport.Handle != IntPtr.Zero ? &handleImport : &fdExport : null, - SType = StructureType.MemoryAllocateInfo, - AllocationSize = memoryRequirements.Size, - MemoryTypeIndex = (uint)VulkanMemoryHelper.FindSuitableMemoryTypeIndex( - Api, - _physicalDevice, - memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit) - }; + Api.AllocateMemory(_device, memoryAllocateInfo, null, + out var imageMemory).ThrowOnError("Failed to allocate image memory"); + + _imageMemory = imageMemory; + - Api.AllocateMemory(_device, memoryAllocateInfo, null, - out var imageMemory).ThrowOnError("Failed to allocate image memory"); + MemorySize = memoryRequirements.Size; - _imageMemory = imageMemory; + Api.BindImageMemory(_device, InternalHandle, _imageMemory, 0).ThrowOnError("Failed to bind image memory"); + var componentMapping = new ComponentMapping( + ComponentSwizzle.Identity, + ComponentSwizzle.Identity, + ComponentSwizzle.Identity, + ComponentSwizzle.Identity); + AspectFlags = ImageAspectFlags.ColorBit; - MemorySize = memoryRequirements.Size; + var subresourceRange = new ImageSubresourceRange(AspectFlags, 0, MipLevels, 0, 1); - Api.BindImageMemory(_device, InternalHandle, _imageMemory, 0).ThrowOnError("Failed to bind image memory"); - var componentMapping = new ComponentMapping( - ComponentSwizzle.Identity, - ComponentSwizzle.Identity, - ComponentSwizzle.Identity, - ComponentSwizzle.Identity); + var imageViewCreateInfo = new ImageViewCreateInfo + { + SType = StructureType.ImageViewCreateInfo, + Image = InternalHandle, + ViewType = ImageViewType.Type2D, + Format = Format, + Components = componentMapping, + SubresourceRange = subresourceRange + }; - AspectFlags = ImageAspectFlags.ColorBit; + Api + .CreateImageView(_device, imageViewCreateInfo, null, out var imageView) + .ThrowOnError("Failed to create image view"); - var subresourceRange = new ImageSubresourceRange(AspectFlags, 0, MipLevels, 0, 1); + _imageView = imageView; - var imageViewCreateInfo = new ImageViewCreateInfo - { - SType = StructureType.ImageViewCreateInfo, - Image = InternalHandle, - ViewType = ImageViewType.Type2D, - Format = Format, - Components = componentMapping, - SubresourceRange = subresourceRange - }; + _currentLayout = ImageLayout.Undefined; - Api - .CreateImageView(_device, imageViewCreateInfo, null, out var imageView) - .ThrowOnError("Failed to create image view"); + TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.NoneKhr); + } + } - _imageView = imageView; - _currentLayout = ImageLayout.Undefined; + private unsafe IntPtr CreateDxgiSharedHandle() + { + using var dxgiResource = _d3dTexture2D.QueryInterface(); - TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.NoneKhr); + void* sharedHandle; + SilkMarshal.ThrowHResult(dxgiResource.CreateSharedHandle( + (SecurityAttributes*)null, + DXGI.SharedResourceRead | DXGI.SharedResourceWrite, + (char*)null, + &sharedHandle)); + + return (IntPtr)sharedHandle; } + public int ExportFd() { if (!Api.TryGetDeviceExtension(_instance, _device, out var ext)) @@ -198,29 +238,50 @@ public IntPtr ExportOpaqueNtHandle() return fd; } - public IPlatformHandle Export() + public unsafe IntPtr ExportIOSurface() + { + if (!Api.TryGetDeviceExtension(_instance, _device, out var ext)) + throw new InvalidOperationException(); + var surfaceExport = new ExportMetalIOSurfaceInfoEXT + { + SType = StructureType.ExportMetalIOSurfaceInfoExt, Image = InternalHandle + }; + var export = new ExportMetalObjectsInfoEXT() + { + SType = StructureType.ExportMetalObjectsInfoExt, PNext = &surfaceExport + }; + ext.ExportMetalObjects(_device, ref export); + if (surfaceExport.IoSurface == IntPtr.Zero) + throw new Exception("Unable to export IOSurfaceRef"); + return surfaceExport.IoSurface; + } + + public unsafe IPlatformHandle Export() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - /*if (_d3dTexture2D != null) + if (_d3dTexture2D.Handle != null) { - using var dxgi = _d3dTexture2D!.QueryInterface(); return new PlatformHandle( - dxgi.CreateSharedHandle(null, SharedResourceFlags.Read | SharedResourceFlags.Write), + CreateDxgiSharedHandle(), KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureNtHandle); - }*/ + } return new PlatformHandle(ExportOpaqueNtHandle(), KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaqueNtHandle); } - else - return new PlatformHandle(new IntPtr(ExportFd()), - KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return new PlatformHandle(ExportIOSurface(), + KnownPlatformGraphicsExternalImageHandleTypes.IOSurfaceRef); + + return new PlatformHandle(new IntPtr(ExportFd()), + KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor); } public ImageTiling Tiling => ImageTiling.Optimal; - //public bool IsDirectXBacked => _d3dTexture2D != null; + public unsafe bool IsDirectXBacked => _d3dTexture2D.Handle != null; internal void TransitionLayout(CommandBuffer commandBuffer, ImageLayout fromLayout, AccessFlags fromAccessFlags, diff --git a/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanInteropContext.cs b/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanInteropContext.cs index 98afc2c..2904a2e 100644 --- a/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanInteropContext.cs +++ b/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanInteropContext.cs @@ -5,10 +5,12 @@ using Drawie.Interop.Avalonia.Core; using Drawie.RenderApi; using Drawie.RenderApi.Vulkan; +using Drawie.RenderApi.Vulkan.Buffers; using Drawie.RenderApi.Vulkan.ContextObjects; using Drawie.RenderApi.Vulkan.Extensions; using DrawiEngine; using Silk.NET.Core.Native; +using Silk.NET.Direct3D11; using Silk.NET.Vulkan; using Silk.NET.Vulkan.Extensions.KHR; @@ -17,11 +19,11 @@ namespace Drawie.Interop.Avalonia.Vulkan.Vk; public class VulkanInteropContext : VulkanContext, IDrawieInteropContext { public VulkanCommandBufferPool Pool { get; private set; } + public ComPtr D3DDevice { get; private set; } private List requiredDeviceExtensions = new List(); private ICompositionGpuInterop gpuInterop; - private DescriptorPool descriptorPool; public VulkanInteropContext(ICompositionGpuInterop gpuInterop) { @@ -37,14 +39,17 @@ public override void Initialize(IVulkanContextInfo contextInfo) Api = Silk.NET.Vulkan.Vk.GetApi(); - TryAddValidationLayer("VK_LAYER_KHRONOS_validation"); deviceExtensions.Add("VK_KHR_get_physical_device_properties2"); deviceExtensions.Add("VK_KHR_external_memory_capabilities"); deviceExtensions.Add("VK_KHR_external_semaphore_capabilities"); - - if(EnableValidationLayers) + //TODO if it crashes with vertie, try adding VK_KHR_dynamic_rendering + + if (EnableValidationLayers) + { + TryAddValidationLayer("VK_LAYER_KHRONOS_validation"); deviceExtensions.Add("VK_EXT_debug_utils"); + } SetupInstance(contextInfo); SetupDebugMessenger(); @@ -57,6 +62,19 @@ public override void Initialize(IVulkanContextInfo contextInfo) GpuInfo = PickPhysicalDevice(); CreateLogicalDevice(); CreatePool(); + CreateD3D11DeviceIfNeeded(); + } + + private unsafe void CreateD3D11DeviceIfNeeded() + { + var physicalDeviceIDProperties = GetPhysicalDeviceIDProperties(PhysicalDevice); + ComPtr d3dDevice = null; + if (physicalDeviceIDProperties.DeviceLuidvalid && + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && + !gpuInterop.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaqueNtHandle) + ) + d3dDevice = D3DMemoryHelper.CreateDeviceByLuid( + MemoryMarshal.Read(new Span(physicalDeviceIDProperties.DeviceLuid, 8))); } private bool SetRequiredDeviceExtensions() @@ -77,6 +95,13 @@ private bool SetRequiredDeviceExtensions() requiredDeviceExtensions.Add("VK_KHR_dedicated_allocation"); requiredDeviceExtensions.Add("VK_KHR_get_memory_requirements2"); } + else if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + if (!gpuInterop.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes + .IOSurfaceRef)) + return false; + requiredDeviceExtensions.AddRange(["VK_EXT_metal_objects", "VK_KHR_timeline_semaphore"]); + } else { if (!gpuInterop.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes @@ -113,7 +138,7 @@ protected override unsafe void CreateLogicalDevice() priorities[j] = 1.0f; } - var features = new PhysicalDeviceFeatures() { SamplerAnisotropy = false }; + var features = new PhysicalDeviceFeatures() { SamplerAnisotropy = false, FillModeNonSolid = true }; var queueCreateInfo = new DeviceQueueCreateInfo() { @@ -183,6 +208,23 @@ protected override unsafe bool IsDeviceSuitable(PhysicalDevice device) return true; } + private unsafe PhysicalDeviceIDProperties GetPhysicalDeviceIDProperties(PhysicalDevice device) + { + var physicalDeviceIDProperties = new PhysicalDeviceIDProperties() + { + SType = StructureType.PhysicalDeviceIDProperties + }; + + var physicalDeviceProperties2 = new PhysicalDeviceProperties2() + { + SType = StructureType.PhysicalDeviceProperties2, PNext = &physicalDeviceIDProperties + }; + + Api!.GetPhysicalDeviceProperties2(device, &physicalDeviceProperties2); + + return physicalDeviceIDProperties; + } + public override unsafe void Dispose() { Pool.Dispose(); diff --git a/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanResources.cs b/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanResources.cs index a8f5aad..b6c0ea6 100644 --- a/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanResources.cs +++ b/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanResources.cs @@ -2,6 +2,7 @@ using Avalonia.Rendering.Composition; using Drawie.Interop.Avalonia.Core; using Drawie.RenderApi; +using Drawie.RenderApi.Abstraction.Textures; namespace Drawie.Interop.Avalonia.Vulkan.Vk; diff --git a/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanSemaphorePair.cs b/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanSemaphorePair.cs index 294edb9..d9e9ce0 100644 --- a/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanSemaphorePair.cs +++ b/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanSemaphorePair.cs @@ -9,10 +9,9 @@ namespace Drawie.Interop.Avalonia.Vulkan.Vk; public class VulkanSemaphorePair : IDisposable { - private readonly VulkanInteropContext _resources; + private readonly VulkanInteropContext _resources; - public unsafe VulkanSemaphorePair(VulkanInteropContext resources, - IReadOnlyList supportedHandleTypes, bool exportable) + public unsafe VulkanSemaphorePair(VulkanInteropContext resources, bool exportable) { _resources = resources; @@ -20,29 +19,26 @@ public unsafe VulkanSemaphorePair(VulkanInteropContext resources, { SType = StructureType.ExportSemaphoreCreateInfo, HandleTypes = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? (supportedHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureNtHandle) - && !supportedHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaqueNtHandle) - ? ExternalSemaphoreHandleTypeFlags.D3D11FenceBit - : ExternalSemaphoreHandleTypeFlags.OpaqueWin32Bit) - : ExternalSemaphoreHandleTypeFlags.OpaqueFDBit + ? ExternalSemaphoreHandleTypeFlags.OpaqueWin32Bit + : ExternalSemaphoreHandleTypeFlags.OpaqueFDBit }; var semaphoreCreateInfo = new SemaphoreCreateInfo { - SType = StructureType.SemaphoreCreateInfo, - PNext = exportable ? &semaphoreExportInfo : null + SType = StructureType.SemaphoreCreateInfo, PNext = exportable ? &semaphoreExportInfo : null }; - resources.Api!.CreateSemaphore(resources.LogicalDevice.Device, semaphoreCreateInfo, null, out var semaphore).ThrowOnError("Failed to create semaphore"); + resources.Api.CreateSemaphore(resources.LogicalDevice.Device, in semaphoreCreateInfo, null, out var semaphore).ThrowOnError(); ImageAvailableSemaphore = semaphore; - resources.Api.CreateSemaphore(resources.LogicalDevice.Device, semaphoreCreateInfo, null, out semaphore).ThrowOnError("Failed to create semaphore"); + resources.Api.CreateSemaphore(resources.LogicalDevice.Device, in semaphoreCreateInfo, null, out semaphore).ThrowOnError(); RenderFinishedSemaphore = semaphore; } public int ExportFd(bool renderFinished) { - if (!_resources.Api!.TryGetDeviceExtension(_resources.Instance, _resources.LogicalDevice.Device, + if (!_resources.Api!.TryGetDeviceExtension(_resources.Instance, + _resources.LogicalDevice.Device, out var ext)) throw new InvalidOperationException(); var info = new SemaphoreGetFdInfoKHR() @@ -54,10 +50,11 @@ public int ExportFd(bool renderFinished) ext.GetSemaphoreF(_resources.LogicalDevice.Device, info, out var fd).ThrowOnError("Failed to export semaphore"); return fd; } - + public IntPtr ExportWin32(bool renderFinished) { - if (!_resources.Api!.TryGetDeviceExtension(_resources.Instance, _resources.LogicalDevice.Device, + if (!_resources.Api!.TryGetDeviceExtension(_resources.Instance, + _resources.LogicalDevice.Device, out var ext)) throw new InvalidOperationException(); var info = new SemaphoreGetWin32HandleInfoKHR() @@ -66,7 +63,8 @@ public IntPtr ExportWin32(bool renderFinished) Semaphore = renderFinished ? RenderFinishedSemaphore : ImageAvailableSemaphore, HandleType = ExternalSemaphoreHandleTypeFlags.OpaqueWin32Bit }; - ext.GetSemaphoreWin32Handle(_resources.LogicalDevice.Device, info, out var fd).ThrowOnError("Failed to export semaphore"); + ext.GetSemaphoreWin32Handle(_resources.LogicalDevice.Device, info, out var fd) + .ThrowOnError("Failed to export semaphore"); return fd; } @@ -86,5 +84,5 @@ public unsafe void Dispose() { _resources.Api!.DestroySemaphore(_resources.LogicalDevice.Device, ImageAvailableSemaphore, null); _resources.Api!.DestroySemaphore(_resources.LogicalDevice.Device, RenderFinishedSemaphore, null); - } + } } diff --git a/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanSwapchain.cs b/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanSwapchain.cs index 08bd891..9f842ec 100644 --- a/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanSwapchain.cs +++ b/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanSwapchain.cs @@ -1,4 +1,5 @@ -using Avalonia; +using System.Runtime.InteropServices; +using Avalonia; using Avalonia.Platform; using Avalonia.Rendering.Composition; using Drawie.Interop.Avalonia.Core; @@ -38,11 +39,13 @@ public class VulkanSwapchainImage : ISwapchainImage private readonly CompositionDrawingSurface _target; private readonly VulkanImage _image; private readonly VulkanSemaphorePair _semaphorePair; - private ICompositionImportedGpuSemaphore? _availableSemaphore, _renderCompletedSemaphore; + private readonly VulkanTimelineSemaphore? _timelineSemaphore; + private ICompositionImportedGpuSemaphore? _availableSemaphore, _renderCompletedSemaphore, _importedTimelineSemaphore; private ICompositionImportedGpuImage? _importedImage; private Task? _lastPresent; public VulkanImage Image => _image; private bool _initial = true; + private ulong _timelineCounter; public VulkanSwapchainImage(VulkanInteropContext vk, PixelSize size, ICompositionGpuInterop interop, CompositionDrawingSurface target) @@ -51,8 +54,12 @@ public VulkanSwapchainImage(VulkanInteropContext vk, PixelSize size, ICompositio _interop = interop; _target = target; Size = size; - _image = new VulkanImage(vk, (uint)Format.R8G8B8A8Unorm, size, true, interop.SupportedImageHandleTypes); - _semaphorePair = new VulkanSemaphorePair(vk, interop.SupportedImageHandleTypes, true); + var format = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? Format.B8G8R8A8Unorm : Format.R8G8B8A8Unorm; + _image = new VulkanImage(vk, (uint)format, size, true, interop.SupportedImageHandleTypes); + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + _timelineSemaphore = new(vk); + else + _semaphorePair = new VulkanSemaphorePair(vk, true); } public async ValueTask DisposeAsync() @@ -68,6 +75,7 @@ public async ValueTask DisposeAsync() await _renderCompletedSemaphore.DisposeAsync(); _semaphorePair.Dispose(); + _timelineSemaphore?.Dispose(); _image.Dispose(); } @@ -84,13 +92,35 @@ public void BeginDraw() ImageLayout.Undefined, AccessFlags.None, ImageLayout.ColorAttachmentOptimal, AccessFlags.ColorAttachmentReadBit); - if (_initial) + if (_image.IsDirectXBacked) + buffer.Submit(null, null, null, null, + new VulkanCommandBufferPool.VulkanCommandBuffer.KeyedMutexSubmitInfo + { + AcquireKey = 0, DeviceMemory = _image.DeviceMemory + }); + else if (_timelineSemaphore != null) + { + unsafe + { + var wait = _timelineCounter; + var submitInfo = new TimelineSemaphoreSubmitInfo + { + PWaitSemaphoreValues = &wait, + WaitSemaphoreValueCount = 1, + SType = StructureType.TimelineSemaphoreSubmitInfo + }; + var waitSemaphores = new[] { _timelineSemaphore.Handle }; + + buffer.Submit(waitSemaphores, pNext: (IntPtr)(&submitInfo)); + } + } + else if (_initial) { _initial = false; buffer.Submit(); } else - buffer.Submit(new[] { _semaphorePair.ImageAvailableSemaphore }, + buffer.Submit(new[] { _semaphorePair!.ImageAvailableSemaphore }, new[] { PipelineStageFlags.AllGraphicsBit }); } @@ -101,22 +131,64 @@ public void Present() buffer.BeginRecording(); _image.TransitionLayout(buffer.InternalHandle, ImageLayout.TransferSrcOptimal, AccessFlags.TransferWriteBit); - buffer.Submit(null, null, new[] { _semaphorePair.RenderFinishedSemaphore }); + if (_image.IsDirectXBacked) + { + buffer.Submit(null, null, null, null, + new VulkanCommandBufferPool.VulkanCommandBuffer.KeyedMutexSubmitInfo + { + DeviceMemory = _image.DeviceMemory, ReleaseKey = 1 + }); + } + else if (_timelineSemaphore != null) + { + unsafe + { + var signal = _timelineCounter + 1; + var submitInfo = new TimelineSemaphoreSubmitInfo + { + PSignalSemaphoreValues = &signal, + SignalSemaphoreValueCount = 1, + SType = StructureType.TimelineSemaphoreSubmitInfo + }; + var signalSemaphores = new[] { _timelineSemaphore.Handle }; + + buffer.Submit(default, signalSemaphores: signalSemaphores, pNext: (IntPtr)(&submitInfo)); + } + } + else + buffer.Submit(null, null, new[] { _semaphorePair!.RenderFinishedSemaphore }); - _availableSemaphore ??= _interop.ImportSemaphore(_semaphorePair.Export(false)); + if (_timelineSemaphore != null) + { + _importedTimelineSemaphore ??= _interop.ImportSemaphore(_timelineSemaphore.Export()); + } + else if (!_image.IsDirectXBacked) + { + _availableSemaphore ??= _interop.ImportSemaphore(_semaphorePair!.Export(false)); - _renderCompletedSemaphore ??= _interop.ImportSemaphore(_semaphorePair.Export(true)); + _renderCompletedSemaphore ??= _interop.ImportSemaphore(_semaphorePair!.Export(true)); + } _importedImage ??= _interop.ImportImage(_image.Export(), new PlatformGraphicsExternalImageProperties { - Format = PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm, + Format = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) + ? PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm + : PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm, Width = Size.Width, Height = Size.Height, MemorySize = _image.MemorySize }); - - _lastPresent = - _target.UpdateWithSemaphoresAsync(_importedImage, _renderCompletedSemaphore!, _availableSemaphore!); + if (_importedTimelineSemaphore != null) + { + _lastPresent = _target.UpdateWithTimelineSemaphoresAsync(_importedImage, + _importedTimelineSemaphore, _timelineCounter + 1, _importedTimelineSemaphore, _timelineCounter + 2); + _timelineCounter += 2; + } + else if (_image.IsDirectXBacked) + _lastPresent = _target.UpdateWithKeyedMutexAsync(_importedImage, 1, 0); + else + _lastPresent = + _target.UpdateWithSemaphoresAsync(_importedImage, _renderCompletedSemaphore!, _availableSemaphore!); } } diff --git a/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanTimelineSemaphore.cs b/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanTimelineSemaphore.cs new file mode 100644 index 0000000..c045300 --- /dev/null +++ b/src/Drawie.Interop.Avalonia.Vulkan/Vk/VulkanTimelineSemaphore.cs @@ -0,0 +1,74 @@ +using System.Runtime.InteropServices; +using Avalonia.Platform; +using Drawie.RenderApi.Vulkan; +using Drawie.RenderApi.Vulkan.Extensions; +using Silk.NET.Vulkan; +using Silk.NET.Vulkan.Extensions.EXT; +using Semaphore = Silk.NET.Vulkan.Semaphore; + +namespace Drawie.Interop.Avalonia.Vulkan.Vk; + +class VulkanTimelineSemaphore : IDisposable +{ + private VulkanContext _resources; + + public unsafe VulkanTimelineSemaphore(VulkanContext resources) + { + _resources = resources; + var mtlEvent = new ExportMetalObjectCreateInfoEXT + { + SType = StructureType.ExportMetalObjectCreateInfoExt, + ExportObjectType = ExportMetalObjectTypeFlagsEXT.SharedEventBitExt + }; + + var semaphoreTypeInfo = new SemaphoreTypeCreateInfoKHR() + { + SType = StructureType.SemaphoreTypeCreateInfo, + SemaphoreType = SemaphoreType.Timeline, + PNext = &mtlEvent + }; + + var semaphoreCreateInfo = new SemaphoreCreateInfo + { + SType = StructureType.SemaphoreCreateInfo, + PNext = &semaphoreTypeInfo, + }; + + resources.Api.CreateSemaphore(resources.LogicalDevice.Device, in semaphoreCreateInfo, null, out var semaphore).ThrowOnError(); + Handle = semaphore; + } + + public Semaphore Handle { get; } + public unsafe void Dispose() + { + _resources.Api.DestroySemaphore(_resources.LogicalDevice.Device, Handle, null); + } + + + public unsafe IntPtr ExportSharedEvent() + { + if (!_resources.Api.TryGetDeviceExtension(_resources.Instance, _resources.LogicalDevice.Device, out var ext)) + throw new InvalidOperationException(); + var eventExport = new ExportMetalSharedEventInfoEXT() + { + SType = StructureType.ExportMetalSharedEventInfoExt, + Semaphore = Handle, + }; + var export = new ExportMetalObjectsInfoEXT() + { + SType = StructureType.ExportMetalObjectsInfoExt, + PNext = &eventExport + }; + ext.ExportMetalObjects(_resources.LogicalDevice.Device, ref export); + if (eventExport.MtlSharedEvent == IntPtr.Zero) + throw new Exception("Unable to export IOSurfaceRef"); + return eventExport.MtlSharedEvent; + } + public IPlatformHandle Export() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return new PlatformHandle(ExportSharedEvent(), + KnownPlatformGraphicsExternalSemaphoreHandleTypes.MetalSharedEvent); + throw new PlatformNotSupportedException(); + } +} diff --git a/src/Drawie.Interop.Avalonia/AppBuilderExtensions.cs b/src/Drawie.Interop.Avalonia/AppBuilderExtensions.cs index d0496ba..ad81f5e 100644 --- a/src/Drawie.Interop.Avalonia/AppBuilderExtensions.cs +++ b/src/Drawie.Interop.Avalonia/AppBuilderExtensions.cs @@ -1,5 +1,4 @@ using Avalonia; -using Avalonia.Controls.ApplicationLifetimes; using Avalonia.OpenGL; using Avalonia.OpenGL.Egl; using Avalonia.Rendering.Composition; @@ -14,7 +13,7 @@ using Drawie.Skia; using DrawiEngine; -namespace Drawie.Interop.VulkanAvalonia; +namespace Drawie.Interop.Avalonia; public static class AppBuilderExtensions { diff --git a/src/Drawie.Interop.Avalonia/Drawie.Interop.Avalonia.csproj b/src/Drawie.Interop.Avalonia/Drawie.Interop.Avalonia.csproj index 043e4d4..e18b5f9 100644 --- a/src/Drawie.Interop.Avalonia/Drawie.Interop.Avalonia.csproj +++ b/src/Drawie.Interop.Avalonia/Drawie.Interop.Avalonia.csproj @@ -1,9 +1,11 @@  - net8.0 + net10.0 enable enable + true + true diff --git a/src/Drawie.JSInterop/Drawie.JSInterop.csproj b/src/Drawie.JSInterop/Drawie.JSInterop.csproj index 6feafeb..b38b2f9 100644 --- a/src/Drawie.JSInterop/Drawie.JSInterop.csproj +++ b/src/Drawie.JSInterop/Drawie.JSInterop.csproj @@ -1,14 +1,17 @@  - net8.0 + net10.0 enable enable true - + + true + PreserveNewest + diff --git a/src/Drawie.JSInterop/JSRuntime.WebGl.cs b/src/Drawie.JSInterop/JSRuntime.WebGl.cs index 450a182..36d6368 100644 --- a/src/Drawie.JSInterop/JSRuntime.WebGl.cs +++ b/src/Drawie.JSInterop/JSRuntime.WebGl.cs @@ -30,7 +30,10 @@ public partial class JSRuntime public static partial void BindBuffer(int handle, int array, int positionBuffer); [JSImport("webgl.bufferData", "drawie.js")] - public static partial void BufferData(int handle, int arrayType, double[] vertices, int usage); + public static partial void BufferData(int handle, int target, int size, int usage); + + [JSImport("webgl.bufferData", "drawie.js")] + public static partial void BufferData(int handle, int arrayType, byte[] data, int usage); [JSImport("webgl.clearColor", "drawie.js")] public static partial void ClearColor(int gl, double r, double g, double b, double a); @@ -88,4 +91,88 @@ public static partial void TexImage2D(int handle, int type, int level, int forma [JSImport("webgl.deleteTexture", "drawie.js")] public static partial void DeleteTexture(int gl, int textureId); + + [JSImport("webgl.viewport", "drawie.js")] + public static partial void Viewport(int gl, int x, int y, int width, int height); + + [JSImport("webgl.bindFramebuffer", "drawie.js")] + public static partial void BindFramebuffer(int gl, int target, int framebuffer); + + [JSImport("webgl.createFramebuffer", "drawie.js")] + public static partial int CreateFramebuffer(int gl); + + [JSImport("webgl.framebufferTexture2D", "drawie.js")] + public static partial void FramebufferTexture2D(int gl, int target, int attachment, int textarget, int texture, int level); + + [JSImport("webgl.checkFramebufferStatus", "drawie.js")] + public static partial int CheckFramebufferStatus(int gl, int target); + + [JSImport("webgl.getError", "drawie.js")] + public static partial int GetError(int gl); + + [JSImport("webgl.deleteFramebuffer", "drawie.js")] + public static partial void DeleteFramebuffer(int gl, int framebuffer); + + [JSImport("webgl.enable", "drawie.js")] + public static partial void Enable(int gl, int cap); + + [JSImport("webgl.disable", "drawie.js")] + public static partial void Disable(int gl, int cap); + + [JSImport("webgl.depthFunc", "drawie.js")] + public static partial void DepthFunc(int gl, int func); + + [JSImport("webgl.clearDepth", "drawie.js")] + public static partial void ClearDepth(int gl, double depth); + + [JSImport("webgl.depthMask", "drawie.js")] + public static partial void DepthMask(int gl, bool value); + + [JSImport("webgl.getParameter", "drawie.js")] + public static partial int GetParameter(int gl, int binding); + + [JSImport("webgl.bindVertexArray", "drawie.js")] + public static partial void BindVertexArray(int gl, int vertexArrayHandle); + + [JSImport("webgl.bindSampler", "drawie.js")] + public static partial void BindSampler(int gl, int slot, int samplerHandle); + + [JSImport("webgl.drawElements", "drawie.js")] + public static partial void DrawElements(int gl, int mode, int count, int type, int offset); + + [JSImport("webgl.blitFramebuffer", "drawie.js")] + public static partial void BlitFramebuffer(int gl, int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, + int dstX1, int dstY1, int mask, int filter); + [JSImport("webgl.createSampler", "drawie.js")] + public static partial int CreateSampler(int glHandle); + + [JSImport("webgl.createVertexArray", "drawie.js")] + public static partial int CreateVertexArray(int gl); + + [JSImport("webgl.uniformBlockBinding", "drawie.js")] + public static partial void UniformBlockBinding(int gl, int programHandle, int blockIndex, int bindingPoint); + + [JSImport("webgl.bindBufferBase", "drawie.js")] + public static partial void BindBufferBase(int gl, int target, int bindingPoint, int buffer); + + [JSImport("webgl.bufferSubData", "drawie.js")] + public static partial void BufferSubData(int gl, int target, int offset, byte[] data); + + [JSImport("webgl.createRenderbuffer", "drawie.js")] + public static partial int CreateRenderbuffer(int api); + + [JSImport("webgl.bindRenderbuffer", "drawie.js")] + public static partial void BindRenderbuffer(int gl, int target, int renderbuffer); + + [JSImport("webgl.deleteRenderbuffer", "drawie.js")] + public static partial void DeleteRenderbuffer(int gl, int renderbuffer); + + [JSImport("webgl.renderbufferStorage", "drawie.js")] + public static partial void RenderbufferStorage(int gl, int target, int internalFormat, int width, int height); + + [JSImport("webgl.framebufferRenderbuffer", "drawie.js")] + public static partial void FramebufferRenderbuffer(int gl, int target, int attachment, int renderbufferTarget, int renderbuffer); + + [JSImport("webgl.getContext", "drawie.js")] + public static partial int GetContext(string canvasId, string ctx); } diff --git a/src/Drawie.JSInterop/drawie.js b/src/Drawie.JSInterop/drawie.js index 68c9e59..35dc97d 100644 --- a/src/Drawie.JSInterop/drawie.js +++ b/src/Drawie.JSInterop/drawie.js @@ -1,5 +1,6 @@ export class Drawie { canvasContextHandles = {}; + canvasContextIds = 0; shaderHandleIds = 0; shaderHandles = {}; @@ -13,9 +14,21 @@ textureHandleIds = 0; textureHandles = {}; + samplerIds = 0; + samplerHandles = {} + + framebufferIds = 0; + framebufferHandles = {} + uniformLocationHandleIds = 0; uniformLocationHandles = {}; + vertexArrayIds = 0; + vertexArrayHandles = {} + + renderbufferIds = 0; + renderbufferHandles = {} + exports = {}; addDrawieImports() { @@ -50,6 +63,44 @@ return null; }, + viewport: (handleId, x, y, width, height) => { + const gl = this.canvasContextHandles[handleId]; + gl.viewport(x, y, width, height); + }, + createFramebuffer: (glHandle) => { + const gl = this.canvasContextHandles[glHandle]; + const webGlFramebuffer = gl.createFramebuffer(); + this.framebufferIds++; + this.framebufferHandles[this.framebufferIds] = webGlFramebuffer; + return this.framebufferIds; + }, + bindFramebuffer: (handleId, target, framebuffer) => { + const gl = this.canvasContextHandles[handleId]; + if(framebuffer === 0) { + gl.bindFramebuffer(target, null); + return; + } + const fb = this.framebufferHandles[framebuffer]; + gl.bindFramebuffer(target, fb); + }, + framebufferTexture2D: (glHandle, target, attachment, textarget, texture, level) => { + const gl = this.canvasContextHandles[glHandle]; + const targetTexture = this.textureHandles[texture] + gl.framebufferTexture2D(target, attachment, textarget, targetTexture, level); + }, + checkFramebufferStatus: (glHandle, target) => { + const gl = this.canvasContextHandles[glHandle]; + return gl.checkFramebufferStatus(target); + }, + getError: (glHandle) => { + const gl = this.canvasContextHandles[glHandle]; + return gl.getError(); + }, + deleteFramebuffer: (glHandle, framebuffer) => { + const gl = this.canvasContextHandles[glHandle]; + gl.deleteFramebuffer(this.framebufferHandles[framebuffer]); + delete this.framebufferHandles[framebuffer]; + }, createProgram: (glHandle) => { const gl = this.canvasContextHandles[glHandle]; @@ -92,9 +143,33 @@ const buffer = this.bufferHandles[bufferId]; gl.bindBuffer(target, buffer); }, - bufferData: (glHandle, target, data, usage) => { + bufferData: (glHandle, target, dataOrSize, usage) => { + const gl = this.canvasContextHandles[glHandle]; + if (typeof dataOrSize === 'number') { + gl.bufferData(target, dataOrSize, usage); + return; + } + + const array = target === 0x8893 ? new Uint16Array(dataOrSize) : new Float32Array(dataOrSize); + gl.bufferData(target, array, usage); + }, + bindBufferBase: (glHandle, target, index, buffer) => { + const gl = this.canvasContextHandles[glHandle]; + const bufferObj = this.bufferHandles[buffer]; + gl.bindBufferBase(target, index, bufferObj); + }, + bufferSubData: (glHandle, target, dstByteOffset, srcData) => { const gl = this.canvasContextHandles[glHandle]; - gl.bufferData(target, new Float32Array(data), usage); + + const data = srcData instanceof Uint8Array + ? srcData + : new Uint8Array(srcData); + + gl.bufferSubData( + target, + dstByteOffset, + data + ); }, clearColor: (glHandle, r, g, b, a) => { const gl = this.canvasContextHandles[glHandle]; @@ -114,7 +189,7 @@ }, useProgram: (glHandle, programId) => { const gl = this.canvasContextHandles[glHandle]; - const program = programHandles[programId]; + const program = this.programHandles[programId]; gl.useProgram(program); }, drawArrays: (glHandle, mode, first, count) => { @@ -126,6 +201,84 @@ const program = this.programHandles[programId]; return gl.getAttribLocation(program, name); }, + enable: (glHandle, cap) => { + const gl = this.canvasContextHandles[glHandle]; + gl.enable(cap); + }, + disable: (glHandle, cap) => { + const gl = this.canvasContextHandles[glHandle]; + gl.disable(cap) + }, + depthFunc: (glHandle, func) => { + const gl = this.canvasContextHandles[glHandle]; + gl.depthFunc(func); + }, + clearDepth: (glHandle, depth) => { + const gl = this.canvasContextHandles[glHandle]; + gl.clearDepth(depth); + }, + depthMask: (glHandle, value) => { + const gl = this.canvasContextHandles[glHandle]; + gl.depthMask(value); + }, + getParameter: (glHandle, param) => { + const gl = this.canvasContextHandles[glHandle]; + const foundParam = gl.getParameter(param); + return foundParam.name; + }, + bindVertexArray: (glHandle, vertexArray) => { + const gl = this.canvasContextHandles[glHandle]; + const vao = this.vertexArrayHandles[vertexArray]; + gl.bindVertexArray(vao); + }, + bindSampler: (glHandle, slot, sampler) => { + const gl = this.canvasContextHandles[glHandle]; + const wglSampler = this.samplerHandles[sampler]; + gl.bindSampler(slot, wglSampler); + }, + uniformBlockBinding: (glHandle, program, blockIndex, bindingPoint) => { + const gl = this.canvasContextHandles[glHandle]; + const wglProgram = this.programHandles[program]; + gl.uniformBlockBinding(wglProgram, blockIndex, bindingPoint); + }, + createRenderbuffer: (glHandle) => { + const gl = this.canvasContextHandles[glHandle]; + const rb = gl.createRenderbuffer() + this.renderbufferIds++; + this.renderbufferHandles[this.renderbufferIds] = rb; + return this.renderbufferIds; + }, + bindRenderbuffer: (glHandle, target, renderbufferId) => { + const gl = this.canvasContextHandles[glHandle]; + const rb = this.renderbufferHandles[renderbufferId]; + gl.bindRenderbuffer(target, rb); + }, + renderbufferStorage: (glHandle, target, internalFormat, width, height) => { + const gl = this.canvasContextHandles[glHandle]; + gl.renderbufferStorage(target, internalFormat, width, height); + }, + deleteRenderbuffer: (glHandle, renderbufferId) => { + const gl = this.canvasContextHandles[glHandle]; + const rb = this.renderbufferHandles[renderbufferId]; + gl.deleteRenderbuffer(rb); + delete this.renderbufferHandles[renderbufferId]; + }, + framebufferRenderbuffer: (glHandle, target, attachment, renderbufferTarget, renderbuffer) => { + const gl = this.canvasContextHandles[glHandle]; + const rb = this.renderbufferHandles[renderbuffer]; + gl.framebufferRenderbuffer(target, attachment, renderbufferTarget, rb); + }, + getContext: (canvasId, contextType) => { + const canvas = document.getElementById(canvasId); + if (!canvas) { + return null; + } + + const handle = canvas.getContext(contextType); + this.canvasContextIds++; + this.canvasContextHandles[this.canvasContextIds] = handle; + return this.canvasContextIds; + }, openSkiaContext: (canvasId) => { const contextAttributes = { alpha: 1, @@ -173,7 +326,7 @@ }, activeTexture: (glHandle, textureUnit) => { const gl = this.canvasContextHandles[glHandle]; - gl.activeTexture(gl.TEXTURE0 + textureUnit); + gl.activeTexture(textureUnit); }, uniform1i: (glHandle, location, value) => { const gl = this.canvasContextHandles[glHandle]; @@ -197,6 +350,28 @@ delete this.textureHandles[textureId]; }, + drawElements: (glHandle, mode, count, type, offset) => { + const gl = this.canvasContextHandles[glHandle]; + gl.drawElements(mode, count, type, offset); + }, + blitFramebuffer: (glHandle, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter) => { + const gl = this.canvasContextHandles[glHandle]; + gl.blitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); + }, + createSampler: (glHandle) => { + const gl = this.canvasContextHandles[glHandle]; + const sampler = gl.createSampler(); + this.samplerIds++; + this.samplerHandles[this.samplerIds] = sampler; + return this.samplerIds; + }, + createVertexArray: (glHandle) => { + const gl = this.canvasContextHandles[glHandle]; + const vao = gl.createVertexArray(); + this.vertexArrayIds++; + this.vertexArrayHandles[this.vertexArrayIds] = vao; + return this.vertexArrayIds; + } }, window: { innerWidth: () => window.innerWidth, diff --git a/src/Drawie.Layer.3D.Test/Core/BufferObject.cs b/src/Drawie.Layer.3D.Test/Core/BufferObject.cs new file mode 100644 index 0000000..d42aa85 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Core/BufferObject.cs @@ -0,0 +1,29 @@ +using Silk.NET.OpenGL; + +namespace SilkNet; + +public sealed class BufferObject : NativeObject where TDataType : unmanaged +{ + private BufferTargetARB _bufferType; + + public unsafe BufferObject(GL glContext, Span data, BufferTargetARB bufferType) : base(glContext, glContext.GenBuffer()) + { + _bufferType = bufferType; + + Bind(); + fixed (void* d = data) + { + glContext.BufferData(bufferType, (nuint)(data.Length * sizeof(TDataType)), d, GLEnum.StaticDraw); + } + } + + public void Bind() + { + GlContext.BindBuffer(_bufferType, Handle); + } + + public override void Dispose() + { + GlContext.DeleteBuffer(Handle); + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Core/Camera.cs b/src/Drawie.Layer.3D.Test/Core/Camera.cs new file mode 100644 index 0000000..ce041d3 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Core/Camera.cs @@ -0,0 +1,65 @@ +using System.Numerics; +using SilkNet.Rendering; + +namespace SilkNet; + +public class Camera +{ + public Vector3 Position { get; set; } + public Vector3 Forward { get; private set; } + public Vector3 Up { get; private set; } + public Vector3 Right { get; private set; } + public float AspectRatio { get; set; } + + public float Yaw { get; set; } = -90f; + public float Pitch { get; set; } + public Frustum Frustum { get; private set; } + + private float _zoom = 45f; + public float Zoom + { + get => _zoom; + set => _zoom = Math.Clamp(Zoom - value, 1f, 45f); + } + + public Matrix4x4 ViewMatrix => Matrix4x4.CreateLookAt(Position, Position + Forward, Up); + + public Matrix4x4 ProjectionMatrix => + Matrix4x4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(Zoom), AspectRatio, 0.1f, 100f); + + public Quaternion Rotation => Quaternion.CreateFromYawPitchRoll(-MathHelper.DegreesToRadians(Yaw), + MathHelper.DegreesToRadians(Pitch), 0f); + + public Camera(Vector3 position, Vector3 forward, Vector3 up, float aspectRatio) + { + Position = position; + Forward = forward; + Up = up; + AspectRatio = aspectRatio; + SetDirection(0, 0); + Frustum = new Frustum(this, MathHelper.DegreesToRadians(Zoom), 0.1f, 100f); + } + + public void RecalculateFrustum() + { + Frustum = new Frustum(this, MathHelper.DegreesToRadians(Zoom), 0.1f, 100f); + } + + public void SetDirection(float xOffset, float yOffset) + { + Yaw += xOffset; + Pitch -= yOffset; + + Pitch = Math.Clamp(Pitch, -89f, 89f); + + var cameraDirection = Vector3.Zero; + cameraDirection.X = MathF.Cos(MathHelper.DegreesToRadians(Yaw)) * MathF.Cos(MathHelper.DegreesToRadians(Pitch)); + cameraDirection.Y = MathF.Sin(MathHelper.DegreesToRadians(Pitch)); + cameraDirection.Z = MathF.Sin(MathHelper.DegreesToRadians(Yaw)) * MathF.Cos(MathHelper.DegreesToRadians(Pitch)); + cameraDirection = Vector3.Normalize(cameraDirection); + + Forward = cameraDirection; + Right = Vector3.Normalize(Vector3.Cross(Forward, Vector3.UnitY)); + Up = Vector3.Normalize(Vector3.Cross(Right, cameraDirection)); + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Core/NativeObject.cs b/src/Drawie.Layer.3D.Test/Core/NativeObject.cs new file mode 100644 index 0000000..2bfbdfe --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Core/NativeObject.cs @@ -0,0 +1,17 @@ +using Silk.NET.OpenGL; + +namespace SilkNet; + +public abstract class NativeObject : IDisposable +{ + public uint Handle { get; } + protected GL GlContext { get; } + + public NativeObject(GL glContext, uint handle) + { + GlContext = glContext; + Handle = handle; + } + + public abstract void Dispose(); +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Core/ShaderLoader.cs b/src/Drawie.Layer.3D.Test/Core/ShaderLoader.cs new file mode 100644 index 0000000..83cba2d --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Core/ShaderLoader.cs @@ -0,0 +1,20 @@ +namespace SilkNet; + +public static class ShaderLoader +{ + public static string VertexShader; + public static string UnlitShader; + public static string LitShader; + public static string BasicVertexShader; + + public static string LoadRaw(string name) + { + string filePath = Path.Join("Shaders", $"{name}.glsl"); + if (File.Exists(filePath)) + { + return File.ReadAllText(filePath); + } + + throw new FileNotFoundException($"File {filePath} not found."); + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Core/Transform.cs b/src/Drawie.Layer.3D.Test/Core/Transform.cs new file mode 100644 index 0000000..70aba94 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Core/Transform.cs @@ -0,0 +1,73 @@ +using System.Numerics; + +namespace SilkNet; + +public class Transform +{ + private Vector3 _position = Vector3.Zero; + private float _scale = 1f; + private Quaternion _rotation = Quaternion.Identity; + private Matrix4x4 _cachedMatrix = Matrix4x4.Identity; + + private bool _isDirty = true; + + public Vector3 Position + { + get => _position; + set + { + _position = value; + _isDirty = true; + } + } + + public Vector3 Right + { + get => Vector3.Transform(Vector3.UnitX, _rotation); + } + + public Vector3 Up + { + get => Vector3.Transform(Vector3.UnitY, _rotation); + } + + public Vector3 Forward + { + get => Vector3.Transform(Vector3.UnitZ, _rotation); + } + + public float Scale + { + get => _scale; + set + { + _scale = value; + _isDirty = true; + } + } + + public Quaternion Rotation + { + get => _rotation; + set + { + _rotation = value; + _isDirty = true; + } + } + + public Matrix4x4 ViewMatrix + { + get + { + if (_isDirty) + { + _cachedMatrix = Matrix4x4.Identity * Matrix4x4.CreateFromQuaternion(Rotation) * Matrix4x4.CreateScale(Scale) * + Matrix4x4.CreateTranslation(Position); + _isDirty = false; + } + + return _cachedMatrix; + } + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Core/VertexArrayObject.cs b/src/Drawie.Layer.3D.Test/Core/VertexArrayObject.cs new file mode 100644 index 0000000..65484d1 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Core/VertexArrayObject.cs @@ -0,0 +1,34 @@ +using Silk.NET.OpenGL; + +namespace SilkNet; + +public sealed class VertexArrayObject : NativeObject + where TVertexType : unmanaged + where TIndexType : unmanaged +{ + + public VertexArrayObject(GL glContext, BufferObject vbo, BufferObject ebo) : base(glContext, glContext.GenVertexArray()) + { + Bind(); + vbo.Bind(); + ebo.Bind(); + } + + public unsafe void VertexAttributePointer(uint index, int count, VertexAttribPointerType type, uint vertexSize, + int offset) + { + int vTypeSize = sizeof(TVertexType); + GlContext.VertexAttribPointer(index, count, type, false, vertexSize * (uint) vTypeSize, (void*) (offset * vTypeSize)); + GlContext.EnableVertexAttribArray(index); + } + + public void Bind() + { + GlContext.BindVertexArray(Handle); + } + + public override void Dispose() + { + GlContext.DeleteVertexArray(Handle); + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Drawie.Layer.3D.Test.csproj b/src/Drawie.Layer.3D.Test/Drawie.Layer.3D.Test.csproj new file mode 100644 index 0000000..d8a4a9e --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Drawie.Layer.3D.Test.csproj @@ -0,0 +1,31 @@ + + + + net10.0 + Drawie.Layer.ThreeD.Test + enable + enable + true + + + + + + + + + + + + + + + PreserveNewest + + + + PreserveNewest + + + + diff --git a/src/Drawie.Layer.3D.Test/Geometry/AABB.cs b/src/Drawie.Layer.3D.Test/Geometry/AABB.cs new file mode 100644 index 0000000..f57b138 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Geometry/AABB.cs @@ -0,0 +1,28 @@ +using System.Numerics; +using Plane = SilkNet.Rendering.Plane; + +namespace SilkNet.Geometry; + +public struct AABB +{ + public Vector3 Center { get; set; } + public Vector3 Extents { get; set; } + + public AABB(Vector3 min, Vector3 max) + { + Center = (min + max) * 0.5f; + Extents = new Vector3(max.X - Center.X, max.Y - Center.Y, max.Z - Center.Z); + } + + public AABB(Vector3 position, float x, float y, float z) + { + Center = position; + Extents = new Vector3(x, y, z); + } + + public bool IsOnOrForwardPlane(Plane plane) + { + float r = Extents.X * Math.Abs(plane.Normal.X) + Extents.Y * Math.Abs(plane.Normal.Y) + Extents.Z * Math.Abs(plane.Normal.Z); + return -r <= plane.GetSignedDistance(Center); + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Geometry/GeometryData.cs b/src/Drawie.Layer.3D.Test/Geometry/GeometryData.cs new file mode 100644 index 0000000..afac2d0 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Geometry/GeometryData.cs @@ -0,0 +1,13 @@ +namespace SilkNet.Geometry; + +public class GeometryData +{ + public float[] Vertices { get; set; } + public uint [] Indices { get; set; } + + public GeometryData(float[] vertices, uint[] indices) + { + Vertices = vertices.ToArray(); + Indices = indices.ToArray(); + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Geometry/GeometryObject.cs b/src/Drawie.Layer.3D.Test/Geometry/GeometryObject.cs new file mode 100644 index 0000000..77435f3 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Geometry/GeometryObject.cs @@ -0,0 +1,23 @@ +using Silk.NET.OpenGL; +using SilkNet.Rendering; + +namespace SilkNet.Geometry; + +public abstract class GeometryObject : IDisposable +{ + public Transform Transform { get; set; } = new Transform(); + public int MaterialIndex { get; set; } = -1; + public GeometryData GeometryData { get; } + + public abstract void OpenDrawingContext(); + public abstract void Draw(GL api); + public abstract bool IsInFrustum(Frustum frustum, Transform transform); + + public GeometryObject(GeometryData geometryData, int materialIndex) + { + GeometryData = geometryData; + MaterialIndex = materialIndex; + } + + public abstract void Dispose(); +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Geometry/Primitives/Cube.cs b/src/Drawie.Layer.3D.Test/Geometry/Primitives/Cube.cs new file mode 100644 index 0000000..c5c1bcf --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Geometry/Primitives/Cube.cs @@ -0,0 +1,126 @@ +using System.Numerics; +using Drawie.Layer.ThreeD.Test; +using Silk.NET.OpenGL; +using SilkNet.Rendering; + +namespace SilkNet.Geometry.Primitives; + +public class Cube : GeometryObject +{ + private static readonly float[] Vertices = new[] + { + //X Y Z Normals U V + -1f, -1f, -1f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, + 1f, -1f, -1f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + 1f, 1f, -1f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, + 1f, 1f, -1f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, + -1f, 1f, -1f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + -1f, -1f, -1f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, + + -1f, -1f, 1f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, + 1f, -1f, 1f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + 1f, 1f, 1f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, + 1f, 1f, 1f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, + -1f, 1f, 1f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + -1f, -1f, 1f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, + + -1f, 1f, 1f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1f, 1f, -1f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + -1f, -1f, -1f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + -1f, -1f, -1f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + -1f, -1f, 1f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1f, 1f, 1f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + + 1f, 1f, 1f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1f, 1f, -1f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + 1f, -1f, -1f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1f, -1f, -1f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1f, -1f, 1f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 1f, 1f, 1f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + + -1f, -1f, -1f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + 1f, -1f, -1f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, + 1f, -1f, 1f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + 1f, -1f, 1f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + -1f, -1f, 1f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, + -1f, -1f, -1f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + + -1f, 1f, -1f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 1f, 1f, -1f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1f, 1f, 1f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + 1f, 1f, 1f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + -1f, 1f, 1f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, + -1f, 1f, -1f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f + }; + + private static readonly uint[] Indices = + { + 0, 1, 3, + 1, 2, 3 + }; + + private BufferObject _vbo; + + private BufferObject _ebo; + private VertexArrayObject _vao; + + public Cube(GL api, int materialIndex) : base(new GeometryData(Vertices, Indices), materialIndex) + { + _ebo = new BufferObject(api, GeometryData.Indices, BufferTargetARB.ElementArrayBuffer); + _vbo = new BufferObject(api, GeometryData.Vertices, BufferTargetARB.ArrayBuffer); + _vao = new VertexArrayObject(api, _vbo, _ebo); + + _vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 8, 0); + _vao.VertexAttributePointer(1, 3, VertexAttribPointerType.Float, 8, 3); + _vao.VertexAttributePointer(2, 2, VertexAttribPointerType.Float, 8, 6); + } + + public override void OpenDrawingContext() + { + _vao.Bind(); + } + + public override void Draw(GL api) + { + api.DrawArrays(PrimitiveType.Triangles, 0, 36); + } + + public override bool IsInFrustum(Frustum frustum, Transform transform) + { + Vector3 position = transform.Position; + Quaternion rotation = transform.Rotation; + + AABB aabb; + + if (rotation.IsIdentity) + { + Vector3 extends = Vector3.One * Transform.Scale; + Vector3 min = position - extends; + Vector3 max = position + extends; + aabb = new AABB(min, max); + } + else + { + Vector3 right = transform.Right * transform.Scale; + Vector3 up = transform.Up * transform.Scale; + Vector3 forward = transform.Forward * transform.Scale; + + float x = Math.Abs(Vector3.Dot(Vector3.UnitX, right)) + Math.Abs(Vector3.Dot(Vector3.UnitX, up)) + Math.Abs(Vector3.Dot(Vector3.UnitX, forward)); + float y = Math.Abs(Vector3.Dot(Vector3.UnitY, right)) + Math.Abs(Vector3.Dot(Vector3.UnitY, up)) + Math.Abs(Vector3.Dot(Vector3.UnitY, forward)); + float z = Math.Abs(Vector3.Dot(Vector3.UnitZ, right)) + Math.Abs(Vector3.Dot(Vector3.UnitZ, up)) + Math.Abs(Vector3.Dot(Vector3.UnitZ, forward)); + aabb = new AABB(position, x, y, z); + } + + + return aabb.IsOnOrForwardPlane(frustum.Left) && aabb.IsOnOrForwardPlane(frustum.Right) && + aabb.IsOnOrForwardPlane(frustum.Top) && aabb.IsOnOrForwardPlane(frustum.Bottom) && + aabb.IsOnOrForwardPlane(frustum.Near) && aabb.IsOnOrForwardPlane(frustum.Far); + } + + public override void Dispose() + { + _vbo.Dispose(); + _ebo.Dispose(); + _vao.Dispose(); + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Geometry/Primitives/Line.cs b/src/Drawie.Layer.3D.Test/Geometry/Primitives/Line.cs new file mode 100644 index 0000000..1d817a3 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Geometry/Primitives/Line.cs @@ -0,0 +1,55 @@ +using System.Numerics; +using Drawie.Layer.ThreeD.Test; +using Silk.NET.OpenGL; +using SilkNet.Rendering; + +namespace SilkNet.Geometry.Primitives; + +public class Line : GeometryObject +{ + private BufferObject _vbo; + private BufferObject _ebo; + private VertexArrayObject _vao; + + private static readonly uint[] Indices = + { + 0, 1 + }; + + public Vector3 Start { get; set; } + public Vector3 End { get; set; } + + public Line(GL api, Vector3 start, Vector3 end, int materialIndex) : base(new GeometryData(new[] { start.X, start.Y, start.Z, end.X, end.Y, end.Z }, Indices), materialIndex) + { + Start = start; + End = end; + + _vbo = new BufferObject(api, GeometryData.Vertices, BufferTargetARB.ArrayBuffer); + _ebo = new BufferObject(api, GeometryData.Indices, BufferTargetARB.ElementArrayBuffer); + _vao = new VertexArrayObject(api, _vbo, _ebo); + + _vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 3, 0); + } + + public override void OpenDrawingContext() + { + _vao.Bind(); + } + + public override void Draw(GL api) + { + api.DrawArrays(PrimitiveType.Lines, 0, 2); + } + + public override bool IsInFrustum(Frustum frustum, Transform transform) + { + return true; + } + + public override void Dispose() + { + _vao.Dispose(); + _vbo.Dispose(); + _ebo.Dispose(); + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Helpers/MathHelper.cs b/src/Drawie.Layer.3D.Test/Helpers/MathHelper.cs new file mode 100644 index 0000000..ce7df55 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Helpers/MathHelper.cs @@ -0,0 +1,9 @@ +namespace SilkNet; + +public static class MathHelper +{ + public static float DegreesToRadians(float degrees) + { + return (float)(degrees * (Math.PI / 180f)); + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Images/dancing-dog.gif b/src/Drawie.Layer.3D.Test/Images/dancing-dog.gif new file mode 100644 index 0000000..39d4366 Binary files /dev/null and b/src/Drawie.Layer.3D.Test/Images/dancing-dog.gif differ diff --git a/src/Drawie.Layer.3D.Test/Images/silkBoxed.png b/src/Drawie.Layer.3D.Test/Images/silkBoxed.png new file mode 100644 index 0000000..6da772b Binary files /dev/null and b/src/Drawie.Layer.3D.Test/Images/silkBoxed.png differ diff --git a/src/Drawie.Layer.3D.Test/Images/silkSpecular.png b/src/Drawie.Layer.3D.Test/Images/silkSpecular.png new file mode 100644 index 0000000..6a5a415 Binary files /dev/null and b/src/Drawie.Layer.3D.Test/Images/silkSpecular.png differ diff --git a/src/Drawie.Layer.3D.Test/Images/texture.png b/src/Drawie.Layer.3D.Test/Images/texture.png new file mode 100644 index 0000000..1492a0a Binary files /dev/null and b/src/Drawie.Layer.3D.Test/Images/texture.png differ diff --git a/src/Drawie.Layer.3D.Test/Misc/Gif.cs b/src/Drawie.Layer.3D.Test/Misc/Gif.cs new file mode 100644 index 0000000..322eec1 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Misc/Gif.cs @@ -0,0 +1,51 @@ +using System.Runtime.InteropServices; +using Silk.NET.OpenGL; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using Texture = SilkNet.Rendering.Texture; + +namespace SilkNet; + +public class Gif : IDisposable +{ + public int FrameCount => _frames.Length; + + private Image _gifImage; + private Texture[] _frames; + private GL _gl; + + public Gif(GL gl, string path) + { + _gifImage = Image.Load(path); + _gl = gl; + LoadFrames(); + } + + public Texture GetFrame(int frame) + { + return _frames[frame]; + } + + private void LoadFrames() + { + _frames = new Texture[_gifImage.Frames.Count]; + for (int i = 0; i < _gifImage.Frames.Count; i++) + { + _frames[i] = new Texture(_gl, GetFrameData(i), (uint)_gifImage.Width, (uint)_gifImage.Height); + } + } + + private Span GetFrameData(int frame) + { + var memoryGroup = _gifImage.Frames[frame].PixelBuffer.MemoryGroup; + return MemoryMarshal.AsBytes(memoryGroup[0].Span); + } + + public void Dispose() + { + foreach (var frame in _frames) + { + frame.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Optimization/MaterialBatcher.cs b/src/Drawie.Layer.3D.Test/Optimization/MaterialBatcher.cs new file mode 100644 index 0000000..0be3ce0 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Optimization/MaterialBatcher.cs @@ -0,0 +1,67 @@ +using SilkNet.Geometry; +using SilkNet.Rendering; + +namespace SilkNet.Optimization; + +public class MaterialBatcher +{ + public Dictionary Batches { get; private set; } = new Dictionary(); + + public MaterialBatcher(IList geometryObjects) + { + RecalculateBatches(geometryObjects); + } + + private void RecalculateBatches(IList geometryObjects) + { + for (var i = 0; i < geometryObjects.Count; i++) + { + var geometryObject = geometryObjects[i]; + var materialIndex = geometryObject.MaterialIndex; + if (Batches.ContainsKey(materialIndex)) + { + Batches[materialIndex].ObjectsCount++; + } + else + { + Batches.Add(materialIndex, new Batch(1, i)); + } + } + + Batches = Batches.OrderBy(x => x.Value).ToDictionary(x => x.Key, x => x.Value); + } + + public void AddObject(GeometryObject geometryObject, int indexOfObject) + { + var materialIndex = geometryObject.MaterialIndex; + if (Batches.ContainsKey(materialIndex)) + { + Batches[materialIndex].ObjectsCount++; + } + else + { + Batches.Add(materialIndex, new Batch(1, indexOfObject)); + } + } + + public void RemoveObject(GeometryObject geometryObject) + { + var materialIndex = geometryObject.MaterialIndex; + if (Batches.ContainsKey(materialIndex)) + { + Batches[materialIndex].ObjectsCount--; + } + } +} + +public class Batch +{ + public int ObjectsCount { get; set; } + public int StartIndex { get; set; } + + public Batch(int objectsCount, int startIndex) + { + ObjectsCount = objectsCount; + StartIndex = startIndex; + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Rendering/Frustum.cs b/src/Drawie.Layer.3D.Test/Rendering/Frustum.cs new file mode 100644 index 0000000..873eb6d --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Rendering/Frustum.cs @@ -0,0 +1,32 @@ +using System.Numerics; + +namespace SilkNet.Rendering; + +public struct Frustum +{ + public Plane Near { get; private set; } + public Plane Far { get; private set; } + public Plane Left { get; private set; } + public Plane Right { get; private set; } + public Plane Top { get; private set; } + public Plane Bottom { get; private set; } + + public Frustum(Camera camera, float fovY, float zNear, float zFar) : this() + { + Recalculate(camera, fovY, zNear, zFar); + } + + public void Recalculate(Camera camera, float fovY, float zNear, float zFar) + { + float halfVerticalSize = zFar * (float)Math.Tan(fovY / 2); + float halfHorizontalSize = halfVerticalSize * camera.AspectRatio; + Vector3 frontMultFar = zFar * camera.Forward; + + Near = new Plane(camera.Position + zNear * camera.Forward, camera.Forward); + Far = new Plane(camera.Position + frontMultFar, -camera.Forward); + Right = new Plane(camera.Position, Vector3.Cross(camera.Up, frontMultFar + camera.Right * halfHorizontalSize)); + Left = new Plane(camera.Position, Vector3.Cross(frontMultFar - camera.Right * halfHorizontalSize, camera.Up)); + Bottom = new Plane(camera.Position, Vector3.Cross(camera.Right, frontMultFar - camera.Up * halfVerticalSize)); + Top = new Plane(camera.Position, Vector3.Cross(frontMultFar + camera.Up * halfVerticalSize, camera.Right)); + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Rendering/MatShader.cs b/src/Drawie.Layer.3D.Test/Rendering/MatShader.cs new file mode 100644 index 0000000..67a8d03 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Rendering/MatShader.cs @@ -0,0 +1,94 @@ +using System.Numerics; +using Silk.NET.OpenGL; + +namespace SilkNet.Rendering; + +public sealed class MatShader : NativeObject +{ + public MatShader(GL glContext, string rawVertex, string rawFragment) : base(glContext, glContext.CreateProgram()) + { + uint vertex = InitShader(ShaderType.VertexShader, rawVertex); + uint fragment = InitShader(ShaderType.FragmentShader, rawFragment); + + GlContext.AttachShader(Handle, vertex); + GlContext.AttachShader(Handle, fragment); + GlContext.LinkProgram(Handle); + + GlContext.GetProgram(Handle, GLEnum.LinkStatus, out var status); + + if (status == 0) + { + throw new Exception($"Program failed to link with error: {GlContext.GetProgramInfoLog(Handle)}"); + } + + GlContext.DetachShader(Handle, vertex); + GlContext.DetachShader(Handle, fragment); + GlContext.DeleteShader(vertex); + GlContext.DeleteShader(fragment); + } + + public void Use() + { + GlContext.UseProgram(Handle); + } + + // Uniforms are properties that applies to the entire geometry + public void SetUniform(string name, int value) + { + int location = GetUniformLocation(name); + GlContext.Uniform1(location, value); + } + + public void SetUniform(string name, float value) + { + int location = GetUniformLocation(name); + GlContext.Uniform1(location, value); + } + + public unsafe void SetUniform(string name, Matrix4x4 transformViewMatrix) + { + int location = GetUniformLocation(name); + GlContext.UniformMatrix4(location, 1, false, (float*)&transformViewMatrix); + } + + public void SetUniform(string name, Vector3 value) + { + int location = GetUniformLocation(name); + GlContext.Uniform3(location, value.X, value.Y, value.Z); + } + + private int GetUniformLocation(string name) + { + int location = GlContext.GetUniformLocation(Handle, name); + if (location == -1) + { + throw new Exception($"{name} uniform not found on the shader."); + } + + return location; + } + + public bool HasUniform(string uniformName) + { + return GlContext.GetUniformLocation(Handle, uniformName) != -1; + } + + private uint InitShader(ShaderType type, string raw) + { + uint handle = GlContext.CreateShader(type); + GlContext.ShaderSource(handle, raw); + GlContext.CompileShader(handle); + string infoLog = GlContext.GetShaderInfoLog(handle); + if (!string.IsNullOrWhiteSpace(infoLog)) + { + throw new Exception($"Compiling shader of type {type} failed with error {infoLog}"); + } + + return handle; + } + + public override void Dispose() + { + GlContext.DeleteProgram(Handle); + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Rendering/Material.cs b/src/Drawie.Layer.3D.Test/Rendering/Material.cs new file mode 100644 index 0000000..3ea7a5b --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Rendering/Material.cs @@ -0,0 +1,125 @@ +using System.Numerics; +using Silk.NET.OpenGL; + +namespace SilkNet.Rendering; + +public class Material : IDisposable +{ + public string Name { get; set; } + public MatShader Shader { get; set; } + public List Properties { get; set; } = new List(); + public Texture[] Textures { get; set; } = new Texture[32]; + + private int _textureCount; + + public Material(string name, MatShader shader) + { + Name = name; + Shader = shader; + + AddProperty("uModel"); + AddProperty("uView"); + AddProperty("uProjection"); + if (shader.HasUniform("viewPos")) + { + AddProperty("viewPos"); + } + } + + public void Use(Camera camera) + { + BindTextures(); + Shader.Use(); + + SetProperty("uView", camera.ViewMatrix); + SetProperty("uProjection", camera.ProjectionMatrix); + if (Shader.HasUniform("viewPos")) + { + SetProperty("viewPos", camera.Position); + } + } + + public void PrepareForObject(Transform transform) + { + SetProperty("uModel", transform.ViewMatrix); + UpdateShader(); + } + + private void BindTextures() + { + for (var i = 0; i < _textureCount; i++) + { + TextureUnit unit = (TextureUnit)(0x84C0 + i); + Textures[i].Bind(unit); + } + } + + public void AddTexture(Texture texture) + { + Textures[_textureCount] = texture; + _textureCount++; + } + + public void AddProperty(string name, T defaultValue = default) where T: struct + { + ShaderProperty property = new ShaderProperty(name, defaultValue); + Properties.Add(property); + } + + public void UpdateShader() + { + foreach (ShaderProperty property in Properties) + { + ApplyToShader(property); + } + } + + public void SetProperty(string name, T value) where T : struct + { + foreach (var property in Properties) + { + if (property.UniformName == name) + { + if (property is ShaderProperty prop) + { + prop.Value = value; + return; + } + + throw new Exception($"Property {name} is not of type {typeof(T)}"); + } + } + + throw new Exception($"Property {name} does not exist"); + } + + private void ApplyToShader(ShaderProperty prop) + { + switch (prop.ObjValue) + { + case float floatValue: + Shader.SetUniform(prop.UniformName, floatValue); + break; + case int intValue: + Shader.SetUniform(prop.UniformName, intValue); + break; + case Vector3 vec3: + Shader.SetUniform(prop.UniformName, vec3); + break; + case Matrix4x4 mat4: + Shader.SetUniform(prop.UniformName, mat4); + break; + default: + throw new Exception($"Property {prop.UniformName} is not a supported type"); + } + } + + public void Dispose() + { + Shader.Dispose(); + foreach (var texture in Textures) + { + texture?.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Rendering/Plane.cs b/src/Drawie.Layer.3D.Test/Rendering/Plane.cs new file mode 100644 index 0000000..ebbc04d --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Rendering/Plane.cs @@ -0,0 +1,20 @@ +using System.Numerics; + +namespace SilkNet.Rendering; + +public struct Plane +{ + public Vector3 Normal { get; set; } + public Vector3 Point { get; set; } + + public float GetSignedDistance(Vector3 point) + { + return Vector3.Dot(Normal, point - Point); + } + + public Plane(Vector3 point, Vector3 normal) + { + Normal = Vector3.Normalize(normal); + Point = point; + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Rendering/ShaderProperty.cs b/src/Drawie.Layer.3D.Test/Rendering/ShaderProperty.cs new file mode 100644 index 0000000..1260212 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Rendering/ShaderProperty.cs @@ -0,0 +1,29 @@ +namespace SilkNet.Rendering; + +public class ShaderProperty +{ + public string UniformName { get; set; } + public object ObjValue { get; set; } + public Type Type { get; set; } + + public ShaderProperty(string name) + { + UniformName = name; + } +} + +public class ShaderProperty : ShaderProperty +{ + public T Value + { + get => (T)ObjValue; + set => ObjValue = value; + } + + public ShaderProperty(string uniformName, T value) : base(uniformName) + { + UniformName = uniformName; + ObjValue = value; + Type = typeof(T); + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Rendering/Texture.cs b/src/Drawie.Layer.3D.Test/Rendering/Texture.cs new file mode 100644 index 0000000..45b5a80 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Rendering/Texture.cs @@ -0,0 +1,70 @@ +using Silk.NET.OpenGL; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace SilkNet.Rendering; + +public sealed class Texture : NativeObject +{ + public Texture(GL glContext, string path) : base(glContext, glContext.GenTexture()) + { + LoadTextureFromPath(path); + SetParameters(); + } + + public Texture(GL glContext, Span data, uint width, uint height) : base(glContext, glContext.GenTexture()) + { + LoadDataFromSpan(data, width, height); + } + + private unsafe void LoadDataFromSpan(Span data, uint width, uint height) + { + fixed (void* d = &data[0]) + { + GlContext.TexImage2D(TextureTarget.Texture2D, 0, (int)InternalFormat.Rgba, width, height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, d); + SetParameters(); + } + } + + private unsafe void LoadTextureFromPath(string path) + { + using var img = Image.Load(path); + // Reserve memory in GPU for whole image + GlContext.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba8, (uint)img.Width, (uint)img.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, null); + + img.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + fixed (void* data = accessor.GetRowSpan(y)) + { + // Load the actual image + GlContext.TexSubImage2D(TextureTarget.Texture2D, 0, 0, y, (uint)accessor.Width, 1, PixelFormat.Rgba, PixelType.UnsignedByte, data); + } + } + }); + } + + private void SetParameters() + { + GlContext.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)GLEnum.ClampToEdge); + GlContext.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)GLEnum.ClampToEdge); + GlContext.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)GLEnum.LinearMipmapLinear); + GlContext.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)GLEnum.Linear); + GlContext.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBaseLevel, 0); + GlContext.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMaxLevel, 8); + + GlContext.GenerateMipmap(TextureTarget.Texture2D); + } + + public void Bind(TextureUnit textureSlot = TextureUnit.Texture0) + { + GlContext.ActiveTexture(textureSlot); + GlContext.BindTexture(TextureTarget.Texture2D, Handle); + } + + public override void Dispose() + { + GlContext.DeleteTexture(Handle); + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Shaders/BasicVertexShader.glsl b/src/Drawie.Layer.3D.Test/Shaders/BasicVertexShader.glsl new file mode 100644 index 0000000..fe3d173 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Shaders/BasicVertexShader.glsl @@ -0,0 +1,17 @@ +#version 330 core +layout (location = 0) in vec3 vPos; + +uniform mat4 uModel; +uniform mat4 uView; +uniform mat4 uProjection; + +out vec3 fPos; + +void main() +{ + //Multiplying our uniform with the vertex position, the multiplication order here does matter. + gl_Position = uProjection * uView * uModel * vec4(vPos, 1.0); + + //We want to know the fragment's position in World space, so we multiply ONLY by uModel and not uView or uProjection + fPos = vec3(uModel * vec4(vPos, 1.0)); +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Shaders/LitShader.glsl b/src/Drawie.Layer.3D.Test/Shaders/LitShader.glsl new file mode 100644 index 0000000..ca78d09 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Shaders/LitShader.glsl @@ -0,0 +1,43 @@ +#version 330 core +in vec3 fNormal; +in vec3 fPos; +in vec2 fTexCoords; + +struct Material { + sampler2D diffuse; + sampler2D specular; + float shininess; +}; + +struct Light { + vec3 position; + vec3 ambient; + vec3 diffuse; + vec3 specular; +}; + +uniform Material material; +uniform Light light; +uniform vec3 viewPos; + +out vec4 FragColor; + +void main() +{ + vec3 ambient = light.ambient * texture(material.diffuse, fTexCoords).rgb; + + vec3 norm = normalize(fNormal); + vec3 lightDirection = normalize(light.position - fPos); + float diff = max(dot(norm, lightDirection), 0.0); + vec3 diffuse = light.diffuse * (diff * texture(material.diffuse, fTexCoords).rgb); + + vec3 viewDirection = normalize(viewPos - fPos); + vec3 reflectDirection = reflect(-lightDirection, norm); + float spec = pow(max(dot(viewDirection, reflectDirection), 0.0), material.shininess); + vec3 specular = light.specular * (spec * texture(material.specular, fTexCoords).rgb); + + //The resulting colour should be the amount of ambient colour + the amount of additional colour provided by the diffuse of the lamp + vec3 result = ambient + diffuse + specular; + + FragColor = vec4(result, 1.0); +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Shaders/UnlitShader.glsl b/src/Drawie.Layer.3D.Test/Shaders/UnlitShader.glsl new file mode 100644 index 0000000..3fb94a5 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Shaders/UnlitShader.glsl @@ -0,0 +1,15 @@ +#version 330 core +in vec2 fUv; + +//A uniform of the type sampler2D will have the storage value of our texture. +uniform sampler2D uTexture0; +uniform vec3 uColor; + + +out vec4 FragColor; + +void main() +{ + //Here we sample the texture based on the Uv coordinates of the fragment + FragColor = vec4(uColor, 1.0f); +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/Shaders/VertexShader.glsl b/src/Drawie.Layer.3D.Test/Shaders/VertexShader.glsl new file mode 100644 index 0000000..b98ba7d --- /dev/null +++ b/src/Drawie.Layer.3D.Test/Shaders/VertexShader.glsl @@ -0,0 +1,26 @@ +#version 330 core +layout (location = 0) in vec3 vPos; +layout (location = 1) in vec3 vNormal; +layout (location = 2) in vec2 vTexCoords; + +uniform mat4 uModel; +uniform mat4 uView; +uniform mat4 uProjection; + +out vec3 fNormal; +out vec3 fPos; +out vec2 fTexCoords; + +void main() +{ + //Multiplying our uniform with the vertex position, the multiplication order here does matter. + gl_Position = uProjection * uView * uModel * vec4(vPos, 1.0); + + //We want to know the fragment's position in World space, so we multiply ONLY by uModel and not uView or uProjection + fPos = vec3(uModel * vec4(vPos, 1.0)); + + //The Normal needs to be in World space too, but needs to account for Scaling of the object + fNormal = mat3(transpose(inverse(uModel))) * vNormal; + + fTexCoords = vTexCoords; +} \ No newline at end of file diff --git a/src/Drawie.Layer.3D.Test/ThreeDTest.cs b/src/Drawie.Layer.3D.Test/ThreeDTest.cs new file mode 100644 index 0000000..be686e8 --- /dev/null +++ b/src/Drawie.Layer.3D.Test/ThreeDTest.cs @@ -0,0 +1,270 @@ +using System.Drawing; +using System.Numerics; +using Drawie.Numerics; +using Drawie.RenderApi; +using Drawie.RenderApi.OpenGL; +using Drawie.Windowing; +using Drawie.Windowing.Input; +using Silk.NET.OpenGL; +using SilkNet; +using SilkNet.Geometry; +using SilkNet.Geometry.Primitives; +using SilkNet.Optimization; +using SilkNet.Rendering; +using Texture = SilkNet.Rendering.Texture; + +namespace Drawie.Layer.ThreeD.Test; + +public class ThreeDTest : ILayer +{ + private static MatShader _lampShader; + private static MatShader _lightingShader; + private static Texture _diffuseMap; + private static Texture _specularMap; + private static Gif _dogGif; + private static Vector3 LampPosition = new Vector3(1.2f, 1.0f, 2.0f); + + private static List _objects = new List(); + private static List _gizmos = new List(); + private static List _materials = new List(); + + private static Camera _camera; + + private static VecD _lastMousePosition; + private static IKeyboard _primaryKeyboard; + + private static Vector3 _lightColor; + private const int GifSpeed = 1; + private static float _normalizedTime; + private static int _currentFrame; + private static double _lastTime; + + //Track when the window started so we can use the time elapsed to rotate the cube + private static DateTime _startTime; + + private static MaterialBatcher _materialBatcher; + + private IWindow window; + + private OpenGlGraphicsContext openglContext; + + public void Initialize(IWindow window) + { + window.Resize += WindowOnResize; + this.window = window; + OnLoad(window.RenderApi.GraphicsContext); + window.Update += OnUpdate; + window.SubscribeToRender("3DTest.Render", "Init", OnRender); + } + + private void WindowOnResize(VecI size) + { + openglContext.Api.Viewport(0, 0, (uint)size.X, (uint)size.Y); + if (_camera != null) + _camera.AspectRatio = (float)size.X / size.Y; + } + + private void OnLoad(IGraphicsContext renderApiGraphicsContext) + { + if (renderApiGraphicsContext is not OpenGlGraphicsContext openGlGraphicsContext) + { + throw new ArgumentException("Only OpenGL backend is supported", nameof(renderApiGraphicsContext)); + } + + var GlContext = openGlGraphicsContext.Api; + openglContext = openGlGraphicsContext; + _startTime = DateTime.UtcNow; + RegisterMouse(window.InputController); + _primaryKeyboard = window.InputController.PrimaryKeyboard; + + ShaderLoader.BasicVertexShader = ShaderLoader.LoadRaw("BasicVertexShader"); + ShaderLoader.VertexShader = ShaderLoader.LoadRaw("VertexShader"); + ShaderLoader.UnlitShader = ShaderLoader.LoadRaw("UnlitShader"); + ShaderLoader.LitShader = ShaderLoader.LoadRaw("LitShader"); + + //The lighting shader will give our main cube its colour multiplied by the lights intensity + _lightingShader = new MatShader(GlContext, ShaderLoader.VertexShader, ShaderLoader.LitShader); + _lampShader = new MatShader(GlContext, ShaderLoader.BasicVertexShader, ShaderLoader.UnlitShader); + + _diffuseMap = new Texture(GlContext, "Images/silkBoxed.png"); + _specularMap = new Texture(GlContext, "Images/silkSpecular.png"); + _dogGif = new Gif(GlContext, "Images/dancing-dog.gif"); + + _camera = new Camera(Vector3.Zero, Vector3.UnitZ, Vector3.UnitY, (float)window.Size.X / window.Size.Y); + + Material cubeMat = new Material("BasicMat", _lightingShader); + cubeMat.AddProperty("material.diffuse", 1f); + cubeMat.AddProperty("material.specular", 1f); + cubeMat.AddProperty("material.shininess", 32f); + + cubeMat.AddProperty("light.specular", Vector3.One); + cubeMat.AddProperty("light.ambient", Vector3.One); + cubeMat.AddProperty("light.diffuse", Vector3.One); + cubeMat.AddProperty("light.position", LampPosition); + + Material unlitMat = new Material("UnlitMat", _lampShader); + unlitMat.AddProperty("uColor", Vector3.One); + + _materials.Add(cubeMat); + _materials.Add(unlitMat); + + SpawnCubes(10, 10, 10); + + _materialBatcher = new MaterialBatcher(_objects); + } + + public static void InstantiateObject(GeometryObject obj) + { + _objects.Add(obj); + _materialBatcher.AddObject(obj, _objects.Count - 1); + } + + private void SpawnCubes(int rows, int columns, int depth) + { + for (int y = 0; y < columns; y++) + { + for (int x = 0; x < rows; x++) + { + for (int z = 0; z < depth; z++) + { + _objects.Add(new Cube(openglContext.Api, 0) + { + Transform = { Position = new Vector3(x * 2.5f, y * 2.5f, z * 2.5f) } + }); + } + } + } + } + + private static void UpdateBasicMaterial() + { + Material cubeMat = _materials[0]; + + var difference = (float)(DateTime.UtcNow - _startTime).TotalSeconds; + _lightColor = Vector3.Zero; + _lightColor.X = MathF.Sin(difference * 2f); + _lightColor.Y = MathF.Sin(difference * 0.7f); + _lightColor.Z = MathF.Sin(difference * 1.3f); + + var diffuseColor = _lightColor * new Vector3(0.5f); + var ambientColor = diffuseColor * new Vector3(1f); + + cubeMat.SetProperty("light.specular", new Vector3(1f, 1f, 1f)); + cubeMat.SetProperty("light.ambient", ambientColor); + cubeMat.SetProperty("light.diffuse", diffuseColor); + cubeMat.SetProperty("light.position", LampPosition); + } + + private static void OnUpdate(double deltaTime) + { + float moveSpeed = 2.5f * (float)deltaTime; + + HandleMovement(moveSpeed); + _camera.RecalculateFrustum(); + } + + private static void HandleMovement(float moveSpeed) + { + if (_primaryKeyboard.IsKeyPressed(Key.W)) + { + _camera.Position += moveSpeed * _camera.Forward; + } + + if (_primaryKeyboard.IsKeyPressed(Key.S)) + { + _camera.Position -= moveSpeed * _camera.Forward; + } + + if (_primaryKeyboard.IsKeyPressed(Key.A)) + { + _camera.Position -= Vector3.Normalize(Vector3.Cross(_camera.Forward, _camera.Up)) * moveSpeed; + } + + if (_primaryKeyboard.IsKeyPressed(Key.D)) + { + _camera.Position += Vector3.Normalize(Vector3.Cross(_camera.Forward, _camera.Up)) * moveSpeed; + } + } + + private void OnRender(double deltaTime) + { + openglContext.Api.Enable(EnableCap.DepthTest); + openglContext.Api.Clear((uint)(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit)); + + UpdateBasicMaterial(); + RenderObjects(); + } + + private void RenderObjects() + { + foreach (var obj in _materialBatcher.Batches) + { + Batch batch = obj.Value; + Material material = _materials[obj.Key]; + material.Use(_camera); + + for (int i = 0; i < batch.ObjectsCount; i++) + { + GeometryObject geometryObject = _objects[batch.StartIndex + i]; + if (!geometryObject.IsInFrustum(_camera.Frustum, geometryObject.Transform)) continue; + + geometryObject.OpenDrawingContext(); + _materials[geometryObject.MaterialIndex].PrepareForObject(geometryObject.Transform); + geometryObject.Draw(openglContext.Api); + } + } + } + + private static void OnClose() + { + _lampShader.Dispose(); + _lightingShader.Dispose(); + _diffuseMap.Dispose(); + _specularMap.Dispose(); + _dogGif.Dispose(); + } + + private void RegisterMouse(InputController input) + { + for (int i = 0; i < input.Pointers.Count; i++) + { + var mouse = input.Pointers[i]; + mouse.PointerMoved += OnMouseMove; + mouse.PointerScrolled += OnScroll; + mouse.PointerClicked += OnMouseClick; + } + } + + private void OnMouseClick(IPointer pointer, PointerButton button, VecD position) + { + if (button == PointerButton.Left) + { + InstantiateObject(new Cube(openglContext.Api, 0) + { + Transform = { Position = _camera.Position + _camera.Forward * 2f } + }); + } + } + + private static void OnScroll(IPointer pointer, VecD scrollDelta) + { + _camera.Zoom = (float)scrollDelta.Y; + } + + private static void OnMouseMove(IPointer pointer, VecD position) + { + float lookSensitivity = 0.1f; + if (_lastMousePosition == default) + { + _lastMousePosition = position; + } + else + { + double offsetX = (position.X - _lastMousePosition.X) * lookSensitivity; + double offsetY = (position.Y - _lastMousePosition.Y) * lookSensitivity; + _lastMousePosition = position; + + _camera.SetDirection((float)offsetX, (float)offsetY); + } + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.UI.ImGui/Drawie.Layer.UI.ImGui.csproj b/src/Drawie.Layer.UI.ImGui/Drawie.Layer.UI.ImGui.csproj new file mode 100644 index 0000000..fc00545 --- /dev/null +++ b/src/Drawie.Layer.UI.ImGui/Drawie.Layer.UI.ImGui.csproj @@ -0,0 +1,17 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + diff --git a/src/Drawie.Layer.UI.ImGui/ImGuiLayer.cs b/src/Drawie.Layer.UI.ImGui/ImGuiLayer.cs new file mode 100644 index 0000000..9a07d61 --- /dev/null +++ b/src/Drawie.Layer.UI.ImGui/ImGuiLayer.cs @@ -0,0 +1,69 @@ +using Drawie.RenderApi; +using Drawie.Rendering; +using Drawie.Host; +using Silk.NET.Core.Contexts; +using Silk.NET.Input; +using Silk.NET.OpenGL; +using Silk.NET.OpenGL.Extensions.ImGui; +using Silk.NET.Windowing; + +namespace Drawie.Layer.UI.ImGui; + +public class ImGuiLayer : ILayer +{ + public Action Render { get; set; } + private IHost _host; + private IOpenGlHostViewRenderApi renderApi; + + private ImGuiController _controller; + + public ImGuiLayer(Action render) + { + Render = render; + } + + public bool IsRenderApiSupported(IHostViewRenderApi api) + { + return api is IOpenGlHostViewRenderApi; + } + + public void Initialize(IHost host) + { + if (host == null) + { + throw new ArgumentNullException(nameof(host)); + } + + if (host.RenderApi is not IOpenGlHostViewRenderApi openGlRenderApi) + { + throw new InvalidOperationException("ImGui only supports OpenGL render APIs."); + } + + renderApi = openGlRenderApi; + this._host = host; + + OnLoaded(); + host.SubscribeToRender("ImGui.Update", "Init", OnEarlyRender); + host.SubscribeToRender("ImGui.Render", "RenderContent", OnRender); + } + + private void OnRender(double dt) + { + Render?.Invoke(dt); + _controller.Render(); + } + + private void OnEarlyRender(double dt) + { + _controller.Update((float)dt); + } + + private void OnLoaded() + { + var gl = new GL(new LamdaNativeContext(renderApi.GetGlInterface())); + + _controller = new ImGuiController(gl, + _host.Native as IView, + _host.InputController.NativeInputController as IInputContext); + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.UI.MiniUi/Controls/Button.cs b/src/Drawie.Layer.UI.MiniUi/Controls/Button.cs new file mode 100644 index 0000000..016ec98 --- /dev/null +++ b/src/Drawie.Layer.UI.MiniUi/Controls/Button.cs @@ -0,0 +1,57 @@ +using Drawie.Backend.Core.Surfaces.PaintImpl; +using Drawie.Backend.Core.Text; +using Drawie.Host.Input; +using Drawie.Layer.UI.MiniUi.Exceptions; +using Drawie.Numerics; + +namespace Drawie.Layer.UI.MiniUi.Controls; + +public static class Button +{ + public static bool Show(string label) + { + MiniUiContext? ctx = MiniUiContext.Active; + + if (ctx == null) throw new MiniUiMissingContextException(); + + RichText rt = new RichText(label); + rt.Fill = true; + Font font = MiniUiStyle.Active.Font; + + VecF bounds = CalculateBounds(rt, font); + + using Paint btnPaint = new Paint(); + + RectD btnBounds = new RectD(ctx.CurrentPosition.X, ctx.CurrentPosition.Y, bounds.X, bounds.Y); + btnBounds.Size += new VecD(MiniUiStyle.Active.Padding * 2, MiniUiStyle.Active.Padding * 2); + + bool hitTest = btnBounds.ContainsInclusive(MiniUiContext.Active.PointerPosition); + btnPaint.Paintable = hitTest ? MiniUiStyle.Active.BackgroundHigh : MiniUiStyle.Active.BackgroundMid; + + OutlinedRectangle.Draw(btnBounds, btnPaint.Paintable, hitTest ? MiniUiStyle.Active.BorderHigh : MiniUiStyle.Active.BorderMid); + + btnPaint.Paintable = MiniUiStyle.Active.Foreground; + rt.FillPaintable = MiniUiStyle.Active.Foreground; + + bool justPressed = !ctx.LastState.PressedPointerButtons[PointerButton.Left] && + ctx.InputController.PrimaryPointer.IsButtonPressed(PointerButton.Left); + + RectD drawBounds = + new RectD( + new VecD(ctx.CurrentPosition.X + MiniUiStyle.Active.Padding, + ctx.CurrentPosition.Y + bounds.Y + MiniUiStyle.Active.Padding / 2f), btnBounds.Size); + + if (ctx.Framebuffer != null) + { + rt.Paint(ctx.Framebuffer?.Canvas, drawBounds.Pos, font, btnPaint, null); + } + + Panel.Advance(btnBounds); + return hitTest && justPressed; + } + + private static VecF CalculateBounds(RichText label, Font? font) + { + return (VecF)label.MeasureBounds(font).Size; + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.UI.MiniUi/Controls/CollapsableGroup.cs b/src/Drawie.Layer.UI.MiniUi/Controls/CollapsableGroup.cs new file mode 100644 index 0000000..8d227eb --- /dev/null +++ b/src/Drawie.Layer.UI.MiniUi/Controls/CollapsableGroup.cs @@ -0,0 +1,115 @@ +using Drawie.Host.Input; +using Drawie.Layer.UI.MiniUi.Exceptions; +using Drawie.Numerics; + +namespace Drawie.Layer.UI.MiniUi.Controls; + +public static class CollapsableGroup +{ + private const string ExpandedGlyph = "M7.47461 10.5L14 3.5L0 3.5L7.47461 10.5Z"; + + private const string CollapsedGlyph = "M10.5 6.52539L3.5 0L3.5 14L10.5 6.52539ZM10.5 6.52539L3.5 0L3.5 14L10.5 6.52539Z"; + + private static readonly Dictionary States = new(); + + private static readonly Stack ActiveGroups = new(); + + private const float GlyphSize = 12; + + private static VecF startPos; + + public static bool Begin(string id, string label) + { + MiniUiContext? ctx = MiniUiContext.Active; + + if (ctx == null) + throw new MiniUiMissingContextException(); + + bool expanded = States.GetValueOrDefault(id, true); + + startPos = ctx.CurrentPosition; + + Layout.BeginMeasure(); + Panel.BeginRow(); + + Glyph.Draw(expanded ? ExpandedGlyph : CollapsedGlyph); + Label.Show(label); + + Panel.EndRow(); + + var measured = Layout.EndMeasure(); + var bounds = measured; + bounds.Size += new VecD(MiniUiStyle.Active.Padding * 2); + + ctx.Framebuffer?.DrawRectangle((float)measured.X, (float)measured.Y, (float)bounds.Width, (float)bounds.Height, MiniUiStyle.Active.BackgroundHigh); + + + Panel.BeginRow(); + + ctx.CurrentPosition += new VecF(MiniUiStyle.Active.Padding, MiniUiStyle.Active.Padding); + Glyph.Draw(expanded ? ExpandedGlyph : CollapsedGlyph, GlyphSize); + ctx.CurrentPosition += new VecF(0, MiniUiStyle.Active.Padding / 2f); + + Label.Show(label); + + Panel.EndRow(); + + bool hovered = bounds.ContainsInclusive(ctx.PointerPosition); + + bool justPressed = + !ctx.LastState.PressedPointerButtons[PointerButton.Left] && + ctx.InputController.PrimaryPointer.IsButtonPressed( + PointerButton.Left); + + if (hovered && justPressed) + { + expanded = !expanded; + States[id] = expanded; + } + + if (!expanded) + { + ctx.CurrentPosition = new VecF( + (float)measured.X, + (float)measured.Bottom); + + return false; + } + + ActiveGroups.Push(new GroupState(bounds)); + + ctx.CurrentPosition = new VecF( + (float)bounds.X + GlyphSize / 2f, + (float)bounds.Bottom + MiniUiStyle.Active.Spacing); + + return true; + } + + public static void End() + { + MiniUiContext? ctx = MiniUiContext.Active; + + if (ctx == null) + throw new MiniUiMissingContextException(); + + if (ActiveGroups.Count == 0) + throw new InvalidOperationException( + "CollapsableGroup.End() was called without a matching Begin()."); + + GroupState state = ActiveGroups.Pop(); + + double bottom = Math.Max( + state.HeaderBounds.Bottom, + ctx.CurrentPosition.Y); + + ctx.CurrentPosition = new VecF( + (float)state.HeaderBounds.X, + (float)bottom); + + RectD bounds = RectD.FromTwoPoints((VecD)startPos, new VecD(state.HeaderBounds.X, ctx.CurrentPosition.Y)); + + Panel.Advance(bounds); + } + + private readonly record struct GroupState(RectD HeaderBounds); +} \ No newline at end of file diff --git a/src/Drawie.Layer.UI.MiniUi/Controls/Glyph.cs b/src/Drawie.Layer.UI.MiniUi/Controls/Glyph.cs new file mode 100644 index 0000000..c7e295d --- /dev/null +++ b/src/Drawie.Layer.UI.MiniUi/Controls/Glyph.cs @@ -0,0 +1,62 @@ +using Drawie.Backend.Core.Numerics; +using Drawie.Backend.Core.Surfaces.PaintImpl; +using Drawie.Backend.Core.Vector; +using Drawie.Layer.UI.MiniUi.Exceptions; +using Drawie.Numerics; + +namespace Drawie.Layer.UI.MiniUi.Controls; + +public static class Glyph +{ + public static void Draw(string svg, double size = 14) + { + MiniUiContext? ctx = MiniUiContext.Active; + + if (ctx == null) + throw new MiniUiMissingContextException(); + + using var path = VectorPath.FromSvgPath(svg); + + RectD bounds = new RectD( + ctx.CurrentPosition.X, + ctx.CurrentPosition.Y, + size, + size); + + RectD pathBounds = path.Bounds; + + if (pathBounds.Width > 0 && pathBounds.Height > 0) + { + double scale = Math.Min( + size / pathBounds.Width, + size / pathBounds.Height); + + double scaledWidth = pathBounds.Width * scale; + double scaledHeight = pathBounds.Height * scale; + + double x = bounds.X + (size - scaledWidth) / 2; + double y = bounds.Y + (size - scaledHeight) / 2; + + var transform = + Matrix3X3.CreateScale((float)scale, (float)scale); + + path.Transform(transform); + + RectD scaledBounds = path.Bounds; + + path.Offset( + new VecD( + x - scaledBounds.X, + y - scaledBounds.Y)); + } + + using Paint paint = new Paint + { + Paintable = MiniUiStyle.Active.Foreground + }; + + ctx.Framebuffer?.Canvas!.DrawPath(path, paint); + + Panel.Advance(bounds); + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.UI.MiniUi/Controls/Label.cs b/src/Drawie.Layer.UI.MiniUi/Controls/Label.cs new file mode 100644 index 0000000..18510cd --- /dev/null +++ b/src/Drawie.Layer.UI.MiniUi/Controls/Label.cs @@ -0,0 +1,45 @@ +using Drawie.Backend.Core.Surfaces.PaintImpl; +using Drawie.Backend.Core.Text; +using Drawie.Layer.UI.MiniUi.Exceptions; +using Drawie.Numerics; + +namespace Drawie.Layer.UI.MiniUi.Controls; + +public static class Label +{ + public static void Show(string text) + { + MiniUiContext? ctx = MiniUiContext.Active; + + if (ctx == null) + throw new MiniUiMissingContextException(); + + RichText rt = new RichText(text) + { + Fill = true, + FillPaintable = MiniUiStyle.Active.Foreground + }; + + Font font = MiniUiStyle.Active.Font; + using Paint paint = new Paint + { + Paintable = MiniUiStyle.Active.Foreground + }; + + VecF size = CalculateBounds(rt, font); + + RectD bounds = new RectD(new VecD(ctx.CurrentPosition.X, ctx.CurrentPosition.Y), (VecD)size); + + if (ctx.Framebuffer != null) + { + rt.Paint(ctx.Framebuffer.Canvas, bounds.Pos + new VecD(0, size.Y), font, paint, null); + } + + Panel.Advance(bounds); + } + + private static VecF CalculateBounds(RichText label, Font? font) + { + return (VecF)label.MeasureBounds(font).Size; + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.UI.MiniUi/Controls/Layout.cs b/src/Drawie.Layer.UI.MiniUi/Controls/Layout.cs new file mode 100644 index 0000000..79f4b7e --- /dev/null +++ b/src/Drawie.Layer.UI.MiniUi/Controls/Layout.cs @@ -0,0 +1,47 @@ +using Drawie.Layer.UI.MiniUi.Exceptions; +using Drawie.Numerics; +using Drawie.Rendering; + +namespace Drawie.Layer.UI.MiniUi.Controls; + +public static class Layout +{ + private static Stack sessions = new Stack(); + + public static void BeginMeasure() + { + MiniUiContext? ctx = MiniUiContext.Active; + + if (ctx == null) + throw new MiniUiMissingContextException(); + + sessions.Push(new MeasurementSession(ctx.CurrentPosition, ctx.Framebuffer)); + ctx.Framebuffer = null; + } + + public static RectD EndMeasure() + { + MiniUiContext? ctx = MiniUiContext.Active; + + if (ctx == null) + throw new MiniUiMissingContextException(); + + var session = sessions.Pop(); + RectD rect = RectD.FromTwoPoints((VecD)session.Cursor, (VecD)ctx.CurrentPosition); + ctx.CurrentPosition = session.Cursor; + ctx.Framebuffer = session.SavedFramebuffer; + return rect; + } + + private struct MeasurementSession + { + public VecF Cursor { get; set; } + public TextureFramebuffer SavedFramebuffer { get; set; } + + public MeasurementSession(VecF cursor, TextureFramebuffer savedFramebuffer) + { + Cursor = cursor; + SavedFramebuffer = savedFramebuffer; + } + } +} diff --git a/src/Drawie.Layer.UI.MiniUi/Controls/OutlinedRectangle.cs b/src/Drawie.Layer.UI.MiniUi/Controls/OutlinedRectangle.cs new file mode 100644 index 0000000..5cfec55 --- /dev/null +++ b/src/Drawie.Layer.UI.MiniUi/Controls/OutlinedRectangle.cs @@ -0,0 +1,49 @@ +using Drawie.Backend.Core.ColorsImpl.Paintables; +using Drawie.Backend.Core.Surfaces.PaintImpl; +using Drawie.Layer.UI.MiniUi.Exceptions; +using Drawie.Numerics; + +namespace Drawie.Layer.UI.MiniUi.Controls; + +public static class OutlinedRectangle +{ + public static void Draw(RectD bounds, Paintable fill, Paintable stroke) + { + MiniUiContext? ctx = MiniUiContext.Active; + + if (ctx == null) + throw new MiniUiMissingContextException(); + + using var paint = new Paint(); + + paint.Style = PaintStyle.StrokeAndFill; + paint.Paintable = stroke; + + double strokeThickness = MiniUiStyle.Active.StrokeThickness; + double radius = MiniUiStyle.Active.Rounding; + + var strokeBounds = bounds.Inflate(strokeThickness); + + ctx.Framebuffer?.Canvas!.DrawRoundRect( + (float)strokeBounds.X, + (float)strokeBounds.Y, + (float)strokeBounds.Width, + (float)strokeBounds.Height, + (float)(radius + strokeThickness), + (float)(radius + strokeThickness), + paint); + + paint.Style = PaintStyle.Fill; + paint.Paintable = fill; + + ctx.Framebuffer?.Canvas!.DrawRoundRect( + (float)bounds.X, + (float)bounds.Y, + (float)bounds.Width, + (float)bounds.Height, + (float)radius, + (float)radius, + paint); + + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.UI.MiniUi/Controls/Panel.cs b/src/Drawie.Layer.UI.MiniUi/Controls/Panel.cs new file mode 100644 index 0000000..59ff04d --- /dev/null +++ b/src/Drawie.Layer.UI.MiniUi/Controls/Panel.cs @@ -0,0 +1,126 @@ +using Drawie.Backend.Core.ColorsImpl.Paintables; +using Drawie.Layer.UI.MiniUi.Exceptions; +using Drawie.Numerics; + +namespace Drawie.Layer.UI.MiniUi.Controls; + +public static class Panel +{ + private static readonly Stack States = new(); + + public static void BeginColumn() + { + MiniUiContext? ctx = MiniUiContext.Active; + + if (ctx == null) + throw new MiniUiMissingContextException(); + + States.Push(new PanelState( + ctx.CurrentPosition, + LayoutDirection.Column)); + } + + public static void EndColumn() + { + MiniUiContext? ctx = MiniUiContext.Active; + + if (ctx == null) + throw new MiniUiMissingContextException(); + + if (States.Count == 0) + throw new InvalidOperationException( + "Panel.EndColumn() was called without a matching Panel.BeginColumn()."); + + PanelState state = States.Pop(); + + if (state.Direction != LayoutDirection.Column) + throw new InvalidOperationException( + "Panel.EndColumn() does not match the current panel layout."); + + Advance(state.OwnSize); + } + + public static void BeginRow() + { + MiniUiContext? ctx = MiniUiContext.Active; + + if (ctx == null) + throw new MiniUiMissingContextException(); + + States.Push(new PanelState(ctx.CurrentPosition, LayoutDirection.Row)); + } + + public static void EndRow() + { + MiniUiContext? ctx = MiniUiContext.Active; + + if (ctx == null) + throw new MiniUiMissingContextException(); + + if (States.Count == 0) + throw new InvalidOperationException( + "Panel.EndRow() was called without a matching Panel.BeginRow()."); + + PanelState state = States.Pop(); + + if (state.Direction != LayoutDirection.Row) + throw new InvalidOperationException( + "Panel.EndRow() does not match the current panel layout."); + + Advance(state.OwnSize); + } + + public static void Advance(RectD bounds) + { + MiniUiContext? ctx = MiniUiContext.Active; + + if (ctx == null) + throw new MiniUiMissingContextException(); + + if (States.Count == 0) + { + ctx.CurrentPosition = new VecF( + ctx.CurrentPosition.X, + (float)bounds.Bottom); + + return; + } + + PanelState state = States.Peek(); + + state.OwnSize = state.OwnSize.Union(bounds); + + switch (state.Direction) + { + case LayoutDirection.Column: + ctx.CurrentPosition = new VecF(state.Position.X, (float)bounds.Bottom + MiniUiStyle.Active.Spacing); + break; + + case LayoutDirection.Row: + ctx.CurrentPosition = new VecF((float)bounds.Right + MiniUiStyle.Active.Spacing, state.Position.Y); + break; + } + } + + private sealed class PanelState + { + public VecF Position { get; } + public LayoutDirection Direction { get; } + + public RectD OwnSize { get; set; } + + public PanelState( + VecF position, + LayoutDirection direction) + { + Position = position; + Direction = direction; + } + } + + private enum LayoutDirection + { + Row, + Column + } +} \ No newline at end of file diff --git a/src/DrawieSample/DrawieSample.csproj b/src/Drawie.Layer.UI.MiniUi/Drawie.Layer.UI.MiniUi.csproj similarity index 64% rename from src/DrawieSample/DrawieSample.csproj rename to src/Drawie.Layer.UI.MiniUi/Drawie.Layer.UI.MiniUi.csproj index f380892..9195a67 100644 --- a/src/DrawieSample/DrawieSample.csproj +++ b/src/Drawie.Layer.UI.MiniUi/Drawie.Layer.UI.MiniUi.csproj @@ -1,13 +1,13 @@  - net8.0 + net10.0 enable enable - + - + diff --git a/src/Drawie.Layer.UI.MiniUi/Exceptions/MiniUiMissingContextException.cs b/src/Drawie.Layer.UI.MiniUi/Exceptions/MiniUiMissingContextException.cs new file mode 100644 index 0000000..27263e7 --- /dev/null +++ b/src/Drawie.Layer.UI.MiniUi/Exceptions/MiniUiMissingContextException.cs @@ -0,0 +1,9 @@ +namespace Drawie.Layer.UI.MiniUi.Exceptions; + +public class MiniUiMissingContextException : Exception +{ + public MiniUiMissingContextException() + : base("No active MiniUi context is available") + { + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.UI.MiniUi/InputState.cs b/src/Drawie.Layer.UI.MiniUi/InputState.cs new file mode 100644 index 0000000..d5d6724 --- /dev/null +++ b/src/Drawie.Layer.UI.MiniUi/InputState.cs @@ -0,0 +1,33 @@ +using Drawie.Host.Input; + +namespace Drawie.Layer.UI.MiniUi; + +public class InputState +{ + // public IReadOnlyDictionary PressedKeys => pressedKeys; + public IReadOnlyDictionary PressedPointerButtons => pressedPointerButtons; + + //private Dictionary pressedKeys = new(); + private Dictionary pressedPointerButtons = new Dictionary(); + + public InputState() + { + int enumRange = Enum.GetValues(typeof(PointerButton)).Length - 1; // - 1 because one state is unknown + for (int i = 0; i < enumRange; i++) + { + pressedPointerButtons.Add((PointerButton)i, false); + } + } + + public void Update(InputController input) + { + //pressedKeys.Clear(); + pressedPointerButtons.Clear(); + + int enumRange = Enum.GetValues(typeof(PointerButton)).Length - 1; // - 1 because one state is unknown + for (int i = 0; i < enumRange; i++) + { + pressedPointerButtons.Add((PointerButton)i, input.PrimaryPointer.IsButtonPressed((PointerButton)i)); + } + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.UI.MiniUi/MiniUILayer.cs b/src/Drawie.Layer.UI.MiniUi/MiniUILayer.cs new file mode 100644 index 0000000..82987db --- /dev/null +++ b/src/Drawie.Layer.UI.MiniUi/MiniUILayer.cs @@ -0,0 +1,49 @@ +using Drawie.Backend.Core; +using Drawie.Backend.Core.Bridge; +using Drawie.Backend.Core.Surfaces; +using Drawie.Host; +using Drawie.Numerics; +using Drawie.RenderApi; +using Drawie.Rendering; + +namespace Drawie.Layer.UI.MiniUi; + +public class MiniUILayer : ILayer +{ + private Action render; + private MiniUiContext context = new MiniUiContext(); + + private IHostViewRenderApi renderApi; + + private Texture renderTexture; + private IHost host; + + public MiniUILayer(Action render) + { + this.render = render; + } + + public bool IsRenderApiSupported(IHostViewRenderApi api) + { + return true; + } + + public void Initialize(IHost host) + { + this.host = host; + renderApi = host.RenderApi; + host.SubscribeToRenderContent("MiniUi.Render", "RenderContent", HostOnRender); + host.Update += HostOnUpdate; + } + + private void HostOnUpdate(double obj) + { + context.Update(host.InputController); + } + + private void HostOnRender(TextureFramebuffer textureFramebuffer, double deltaTime) + { + using var ctx = context.MakeActive(textureFramebuffer); + render?.Invoke(deltaTime); + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.UI.MiniUi/MiniUiContext.cs b/src/Drawie.Layer.UI.MiniUi/MiniUiContext.cs new file mode 100644 index 0000000..e24965b --- /dev/null +++ b/src/Drawie.Layer.UI.MiniUi/MiniUiContext.cs @@ -0,0 +1,41 @@ +using Drawie.Host.Input; +using Drawie.Numerics; +using Drawie.Rendering; + +namespace Drawie.Layer.UI.MiniUi; + +public class MiniUiContext : IDisposable +{ + public static MiniUiContext? Active { get; private set; } + + public TextureFramebuffer? Framebuffer { get; set; } + public VecF CurrentPosition { get; set; } + + public VecD PointerPosition {get; private set;} + public InputController InputController { get; private set; } + + public InputState LastState { get; private set; } = new InputState(); + + + public IDisposable MakeActive(TextureFramebuffer fb) + { + Active = this; + Framebuffer = fb; + CurrentPosition = VecI.Zero; + return this; + } + + public void Update(InputController input) + { + InputController = input; + PointerPosition = input.PrimaryPointer?.Position ?? new VecD(-1, -1); + } + + public void Dispose() + { + LastState.Update(InputController); + Framebuffer = null; + CurrentPosition = VecI.Zero; + if (Active == this) Active = null; + } +} \ No newline at end of file diff --git a/src/Drawie.Layer.UI.MiniUi/MiniUiStyle.cs b/src/Drawie.Layer.UI.MiniUi/MiniUiStyle.cs new file mode 100644 index 0000000..9808b1f --- /dev/null +++ b/src/Drawie.Layer.UI.MiniUi/MiniUiStyle.cs @@ -0,0 +1,50 @@ +using Drawie.Backend.Core.ColorsImpl; +using Drawie.Backend.Core.ColorsImpl.Paintables; +using Drawie.Backend.Core.Text; +using Drawie.Numerics; + +namespace Drawie.Layer.UI.MiniUi; + +public class MiniUiStyle +{ + public static MiniUiStyle Default { get; } = new MiniUiStyle() + { + Foreground = new ColorPaintable(Colors.White), + BackgroundLow = new ColorPaintable(Color.FromHex("#202020")), + BackgroundMid = new ColorPaintable(Color.FromHex("#252525")), + BackgroundHigh = new ColorPaintable(Color.FromHex("#303030")), + BorderMid = new ColorPaintable(Color.FromHex("#303030")), + BorderHigh = new ColorPaintable(Color.FromHex("#404040")) + }; + + public static MiniUiStyle Active { get; set; } = Default; + + public Paintable BackgroundLow { get; set; } + public Paintable BackgroundMid { get; set; } + public Paintable BackgroundHigh { get; set; } + public Paintable Foreground { get; set; } + public Paintable BorderMid { get; set; } + public Paintable BorderHigh { get; set; } + public FontFamilyName FontFamily { get; set; } = new FontFamilyName("$Default"); + public float FontSize { get; set; } = 12; + public float Padding { get; set; } = 4; + public float Spacing { get; set; } = 8; + public float Rounding { get; set; } = 2; + public Font Font => CreateFont(); + public float StrokeThickness { get; set; } = 1.5f; + + private static Font cachedFont; + + private Font CreateFont() + { + if (cachedFont == null || cachedFont.Size != FontSize || cachedFont.Family.Name != FontFamily.Name) + { + cachedFont?.Dispose(); + var font = Font.FromFontFamily(FontFamily); + font.Size = FontSize; + cachedFont = font; + } + + return cachedFont; + } +} \ No newline at end of file diff --git a/src/Drawie.Numerics/Drawie.Numerics.csproj b/src/Drawie.Numerics/Drawie.Numerics.csproj index 4a24632..2c5fb06 100644 --- a/src/Drawie.Numerics/Drawie.Numerics.csproj +++ b/src/Drawie.Numerics/Drawie.Numerics.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable true diff --git a/src/Drawie.Numerics/Vec3D.cs b/src/Drawie.Numerics/Vec3D.cs index ebb2c71..8168ff6 100644 --- a/src/Drawie.Numerics/Vec3D.cs +++ b/src/Drawie.Numerics/Vec3D.cs @@ -1,7 +1,14 @@ -namespace Drawie.Numerics; +using System.Numerics; + +namespace Drawie.Numerics; public struct Vec3D { + public static Vec3D Zero { get; } = new(0, 0, 0); + public static Vec3D UnitX { get; } = new(1, 0, 0); + public static Vec3D UnitY { get; } = new(0, 1, 0); + public static Vec3D UnitZ { get; } = new(0, 0, 1); + public double X { get; set; } public double Y { get; set; } public double Z { get; set; } @@ -12,7 +19,6 @@ public struct Vec3D public double Length => Math.Sqrt(LengthSquared); public double LengthSquared => X * X + Y * Y + Z * Z; - public static Vec3D Zero { get; } = new(0, 0, 0); public Vec3D(double x, double y, double z) { @@ -66,6 +72,15 @@ public Vec3D Signs() public double Dot(Vec3D other) => (X * other.X) + (Y * other.Y) + (Z * other.Z); + public Vec3D Cross(Vec3D other) + { + return new Vec3D( + Y * other.Z - Z * other.Y, + Z * other.X - X * other.Z, + X * other.Y - Y * other.X + ); + } + public Vec3D Multiply(Vec3D other) { return new Vec3D(X * other.X, Y * other.Y, Z * other.Z); @@ -173,4 +188,12 @@ public bool AlmostEquals(Vec3D other, double axisEpsilon = 0.001) double dZ = Math.Abs(Z - other.Z); return dX < axisEpsilon && dY < axisEpsilon && dZ < axisEpsilon; } + + public static Vec3D Transform(Vec3D vector, Quaternion rotation) + { + // Maybe one day we can implement our own formula ;P + var v = new Vector3((float)vector.X, (float)vector.Y, (float)vector.Z); + var transformed = Vector3.Transform(v, rotation); + return new Vec3D(transformed.X, transformed.Y, transformed.Z); + } } diff --git a/src/Drawie.RenderApi.OpenGl/Drawie.RenderApi.OpenGl.csproj b/src/Drawie.RenderApi.OpenGl/Drawie.RenderApi.OpenGl.csproj index 8f1eca8..6013806 100644 --- a/src/Drawie.RenderApi.OpenGl/Drawie.RenderApi.OpenGl.csproj +++ b/src/Drawie.RenderApi.OpenGl/Drawie.RenderApi.OpenGl.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable true diff --git a/src/Drawie.RenderApi.OpenGl/Extensions/BufferUsageExtensions.cs b/src/Drawie.RenderApi.OpenGl/Extensions/BufferUsageExtensions.cs new file mode 100644 index 0000000..8c681eb --- /dev/null +++ b/src/Drawie.RenderApi.OpenGl/Extensions/BufferUsageExtensions.cs @@ -0,0 +1,19 @@ +using Drawie.RenderApi.Abstraction.Buffers; +using Silk.NET.OpenGL; + +namespace Drawie.RenderApi.OpenGL.Extensions; + +public static class BufferUsageExtensions +{ + public static BufferTargetARB ToOpenGlTargetARB(this BufferUsage usage) + { + return usage switch + { + BufferUsage.Vertex => BufferTargetARB.ArrayBuffer, + BufferUsage.Index => BufferTargetARB.ElementArrayBuffer, + BufferUsage.Uniform => BufferTargetARB.UniformBuffer, + BufferUsage.Storage => BufferTargetARB.ShaderStorageBuffer, + _ => throw new ArgumentOutOfRangeException() + }; + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.OpenGl/OpenGlBuffer.cs b/src/Drawie.RenderApi.OpenGl/OpenGlBuffer.cs new file mode 100644 index 0000000..5923985 --- /dev/null +++ b/src/Drawie.RenderApi.OpenGl/OpenGlBuffer.cs @@ -0,0 +1,63 @@ +using Drawie.RenderApi.Abstraction; +using Drawie.RenderApi.Abstraction.Buffers; +using Drawie.RenderApi.OpenGL.Extensions; +using Silk.NET.OpenGL; + +namespace Drawie.RenderApi.OpenGL; + +public class OpenGlBuffer : IBuffer where TData : unmanaged +{ + public uint NativeHandle => openglHandle; + public BufferUsage Usage { get; } + public ulong Size { get; } + + private uint openglHandle; + private GL api; + + + public OpenGlBuffer(GL api, BufferUsage usage, TData[]? data = null) + { + Usage = usage; + this.api = api; + + openglHandle = api.GenBuffer(); + if (data != null) + { + unsafe + { + Size = (uint)data.Length; + fixed (void* d = data) + { + BufferTargetARB bufferType = ToBufferType(); + api.BindBuffer(bufferType, openglHandle); + api.BufferData(bufferType, (nuint)(Size * (uint)sizeof(TData)), d, BufferUsageARB.StaticDraw); + } + } + } + + if (usage == BufferUsage.Vertex) + { + VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 8, 0); + VertexAttributePointer(1, 3, VertexAttribPointerType.Float, 8, 3); + VertexAttributePointer(2, 2, VertexAttribPointerType.Float, 8, 6); + } + } + + public void Dispose() + { + api.DeleteBuffer(openglHandle); + } + + private BufferTargetARB ToBufferType() + { + return Usage.ToOpenGlTargetARB(); + } + + private unsafe void VertexAttributePointer(uint index, int count, VertexAttribPointerType type, uint vertexSize, + int offset) + { + int vTypeSize = sizeof(float); + api.VertexAttribPointer(index, count, type, false, vertexSize * (uint) vTypeSize, (void*) (offset * vTypeSize)); + api.EnableVertexAttribArray(index); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.OpenGl/OpenGlBufferList.cs b/src/Drawie.RenderApi.OpenGl/OpenGlBufferList.cs new file mode 100644 index 0000000..f5c3883 --- /dev/null +++ b/src/Drawie.RenderApi.OpenGl/OpenGlBufferList.cs @@ -0,0 +1,8 @@ +using Drawie.RenderApi.Abstraction.Buffers; + +namespace Drawie.RenderApi.OpenGL; + +public class OpenGlBufferList : IBufferGroupList +{ + public List Buffers { get; } = new List(); +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.OpenGl/OpenGlCommandList.cs b/src/Drawie.RenderApi.OpenGl/OpenGlCommandList.cs new file mode 100644 index 0000000..1979691 --- /dev/null +++ b/src/Drawie.RenderApi.OpenGl/OpenGlCommandList.cs @@ -0,0 +1,110 @@ +using Drawie.RenderApi.Abstraction.Buffers; +using Drawie.RenderApi.Abstraction.CommandRecording; +using Drawie.RenderApi.Abstraction.Pipeline; +using Drawie.RenderApi.Abstraction.RenderTargets; +using Drawie.RenderApi.Abstraction.Shaders; +using Drawie.RenderApi.Abstraction.Textures; +using Drawie.RenderApi.OpenGL.Extensions; +using Silk.NET.OpenGL; + +namespace Drawie.RenderApi.OpenGL; + +public class OpenGlCommandList(GL api) : CommandList +{ + public GL Api { get; } = api; + + private IRenderTarget source; + + private uint originalFb; + + private int lastBoundTextureSlot = 0; + private IPipeline boundPipeline; + + public override void BeginRenderPass(IRenderTarget fb) + { + source = fb; + lastBoundTextureSlot = 0; + ClearInstructions(); + RecordInstruction(() => + { + originalFb = (uint)Api.GetInteger(GLEnum.FramebufferBinding); + Api.BindFramebuffer(FramebufferTarget.Framebuffer, (uint)fb.SurfaceId); + }); + } + + public override void SetPipeline(IPipeline pipeline) + { + boundPipeline = pipeline; + } + + public override void BindPipeline() + { + RecordInstruction(() => boundPipeline.Apply(this)); + } + + public override PreparedTexture PrepareTexture(ITexture texture) + { + return new PreparedTexture(texture.TextureId); + } + + public override void UpdateUniforms(List blocks, List textures, List samplers) + { + + } + + public override void RestoreTexture(PreparedTexture preparedTextureValue) + { + // no op + } + + public override void SetBuffers(IBufferGroup bufferGroup) + { + RecordInstruction(() => { Api.BindVertexArray(bufferGroup.Handle); }); + } + + public override void BindTexture(PreparedTexture texture, ISampler sampler) + { + if (sampler is not OpenGlSampler openGlSampler) throw new ArgumentException("Cannot bind non opengl samplers"); + // if vk:binding has binding set to 1, we need to update it at Texture1, generally, for binding transformation matrices we want to use + // binding 0, so binding 1 is a good assumption. Ideally shader reflection can resolve that but I guess it's fine for now + + RecordInstruction(() => + { + int textureUnit = lastBoundTextureSlot + 1; + Api.ActiveTexture(TextureUnit.Texture0 + textureUnit); + Api.BindTexture(TextureTarget.Texture2D, (uint)texture.Handle); + Api.BindSampler((uint)textureUnit, openGlSampler.Handle); + lastBoundTextureSlot++; + }); + } + + public override unsafe void DrawIndexed(int indexCount) + { + RecordInstruction(() => + { + Api.DrawElements(PrimitiveType.Triangles, (uint)indexCount, DrawElementsType.UnsignedInt, (void*)0); + }); + } + + public override RecordedRenderPass EndRenderPass(IRenderTarget blitTo) + { + RecordInstruction(() => + { + Api.BindFramebuffer(FramebufferTarget.ReadFramebuffer, (uint)source.SurfaceId); + Api.BindFramebuffer(FramebufferTarget.DrawFramebuffer, (uint)blitTo.SurfaceId); + Api.BlitFramebuffer( + 0, 0, source.Size.X, source.Size.Y, + 0, 0, blitTo.Size.X, blitTo.Size.Y, + ClearBufferMask.ColorBufferBit, + BlitFramebufferFilter.Nearest); + Api.BindFramebuffer(FramebufferTarget.Framebuffer, originalFb); + }); + return ToRenderPass(); + } + + public override RecordedRenderPass EndRenderPass() + { + RecordInstruction(() => Api.BindFramebuffer(FramebufferTarget.Framebuffer, originalFb)); + return ToRenderPass(); + } +} diff --git a/src/Drawie.RenderApi.OpenGl/OpenGlContext.cs b/src/Drawie.RenderApi.OpenGl/OpenGlContext.cs index 89cf1fd..e45f42e 100644 --- a/src/Drawie.RenderApi.OpenGl/OpenGlContext.cs +++ b/src/Drawie.RenderApi.OpenGl/OpenGlContext.cs @@ -4,6 +4,24 @@ public class OpenGlContext : IOpenGlContext { private Func getGlInterface; public bool IsGlViaAngle { get; } + + private Dictionary Textures { get; } = new Dictionary(); + + public void AddManagedTexture(IOpenGlTexture texture) + { + Textures.Add(texture.TextureId, texture); + } + + public IOpenGlTexture? GetManagedTexture(ulong textureId) + { + Textures.TryGetValue(textureId, out var texture); + return texture; + } + + public void RemoveManagedTexture(ulong textureId) + { + Textures.Remove(textureId); + } public OpenGlContext(Func getGlInterface, bool isGlViaAngle) { diff --git a/src/Drawie.RenderApi.OpenGl/OpenGlDepthBuffer.cs b/src/Drawie.RenderApi.OpenGl/OpenGlDepthBuffer.cs new file mode 100644 index 0000000..e1312c3 --- /dev/null +++ b/src/Drawie.RenderApi.OpenGl/OpenGlDepthBuffer.cs @@ -0,0 +1,68 @@ +using Drawie.RenderApi.Abstraction.Textures; +using Silk.NET.OpenGL; + +namespace Drawie.RenderApi.OpenGL; + +public sealed class OpenGlDepthBuffer : IDisposable +{ + public uint RenderbufferId { get; } + + public int Width { get; } + public int Height { get; } + public int Samples { get; } + + private GL Api { get; } + public InternalFormat Format { get; set; } + + public OpenGlDepthBuffer(GL api, int width, int height, DepthFormat depth, int samples) + { + Api = api; + + Width = width; + Height = height; + Samples = samples; + + RenderbufferId = Api.GenRenderbuffer(); + Format = ToOpenglDepth(depth); + + Api.BindRenderbuffer( + RenderbufferTarget.Renderbuffer, + RenderbufferId); + + + if (samples == 1) + { + Api.RenderbufferStorage( + RenderbufferTarget.Renderbuffer, + Format, + (uint)width, + (uint)height); + } + else + { + Api.RenderbufferStorageMultisample(RenderbufferTarget.Renderbuffer, (uint)Samples, Format, (uint)Width, (uint)Height); + } + + Api.BindRenderbuffer( + RenderbufferTarget.Renderbuffer, + 0); + } + + private InternalFormat ToOpenglDepth(DepthFormat depth) + { + switch (depth) + { + case DepthFormat.NoDepth: + throw new ArgumentException("Cannot create depth with NoDepth format"); + case DepthFormat.Depth24Stencil8: + return InternalFormat.Depth24Stencil8; + default: + throw new ArgumentOutOfRangeException(nameof(depth), depth, null); + } + } + + public void Dispose() + { + Api.DeleteRenderbuffer(RenderbufferId); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.OpenGl/OpenGlDevice.cs b/src/Drawie.RenderApi.OpenGl/OpenGlDevice.cs new file mode 100644 index 0000000..0444e4d --- /dev/null +++ b/src/Drawie.RenderApi.OpenGl/OpenGlDevice.cs @@ -0,0 +1,149 @@ +using System.Numerics; +using Drawie.Numerics; +using Drawie.RenderApi.Abstraction; +using Drawie.RenderApi.Abstraction.Buffers; +using Drawie.RenderApi.Abstraction.CommandRecording; +using Drawie.RenderApi.Abstraction.Pipeline; +using Drawie.RenderApi.Abstraction.RenderTargets; +using Drawie.RenderApi.Abstraction.Shaders; +using Drawie.RenderApi.Abstraction.Textures; +using Silk.NET.Core.Contexts; +using Silk.NET.OpenGL; +using ShaderType = Drawie.Backend.Shaders.Common.ShaderType; + +namespace Drawie.RenderApi.OpenGL; + +public class OpenGlDevice : IGraphicsDevice +{ + private readonly IOpenGlContext context; + private int handleCounter = 0; + public GL Api { get; } + + public OpenGlDevice(IOpenGlContext graphicsContext) + { + Api = new GL(new LamdaNativeContext(graphicsContext.GetGlInterface)); + context = graphicsContext; + } + + public IBuffer CreateBuffer(BufferUsage usage, TData[]? data) where TData : unmanaged + { + return new OpenGlBuffer(Api, usage, data); + } + + public ITexture CreateTexture(TextureDesc desc) + { + var texture = new OpenGlTexture(Api, desc.Width, desc.Height, desc.Samples); + context.AddManagedTexture(texture); + return texture; + } + + public IPipeline CreatePipeline(PipelineDesc desc) + { + return new OpenGlPipeline(desc, Api); + } + + public ICommandList CreateCommandList() + { + return new OpenGlCommandList(Api); + } + + public ISampler CreateSampler(SamplerDesc desc) + { + return new OpenGlSampler(Api); + } + + public void Submit(RecordedRenderPass recordedRenderPass) + { + recordedRenderPass.Execute(); + } + + public unsafe IShaderProgram CreateShaderProgram(ShaderProgramDesc desc) + { + var program = Api.CreateProgram(); + + uint[] shaders = new uint[desc.Shaders.Count]; + + for (var i = 0; i < desc.Shaders.Count; i++) + { + var shader = desc.Shaders[i]; + shaders[i] = Api.CreateShader( + ToOpenGlShaderType(shader.Type)); + + uint shaderPtr = shaders[i]; + fixed (byte* bytes = shader.Bytes) + { + Api.ShaderBinary( + 1, + &shaderPtr, + ShaderBinaryFormat.ShaderBinaryFormatSpirV, + bytes, + (uint)shader.Bytes.Length); + } + + Api.SpecializeShader( + shaderPtr, + shader.EntryName, + 0, + null, + null); + + Api.AttachShader(program, shaderPtr); + } + + Api.LinkProgram(program); + + Api.GetProgram(program, GLEnum.LinkStatus, out var status); + + if (status == 0) + { + throw new Exception($"Program failed to link with error: {Api.GetProgramInfoLog(program)}"); + } + + for (var i = 0; i < shaders.Length; i++) + { + if (shaders[i] != 0) + { + Api.DetachShader(program, shaders[i]); + Api.DeleteShader(shaders[i]); + } + } + + return new OpenGlShaderProgram(Api, program); + } + + + public IRenderTarget CreateRenderTarget(TextureDesc textureDesc) + { + return new OpenGlRenderTarget(Api, textureDesc); + } + + public IBufferGroup CreateBufferGroup() + { + return new OpenGlVertexArrayObject(Api); + } + + public void DisposeTexture(ulong textureHandle) + { + (context.GetManagedTexture(textureHandle) as IDisposable)?.Dispose(); + context.RemoveManagedTexture(textureHandle); + } + + + private Silk.NET.OpenGL.ShaderType ToOpenGlShaderType(ShaderType shaderType) + { + switch (shaderType) + { + case ShaderType.Vertex: + return Silk.NET.OpenGL.ShaderType.VertexShader; + case ShaderType.Fragment: + return Silk.NET.OpenGL.ShaderType.FragmentShader; + default: + throw new ArgumentOutOfRangeException(nameof(shaderType), shaderType, null); + } + } + + public void Dispose() + { + + } +} diff --git a/src/Drawie.RenderApi.OpenGl/OpenGlGraphicsContext.cs b/src/Drawie.RenderApi.OpenGl/OpenGlGraphicsContext.cs new file mode 100644 index 0000000..88e9d4a --- /dev/null +++ b/src/Drawie.RenderApi.OpenGl/OpenGlGraphicsContext.cs @@ -0,0 +1,29 @@ +using Drawie.RenderApi.Abstraction.Textures; +using Silk.NET.Core.Contexts; +using Silk.NET.OpenGL; + +namespace Drawie.RenderApi.OpenGL; + +public class OpenGlGraphicsContext(GL api, IGLContext glContext) : IGraphicsContext +{ + public readonly IGLContext GlContext = glContext; + public readonly GL Api = api; + private Dictionary ownedTextures = new Dictionary(); + + public void MakeCurrent() + { + GlContext.MakeCurrent(); + } + + public OpenGlTexture CreateTexture(uint id, int width, int height) + { + var tex = new OpenGlTexture(id, Api, width, height); + ownedTextures.Add(id, tex); + return tex; + } + + public void AddManagedTexture(IOpenGlTexture texture) + { + ownedTextures.Add(texture.TextureId, texture); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.OpenGl/OpenGlWindowRenderApi.cs b/src/Drawie.RenderApi.OpenGl/OpenGlHostViewRenderApi.cs similarity index 57% rename from src/Drawie.RenderApi.OpenGl/OpenGlWindowRenderApi.cs rename to src/Drawie.RenderApi.OpenGl/OpenGlHostViewRenderApi.cs index c13cc9a..78e13db 100644 --- a/src/Drawie.RenderApi.OpenGl/OpenGlWindowRenderApi.cs +++ b/src/Drawie.RenderApi.OpenGl/OpenGlHostViewRenderApi.cs @@ -1,22 +1,28 @@ -using System.Drawing; -using Drawie.Numerics; -using Drawie.RenderApi.OpenGL.Exceptions; +using Drawie.Numerics; +using Drawie.RenderApi.Abstraction.Textures; using Silk.NET.Core.Contexts; using Silk.NET.OpenGL; namespace Drawie.RenderApi.OpenGL; -public class OpenGlWindowRenderApi : IOpenGlWindowRenderApi +public class OpenGlHostViewRenderApi : IOpenGlHostViewRenderApi { public event Action? FramebufferResized; public ITexture RenderTexture => texture; + Func IOpenGlHostViewRenderApi.GetGlInterface() + { + return name => Context.GetProcAddress(name); + } + public IGLContext Context { get; private set; } private GL Api { get; set; } private OpenGlTexture texture; + public IGraphicsContext GraphicsContext { get; private set; } + public unsafe void CreateInstance(object contextObject, VecI framebufferSize) { if (contextObject is not IGLContext glContext) @@ -24,12 +30,15 @@ public unsafe void CreateInstance(object contextObject, VecI framebufferSize) Context = glContext; Api = GL.GetApi(glContext); - texture = new OpenGlTexture(0, Api); // default framebuffer texture + var graphicsContext = new OpenGlGraphicsContext(Api, glContext); + texture = graphicsContext.CreateTexture(0, framebufferSize.X, framebufferSize.Y); // default framebuffer texture + GraphicsContext = graphicsContext; } public void DestroyInstance() { Api = null; + GraphicsContext = null; } public void UpdateFramebufferSize(int width, int height) diff --git a/src/Drawie.RenderApi.OpenGl/OpenGlPipeline.cs b/src/Drawie.RenderApi.OpenGl/OpenGlPipeline.cs new file mode 100644 index 0000000..374d530 --- /dev/null +++ b/src/Drawie.RenderApi.OpenGl/OpenGlPipeline.cs @@ -0,0 +1,77 @@ +using System.Drawing; +using System.Windows.Input; +using Drawie.Backend.Vertie.Core; +using Drawie.RenderApi.Abstraction.CommandRecording; +using Drawie.RenderApi.Abstraction.Pipeline; +using Silk.NET.OpenGL; + +namespace Drawie.RenderApi.OpenGL; + +public class OpenGlPipeline : IPipeline +{ + public GL Api { get; } + public PipelineDesc Description { get; } + + public OpenGlPipeline(PipelineDesc description, GL api) + { + Description = description; + Api = api; + } + + public void Apply(ICommandList list) + { + Api.Viewport(new Rectangle(Description.Viewport.X, Description.Viewport.Y, Description.Viewport.Width, + Description.Viewport.Height)); + + if (Description.Blend.Enabled) + { + Api.Enable(EnableCap.Blend); + } + else + { + Api.Disable(EnableCap.Blend); + } + + if (Description.Depth.Enabled) + { + Api.Enable(EnableCap.DepthTest); + Api.DepthFunc(ToOpenGlDesc(Description.Depth.DepthCompare)); + Api.DepthMask(true); + + Api.ClearDepth(1.0); + } + else + { + Api.Disable(EnableCap.DepthTest); + } + + if (Description.Rasterizer.Samples != 1) + { + Api.Enable(EnableCap.Multisample); + } + + Api.Enable(EnableCap.CullFace); + Api.CullFace(TriangleFace.Back); + Api.ClearColor(0, 0, 0, 1); + Api.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit); + Api.PolygonMode(TriangleFace.FrontAndBack, Description.Rasterizer.RenderMode == RenderMode.Wireframe ? PolygonMode.Line : PolygonMode.Fill); + + Description.ShaderProgram?.Use(); + } + + private DepthFunction ToOpenGlDesc(DepthCompareType depthDepthCompare) + { + return depthDepthCompare switch + { + DepthCompareType.Less => DepthFunction.Less, + DepthCompareType.LessEqual => DepthFunction.Lequal, + DepthCompareType.Equal => DepthFunction.Equal, + DepthCompareType.Greater => DepthFunction.Greater, + DepthCompareType.GreaterEqual => DepthFunction.Gequal, + DepthCompareType.Always => DepthFunction.Always, + DepthCompareType.Never => DepthFunction.Never, + DepthCompareType.NotEqual => DepthFunction.Notequal, + _ => throw new ArgumentOutOfRangeException(nameof(depthDepthCompare), depthDepthCompare, null) + }; + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.OpenGl/OpenGlRenderApi.cs b/src/Drawie.RenderApi.OpenGl/OpenGlRenderApi.cs index 19b5c0d..b0b1b13 100644 --- a/src/Drawie.RenderApi.OpenGl/OpenGlRenderApi.cs +++ b/src/Drawie.RenderApi.OpenGl/OpenGlRenderApi.cs @@ -1,10 +1,15 @@ -namespace Drawie.RenderApi.OpenGL; +using Drawie.RenderApi.Abstraction; +using Silk.NET.Core.Contexts; +using Silk.NET.OpenGL; + +namespace Drawie.RenderApi.OpenGL; public class OpenGlRenderApi : IOpenGlRenderApi { - private List windowRenderApis = new List(); + private List windowRenderApis = new List(); - public IReadOnlyCollection WindowRenderApis => windowRenderApis; + public IReadOnlyCollection WindowRenderApis => windowRenderApis; + public IGraphicsDevice GraphicsDevice { get; private set; } public IOpenGlContext OpenGlContext { @@ -30,13 +35,29 @@ public OpenGlRenderApi() public OpenGlRenderApi(IOpenGlContext context) { this.context = context; + CreateGraphicsDevice(context); } - public IWindowRenderApi CreateWindowRenderApi() + public IHostViewRenderApi CreateWindowRenderApi() { - OpenGlWindowRenderApi renderApi = new OpenGlWindowRenderApi(); + OpenGlHostViewRenderApi renderApi = new OpenGlHostViewRenderApi(); windowRenderApis.Add(renderApi); + if (GraphicsDevice == null) + { + CreateGraphicsDevice(OpenGlContext); + } + return renderApi; } + + private void CreateGraphicsDevice(IOpenGlContext context) + { + GraphicsDevice = new OpenGlDevice(context); + } + + public void Dispose() + { + GraphicsDevice.Dispose(); + } } diff --git a/src/Drawie.RenderApi.OpenGl/OpenGlRenderTarget.cs b/src/Drawie.RenderApi.OpenGl/OpenGlRenderTarget.cs new file mode 100644 index 0000000..44f1dc1 --- /dev/null +++ b/src/Drawie.RenderApi.OpenGl/OpenGlRenderTarget.cs @@ -0,0 +1,81 @@ +using Drawie.Numerics; +using Drawie.RenderApi.Abstraction.RenderTargets; +using Drawie.RenderApi.Abstraction.Textures; +using Silk.NET.OpenGL; + +namespace Drawie.RenderApi.OpenGL; + +public sealed class OpenGlRenderTarget : IDisposable, IRenderTarget +{ + public OpenGlTexture Color { get; } + public OpenGlDepthBuffer? Depth { get; } + + public ulong SurfaceId { get; } + public VecI Size => new VecI(Width, Height); + + public int Width => Color.Width; + public int Height => Color.Height; + + private GL Api { get; } + + public OpenGlRenderTarget( + GL api, + TextureDesc desc) + { + Api = api; + + Color = new OpenGlTexture( + api, + desc.Width, + desc.Height, desc.Samples); + + if (desc.Depth != DepthFormat.NoDepth) + Depth = new OpenGlDepthBuffer( + api, + desc.Width, + desc.Height, desc.Depth, desc.Samples); + + SurfaceId = Api.GenFramebuffer(); + + Api.BindFramebuffer( + FramebufferTarget.Framebuffer, + (uint)SurfaceId); + + var target = desc.Samples == 1 ? TextureTarget.Texture2D : TextureTarget.Texture2DMultisample; + Api.FramebufferTexture2D( + FramebufferTarget.Framebuffer, + FramebufferAttachment.ColorAttachment0, + target, + (uint)Color.TextureId, + 0); + + if (Depth != null) + { + Api.FramebufferRenderbuffer( + FramebufferTarget.Framebuffer, + FramebufferAttachment.DepthStencilAttachment, + RenderbufferTarget.Renderbuffer, + Depth.RenderbufferId); + } + + var status = Api.CheckFramebufferStatus( + FramebufferTarget.Framebuffer); + if (status != GLEnum.FramebufferComplete) + { + throw new InvalidOperationException( + $"OpenGL framebuffer is incomplete: {status}"); + } + + Api.BindFramebuffer( + FramebufferTarget.Framebuffer, + 0); + } + + public void Dispose() + { + Api.DeleteFramebuffer((uint)SurfaceId); + + Depth?.Dispose(); + Color.Dispose(); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.OpenGl/OpenGlSampler.cs b/src/Drawie.RenderApi.OpenGl/OpenGlSampler.cs new file mode 100644 index 0000000..c92991d --- /dev/null +++ b/src/Drawie.RenderApi.OpenGl/OpenGlSampler.cs @@ -0,0 +1,33 @@ +using Drawie.RenderApi.Abstraction.Textures; +using Silk.NET.OpenGL; + +namespace Drawie.RenderApi.OpenGL; + +public class OpenGlSampler : ISampler +{ + public uint Handle { get; } + + public OpenGlSampler(GL api) + { + Handle = api.CreateSampler(); + api.SamplerParameter( + Handle, + SamplerParameterI.MinFilter, + (int)TextureMinFilter.Linear); + + api.SamplerParameter( + Handle, + SamplerParameterI.MagFilter, + (int)TextureMagFilter.Linear); + + api.SamplerParameter( + Handle, + SamplerParameterI.WrapS, + (int)TextureWrapMode.Repeat); + + api.SamplerParameter( + Handle, + SamplerParameterI.WrapT, + (int)TextureWrapMode.Repeat); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.OpenGl/OpenGlShaderProgram.cs b/src/Drawie.RenderApi.OpenGl/OpenGlShaderProgram.cs new file mode 100644 index 0000000..7751bfa --- /dev/null +++ b/src/Drawie.RenderApi.OpenGl/OpenGlShaderProgram.cs @@ -0,0 +1,106 @@ +using System.Runtime.InteropServices; +using Drawie.Backend.Shaders.Common; +using Drawie.RenderApi.Abstraction.Shaders; +using Silk.NET.OpenGL; + +namespace Drawie.RenderApi.OpenGL; + +public class OpenGlShaderProgram : IShaderProgram +{ + public uint ProgramHandle { get; } + public GL Api { get; } + + private Dictionary uniformBlockUbos = new Dictionary(); + + public OpenGlShaderProgram(GL gl, uint programHandle) + { + ProgramHandle = programHandle; + Api = gl; + } + + public void Use() + { + Api.UseProgram(ProgramHandle); + } + + public unsafe void UpdateUniforms(List uniformBlocks) + { + uint bindingPoint = 0; + + foreach (var uniformBlock in uniformBlocks) + { + int blockIndex = uniformBlock.ShaderLayout.Index; + int blockSize = uniformBlock.ShaderLayout.Size; + + if (blockIndex == int.MaxValue) + continue; + + Api.UniformBlockBinding( + ProgramHandle, + (uint)blockIndex, + bindingPoint); + + if (!uniformBlockUbos.TryGetValue( + uniformBlock.Name, + out uint ubo)) + { + ubo = Api.GenBuffer(); + uniformBlockUbos.Add( + uniformBlock.Name, + ubo); + } + + Api.BindBuffer( + BufferTargetARB.UniformBuffer, + ubo); + + Api.BufferData( + BufferTargetARB.UniformBuffer, + (nuint)blockSize, + null, + BufferUsageARB.DynamicDraw); + + Api.BindBufferBase( + BufferTargetARB.UniformBuffer, + bindingPoint, + ubo); + + // Upload individual properties. + for (var i = 0; i < uniformBlock.Properties.Count; i++) + { + var property = uniformBlock.Properties[i]; + UploadProperty(property.ObjValue, uniformBlock.ShaderLayout.UniformProperties[i]); + } + + Api.BindBuffer( + BufferTargetARB.UniformBuffer, + 0); + + bindingPoint++; + } + } + + private unsafe void UploadProperty( + object value, + PropertyLayout layout) + { + int size = layout.Size; + + GCHandle handle = GCHandle.Alloc( + value, + GCHandleType.Pinned); + + try + { + Api.BufferSubData( + BufferTargetARB.UniformBuffer, + layout.Offset, + (nuint)size, + (void*)handle.AddrOfPinnedObject()); + } + finally + { + handle.Free(); + } + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.OpenGl/OpenGlTexture.cs b/src/Drawie.RenderApi.OpenGl/OpenGlTexture.cs index d21dbd4..7e86708 100644 --- a/src/Drawie.RenderApi.OpenGl/OpenGlTexture.cs +++ b/src/Drawie.RenderApi.OpenGl/OpenGlTexture.cs @@ -4,46 +4,126 @@ namespace Drawie.RenderApi.OpenGL; public class OpenGlTexture : IOpenGlTexture, IDisposable { - public uint TextureId { get; } + public ulong TextureId { get; } - private GL Api { get; set; } - - public OpenGlTexture(uint textureId, GL api) + public int Width { get; } + public int Height { get; } + public int Samples { get; } + + private GL Api { get; } + + public OpenGlTexture(uint textureId, GL api, int width, int height) { TextureId = textureId; Api = api; + Width = width; + Height = height; + Samples = 1; } - public unsafe OpenGlTexture(GL api, int width, int height) + public unsafe OpenGlTexture(GL api, int width, int height, int samples) { - TextureId = api.GenTexture(); + Api = api; + + Width = width; + Height = height; + Samples = samples; + + TextureId = Api.GenTexture(); + Activate(0); + Bind(); + + if (samples == 1) + { + Api.TexImage2D( + TextureTarget.Texture2D, + 0, + InternalFormat.Rgba, + (uint)width, + (uint)height, + 0, + PixelFormat.Rgba, + PixelType.UnsignedByte, + null); + } + else + { + Api.TexImage2DMultisample( + TextureTarget.Texture2DMultisample, + (uint)samples, + InternalFormat.Rgb, + (uint)width, + (uint)height, true); + } + + ApplyParameters(); + } + + public unsafe OpenGlTexture(GL api, int width, int height, Span data, PixelFormat format = PixelFormat.Rgba) + { Api = api; + + Width = width; + Height = height; + + TextureId = Api.GenTexture(); + Activate(0); Bind(); + + LoadTextureFromBytes(data, format); - Api.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba, (uint)width, (uint)height, 0, - PixelFormat.Rgba, - PixelType.UnsignedByte, null); + ApplyParameters(); + } + + private void ApplyParameters() + { + Api.TexParameterI( + TextureTarget.Texture2D, + TextureParameterName.TextureWrapS, + (int)GLEnum.ClampToEdge); - Api.TexParameterI(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)GLEnum.Repeat); - Api.TexParameterI(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)GLEnum.Repeat); - Api.TexParameterI(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)GLEnum.Nearest); - Api.TexParameterI(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)GLEnum.Nearest); + Api.TexParameterI( + TextureTarget.Texture2D, + TextureParameterName.TextureWrapT, + (int)GLEnum.ClampToEdge); + + Api.TexParameterI( + TextureTarget.Texture2D, + TextureParameterName.TextureMinFilter, + (int)GLEnum.Nearest); + + Api.TexParameterI( + TextureTarget.Texture2D, + TextureParameterName.TextureMagFilter, + (int)GLEnum.Nearest); + } + + private unsafe void LoadTextureFromBytes(Span data, PixelFormat format) + { + fixed (void* d = &data[0]) + { + Api.TexImage2D(TextureTarget.Texture2D, 0, (int)InternalFormat.Rgba, (uint)Width, (uint)Height, 0, format, PixelType.UnsignedByte, d); + } } public void Bind() { - Api.BindTexture(TextureTarget.Texture2D, TextureId); + var target = Samples == 1 ? TextureTarget.Texture2D : TextureTarget.Texture2DMultisample; + Api.BindTexture( + target, + (uint)TextureId); } public void Activate(int textureUnit) { - Api.ActiveTexture(TextureUnit.Texture0 + textureUnit); + Api.ActiveTexture( + TextureUnit.Texture0 + textureUnit); } public void Dispose() { - Api.DeleteTexture(TextureId); + Api.DeleteTexture((uint)TextureId); } -} +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.OpenGl/OpenGlVertexArrayObject.cs b/src/Drawie.RenderApi.OpenGl/OpenGlVertexArrayObject.cs new file mode 100644 index 0000000..8dc2b3b --- /dev/null +++ b/src/Drawie.RenderApi.OpenGl/OpenGlVertexArrayObject.cs @@ -0,0 +1,27 @@ +using Drawie.RenderApi.Abstraction.Buffers; +using Silk.NET.OpenGL; + +namespace Drawie.RenderApi.OpenGL; + +public class OpenGlVertexArrayObject : IBufferGroup +{ + public uint Handle { get; } + + public GL Api { get; } + + private OpenGlBufferList bufferList; + + public OpenGlVertexArrayObject(GL api) + { + Api = api; + Handle = Api.GenVertexArray(); + bufferList = new OpenGlBufferList(); + } + + public void Open(Action list) + { + Api.BindVertexArray(Handle); + list(bufferList); + Api.BindVertexArray(0); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.Vulkan/Buffers/BufferObject.cs b/src/Drawie.RenderApi.Vulkan/Buffers/BufferObject.cs index 09219f8..79b8beb 100644 --- a/src/Drawie.RenderApi.Vulkan/Buffers/BufferObject.cs +++ b/src/Drawie.RenderApi.Vulkan/Buffers/BufferObject.cs @@ -1,15 +1,17 @@ -using Drawie.RenderApi.Vulkan.Exceptions; +using Drawie.RenderApi.Abstraction.Buffers; +using Drawie.RenderApi.Vulkan.Exceptions; using Silk.NET.Vulkan; using Buffer = Silk.NET.Vulkan.Buffer; namespace Drawie.RenderApi.Vulkan.Buffers; -public class BufferObject : IDisposable +public class BufferObject : IDisposable, IBuffer { public ulong Size { get; set; } public Buffer VkBuffer => vkBuffer; public DeviceMemory VkBufferMemory => vkBufferMemory; + public BufferUsage Usage { get; } private Silk.NET.Vulkan.Buffer vkBuffer = default; private DeviceMemory vkBufferMemory = default; @@ -20,12 +22,13 @@ public class BufferObject : IDisposable protected unsafe BufferObject(Vk vk, Device device, PhysicalDevice physicalDevice, ulong size, BufferUsageFlags usage, - MemoryPropertyFlags properties) + MemoryPropertyFlags properties, BufferUsage purpose) { Size = size; this.vk = vk; this.device = device; this.physicalDevice = physicalDevice; + Usage = purpose; BufferCreateInfo bufferInfo = new() { @@ -87,4 +90,5 @@ public unsafe void Dispose() vk!.DestroyBuffer(device, vkBuffer, null); vk!.FreeMemory(device, vkBufferMemory, null); } + } \ No newline at end of file diff --git a/src/Drawie.RenderApi.Vulkan/Buffers/IndexBuffer.cs b/src/Drawie.RenderApi.Vulkan/Buffers/IndexBuffer.cs index 5d812a6..64499fc 100644 --- a/src/Drawie.RenderApi.Vulkan/Buffers/IndexBuffer.cs +++ b/src/Drawie.RenderApi.Vulkan/Buffers/IndexBuffer.cs @@ -1,11 +1,12 @@ -using Silk.NET.Vulkan; +using Drawie.RenderApi.Abstraction.Buffers; +using Silk.NET.Vulkan; namespace Drawie.RenderApi.Vulkan.Buffers; public class IndexBuffer : BufferObject { public IndexBuffer(Vk vk, Device device, PhysicalDevice physicalDevice, ulong size) - : base(vk, device, physicalDevice, size, BufferUsageFlags.TransferDstBit | BufferUsageFlags.IndexBufferBit, MemoryPropertyFlags.DeviceLocalBit) + : base(vk, device, physicalDevice, size, BufferUsageFlags.TransferDstBit | BufferUsageFlags.IndexBufferBit, MemoryPropertyFlags.DeviceLocalBit, BufferUsage.Index) { } diff --git a/src/Drawie.RenderApi.Vulkan/Buffers/StagingBuffer.cs b/src/Drawie.RenderApi.Vulkan/Buffers/StagingBuffer.cs index 3bf0c74..63958d8 100644 --- a/src/Drawie.RenderApi.Vulkan/Buffers/StagingBuffer.cs +++ b/src/Drawie.RenderApi.Vulkan/Buffers/StagingBuffer.cs @@ -1,4 +1,5 @@ -using Silk.NET.Vulkan; +using Drawie.RenderApi.Abstraction.Buffers; +using Silk.NET.Vulkan; namespace Drawie.RenderApi.Vulkan.Buffers; @@ -9,7 +10,7 @@ public StagingBuffer(VulkanContext context, ulong size) : this(context.Api!, con } public StagingBuffer(Vk vk, Device device, PhysicalDevice physicalDevice, ulong size) : base(vk, device, physicalDevice, size, BufferUsageFlags.TransferSrcBit, -MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit) +MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit, BufferUsage.Transfer) { } } \ No newline at end of file diff --git a/src/Drawie.RenderApi.Vulkan/Buffers/UniformBuffer.cs b/src/Drawie.RenderApi.Vulkan/Buffers/UniformBuffer.cs index 9dc4339..05a079d 100644 --- a/src/Drawie.RenderApi.Vulkan/Buffers/UniformBuffer.cs +++ b/src/Drawie.RenderApi.Vulkan/Buffers/UniformBuffer.cs @@ -1,10 +1,11 @@ -using Silk.NET.Vulkan; +using Drawie.RenderApi.Abstraction.Buffers; +using Silk.NET.Vulkan; namespace Drawie.RenderApi.Vulkan.Buffers; public class UniformBuffer : BufferObject { - public UniformBuffer(Vk vk, Device device, PhysicalDevice physicalDevice, ulong size) : base(vk, device, physicalDevice, size, BufferUsageFlags.UniformBufferBit, MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit) + public UniformBuffer(Vk vk, Device device, PhysicalDevice physicalDevice, ulong size) : base(vk, device, physicalDevice, size, BufferUsageFlags.UniformBufferBit, MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit, BufferUsage.Uniform) { } diff --git a/src/Drawie.RenderApi.Vulkan/Buffers/VertexBuffer.cs b/src/Drawie.RenderApi.Vulkan/Buffers/VertexBuffer.cs index 4bc26d5..2fe54b5 100644 --- a/src/Drawie.RenderApi.Vulkan/Buffers/VertexBuffer.cs +++ b/src/Drawie.RenderApi.Vulkan/Buffers/VertexBuffer.cs @@ -1,10 +1,11 @@ -using Silk.NET.Vulkan; +using Drawie.RenderApi.Abstraction.Buffers; +using Silk.NET.Vulkan; namespace Drawie.RenderApi.Vulkan.Buffers; public class VertexBuffer : BufferObject { - public VertexBuffer(Vk vk, Device device, PhysicalDevice physicalDevice, ulong size) : base(vk, device, physicalDevice, size, BufferUsageFlags.TransferDstBit | BufferUsageFlags.VertexBufferBit, MemoryPropertyFlags.DeviceLocalBit) + public VertexBuffer(Vk vk, Device device, PhysicalDevice physicalDevice, ulong size) : base(vk, device, physicalDevice, size, BufferUsageFlags.TransferDstBit | BufferUsageFlags.VertexBufferBit, MemoryPropertyFlags.DeviceLocalBit, BufferUsage.Vertex) { } diff --git a/src/Drawie.RenderApi.Vulkan/Buffers/VulkanImageAttachment.cs b/src/Drawie.RenderApi.Vulkan/Buffers/VulkanImageAttachment.cs new file mode 100644 index 0000000..7d0969a --- /dev/null +++ b/src/Drawie.RenderApi.Vulkan/Buffers/VulkanImageAttachment.cs @@ -0,0 +1,328 @@ +using Drawie.RenderApi.Vulkan.Exceptions; +using Drawie.RenderApi.Vulkan.Helpers; +using Silk.NET.Vulkan; +using Image = Silk.NET.Vulkan.Image; + +namespace Drawie.RenderApi.Vulkan.Buffers; + +public sealed unsafe class VulkanImageAttachment : IDisposable +{ + private readonly Vk vk; + private readonly Device device; + private readonly PhysicalDevice physicalDevice; + private readonly CommandPool commandPool; + private readonly Queue graphicsQueue; + + public Image Image { get; private set; } + public DeviceMemory Memory { get; private set; } + public ImageView View { get; private set; } + + public Format Format { get; } + public uint Width { get; } + public uint Height { get; } + public ImageAspectFlags AspectMask { get; } + public ImageUsageFlags Usage { get; } + public ImageLayout Layout { get; private set; } = ImageLayout.Undefined; + public SampleCountFlags Samples { get; private set; } + + + public VulkanImageAttachment( + Vk vk, + Device device, + PhysicalDevice physicalDevice, + CommandPool commandPool, + Queue graphicsQueue, + uint width, + uint height, + Format format, + ImageUsageFlags usage, + ImageAspectFlags aspectMask, SampleCountFlags samples) + { + this.vk = vk; + this.device = device; + this.physicalDevice = physicalDevice; + this.commandPool = commandPool; + this.graphicsQueue = graphicsQueue; + + Width = width; + Height = height; + Format = format; + Usage = usage; + AspectMask = aspectMask; + Samples = samples; + + CreateImage(); + AllocateMemory(); + CreateView(); + } + + private void CreateImage() + { + ImageCreateInfo imageInfo = new() + { + SType = StructureType.ImageCreateInfo, + ImageType = ImageType.Type2D, + Extent = new Extent3D(Width, Height, 1), + MipLevels = 1, + ArrayLayers = 1, + Format = Format, + Tiling = ImageTiling.Optimal, + InitialLayout = ImageLayout.Undefined, + Usage = Usage, + Samples = Samples, + SharingMode = SharingMode.Exclusive + }; + + if (vk.CreateImage(device, &imageInfo, null, out var img) != Result.Success) + throw new VulkanException("Failed to create image attachment."); + + Image = img; + } + + private void AllocateMemory() + { + vk.GetImageMemoryRequirements(device, Image, out var requirements); + + MemoryAllocateInfo allocInfo = new() + { + SType = StructureType.MemoryAllocateInfo, + AllocationSize = requirements.Size, + MemoryTypeIndex = BufferObject.FindMemoryType( + vk, + physicalDevice, + requirements.MemoryTypeBits, + MemoryPropertyFlags.DeviceLocalBit) + }; + + if (vk.AllocateMemory(device, &allocInfo, null, out var memory) != Result.Success) + throw new VulkanException("Failed to allocate image attachment memory."); + + Memory = memory; + + if (vk.BindImageMemory(device, Image, Memory, 0) != Result.Success) + throw new VulkanException("Failed to bind image attachment memory."); + } + + private void CreateView() + { + ImageViewCreateInfo viewInfo = new() + { + SType = StructureType.ImageViewCreateInfo, + Image = Image, + ViewType = ImageViewType.Type2D, + Format = Format, + SubresourceRange = new ImageSubresourceRange + { + AspectMask = AspectMask, + BaseMipLevel = 0, + LevelCount = 1, + BaseArrayLayer = 0, + LayerCount = 1 + } + }; + + if (vk.CreateImageView(device, &viewInfo, null, out var view) != Result.Success) + throw new VulkanException("Failed to create image attachment view."); + + View = view; + } + + public void TransitionLayout( + ImageLayout newLayout, + CommandBuffer? commandBuffer = null) + { + if (commandBuffer.HasValue) + { + TransitionLayout( + commandBuffer.Value, + Layout, + newLayout); + + Layout = newLayout; + return; + } + + using var session = new SingleTimeCommandBufferSession( + vk, + commandPool, + device, + graphicsQueue); + + TransitionLayout( + session.CommandBuffer, + Layout, + newLayout); + + Layout = newLayout; + } + + public void TransitionLayout( + ImageLayout oldLayout, + ImageLayout newLayout, + CommandBuffer? commandBuffer = null) + { + if (commandBuffer.HasValue) + { + TransitionLayout( + commandBuffer.Value, + oldLayout, + newLayout); + + Layout = newLayout; + return; + } + + using var session = new SingleTimeCommandBufferSession( + vk, + commandPool, + device, + graphicsQueue); + + TransitionLayout( + session.CommandBuffer, + oldLayout, + newLayout); + + Layout = newLayout; + } + + + private void TransitionLayout( + CommandBuffer commandBuffer, + ImageLayout oldLayout, + ImageLayout newLayout) + { + var barrier = new ImageMemoryBarrier + { + SType = StructureType.ImageMemoryBarrier, + OldLayout = oldLayout, + NewLayout = newLayout, + SrcQueueFamilyIndex = Vk.QueueFamilyIgnored, + DstQueueFamilyIndex = Vk.QueueFamilyIgnored, + Image = Image, + SubresourceRange = new ImageSubresourceRange + { + AspectMask = AspectMask, + BaseMipLevel = 0, + LevelCount = 1, + BaseArrayLayer = 0, + LayerCount = 1 + } + }; + + PipelineStageFlags sourceStage; + PipelineStageFlags destinationStage; + + switch (oldLayout) + { + case ImageLayout.Undefined: + barrier.SrcAccessMask = 0; + sourceStage = PipelineStageFlags.TopOfPipeBit; + break; + + case ImageLayout.ColorAttachmentOptimal: + barrier.SrcAccessMask = + AccessFlags.ColorAttachmentWriteBit; + sourceStage = + PipelineStageFlags.ColorAttachmentOutputBit; + break; + + case ImageLayout.DepthStencilAttachmentOptimal: + barrier.SrcAccessMask = + AccessFlags.DepthStencilAttachmentWriteBit; + sourceStage = + PipelineStageFlags.EarlyFragmentTestsBit | + PipelineStageFlags.LateFragmentTestsBit; + break; + + case ImageLayout.ShaderReadOnlyOptimal: + barrier.SrcAccessMask = + AccessFlags.ShaderReadBit; + sourceStage = + PipelineStageFlags.FragmentShaderBit; + break; + + case ImageLayout.PresentSrcKhr: + barrier.SrcAccessMask = 0; + sourceStage = + PipelineStageFlags.BottomOfPipeBit; + break; + + default: + barrier.SrcAccessMask = AccessFlags.MemoryReadBit; + sourceStage = PipelineStageFlags.BottomOfPipeBit; + break; + } + + switch (newLayout) + { + case ImageLayout.ColorAttachmentOptimal: + barrier.DstAccessMask = + AccessFlags.ColorAttachmentWriteBit; + destinationStage = + PipelineStageFlags.ColorAttachmentOutputBit; + break; + + case ImageLayout.DepthStencilAttachmentOptimal: + barrier.DstAccessMask = + AccessFlags.DepthStencilAttachmentReadBit | + AccessFlags.DepthStencilAttachmentWriteBit; + + destinationStage = + PipelineStageFlags.EarlyFragmentTestsBit | + PipelineStageFlags.LateFragmentTestsBit; + break; + + case ImageLayout.ShaderReadOnlyOptimal: + barrier.DstAccessMask = + AccessFlags.ShaderReadBit; + destinationStage = + PipelineStageFlags.FragmentShaderBit; + break; + + case ImageLayout.PresentSrcKhr: + barrier.DstAccessMask = 0; + destinationStage = + PipelineStageFlags.BottomOfPipeBit; + break; + + default: + barrier.DstAccessMask = AccessFlags.MemoryReadBit; + destinationStage = PipelineStageFlags.BottomOfPipeBit; + break; + } + + vk.CmdPipelineBarrier( + commandBuffer, + sourceStage, + destinationStage, + 0, + 0, + null, + 0, + null, + 1, + &barrier); + } + + public void Dispose() + { + if (View.Handle != 0) + { + vk.DestroyImageView(device, View, null); + View = default; + } + + if (Image.Handle != 0) + { + vk.DestroyImage(device, Image, null); + Image = default; + } + + if (Memory.Handle != 0) + { + vk.FreeMemory(device, Memory, null); + Memory = default; + } + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.Vulkan/Buffers/VulkanTexture.cs b/src/Drawie.RenderApi.Vulkan/Buffers/VulkanTexture.cs index 0430c96..cc03ec3 100644 --- a/src/Drawie.RenderApi.Vulkan/Buffers/VulkanTexture.cs +++ b/src/Drawie.RenderApi.Vulkan/Buffers/VulkanTexture.cs @@ -1,4 +1,5 @@ using Drawie.Numerics; +using Drawie.RenderApi.Abstraction.Textures; using Drawie.RenderApi.Vulkan.Exceptions; using Drawie.RenderApi.Vulkan.Helpers; using Silk.NET.Vulkan; @@ -9,9 +10,8 @@ namespace Drawie.RenderApi.Vulkan.Buffers; public class VulkanTexture : IDisposable, IVkTexture { - public ImageView ImageView { get; private set; } public Sampler Sampler => sampler; - public Image VkImage => textureImage; + public Image VkImage => colorAttachment.Image; private Vk Vk { get; } private Device LogicalDevice { get; } private PhysicalDevice PhysicalDevice { get; } @@ -21,20 +21,39 @@ public class VulkanTexture : IDisposable, IVkTexture private Queue GraphicsQueue { get; } public uint QueueFamily { get; } = 0; public uint ImageFormat { get; private set; } - public ulong ImageHandle => textureImage.Handle; + public ulong ImageHandle => colorAttachment.Image.Handle; public uint Tiling { get; } public uint UsageFlags { get; set; } - public uint Layout => ColorAttachmentOptimal; + public uint Layout => (uint)ImageLayout.ColorAttachmentOptimal; public uint TargetSharingMode { get; } = (uint)SharingMode.Exclusive; - public static uint ColorAttachmentOptimal => (uint)ImageLayout.ColorAttachmentOptimal; - public static uint ShaderReadOnlyOptimal => (uint)ImageLayout.ShaderReadOnlyOptimal; + ulong ITexture.TextureId => ImageHandle; + public uint Width { get; } + public uint Height { get; } - private Image textureImage; - private DeviceMemory textureImageMemory; + public VulkanImageAttachment ColorAttachment => colorAttachment; + public VulkanImageAttachment? DepthAttachment => depthAttachment; + public VulkanImageAttachment? MsaaResolvedColorAttachment => msaaResolvedColorAttachment; + + public uint Attachments + { + get + { + uint count = 1; + if (DepthAttachment != null) count++; + if (MsaaResolvedColorAttachment != null) count++; + return count; + } + } + + private VulkanImageAttachment colorAttachment; + private VulkanImageAttachment? depthAttachment; + private VulkanImageAttachment? msaaResolvedColorAttachment; private Sampler sampler; - - public unsafe VulkanTexture(Vk vk, Device logicalDevice, PhysicalDevice physicalDevice, CommandPool commandPool, - Queue graphicsQueue, uint queueFamily, VecI size) + private bool isExternalSampler = false; + + + public VulkanTexture(Vk vk, Device logicalDevice, PhysicalDevice physicalDevice, CommandPool commandPool, + Queue graphicsQueue, uint queueFamily, TextureDesc desc, Sampler? sampler = null) { Vk = vk; LogicalDevice = logicalDevice; @@ -42,44 +61,112 @@ public unsafe VulkanTexture(Vk vk, Device logicalDevice, PhysicalDevice physical CommandPool = commandPool; GraphicsQueue = graphicsQueue; QueueFamily = queueFamily; + ImageFormat = (uint)ToVkFormat(desc.Format); + Tiling = (uint)ImageTiling.Optimal; + UsageFlags = (uint)(ImageUsageFlags.SampledBit | ImageUsageFlags.TransferSrcBit | + ImageUsageFlags.TransferDstBit | ImageUsageFlags.ColorAttachmentBit); + Width = (uint)desc.Width; + Height = (uint)desc.Height; + + colorAttachment = new VulkanImageAttachment( + Vk, + LogicalDevice, + PhysicalDevice, + CommandPool, + GraphicsQueue, + (uint)desc.Width, + (uint)desc.Height, + ToVkFormat(desc.Format), + ImageUsageFlags.SampledBit | + ImageUsageFlags.TransferSrcBit | + ImageUsageFlags.TransferDstBit | + ImageUsageFlags.ColorAttachmentBit, + ImageAspectFlags.ColorBit, FormatExtensions.ToSampleFlags(desc.Samples)); + + colorAttachment.TransitionLayout( + ImageLayout.ColorAttachmentOptimal); + + if (desc.Depth != DepthFormat.NoDepth) + { + var depthFormat = desc.Depth.ToVkFormat(); + + depthAttachment = new VulkanImageAttachment( + Vk, + LogicalDevice, + PhysicalDevice, + CommandPool, + GraphicsQueue, + (uint)desc.Width, + (uint)desc.Height, + depthFormat, + ImageUsageFlags.DepthStencilAttachmentBit, + ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit, FormatExtensions.ToSampleFlags(desc.Samples)); + + depthAttachment.TransitionLayout(ImageLayout.DepthStencilAttachmentOptimal); + } - /*var imageSize = (ulong)size.X * (ulong)size.Y * 4; - - using var stagingBuffer = new StagingBuffer(vk, logicalDevice, physicalDevice, imageSize); + if (desc.Samples > 1) + { + msaaResolvedColorAttachment = new VulkanImageAttachment( + Vk, + LogicalDevice, + PhysicalDevice, + CommandPool, + GraphicsQueue, + (uint)desc.Width, + (uint)desc.Height, + ToVkFormat(desc.Format), + ImageUsageFlags.SampledBit | + ImageUsageFlags.TransferSrcBit | + ImageUsageFlags.TransferDstBit | + ImageUsageFlags.ColorAttachmentBit, + ImageAspectFlags.ColorBit, SampleCountFlags.Count1Bit); + + msaaResolvedColorAttachment.TransitionLayout(ImageLayout.ColorAttachmentOptimal); + } - void* data; - vk!.MapMemory(LogicalDevice, stagingBuffer.VkBufferMemory, 0, imageSize, 0, &data); - image.CopyPixelDataTo(new Span(data, (int)imageSize)); - vk!.UnmapMemory(LogicalDevice, stagingBuffer.VkBufferMemory);*/ + if (sampler != null) + { + this.sampler = sampler.Value; + isExternalSampler = true; + } + else + { + CreateSampler(); + } + } - ImageFormat = (uint)Format.R8G8B8A8Unorm; - Tiling = (uint)ImageTiling.Optimal; - UsageFlags = (uint)(ImageUsageFlags.SampledBit | ImageUsageFlags.TransferSrcBit | ImageUsageFlags.TransferDstBit | ImageUsageFlags.ColorAttachmentBit); - CreateImage((uint)size.X, (uint)size.Y, (Format)ImageFormat, (ImageTiling)Tiling, - (ImageUsageFlags)UsageFlags, MemoryPropertyFlags.DeviceLocalBit); + private Format ToVkFormat(TextureFormat descFormat) + { + return descFormat switch + { + TextureFormat.RGBA8_Unorm => Format.R8G8B8A8Unorm, + _ => throw new ArgumentOutOfRangeException(nameof(descFormat), descFormat, null) + }; + } - /*TransitionImageLayout(textureImage, Format.R8G8B8A8Srgb, ImageLayout.Undefined, ImageLayout.TransferDstOptimal); - CopyBufferToImage(stagingBuffer.VkBuffer, textureImage, (uint)size.X, (uint)size.Y); - TransitionImageLayout(textureImage, Format.R8G8B8A8Srgb, ImageLayout.TransferDstOptimal, - ImageLayout.ShaderReadOnlyOptimal);*/ - - TransitionImageLayout(textureImage, (Format)ImageFormat, ImageLayout.Undefined, ImageLayout.ColorAttachmentOptimal); - ImageView = ImageUtility.CreateViewForImage(Vk, LogicalDevice, textureImage, Format.R8G8B8A8Unorm); - - CreateSampler(); - } - - public void MakeReadOnly() { - TransitionLayoutTo(ColorAttachmentOptimal, ShaderReadOnlyOptimal); + colorAttachment.TransitionLayout(ImageLayout.ShaderReadOnlyOptimal); + } + + public void MakeReadOnly(CommandBuffer cmdBuffer) + { + colorAttachment.TransitionLayout(ImageLayout.ShaderReadOnlyOptimal, cmdBuffer); } public void MakeWriteable() { - TransitionLayoutTo(ShaderReadOnlyOptimal, ColorAttachmentOptimal); + colorAttachment.TransitionLayout(ImageLayout.ColorAttachmentOptimal); + } + + public event Action? Disposing; + + public void MakeWriteable(CommandBuffer cmdBuffer) + { + colorAttachment.TransitionLayout(ImageLayout.ColorAttachmentOptimal, cmdBuffer); } private unsafe void CreateSampler() @@ -93,7 +180,7 @@ private unsafe void CreateSampler() AddressModeV = SamplerAddressMode.Repeat, AddressModeW = SamplerAddressMode.Repeat, AnisotropyEnable = false, - MaxAnisotropy = 1, + MaxAnisotropy = 1, BorderColor = BorderColor.IntOpaqueBlack, UnnormalizedCoordinates = false, CompareEnable = false, @@ -103,7 +190,7 @@ private unsafe void CreateSampler() MinLod = 0, MaxLod = 0 }; - + fixed (Sampler* samplerPtr = &sampler) { if (Vk.CreateSampler(LogicalDevice, &samplerCreateInfo, null, samplerPtr) != Result.Success) @@ -111,121 +198,6 @@ private unsafe void CreateSampler() } } - private unsafe void CreateImage(uint width, uint height, Format format, ImageTiling tiling, ImageUsageFlags usage, - MemoryPropertyFlags properties) - { - ImageCreateInfo imageInfo = new() - { - SType = StructureType.ImageCreateInfo, - ImageType = ImageType.Type2D, - Extent = new Extent3D(width, height, 1), - MipLevels = 1, - ArrayLayers = 1, - Format = format, - Tiling = tiling, - InitialLayout = ImageLayout.Undefined, - Usage = usage, - Samples = SampleCountFlags.Count1Bit, - SharingMode = SharingMode.Exclusive - }; - - fixed (Image* imagePtr = &textureImage) - { - if (Vk.CreateImage(LogicalDevice, &imageInfo, null, imagePtr) != Result.Success) - throw new VulkanException("Failed to create an image."); - } - - Vk.GetImageMemoryRequirements(LogicalDevice, textureImage, out var memRequirements); - - MemoryAllocateInfo allocInfo = new() - { - SType = StructureType.MemoryAllocateInfo, - AllocationSize = memRequirements.Size, - MemoryTypeIndex = - BufferObject.FindMemoryType(Vk, PhysicalDevice, memRequirements.MemoryTypeBits, properties) - }; - - fixed (DeviceMemory* memoryPtr = &textureImageMemory) - { - if (Vk.AllocateMemory(LogicalDevice, &allocInfo, null, memoryPtr) != Result.Success) - throw new VulkanException("Failed to allocate image memory."); - } - - Vk.BindImageMemory(LogicalDevice, textureImage, textureImageMemory, 0); - } - - private unsafe void TransitionImageLayout(Image image, Format format, ImageLayout oldLayout, ImageLayout newLayout) - { - using var commandBuffer = new SingleTimeCommandBufferSession(Vk, CommandPool, LogicalDevice, GraphicsQueue); - - TransitionImageLayout(image, oldLayout, newLayout, commandBuffer.CommandBuffer); - } - - private unsafe void TransitionImageLayout(Image image, ImageLayout oldLayout, ImageLayout newLayout, - CommandBuffer commandBuffer) - { - var barrier = new ImageMemoryBarrier() - { - SType = StructureType.ImageMemoryBarrier, - OldLayout = oldLayout, - NewLayout = newLayout, - SrcQueueFamilyIndex = Vk.QueueFamilyIgnored, - DstQueueFamilyIndex = Vk.QueueFamilyIgnored, - Image = image, - SubresourceRange = new ImageSubresourceRange() - { - AspectMask = ImageAspectFlags.ColorBit, - BaseMipLevel = 0, - LevelCount = 1, - BaseArrayLayer = 0, - LayerCount = 1 - } - }; - - PipelineStageFlags sourceStage; - PipelineStageFlags destinationStage; - - if (oldLayout == ImageLayout.Undefined) - { - barrier.SrcAccessMask = 0; - sourceStage = PipelineStageFlags.TopOfPipeBit; - } - else if (oldLayout == ImageLayout.ColorAttachmentOptimal) - { - barrier.SrcAccessMask = AccessFlags.ColorAttachmentWriteBit; - sourceStage = PipelineStageFlags.ColorAttachmentOutputBit; - } - else if (oldLayout == ImageLayout.ShaderReadOnlyOptimal) - { - barrier.SrcAccessMask = AccessFlags.ShaderReadBit; - sourceStage = PipelineStageFlags.FragmentShaderBit; - } - else - { - barrier.SrcAccessMask = AccessFlags.MemoryReadBit; - sourceStage = PipelineStageFlags.BottomOfPipeBit; - } - - if (newLayout == ImageLayout.ColorAttachmentOptimal) - { - barrier.DstAccessMask = AccessFlags.ColorAttachmentWriteBit; - destinationStage = PipelineStageFlags.ColorAttachmentOutputBit; - } - else if (newLayout == ImageLayout.ShaderReadOnlyOptimal) - { - barrier.DstAccessMask = AccessFlags.ShaderReadBit; - destinationStage = PipelineStageFlags.FragmentShaderBit; - } - else - { - barrier.DstAccessMask = AccessFlags.MemoryReadBit; - destinationStage = PipelineStageFlags.BottomOfPipeBit; - } - - Vk.CmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0, null, 0, null, 1, - barrier); - } - private unsafe void CopyBufferToImage(Buffer buffer, Image image, uint width, uint height) { using var commandBuffer = new SingleTimeCommandBufferSession(Vk, CommandPool, LogicalDevice, GraphicsQueue); @@ -237,10 +209,7 @@ private unsafe void CopyBufferToImage(Buffer buffer, Image image, uint width, ui BufferImageHeight = 0, ImageSubresource = new ImageSubresourceLayers() { - AspectMask = ImageAspectFlags.ColorBit, - MipLevel = 0, - BaseArrayLayer = 0, - LayerCount = 1 + AspectMask = ImageAspectFlags.ColorBit, MipLevel = 0, BaseArrayLayer = 0, LayerCount = 1 }, ImageOffset = new Offset3D(0, 0, 0), ImageExtent = new Extent3D(width, height, 1) @@ -251,20 +220,13 @@ private unsafe void CopyBufferToImage(Buffer buffer, Image image, uint width, ui public unsafe void Dispose() { - Vk.DestroySampler(LogicalDevice, sampler, null); - Vk.DestroyImageView(LogicalDevice, ImageView, null); - - Vk.DestroyImage(LogicalDevice, textureImage, null); - Vk.FreeMemory(LogicalDevice, textureImageMemory, null); - } - - public void TransitionLayoutTo(uint from, uint to) - { - TransitionImageLayout(textureImage, (Format)ImageFormat, (ImageLayout)from, (ImageLayout)to); - } - - public void TransitionLayoutTo(CommandBuffer buffer, ImageLayout from, ImageLayout to) - { - TransitionImageLayout(textureImage, from, to, buffer); + Disposing?.Invoke(); + if (!isExternalSampler) + { + Vk.DestroySampler(LogicalDevice, sampler, null); + } + ColorAttachment.Dispose(); + DepthAttachment?.Dispose(); + MsaaResolvedColorAttachment?.Dispose(); } } diff --git a/src/Drawie.RenderApi.Vulkan/Drawie.RenderApi.Vulkan.csproj b/src/Drawie.RenderApi.Vulkan/Drawie.RenderApi.Vulkan.csproj index 3e989ea..f4c1e9a 100644 --- a/src/Drawie.RenderApi.Vulkan/Drawie.RenderApi.Vulkan.csproj +++ b/src/Drawie.RenderApi.Vulkan/Drawie.RenderApi.Vulkan.csproj @@ -1,17 +1,17 @@  - net8.0 + net10.0 enable enable true - - - - + + + + diff --git a/src/Drawie.RenderApi.Vulkan/Helpers/FormatExtensions.cs b/src/Drawie.RenderApi.Vulkan/Helpers/FormatExtensions.cs new file mode 100644 index 0000000..00a3eff --- /dev/null +++ b/src/Drawie.RenderApi.Vulkan/Helpers/FormatExtensions.cs @@ -0,0 +1,35 @@ +using Drawie.RenderApi.Abstraction.Textures; +using Silk.NET.Vulkan; + +namespace Drawie.RenderApi.Vulkan.Helpers; + +public static class FormatExtensions +{ + public static Format ToVkFormat(this DepthFormat descDepth) + { + switch (descDepth) + { + case DepthFormat.NoDepth: + throw new ArgumentException("No depth is not supported depth format."); + case DepthFormat.Depth24Stencil8: + return Format.D24UnormS8Uint; + default: + throw new ArgumentOutOfRangeException(nameof(descDepth), descDepth, null); + } + } + + public static SampleCountFlags ToSampleFlags(int samples) + { + return samples switch + { + 1 => SampleCountFlags.Count1Bit, + 2 => SampleCountFlags.Count2Bit, + 4 => SampleCountFlags.Count4Bit, + 8 => SampleCountFlags.Count8Bit, + 16 => SampleCountFlags.Count16Bit, + 32 => SampleCountFlags.Count32Bit, + 64 => SampleCountFlags.Count64Bit, + _ => throw new ArgumentOutOfRangeException(nameof(samples), samples, null) + }; + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.Vulkan/Helpers/UniformUtility.cs b/src/Drawie.RenderApi.Vulkan/Helpers/UniformUtility.cs new file mode 100644 index 0000000..7429a0d --- /dev/null +++ b/src/Drawie.RenderApi.Vulkan/Helpers/UniformUtility.cs @@ -0,0 +1,75 @@ +using System.Numerics; +using System.Runtime.InteropServices; +using Drawie.RenderApi.Abstraction.Shaders; +using Drawie.RenderApi.Vulkan.Buffers; +using Silk.NET.Vulkan; + +namespace Drawie.RenderApi.Vulkan.Helpers; + +public static class UniformUtility +{ + public static void SerializeToBuffer(UniformBlock block, UniformBuffer buffer) + { + var data = new byte[block.ShaderLayout.Size]; + foreach (var property in block.Properties) + { + var layout = block.ShaderLayout.UniformProperties + .FirstOrDefault(x => x.Name == property.UniformName); + + Write( + data, + layout.Offset, + property.ObjValue); + } + + buffer.SetData(data); + } + + private static void Write( + byte[] destination, + int offset, + object value) + { + switch (value) + { + case float v: + BitConverter.TryWriteBytes( + destination.AsSpan(offset, sizeof(float)), + v); + break; + case int v: + BitConverter.TryWriteBytes( + destination.AsSpan(offset, sizeof(int)), + v); + break; + case uint v: + BitConverter.TryWriteBytes( + destination.AsSpan(offset, sizeof(uint)), + v); + break; + case Vector2 v: + MemoryMarshal.Write( + destination.AsSpan(offset), + in v); + break; + case Vector3 v: + MemoryMarshal.Write( + destination.AsSpan(offset), + in v); + break; + case Vector4 v: + MemoryMarshal.Write( + destination.AsSpan(offset), + in v); + break; + case Matrix4x4 v: + MemoryMarshal.Write( + destination.AsSpan(offset), + in v); + break; + default: + throw new NotSupportedException( + $"Cannot serialize uniform value of type {value.GetType()}."); + } + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.Vulkan/Primitives.cs b/src/Drawie.RenderApi.Vulkan/Primitives.cs index 6dfbfa8..cce21c6 100644 --- a/src/Drawie.RenderApi.Vulkan/Primitives.cs +++ b/src/Drawie.RenderApi.Vulkan/Primitives.cs @@ -5,7 +5,7 @@ namespace Drawie.RenderApi.Vulkan; public static class Primitives { - public static Vertex[] Vertices = new Vertex[] + public static Vertex2D[] Vertices = new Vertex2D[] { new() { diff --git a/src/Drawie.RenderApi.Vulkan/Stages/Builders/GraphicsPipelineBuilder.cs b/src/Drawie.RenderApi.Vulkan/Stages/Builders/GraphicsPipelineBuilder.cs index 27eeb49..ff1906f 100644 --- a/src/Drawie.RenderApi.Vulkan/Stages/Builders/GraphicsPipelineBuilder.cs +++ b/src/Drawie.RenderApi.Vulkan/Stages/Builders/GraphicsPipelineBuilder.cs @@ -1,4 +1,5 @@ using Drawie.RenderApi.Vulkan.Exceptions; +using Drawie.RenderApi.Vulkan.Helpers; using Drawie.RenderApi.Vulkan.Structs; using Silk.NET.Vulkan; @@ -10,6 +11,13 @@ public class GraphicsPipelineBuilder public Device LogicalDevice { get; set; } public List Stages { get; } = new(); public RenderPassBuilder RenderPassBuilder { get; set; } + public GraphicsPipelineVertexLayoutBuilder VertexLayoutBuilder { get; set; } + + public CullModeFlags CullMode { get; set; } = CullModeFlags.None; + public FrontFace FrontFace { get; set; } = FrontFace.Clockwise; + public bool HasDepthStencil { get; set; } + public PolygonMode PolygonMode { get; set; } = PolygonMode.Fill; + public bool DoNotDisposeStages { get; set; } public GraphicsPipelineBuilder(Vk vk, Device logicalDevice) { @@ -17,6 +25,24 @@ public GraphicsPipelineBuilder(Vk vk, Device logicalDevice) LogicalDevice = logicalDevice; } + public GraphicsPipelineBuilder WithPolygonMode(PolygonMode polygonMode) + { + PolygonMode = polygonMode; + return this; + } + + public GraphicsPipelineBuilder WithCullMode(CullModeFlags cullMode) + { + CullMode = cullMode; + return this; + } + + public GraphicsPipelineBuilder WithFrontFace(FrontFace frontFace) + { + FrontFace = frontFace; + return this; + } + public GraphicsPipelineBuilder AddStage(Action stageBuilder) { GraphicsPipelineStageBuilder stage = new(Vk, LogicalDevice); @@ -26,7 +52,17 @@ public GraphicsPipelineBuilder AddStage(Action sta Stages.Add(stage); return this; } - + + public GraphicsPipelineBuilder WithVertexLayout(Action vertexLayoutBuilder) + { + GraphicsPipelineVertexLayoutBuilder builder = new GraphicsPipelineVertexLayoutBuilder(); + + vertexLayoutBuilder(builder); + + VertexLayoutBuilder = builder; + return this; + } + public GraphicsPipelineBuilder WithRenderPass(Action renderPassBuilder) { RenderPassBuilder = new(Vk, LogicalDevice); @@ -34,25 +70,37 @@ public GraphicsPipelineBuilder WithRenderPass(Action renderPa return this; } - public unsafe GraphicsPipeline Create(Extent2D swapChainExtent, Format swapChainImageFormat, + public GraphicsPipelineBuilder WithDepth() + { + HasDepthStencil = true; + return this; + } + + public unsafe GraphicsPipeline Create(Extent2D extent, Format imageFormat, ImageLayout finalLayout, - ref DescriptorSetLayout descriptorSetLayout) + DescriptorSetLayout[] descriptorSetLayouts) { if (Stages.Count == 0) throw new GraphicsPipelineBuilderException("No stages were added to the pipeline."); - if (RenderPassBuilder == null) throw new GraphicsPipelineBuilderException("No render pass was added to the pipeline."); + if (RenderPassBuilder == null) + throw new GraphicsPipelineBuilderException("No render pass was added to the pipeline."); + if(VertexLayoutBuilder == null) throw new GraphicsPipelineBuilderException("No vertex layout was added to the pipeline."); - RenderPass renderPass = RenderPassBuilder.Create(swapChainImageFormat, finalLayout); + RenderPass renderPass = RenderPassBuilder.Create(imageFormat, finalLayout); var stages = stackalloc PipelineShaderStageCreateInfo[Stages.Count]; for (var i = 0; i < Stages.Count; i++) stages[i] = Stages[i].Build(); + + var (bindingDescription, attributeDescriptions) = VertexLayoutBuilder.Build(); - var bindingDescription = Vertex.GetBindingDescription(); - var attributeDescriptions = Vertex.GetAttributeDescriptions(); + DescriptorSetLayout* layouts = stackalloc DescriptorSetLayout[descriptorSetLayouts.Length]; + for (var i = 0; i < descriptorSetLayouts.Length; i++) + { + var descriptorSetLayout = descriptorSetLayouts[i]; + layouts[i] = descriptorSetLayout; + } fixed (VertexInputAttributeDescription* attributeDescriptionsPtr = attributeDescriptions) - fixed (DescriptorSetLayout* descriptorPtr = &descriptorSetLayout) { - PipelineVertexInputStateCreateInfo vertexInputInfo = new() { SType = StructureType.PipelineVertexInputStateCreateInfo, @@ -73,8 +121,8 @@ public unsafe GraphicsPipeline Create(Extent2D swapChainExtent, Format swapChain { X = 0.0f, Y = 0.0f, - Width = (float)swapChainExtent.Width, - Height = (float)swapChainExtent.Height, + Width = extent.Width, + Height = extent.Height, MinDepth = 0.0f, MaxDepth = 1.0f }; @@ -82,7 +130,7 @@ public unsafe GraphicsPipeline Create(Extent2D swapChainExtent, Format swapChain Rect2D scissor = new() { Offset = new Offset2D(0, 0), - Extent = swapChainExtent + Extent = extent }; PipelineViewportStateCreateInfo viewportState = new() @@ -99,19 +147,30 @@ public unsafe GraphicsPipeline Create(Extent2D swapChainExtent, Format swapChain SType = StructureType.PipelineRasterizationStateCreateInfo, DepthClampEnable = false, RasterizerDiscardEnable = false, - PolygonMode = PolygonMode.Fill, + PolygonMode = PolygonMode, LineWidth = 1.0f, - CullMode = CullModeFlags.None, - /*CullMode = CullModeFlags.BackBit, - FrontFace = FrontFace.Clockwise,*/ + CullMode = CullMode, + FrontFace = FrontFace, DepthBiasEnable = false }; + PipelineDepthStencilStateCreateInfo depthStencil = new() + { + SType = StructureType.PipelineDepthStencilStateCreateInfo, + + DepthTestEnable = HasDepthStencil, + DepthWriteEnable = HasDepthStencil, + DepthCompareOp = CompareOp.Less, + + DepthBoundsTestEnable = false, + StencilTestEnable = false + }; + PipelineMultisampleStateCreateInfo multisampling = new() { SType = StructureType.PipelineMultisampleStateCreateInfo, SampleShadingEnable = false, - RasterizationSamples = SampleCountFlags.Count1Bit + RasterizationSamples = FormatExtensions.ToSampleFlags(RenderPassBuilder.Samples) }; PipelineColorBlendAttachmentState colorBlendAttachment = new() @@ -139,8 +198,8 @@ public unsafe GraphicsPipeline Create(Extent2D swapChainExtent, Format swapChain { SType = StructureType.PipelineLayoutCreateInfo, PushConstantRangeCount = 0, - SetLayoutCount = 1, - PSetLayouts = descriptorPtr + SetLayoutCount = (uint)descriptorSetLayouts.Length, + PSetLayouts = layouts }; if (Vk!.CreatePipelineLayout(LogicalDevice, in pipelineLayoutInfo, null, out var pipelineLayout) != @@ -158,6 +217,7 @@ public unsafe GraphicsPipeline Create(Extent2D swapChainExtent, Format swapChain PRasterizationState = &rasterizer, PMultisampleState = &multisampling, PColorBlendState = &colorBlending, + PDepthStencilState = &depthStencil, Layout = pipelineLayout, RenderPass = renderPass, Subpass = 0, @@ -168,7 +228,8 @@ public unsafe GraphicsPipeline Create(Extent2D swapChainExtent, Format swapChain out var graphicsPipeline) != Result.Success) throw new VulkanException("Failed to create graphics pipeline."); - foreach (var stage in Stages) stage.Dispose(); + if(!DoNotDisposeStages) + foreach (var stage in Stages) stage.Dispose(); return new GraphicsPipeline(Vk, LogicalDevice, pipelineLayout, graphicsPipeline, renderPass); } diff --git a/src/Drawie.RenderApi.Vulkan/Stages/Builders/GraphicsPipelineStageBuilder.cs b/src/Drawie.RenderApi.Vulkan/Stages/Builders/GraphicsPipelineStageBuilder.cs index 243d1ae..8f0ad36 100644 --- a/src/Drawie.RenderApi.Vulkan/Stages/Builders/GraphicsPipelineStageBuilder.cs +++ b/src/Drawie.RenderApi.Vulkan/Stages/Builders/GraphicsPipelineStageBuilder.cs @@ -12,7 +12,8 @@ public class GraphicsPipelineStageBuilder : IDisposable public Device LogicalDevice { get; set; } public GraphicsPipelineStageType Type { get; set; } - public string ShaderPath { get; set; } + public string? ShaderPath { get; set; } + public byte[]? ShaderBytes { get; set; } public string EntryName { get; set; } = "main"; @@ -34,20 +35,24 @@ public unsafe PipelineShaderStageCreateInfo Build() { if (created) { - throw new GraphicsPipelineBuilderException("Stage was already created"); + return createdStage; } - using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(ShaderPath); - if (stream == null) + byte[] code = ShaderBytes ?? Array.Empty(); + if (ShaderBytes == null) { - throw new GraphicsPipelineBuilderException("Shader file not found"); - } + using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(ShaderPath); + if (stream == null) + { + throw new GraphicsPipelineBuilderException("Shader file not found"); + } - byte[] code = new byte[stream.Length]; - var read = stream.Read(code, 0, code.Length); - if (read != code.Length) - { - throw new GraphicsPipelineBuilderException("Failed to read shader file"); + code = new byte[stream.Length]; + var read = stream.Read(code, 0, code.Length); + if (read != code.Length) + { + throw new GraphicsPipelineBuilderException("Failed to read shader file"); + } } shaderModule = CreateShaderModule(code); diff --git a/src/Drawie.RenderApi.Vulkan/Stages/Builders/GraphicsPipelineVertexLayoutBuilder.cs b/src/Drawie.RenderApi.Vulkan/Stages/Builders/GraphicsPipelineVertexLayoutBuilder.cs new file mode 100644 index 0000000..31f1cc0 --- /dev/null +++ b/src/Drawie.RenderApi.Vulkan/Stages/Builders/GraphicsPipelineVertexLayoutBuilder.cs @@ -0,0 +1,64 @@ +using Silk.NET.Vulkan; + +namespace Drawie.RenderApi.Vulkan.Stages.Builders; + +public class GraphicsPipelineVertexLayoutBuilder +{ + public List Components { get; set; } = new(); + public uint Binding { get; set; } = 0; + + + public GraphicsPipelineVertexLayoutBuilder WithVec2() + { + Components.Add(new VertexAttributeLayout { ComponentCount = 2, Format = Format.R32G32Sfloat }); + return this; + } + + public GraphicsPipelineVertexLayoutBuilder WithVec3() + { + Components.Add(new VertexAttributeLayout() { ComponentCount = 3, Format = Format.R32G32B32Sfloat }); + return this; + } + + public unsafe (VertexInputBindingDescription, VertexInputAttributeDescription[]) Build() + { + int stride = 0; + foreach (var component in Components) + { + stride += component.Size; + } + + var bindingDesc = new VertexInputBindingDescription + { + Binding = Binding, + Stride = (uint)stride, + InputRate = VertexInputRate.Vertex + }; + + var descriptions = new VertexInputAttributeDescription[Components.Count]; + int offset = 0; + for (var index = 0; index < Components.Count; index++) + { + var component = Components[index]; + descriptions[index] = new VertexInputAttributeDescription() + { + Binding = Binding, + Location = (uint)index, + Format = component.Format, + Offset = (uint)offset, + }; + + offset += component.ComponentCount * sizeof(float); + } + + return (bindingDesc, descriptions); + } +} + +public struct VertexAttributeLayout +{ + public Format Format { get; set; } + public int ComponentCount { get; set; } + + public int Size => ComponentCount * sizeof(float); +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.Vulkan/Stages/Builders/RenderPassBuilder.cs b/src/Drawie.RenderApi.Vulkan/Stages/Builders/RenderPassBuilder.cs index 5750be9..5a9505d 100644 --- a/src/Drawie.RenderApi.Vulkan/Stages/Builders/RenderPassBuilder.cs +++ b/src/Drawie.RenderApi.Vulkan/Stages/Builders/RenderPassBuilder.cs @@ -1,4 +1,5 @@ using Drawie.RenderApi.Vulkan.Exceptions; +using Drawie.RenderApi.Vulkan.Helpers; using Silk.NET.Vulkan; namespace Drawie.RenderApi.Vulkan.Stages.Builders; @@ -8,24 +9,53 @@ public class RenderPassBuilder : IDisposable public Vk Vk { get; set; } public Device LogicalDevice { get; set; } + public bool WithDepthStencil { get; set; } + public Format DepthStencilFormat { get; set; } + public int Samples { get; set; } = 1; + public RenderPassBuilder(Vk vk, Device logicalDevice) { Vk = vk; LogicalDevice = logicalDevice; } - public unsafe RenderPass Create(Format swapChainImageFormat, ImageLayout finalLayout) + public RenderPassBuilder WithDepth(Format depthStencilFormat) + { + WithDepthStencil = true; + DepthStencilFormat = depthStencilFormat; + return this; + } + + public RenderPassBuilder WithSamples(int samples) + { + Samples = samples; + return this; + } + + public unsafe RenderPass Create(Format format, ImageLayout imageLayout) { AttachmentDescription colorAttachment = new() { - Format = swapChainImageFormat, - Samples = SampleCountFlags.Count1Bit, + Format = format, + Samples = FormatExtensions.ToSampleFlags(Samples), LoadOp = AttachmentLoadOp.Clear, StoreOp = AttachmentStoreOp.Store, StencilLoadOp = AttachmentLoadOp.DontCare, StencilStoreOp = AttachmentStoreOp.DontCare, InitialLayout = ImageLayout.Undefined, - FinalLayout = finalLayout + FinalLayout = Samples == 1 ? imageLayout : ImageLayout.ColorAttachmentOptimal + }; + + AttachmentDescription depthAttachment = new() + { + Format = DepthStencilFormat, + Samples = FormatExtensions.ToSampleFlags(Samples), + InitialLayout = ImageLayout.Undefined, + FinalLayout = ImageLayout.DepthStencilAttachmentOptimal, + LoadOp = AttachmentLoadOp.Clear, + StoreOp = AttachmentStoreOp.DontCare, + StencilLoadOp = AttachmentLoadOp.Clear, + StencilStoreOp = AttachmentStoreOp.DontCare, }; AttachmentReference colorAttachmentRef = new() @@ -34,28 +64,99 @@ public unsafe RenderPass Create(Format swapChainImageFormat, ImageLayout finalLa Layout = ImageLayout.ColorAttachmentOptimal }; + AttachmentReference depthAttachmentRef = new() + { + Attachment = 1, + Layout = ImageLayout.DepthStencilAttachmentOptimal + }; + SubpassDescription subpass = new() { PipelineBindPoint = PipelineBindPoint.Graphics, ColorAttachmentCount = 1, - PColorAttachments = &colorAttachmentRef + PColorAttachments = &colorAttachmentRef, + PDepthStencilAttachment = WithDepthStencil ? &depthAttachmentRef : null, }; - SubpassDependency dependency = new() + AttachmentDescription colorResolveAttachment = new() { - SrcSubpass = Vk.SubpassExternal, - DstSubpass = 0, - SrcStageMask = PipelineStageFlags.ColorAttachmentOutputBit, - SrcAccessMask = 0, - DstStageMask = PipelineStageFlags.ColorAttachmentOutputBit, - DstAccessMask = AccessFlags.ColorAttachmentWriteBit + Format = format, + Samples = SampleCountFlags.Count1Bit, + LoadOp = AttachmentLoadOp.DontCare, + StoreOp = AttachmentStoreOp.Store, + StencilLoadOp = AttachmentLoadOp.DontCare, + StencilStoreOp = AttachmentStoreOp.DontCare, + InitialLayout = ImageLayout.Undefined, + FinalLayout = imageLayout }; + + if (Samples > 1) + { + AttachmentReference colorResolveAttachmentRef = new() + { + Attachment = (uint)(WithDepthStencil ? 2 : 1), + Layout = ImageLayout.ColorAttachmentOptimal, + }; + + subpass.PResolveAttachments = &colorResolveAttachmentRef; + } + + SubpassDependency dependency = default; + if (!WithDepthStencil) + { + dependency = new() + { + SrcSubpass = Vk.SubpassExternal, + DstSubpass = 0, + SrcStageMask = PipelineStageFlags.ColorAttachmentOutputBit, + SrcAccessMask = AccessFlags.None, + DstStageMask = PipelineStageFlags.ColorAttachmentOutputBit, + DstAccessMask = AccessFlags.ColorAttachmentWriteBit + }; + } + else + { + dependency = new() + { + SrcSubpass = Vk.SubpassExternal, + DstSubpass = 0, + SrcStageMask = PipelineStageFlags.ColorAttachmentOutputBit | PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit, + SrcAccessMask = AccessFlags.ColorAttachmentWriteBit | AccessFlags.DepthStencilAttachmentWriteBit, + + DstStageMask = PipelineStageFlags.ColorAttachmentOutputBit | PipelineStageFlags.EarlyFragmentTestsBit, + DstAccessMask = AccessFlags.ColorAttachmentWriteBit | AccessFlags.DepthStencilAttachmentWriteBit + }; + } + + uint attachmentCount = 1; + if (WithDepthStencil) attachmentCount++; + if (Samples > 1) attachmentCount++; + + AttachmentDescription* attachments = &colorAttachment; + if (attachmentCount > 1) + { + var attachmentDescriptions = stackalloc AttachmentDescription[(int)attachmentCount]; + attachmentDescriptions[0] = colorAttachment; + int i = 1; + if (WithDepthStencil) + { + attachmentDescriptions[1] = depthAttachment; + i++; + } + + if (Samples > 1) + { + attachmentDescriptions[i] = colorResolveAttachment; + } + + attachments = attachmentDescriptions; + } RenderPassCreateInfo renderPassInfo = new() { SType = StructureType.RenderPassCreateInfo, - AttachmentCount = 1, - PAttachments = &colorAttachment, + AttachmentCount = attachmentCount, + PAttachments = attachments, SubpassCount = 1, PSubpasses = &subpass, DependencyCount = 1, @@ -68,7 +169,6 @@ public unsafe RenderPass Create(Format swapChainImageFormat, ImageLayout finalLa return renderPass; } - public void Dispose() { } diff --git a/src/Drawie.RenderApi.Vulkan/Structs/Vertex.cs b/src/Drawie.RenderApi.Vulkan/Structs/Vertex2D.cs similarity index 79% rename from src/Drawie.RenderApi.Vulkan/Structs/Vertex.cs rename to src/Drawie.RenderApi.Vulkan/Structs/Vertex2D.cs index bc26a4a..aba56ba 100644 --- a/src/Drawie.RenderApi.Vulkan/Structs/Vertex.cs +++ b/src/Drawie.RenderApi.Vulkan/Structs/Vertex2D.cs @@ -5,7 +5,7 @@ namespace Drawie.RenderApi.Vulkan.Structs; -public struct Vertex +public struct Vertex2D { public Vector2D Position; public Vector3D Color; @@ -16,7 +16,7 @@ public static VertexInputBindingDescription GetBindingDescription() return new VertexInputBindingDescription { Binding = 0, - Stride = (uint)Unsafe.SizeOf(), + Stride = (uint)Unsafe.SizeOf(), InputRate = VertexInputRate.Vertex }; } @@ -30,21 +30,21 @@ public static VertexInputAttributeDescription[] GetAttributeDescriptions() Binding = 0, Location = 0, Format = Format.R32G32Sfloat, - Offset = (uint)Marshal.OffsetOf(nameof(Position)) + Offset = (uint)Marshal.OffsetOf(nameof(Position)) }, new VertexInputAttributeDescription { Binding = 0, Location = 1, Format = Format.R32G32B32Sfloat, - Offset = (uint)Marshal.OffsetOf(nameof(Color)) + Offset = (uint)Marshal.OffsetOf(nameof(Color)) }, new VertexInputAttributeDescription() { Binding = 0, Location = 2, Format = Format.R32G32Sfloat, - Offset = (uint)Marshal.OffsetOf(nameof(TexCoord)) + Offset = (uint)Marshal.OffsetOf(nameof(TexCoord)) } }; } diff --git a/src/Drawie.RenderApi.Vulkan/VulkanBufferGroup.cs b/src/Drawie.RenderApi.Vulkan/VulkanBufferGroup.cs new file mode 100644 index 0000000..a9082a8 --- /dev/null +++ b/src/Drawie.RenderApi.Vulkan/VulkanBufferGroup.cs @@ -0,0 +1,18 @@ +using Drawie.RenderApi.Abstraction.Buffers; +using Drawie.RenderApi.Vulkan.Buffers; + +namespace Drawie.RenderApi.Vulkan; + +internal sealed class VulkanBufferGroup : IBufferGroup +{ + private static uint counter; + + public uint Handle { get; } = counter++; + + private VulkanBufferGroupList buffers = new VulkanBufferGroupList(); + + public void Open(Action list) + { + list(buffers); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.Vulkan/VulkanBufferGroupList.cs b/src/Drawie.RenderApi.Vulkan/VulkanBufferGroupList.cs new file mode 100644 index 0000000..c460ea5 --- /dev/null +++ b/src/Drawie.RenderApi.Vulkan/VulkanBufferGroupList.cs @@ -0,0 +1,16 @@ +using Drawie.RenderApi.Abstraction.Buffers; +using Drawie.RenderApi.Vulkan.Buffers; + +namespace Drawie.RenderApi.Vulkan; + +internal sealed class VulkanBufferGroupList : IBufferGroupList +{ + public List Buffers { get; } = new(); + + public IVkBuffer? IndexBuffer => Buffers.FirstOrDefault(x => x.Usage == BufferUsage.Index) as IVkBuffer; + public IVkBuffer? VertexBuffer => Buffers.FirstOrDefault(x => x.Usage == BufferUsage.Vertex) as IVkBuffer; + + public VulkanBufferGroupList() + { + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.Vulkan/VulkanCommandList.cs b/src/Drawie.RenderApi.Vulkan/VulkanCommandList.cs new file mode 100644 index 0000000..47ebfc3 --- /dev/null +++ b/src/Drawie.RenderApi.Vulkan/VulkanCommandList.cs @@ -0,0 +1,566 @@ +using Drawie.RenderApi.Abstraction.Buffers; +using Drawie.RenderApi.Abstraction.CommandRecording; +using Drawie.RenderApi.Abstraction.Pipeline; +using Drawie.RenderApi.Abstraction.RenderTargets; +using Drawie.RenderApi.Abstraction.Shaders; +using Drawie.RenderApi.Abstraction.Textures; +using Drawie.RenderApi.Vulkan.Buffers; +using Drawie.RenderApi.Vulkan.Exceptions; +using Drawie.RenderApi.Vulkan.Helpers; +using Silk.NET.Vulkan; + +namespace Drawie.RenderApi.Vulkan; + +internal sealed class VulkanCommandList : CommandList, IDisposable +{ + public CommandBuffer CommandBuffer => commandBuffer; + public Dictionary BufferCache { get; set; } + + private readonly VulkanContext context; + private readonly CommandPool commandPool; + + private CommandBuffer commandBuffer; + private bool recording; + + private VulkanRenderTarget? renderTarget; + private VulkanPipeline? pipeline; + + public VulkanCommandList( + VulkanContext context, + CommandPool commandPool) + { + this.context = context; + this.commandPool = commandPool; + commandBuffer = AllocateCommandBuffer(); + BeginCommandBuffer(commandBuffer); + } + + public override void BeginRenderPass(IRenderTarget fb) + { + if (recording) + throw new InvalidOperationException( + "A Vulkan render pass is already being recorded."); + + if (fb is not VulkanRenderTarget target) + throw new ArgumentException( + "Render target must be a Vulkan render target.", + nameof(fb)); + + renderTarget = target; + + renderTarget.CreateFramebufferFor(pipeline.GraphicsPipeline.VkRenderPass); + + recording = true; + + BeginRendering(target); + } + + public override void SetPipeline(IPipeline pipeline) + { + if (pipeline is not VulkanPipeline vkPipeline) throw new ArgumentException("Only VulkanPipeline is supported"); + this.pipeline = vkPipeline; + //vkPipeline.DescriptorPool.Reset(); + } + + public override void SetBuffers(IBufferGroup bufferGroup) + { + if (!recording) + throw new InvalidOperationException( + "BeginRenderPass must be called first."); + + if (bufferGroup is not VulkanBufferGroup vkBuffers) + throw new ArgumentException( + "Buffer group must be a Vulkan buffer group.", + nameof(bufferGroup)); + + BindBuffers(vkBuffers); + } + + public override void BindPipeline() + { + if (!recording) + throw new InvalidOperationException( + "BeginRenderPass must be called first."); + + if (pipeline == null) throw new NullReferenceException("Pipeline is was not set"); + + pipeline.Apply(this); + } + + public override PreparedTexture PrepareTexture(ITexture texture) + { + var vkTex = context.ManagedTextures[texture.TextureId]; + + if (vkTex is not VulkanTexture vkTexture) throw new ArgumentException("Only IVkTexture's are valid"); + vkTexture.MakeReadOnly(commandBuffer); + + return new PreparedTexture(texture.TextureId); + } + + public override void UpdateUniforms(List blocks, List textures, + List samplers) + { + foreach (var block in blocks) + { + UniformBuffer? buffer = null; + if (BufferCache.TryGetValue(block.UniformBlockId, out var uniformBuffer)) + { + buffer = uniformBuffer; + } + else + { + buffer = new UniformBuffer(context.Api, context.LogicalDevice.Device, context.PhysicalDevice, + (ulong)block.ShaderLayout.Size); + BufferCache[block.UniformBlockId] = buffer; + } + + UniformUtility.SerializeToBuffer(block, buffer); + // TODO first texture is temporary as shader only supports single texture + UpdateUniformDescriptor(buffer, (ulong)block.ShaderLayout.Size, textures.FirstOrDefault(), samplers.FirstOrDefault()); + } + } + + public override void RestoreTexture(PreparedTexture preparedTextureValue) + { + var target = context.ManagedTextures[preparedTextureValue.Handle]; + if(target is not VulkanTexture vkTex) throw new ArgumentException("Only VulkanTexture's are valid"); + vkTex.MakeReadOnly(commandBuffer); + } + + private unsafe void UpdateUniformDescriptor(UniformBuffer buffer, ulong size, PreparedTexture texture, ISampler sampler) + { + if (texture.Handle == 0 || sampler == default) + return; + + var vkTexture = context.ManagedTextures[texture.Handle] as VulkanTexture; + var vkSampler = sampler as VulkanSampler; + // TODO: better id + var set = pipeline.DescriptorPool.GetOrAllocateDescriptorSet(0, buffer.VkBuffer.Handle); + DescriptorBufferInfo bufferInfo = new() + { + Buffer = buffer.VkBuffer, + Offset = 0, + Range = size + }; + + WriteDescriptorSet write = new() + { + SType = StructureType.WriteDescriptorSet, + DstSet = set, + DstBinding = 0, + DstArrayElement = 0, + DescriptorCount = 1, + DescriptorType = DescriptorType.UniformBuffer, + PBufferInfo = &bufferInfo + }; + + var imageInfo = new DescriptorImageInfo + { + Sampler = vkSampler.VkSampler, + ImageView = vkTexture.ColorAttachment.View, + ImageLayout = ImageLayout.ShaderReadOnlyOptimal + }; + + var writeImgSampler = new WriteDescriptorSet + { + SType = StructureType.WriteDescriptorSet, + DstSet = set, + DstBinding = 1, + DstArrayElement = 0, + DescriptorCount = 1, + DescriptorType = DescriptorType.CombinedImageSampler, + PImageInfo = &imageInfo + }; + + WriteDescriptorSet* sets = stackalloc WriteDescriptorSet[2]; + sets[0] = write; + sets[1] = writeImgSampler; + + context.Api.UpdateDescriptorSets( + context.LogicalDevice.Device, + 2, + sets, + 0, + null); + + context.Api!.CmdBindDescriptorSets( + commandBuffer, + PipelineBindPoint.Graphics, + pipeline.GraphicsPipeline.VkPipelineLayout, + 0, + 1, + in set, + 0, + null); + } + + public override void BindTexture( + PreparedTexture texture, + ISampler sampler) + { + if (!recording) + throw new InvalidOperationException( + "BeginRenderPass must be called first."); + + if (sampler is not VulkanSampler vkSampler) + throw new ArgumentException( + "Sampler must be a Vulkan sampler.", + nameof(sampler)); + + var vkTex = context.ManagedTextures[texture.Handle]; + + if (vkTex is not VulkanTexture vkTexture) throw new ArgumentException("Only IVkTexture's are valid"); + + BindTextureDescriptor( + vkTexture, + vkSampler); + } + + public override void DrawIndexed(int indexCount) + { + if (!recording) + throw new InvalidOperationException( + "BeginRenderPass must be called first."); + + context.Api!.CmdDrawIndexed(commandBuffer, (uint)indexCount, 1, 0, 0, 0); + } + + public override RecordedRenderPass EndRenderPass() + { + EnsureRecording(); + + //context.DynamicRendering.CmdEndRendering(commandBuffer); + context.Api!.CmdEndRenderPass(commandBuffer); + + EndCommandBuffer(commandBuffer); + + recording = false; + pipeline = null; + + return new VulkanRecordedRenderPass(context, commandBuffer, commandPool); + } + + public override RecordedRenderPass EndRenderPass( + IRenderTarget blitTo) + { + EnsureRecording(); + + var vkTex = context.ManagedTextures[blitTo.SurfaceId]; + + if (vkTex is not VulkanTexture destination) + throw new ArgumentException( + "Destination must be a Vulkan render target.", + nameof(blitTo)); + + context.Api!.CmdEndRenderPass(commandBuffer); + + Blit(renderTarget!.Texture, destination); + + EndCommandBuffer(commandBuffer); + + recording = false; + + return new VulkanRecordedRenderPass(context, commandBuffer, commandPool); + } + + private void EnsureRecording() + { + if (!recording) + throw new InvalidOperationException( + "No Vulkan render pass is currently being recorded."); + } + + private unsafe void BindTextureDescriptor( + VulkanTexture texture, + VulkanSampler sampler) + { + var set = pipeline.DescriptorPool.GetOrAllocateDescriptorSet(1, texture.ImageHandle); + var imageInfo = new DescriptorImageInfo + { + Sampler = sampler.VkSampler, + ImageView = texture.ColorAttachment.View, + ImageLayout = ImageLayout.ShaderReadOnlyOptimal + }; + + var write = new WriteDescriptorSet + { + SType = StructureType.WriteDescriptorSet, + DstSet = set, + DstBinding = 1, + DstArrayElement = 0, + DescriptorCount = 1, + DescriptorType = DescriptorType.CombinedImageSampler, + PImageInfo = &imageInfo + }; + + context.Api!.UpdateDescriptorSets( + context.LogicalDevice.Device, + 1, + &write, + 0, + null); + + context.Api!.CmdBindDescriptorSets( + commandBuffer, + PipelineBindPoint.Graphics, + pipeline.GraphicsPipeline.VkPipelineLayout, + 1, + 1, + in set, + 0, + null); + } + + private unsafe CommandBuffer AllocateCommandBuffer() + { + CommandBufferAllocateInfo info = new() + { + SType = StructureType.CommandBufferAllocateInfo, + CommandPool = commandPool, + Level = CommandBufferLevel.Primary, + CommandBufferCount = 1 + }; + + if (context.Api!.AllocateCommandBuffers( + context.LogicalDevice.Device, + in info, + out var result) != Result.Success) + { + throw new VulkanException( + "Failed to allocate Vulkan command buffer."); + } + + return result; + } + + private void BeginCommandBuffer(CommandBuffer buffer) + { + CommandBufferBeginInfo info = new() + { + SType = StructureType.CommandBufferBeginInfo, + Flags = CommandBufferUsageFlags.OneTimeSubmitBit, + }; + + if (context.Api!.BeginCommandBuffer( + buffer, + in info) != Result.Success) + { + throw new VulkanException( + "Failed to begin Vulkan command buffer."); + } + } + + private void EndCommandBuffer(CommandBuffer buffer) + { + if (context.Api!.EndCommandBuffer(buffer) != Result.Success) + throw new VulkanException( + "Failed to end Vulkan command buffer."); + } + + private void BeginRendering(VulkanRenderTarget target) + { + //BeginDynamicRendering(target); + BeginRenderPass(target); + } + + private unsafe void BeginRenderPass(VulkanRenderTarget target) + { + target.Texture.MakeWriteable(commandBuffer); + RenderPassBeginInfo renderPassInfo = new() + { + SType = StructureType.RenderPassBeginInfo, + RenderPass = pipeline.GraphicsPipeline.VkRenderPass, + Framebuffer = renderTarget.Framebuffer.Value, + RenderArea = new Rect2D + { + Offset = new Offset2D(0, 0), + Extent = new Extent2D((uint)renderTarget.Size.X, (uint)renderTarget.Size.Y) + } + }; + ClearValue clearColor = new() + { + Color = new ClearColorValue() { Float32_0 = 0, Float32_1 = 0, Float32_2 = 0, Float32_3 = 1 } + }; + + ClearValue clearDepth = new() + { + DepthStencil = new ClearDepthStencilValue(1, 0) + }; + + ClearValue clearMsaaResolved = new() + { + Color = new ClearColorValue() { Float32_0 = 1, Float32_1 = 1, Float32_2 = 1, Float32_3 = 1 } + }; + + ClearValue* clearValues = stackalloc ClearValue[(int)target.Texture.Attachments]; + clearValues[0] = clearColor; + int i = 0; + if (target.Texture.DepthAttachment != null) + { + clearValues[1] = clearDepth; + i++; + } + + if (target.Texture.MsaaResolvedColorAttachment != null) + { + clearValues[i] = clearMsaaResolved; + } + + renderPassInfo.ClearValueCount = target.Texture.Attachments; + renderPassInfo.PClearValues = clearValues; + + context.Api!.CmdBeginRenderPass(commandBuffer, &renderPassInfo, SubpassContents.Inline); + } + + /*private unsafe void BeginDynamicRendering(VulkanRenderTarget target) + { + target.Texture.MakeWriteable(commandBuffer); + + RenderingAttachmentInfo colorAttachment = new() + { + SType = StructureType.RenderingAttachmentInfo, + ImageView = target.Texture.ColorAttachment.View, + ImageLayout = ImageLayout.ColorAttachmentOptimal, + LoadOp = AttachmentLoadOp.Clear, + StoreOp = AttachmentStoreOp.Store, + ClearValue = new ClearValue + { + Color = new ClearColorValue(0f, 0f, 0f, 1f) + }, + }; + + RenderingAttachmentInfo depthAttachment = default; + + if (target.Texture.DepthAttachment is not null) + { + depthAttachment = new RenderingAttachmentInfo + { + SType = StructureType.RenderingAttachmentInfo, + ImageView = target.Texture.DepthAttachment.View, + ImageLayout = ImageLayout.DepthStencilAttachmentOptimal, + LoadOp = AttachmentLoadOp.Clear, + StoreOp = AttachmentStoreOp.Store, + ClearValue = new ClearValue + { + DepthStencil = new ClearDepthStencilValue(1f, 0) + } + }; + } + + if (target.Texture.MsaaResolvedColorAttachment != null) + { + colorAttachment.ResolveMode = ResolveModeFlags.AverageBit; + colorAttachment.ResolveImageView = target.Texture.MsaaResolvedColorAttachment.View; + colorAttachment.ResolveImageLayout = ImageLayout.ColorAttachmentOptimal; + } + + Rect2D renderArea = new() + { + Offset = new Offset2D(0, 0), + Extent = new Extent2D( + (uint)target.Size.X, + (uint)target.Size.Y) + }; + + RenderingInfo renderingInfo = new() + { + SType = StructureType.RenderingInfo, + RenderArea = renderArea, + LayerCount = 1, + ColorAttachmentCount = 1, + PColorAttachments = &colorAttachment, + }; + + if (target.Texture.DepthAttachment is not null) + renderingInfo.PDepthAttachment = &depthAttachment; + + context.DynamicRendering?.CmdBeginRendering(commandBuffer, &renderingInfo); + }*/ + + private unsafe void BindBuffers(VulkanBufferGroup group) + { + group.Open((list) => + { + var bufList = list as VulkanBufferGroupList; + if (bufList.VertexBuffer != null) + { + var buffer = bufList.VertexBuffer.NativeBuffer.VkBuffer; + ulong offset = 0; + + context.Api!.CmdBindVertexBuffers( + commandBuffer, + 0, + 1, + &buffer, + &offset); + } + + if (bufList.IndexBuffer is not null) + { + context.Api!.CmdBindIndexBuffer( + commandBuffer, + bufList.IndexBuffer.NativeBuffer.VkBuffer, + 0, + IndexType.Uint32); + } + }); + } + + private void Blit( + VulkanTexture source, + VulkanTexture destination) + { + var sourceAttachment = source.MsaaResolvedColorAttachment ?? source.ColorAttachment; + sourceAttachment.TransitionLayout(ImageLayout.TransferSrcOptimal, commandBuffer); + destination.ColorAttachment.TransitionLayout(ImageLayout.TransferDstOptimal, commandBuffer); + + ImageBlit region = new() + { + SrcSubresource = new ImageSubresourceLayers + { + AspectMask = ImageAspectFlags.ColorBit, + MipLevel = 0, + BaseArrayLayer = 0, + LayerCount = 1 + }, + + DstSubresource = new ImageSubresourceLayers + { + AspectMask = ImageAspectFlags.ColorBit, + MipLevel = 0, + BaseArrayLayer = 0, + LayerCount = 1 + } + }; + + region.SrcOffsets[0] = new Offset3D(0, 0, 0); + region.SrcOffsets[1] = new Offset3D( + (int)source.Width, + (int)source.Height, + 1); + + // Y is flipped at blit level + region.DstOffsets[0] = new Offset3D(0, (int)destination.Height, 0); + region.DstOffsets[1] = new Offset3D( + (int)destination.Width, + 0, + 1); + + context.Api!.CmdBlitImage( + commandBuffer, + sourceAttachment.Image, + ImageLayout.TransferSrcOptimal, + destination.VkImage, + ImageLayout.TransferDstOptimal, + 1, + in region, + Filter.Nearest); + + destination.ColorAttachment.TransitionLayout(ImageLayout.ColorAttachmentOptimal, commandBuffer); + } + + public unsafe void Dispose() + { + context.Api.DestroyCommandPool(context.LogicalDevice.Device, commandPool, null); + } +} diff --git a/src/Drawie.RenderApi.Vulkan/VulkanContext.cs b/src/Drawie.RenderApi.Vulkan/VulkanContext.cs index 7742bc1..0abd025 100644 --- a/src/Drawie.RenderApi.Vulkan/VulkanContext.cs +++ b/src/Drawie.RenderApi.Vulkan/VulkanContext.cs @@ -1,9 +1,9 @@ -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; +using Drawie.RenderApi.Abstraction.Textures; +using Drawie.RenderApi.Vulkan.Buffers; using Drawie.RenderApi.Vulkan.ContextObjects; using Drawie.RenderApi.Vulkan.Exceptions; using Drawie.RenderApi.Vulkan.Extensions; -using Drawie.RenderApi.Vulkan.Helpers; using Silk.NET.Core; using Silk.NET.Core.Native; using Silk.NET.Vulkan; @@ -12,7 +12,7 @@ namespace Drawie.RenderApi.Vulkan; -public abstract class VulkanContext : IDisposable, IVulkanContext +public abstract class VulkanContext : IDisposable, IVulkanContext, IGraphicsContext { public Vk? Api { get; protected set; } @@ -38,6 +38,11 @@ public Instance Instance public GpuInfo GpuInfo { get; set; } + public IReadOnlyDictionary ManagedTextures => managedTextures; + + + private Dictionary managedTextures = new Dictionary(); + private Instance instance; protected List validationLayers = new List(); @@ -63,6 +68,27 @@ public VulkanContext() public abstract void Initialize(IVulkanContextInfo contextInfo); + void IGraphicsContext.MakeCurrent() + { + // no op + } + + public void AddManagedTexture(ITexture texture, ulong handle) + { + if (texture is not IVkTexture vkTexture) throw new ArgumentException("Can't manage non IVkTexture"); + managedTextures[handle] = vkTexture; + + vkTexture.Disposing += () => + { + RemoveManagedTexture(texture.TextureId); + }; + } + + public void RemoveManagedTexture(ulong handle) + { + managedTextures.Remove(handle); + } + protected unsafe void SetupInstance(IVulkanContextInfo contextInfo) { ThrowIfValidationLayersNotSupported(); @@ -70,17 +96,18 @@ protected unsafe void SetupInstance(IVulkanContextInfo contextInfo) ApplicationInfo appInfo = new() { SType = StructureType.ApplicationInfo, - PApplicationName = (byte*)Marshal.StringToHGlobalAnsi("Drawie"), + PApplicationName = (byte*)Marshal.StringToHGlobalAnsi("Drawie App"), ApplicationVersion = new Version32(1, 0, 0), - PEngineName = (byte*)Marshal.StringToHGlobalAnsi("Drawie Engine"), - EngineVersion = new Version32(1, 0, 0), - ApiVersion = Vk.Version12 + PEngineName = (byte*)Marshal.StringToHGlobalAnsi("Drawie"), + EngineVersion = new Version32(2, 0, 0), + ApiVersion = Vk.Version11 }; InstanceCreateInfo createInfo = new() { SType = StructureType.InstanceCreateInfo, - PApplicationInfo = &appInfo + PApplicationInfo = &appInfo, + Flags = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? InstanceCreateFlags.EnumeratePortabilityBitKhr : default }; var extensions = GetExtensions(contextInfo); @@ -131,6 +158,7 @@ protected unsafe void SetupDebugMessenger() protected unsafe GpuInfo PickPhysicalDevice() { var devices = Api!.GetPhysicalDevices(Instance); + List<(GpuInfo, PhysicalDevice)?> suitableDevices = new List<(GpuInfo, PhysicalDevice)?>(); foreach (var device in devices) { if (IsDeviceSuitable(device)) @@ -141,15 +169,22 @@ protected unsafe GpuInfo PickPhysicalDevice() if (deviceName == null) throw new VulkanException("Failed to get device name."); - GpuInfo gpuInfo = new(deviceName, VendorById(props.VendorID)); - PhysicalDevice = device; - return gpuInfo; + GpuInfo gpuInfo = new(deviceName, VendorById(props.VendorID), props.DeviceType == PhysicalDeviceType.DiscreteGpu); + suitableDevices.Add((gpuInfo, device)); } } + if(suitableDevices.Count > 0) + { + var selectedGpu = + suitableDevices.FirstOrDefault(x => x?.Item1.IsDiscreteGpu is true, null) ?? suitableDevices[0]; + PhysicalDevice = selectedGpu?.Item2 ?? default; + return selectedGpu?.Item1 ?? new GpuInfo("Unknown", "Unknown", false); + } + if (PhysicalDevice.Handle == 0) throw new VulkanException("Failed to find a suitable Vulkan GPU."); - return new GpuInfo("Unknown", "Unknown"); + return new GpuInfo("Unknown", "Unknown", false); } private string VendorById(uint vendorId) @@ -173,11 +208,11 @@ private string VendorById(uint vendorId) private unsafe string[] GetExtensions(IVulkanContextInfo contextInfo) { string[] contextExtensions = contextInfo.GetInstanceExtensions(); - if (EnableValidationLayers) + if (EnableValidationLayers && !contextExtensions.Contains(ExtDebugUtils.ExtensionName)) { - return contextExtensions.Append(ExtDebugUtils.ExtensionName).ToArray(); + contextExtensions = [.. contextExtensions, ExtDebugUtils.ExtensionName]; } - + return contextExtensions; } diff --git a/src/Drawie.RenderApi.Vulkan/VulkanDescriptorPool.cs b/src/Drawie.RenderApi.Vulkan/VulkanDescriptorPool.cs new file mode 100644 index 0000000..72af4c9 --- /dev/null +++ b/src/Drawie.RenderApi.Vulkan/VulkanDescriptorPool.cs @@ -0,0 +1,61 @@ +using Drawie.RenderApi.Vulkan.Exceptions; +using Silk.NET.Vulkan; + +namespace Drawie.RenderApi.Vulkan; + +public class VulkanDescriptorPool +{ + public VulkanContext Context { get; } + public DescriptorPool DescriptorPool { get; } + + public DescriptorSetLayout[] DescriptorSetLayouts { get; } + + private Dictionary sets = new Dictionary(); + + public VulkanDescriptorPool(VulkanContext context, DescriptorPool descriptorPool, + DescriptorSetLayout[] descriptorSetLayouts) + { + Context = context; + DescriptorPool = descriptorPool; + DescriptorSetLayouts = descriptorSetLayouts; + } + + public void Reset() + { + sets.Clear(); + } + + public unsafe DescriptorSet GetOrAllocateDescriptorSet(int layoutIndex, ulong forHandle) + { + if (sets.TryGetValue(forHandle, out var set)) return set; + + DescriptorSetLayout layout = DescriptorSetLayouts[layoutIndex]; + + DescriptorSetAllocateInfo allocInfo = new() + { + SType = StructureType.DescriptorSetAllocateInfo, + DescriptorPool = DescriptorPool, + DescriptorSetCount = 1, + PSetLayouts = &layout + }; + + DescriptorSet descriptorSet; + + var result = Context.Api.AllocateDescriptorSets( + Context.LogicalDevice.Device, + in allocInfo, + &descriptorSet); + + if (result != Result.Success) + throw new VulkanException( + $"Failed to allocate descriptor set: {result}"); + + sets[forHandle] = descriptorSet; + return descriptorSet; + } + + public unsafe void Dispose() + { + Context.Api.DestroyDescriptorPool(Context.LogicalDevice.Device, DescriptorPool, null); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.Vulkan/VulkanGraphicsDevice.cs b/src/Drawie.RenderApi.Vulkan/VulkanGraphicsDevice.cs new file mode 100644 index 0000000..db24c64 --- /dev/null +++ b/src/Drawie.RenderApi.Vulkan/VulkanGraphicsDevice.cs @@ -0,0 +1,244 @@ +using System.Runtime.CompilerServices; +using Drawie.Numerics; +using Drawie.RenderApi.Abstraction; +using Drawie.RenderApi.Abstraction.Buffers; +using Drawie.RenderApi.Abstraction.CommandRecording; +using Drawie.RenderApi.Abstraction.Pipeline; +using Drawie.RenderApi.Abstraction.RenderTargets; +using Drawie.RenderApi.Abstraction.Shaders; +using Drawie.RenderApi.Abstraction.Textures; +using Drawie.RenderApi.Vulkan.Buffers; +using Drawie.RenderApi.Vulkan.Exceptions; +using Drawie.RenderApi.Vulkan.Helpers; +using Silk.NET.Vulkan; +using IAbstractionTexture = Drawie.RenderApi.Abstraction.Textures.ITexture; + +namespace Drawie.RenderApi.Vulkan; + +public sealed class VulkanGraphicsDevice : IGraphicsDevice +{ + private readonly VulkanContext context; + private readonly CommandPool commandPool; + private VulkanPipeline? pipeline; + private VulkanSampler globalSampler; + + private Dictionary bufferCache = new Dictionary(); + + private List disposables = new List(); + + public VulkanGraphicsDevice(VulkanContext context) + { + if (context.Api is null) + throw new InvalidOperationException( + "Vulkan context must be initialized before creating a graphics device."); + + this.context = context; + commandPool = CreateCommandPool(); + globalSampler = new VulkanSampler(context, new SamplerDesc()); + } + + private unsafe CommandPool CreateCommandPool() + { + var queueFamilyIndices = SetupUtility.FindQueueFamilies(context.Api, context.PhysicalDevice, null, null); + + CommandPoolCreateInfo poolInfo = new() + { + SType = StructureType.CommandPoolCreateInfo, + QueueFamilyIndex = queueFamilyIndices.GraphicsFamily!.Value + }; + + if (context.Api!.CreateCommandPool(context.LogicalDevice.Device, in poolInfo, null, out var cmdPool) != + Result.Success) + throw new VulkanException("Failed to create command pool."); + + return cmdPool; + } + + public IBuffer CreateBuffer( + BufferUsage usage, + TData[]? data) + where TData : unmanaged + { + var buffer = new VulkanBuffer( + context, + commandPool, + usage, + data); + disposables.Add(buffer); + + return buffer; + } + + public IAbstractionTexture CreateTexture(TextureDesc desc) + { + var vkTex = new VulkanTexture( + context.Api!, + context.LogicalDevice.Device, + context.PhysicalDevice, + commandPool, + context.GraphicsQueue, + context.GraphicsQueueFamilyIndex, + desc, globalSampler.VkSampler); + + context.AddManagedTexture(vkTex, vkTex.ImageHandle); + disposables.Add(vkTex); + return vkTex; + } + + public IPipeline CreatePipeline(PipelineDesc desc) + { + if (pipeline == null || !pipeline.Description.Equals(desc)) + { + if (pipeline != null) + { + disposables.Remove(pipeline); + } + + pipeline?.Dispose(); + pipeline = new VulkanPipeline(context, desc); + disposables.Add(pipeline); + } + + return pipeline; + } + + public ICommandList CreateCommandList() + { + var cmdList = new VulkanCommandList(context, commandPool) { BufferCache = bufferCache }; + disposables.Add(cmdList); + return cmdList; + } + + public ISampler CreateSampler(SamplerDesc desc) + { + var sampler = new VulkanSampler(context, desc); + disposables.Add(sampler); + return sampler; + } + + public void Submit(RecordedRenderPass cmdList) + { + cmdList.Execute.Invoke(); + } + + public IShaderProgram CreateShaderProgram(ShaderProgramDesc desc) + { + var program = new VulkanShaderProgram(context, desc); + disposables.Add(program); + return program; + } + + public IRenderTarget CreateRenderTarget(TextureDesc textureDesc) + { + var texture = (VulkanTexture)CreateTexture(textureDesc); + return new VulkanRenderTarget(context, texture, new VecI(textureDesc.Width, textureDesc.Height)); + } + + public IBufferGroup CreateBufferGroup() + { + return new VulkanBufferGroup(); + } + + public void DisposeTexture(ulong textureHandle) + { + if (context.ManagedTextures.TryGetValue(textureHandle, out var texture)) + { + (texture as IDisposable)?.Dispose(); + context.RemoveManagedTexture(textureHandle); + } + } + + public unsafe void Dispose() + { + foreach (var uniformBuffer in bufferCache) + { + uniformBuffer.Value.Dispose(); + } + + globalSampler?.Dispose(); + + context.Api!.DestroyCommandPool( + context.LogicalDevice.Device, + commandPool, + null); + + foreach (var disposable in disposables) + { + disposable.Dispose(); + } + } +} + +internal interface IVkBuffer : IBuffer +{ + public BufferObject NativeBuffer { get; } +} + +internal sealed class VulkanBuffer : IVkBuffer, IBuffer, IDisposable where T : unmanaged +{ + private readonly VulkanContext context; + private readonly CommandPool commandPool; + + public BufferUsage Usage { get; } + public ulong Size { get; } + public BufferObject NativeBuffer { get; } + + public VulkanBuffer(VulkanContext context, CommandPool commandPool, BufferUsage usage, T[] data) + { + this.context = context; + this.commandPool = commandPool; + Usage = usage; + Size = (ulong)Unsafe.SizeOf() * (ulong)data.Length; + NativeBuffer = CreateNativeBuffer(Size, usage); + + if (data is { Length: > 0 }) + Upload(data); + } + + public unsafe void Dispose() + { + NativeBuffer.Dispose(); + } + + private BufferObject CreateNativeBuffer(ulong size, BufferUsage usage) + { + return usage switch + { + BufferUsage.Vertex => new VertexBuffer(context, size), + BufferUsage.Index => new IndexBuffer(context, size), + BufferUsage.Uniform => new UniformBuffer(context.Api!, context.LogicalDevice.Device, context.PhysicalDevice, + size), + BufferUsage.Storage => new VulkanStorageBuffer(context, size), + _ => throw new ArgumentOutOfRangeException(nameof(usage), usage, null) + }; + } + + private void Upload(T[] data) + { + switch (Usage) + { + case BufferUsage.Vertex: + case BufferUsage.Index: + { + using var stagingBuffer = new StagingBuffer(context, Size); + stagingBuffer.SetData(data); + CopyBuffer(stagingBuffer, NativeBuffer, Size); + break; + } + case BufferUsage.Uniform: + case BufferUsage.Storage: + NativeBuffer.SetData(data); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private unsafe void CopyBuffer(BufferObject srcBuffer, BufferObject dstBuffer, ulong size) + { + using var session = new SingleTimeCommandBufferSession(context, commandPool); + + BufferCopy copyRegion = new() { Size = size }; + context.Api!.CmdCopyBuffer(session.CommandBuffer, srcBuffer.VkBuffer, dstBuffer.VkBuffer, 1, copyRegion); + } +} diff --git a/src/Drawie.RenderApi.Vulkan/VulkanWindowRenderApi.cs b/src/Drawie.RenderApi.Vulkan/VulkanHostViewRenderApi.cs similarity index 94% rename from src/Drawie.RenderApi.Vulkan/VulkanWindowRenderApi.cs rename to src/Drawie.RenderApi.Vulkan/VulkanHostViewRenderApi.cs index a9b431b..d2db99b 100644 --- a/src/Drawie.RenderApi.Vulkan/VulkanWindowRenderApi.cs +++ b/src/Drawie.RenderApi.Vulkan/VulkanHostViewRenderApi.cs @@ -1,13 +1,14 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Drawie.Numerics; +using Drawie.RenderApi.Abstraction; +using Drawie.RenderApi.Abstraction.Textures; using Drawie.RenderApi.Vulkan.Buffers; using Drawie.RenderApi.Vulkan.Exceptions; using Drawie.RenderApi.Vulkan.Helpers; using Drawie.RenderApi.Vulkan.Stages; using Drawie.RenderApi.Vulkan.Stages.Builders; using Drawie.RenderApi.Vulkan.Structs; -using Silk.NET.Maths; using Silk.NET.Vulkan; using Silk.NET.Vulkan.Extensions.KHR; using Buffer = Silk.NET.Vulkan.Buffer; @@ -15,7 +16,7 @@ namespace Drawie.RenderApi.Vulkan; -public class VulkanWindowRenderApi : IVulkanWindowRenderApi +public class VulkanHostViewRenderApi : IVulkanHostViewRenderApi { private const int MAX_FRAMES_IN_FLIGHT = 2; @@ -54,13 +55,15 @@ public class VulkanWindowRenderApi : IVulkanWindowRenderApi private Fence[]? imagesInFlight; private int currentFrame = 0; public event Action? FramebufferResized; - ITexture IWindowRenderApi.RenderTexture => texture; + ITexture IHostViewRenderApi.RenderTexture => texture; - public VulkanWindowContext Context => context; + public VulkanWindowContext Context => context; + + public event Action Initialized; - IVulkanContext IVulkanWindowRenderApi.Context => context; + IVulkanContext IVulkanHostViewRenderApi.Context => context; - public VulkanWindowRenderApi(VulkanWindowContext context) + public VulkanHostViewRenderApi(VulkanWindowContext context) { this.context = context; } @@ -72,9 +75,11 @@ public void UpdateFramebufferSize(int width, int height) public void PrepareTextureToWrite() { - texture.TransitionLayoutTo(VulkanTexture.ShaderReadOnlyOptimal, VulkanTexture.ColorAttachmentOptimal); + texture.MakeWriteable(); } + public IGraphicsContext GraphicsContext { get; private set; } + public void CreateInstance(object contextObject, VecI framebufferSize) { if (contextObject is not IVulkanContextInfo vkContext) throw new VulkanNotSupportedException(); @@ -105,6 +110,9 @@ public void CreateInstance(object contextObject, VecI framebufferSize) CreateSyncObjects(); lastFramebufferSize = framebufferSize; + + GraphicsContext = context; + Initialized?.Invoke(); } public unsafe void DestroyInstance() @@ -192,8 +200,9 @@ private unsafe void CreateDescriptorSetLayout() public void CreateTextureImage() { - texture = new VulkanTexture(context.Api!, context.LogicalDevice.Device, context.PhysicalDevice, commandPool, - context.GraphicsQueue, context.GraphicsQueueFamilyIndex, framebufferSize); + texture = new VulkanTexture(context.Api!, context.LogicalDevice.Device, context.PhysicalDevice, commandPool, context.GraphicsQueue, context.GraphicsQueueFamilyIndex, + new TextureDesc { Width = framebufferSize.X, Height = framebufferSize.Y, Samples = 1, Depth = DepthFormat.NoDepth, Format = TextureFormat.RGBA8_Unorm }); + context.AddManagedTexture(texture, texture.ImageHandle); texture.MakeReadOnly(); } @@ -249,7 +258,7 @@ private unsafe void CreateDescriptorSets() DescriptorImageInfo imageInfo = new() { Sampler = texture.Sampler, - ImageView = texture.ImageView, + ImageView = texture.ColorAttachment.View, ImageLayout = ImageLayout.ShaderReadOnlyOptimal }; @@ -420,7 +429,7 @@ public unsafe void Render(double deltaTime) private void UpdateTextureLayout() { - texture.TransitionLayoutTo(VulkanTexture.ColorAttachmentOptimal, VulkanTexture.ShaderReadOnlyOptimal); + texture.MakeReadOnly(); } private unsafe void CreateCommandPool() @@ -545,12 +554,13 @@ private void CreateGraphicsPipeline() builder .AddStage(stage => stage.OfType(GraphicsPipelineStageType.Vertex).WithShader("Drawie.RenderApi.Vulkan.Shaders.vert.spv")) .AddStage(stage => stage.OfType(GraphicsPipelineStageType.Fragment).WithShader("Drawie.RenderApi.Vulkan.Shaders.frag.spv")) + .WithVertexLayout(layout => layout.WithVec2().WithVec3().WithVec2()) .WithRenderPass(renderPass => { - /*TODO: Add some meaningful stuff*/ + renderPass.WithSamples(1); }); - graphicsPipeline = builder.Create(swapChainExtent, swapChainImageFormat, ImageLayout.PresentSrcKhr, ref descriptorSetLayout); + graphicsPipeline = builder.Create(swapChainExtent, swapChainImageFormat, ImageLayout.PresentSrcKhr, [descriptorSetLayout]); } private unsafe void CreateImageViews() @@ -681,7 +691,7 @@ private unsafe void CreateIndexBuffer() private void CreateVertexBuffer() { - var bufferSize = (ulong)Marshal.SizeOf() * (ulong)Primitives.Vertices.Length; + var bufferSize = (ulong)Marshal.SizeOf() * (ulong)Primitives.Vertices.Length; using StagingBuffer stagingBuffer = new(context, bufferSize); @@ -703,4 +713,4 @@ private void CopyBuffer(BufferObject srcBuffer, BufferObject dstBuffer, ulong si context.Api!.CmdCopyBuffer(commandBuffer.CommandBuffer, srcBuffer.VkBuffer, dstBuffer.VkBuffer, 1, copyRegion); } -} +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.Vulkan/VulkanPipeline.cs b/src/Drawie.RenderApi.Vulkan/VulkanPipeline.cs new file mode 100644 index 0000000..ddde7ac --- /dev/null +++ b/src/Drawie.RenderApi.Vulkan/VulkanPipeline.cs @@ -0,0 +1,88 @@ +using Drawie.Backend.Vertie.Core; +using Drawie.RenderApi.Abstraction.CommandRecording; +using Drawie.RenderApi.Abstraction.Pipeline; +using Drawie.RenderApi.Abstraction.Textures; +using Drawie.RenderApi.Vulkan.Helpers; +using Drawie.RenderApi.Vulkan.Stages; +using Drawie.RenderApi.Vulkan.Stages.Builders; +using Silk.NET.Vulkan; + +namespace Drawie.RenderApi.Vulkan; + +internal sealed class VulkanPipeline : IPipeline, IDisposable +{ + private readonly VulkanContext context; + + public PipelineDesc Description { get; } + + public Pipeline Pipeline { get; } + public GraphicsPipeline GraphicsPipeline => graphicsPipeline; + public VulkanDescriptorPool DescriptorPool => program.DescriptorPool; + + public GraphicsPipelineBuilder Builder { get; } + + private VulkanShaderProgram program; + + private GraphicsPipeline graphicsPipeline; + + public VulkanPipeline( + VulkanContext context, + PipelineDesc desc) + { + this.context = context; + Description = desc; + if (desc.ShaderProgram is not VulkanShaderProgram vulkanShaderProgram) + throw new ArgumentException("Invalid Shader Program type"); + program = vulkanShaderProgram; + + Builder = new GraphicsPipelineBuilder(context.Api, context.LogicalDevice.Device); + graphicsPipeline = CreatePipeline(); + Pipeline = graphicsPipeline.VkPipeline; + } + + public unsafe void Apply(ICommandList cmdList) + { + if (cmdList is not VulkanCommandList commandList) + throw new ArgumentNullException("Only vulkan command list is supported"); + + context.Api!.CmdBindPipeline( + commandList.CommandBuffer, + PipelineBindPoint.Graphics, + Pipeline); + + /* + context.Api.CmdBindDescriptorSets(commandList.CommandBuffer, PipelineBindPoint.Graphics, + GraphicsPipeline.VkPipelineLayout, 0, 1, in descriptorSet, 0, null); + */ + } + + private GraphicsPipeline CreatePipeline() + { + Builder.WithVertexLayout(layout => layout.WithVec3().WithVec3().WithVec2()); + Builder.WithPolygonMode(Description.Rasterizer.RenderMode == RenderMode.Default + ? PolygonMode.Fill + : PolygonMode.Line) + .WithCullMode(CullModeFlags.BackBit) + .WithFrontFace(FrontFace.Clockwise); + + Builder.Stages.Add(program.VertexStageBuilder); + Builder.Stages.Add(program.FragmentStageBuilder); + Builder.DoNotDisposeStages = true; + Builder.WithRenderPass(builder => + { + builder.WithDepth(Description.Depth.Format.ToVkFormat()) + .WithSamples(Description.Rasterizer.Samples); + }); + Builder.WithDepth(); + + var pipeline = Builder.Create(new Extent2D((uint)Description.Viewport.Width, (uint)Description.Viewport.Height), + Format.R8G8B8A8Unorm, ImageLayout.ColorAttachmentOptimal, [program.DescriptorSetLayout]); + + return pipeline; + } + + public void Dispose() + { + graphicsPipeline.Dispose(); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.Vulkan/VulkanRecordedRenderPass.cs b/src/Drawie.RenderApi.Vulkan/VulkanRecordedRenderPass.cs new file mode 100644 index 0000000..c9ce075 --- /dev/null +++ b/src/Drawie.RenderApi.Vulkan/VulkanRecordedRenderPass.cs @@ -0,0 +1,71 @@ +using Drawie.RenderApi.Abstraction.CommandRecording; +using Drawie.RenderApi.Vulkan.Exceptions; +using Silk.NET.Vulkan; + +namespace Drawie.RenderApi.Vulkan; + +internal sealed class VulkanRecordedRenderPass : RecordedRenderPass +{ + public CommandBuffer CommandBuffer { get; } + public VulkanContext Context { get; } + public CommandPool Pool { get; } + + private bool submitted; + + public VulkanRecordedRenderPass( + VulkanContext context, + CommandBuffer commandBuffer, + CommandPool commandPool) + { + CommandBuffer = commandBuffer; + Context = context; + Pool = commandPool; + + Execute = Submit; + } + + private unsafe void Submit() + { + if (submitted) + throw new InvalidOperationException( + "This Vulkan render pass has already been submitted."); + + submitted = true; + + var commandBuffer = CommandBuffer; + + SubmitInfo submitInfo = new() + { + SType = StructureType.SubmitInfo, + CommandBufferCount = 1, + PCommandBuffers = &commandBuffer + }; + + var result = Context.Api!.QueueSubmit( + Context.GraphicsQueue, + 1, + in submitInfo, + default); + + if (result != Result.Success) + { + submitted = false; + + throw new VulkanException( + $"Failed to submit Vulkan command buffer: {result}"); + } + + result = Context.Api.QueueWaitIdle( + Context.GraphicsQueue); + + if (result != Result.Success) + throw new VulkanException( + $"Failed waiting for Vulkan queue: {result}"); + + Context.Api.FreeCommandBuffers( + Context.LogicalDevice.Device, + Pool, + 1, + in commandBuffer); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.Vulkan/VulkanRenderApi.cs b/src/Drawie.RenderApi.Vulkan/VulkanRenderApi.cs index 29ee50b..000ad12 100644 --- a/src/Drawie.RenderApi.Vulkan/VulkanRenderApi.cs +++ b/src/Drawie.RenderApi.Vulkan/VulkanRenderApi.cs @@ -1,15 +1,17 @@ +using Drawie.RenderApi.Abstraction; using Silk.NET.Vulkan; namespace Drawie.RenderApi.Vulkan; public class VulkanRenderApi : IVulkanRenderApi { - private List windowRenderApis = new List(); - public IReadOnlyCollection WindowRenderApis => windowRenderApis; + private List windowRenderApis = new List(); + public IReadOnlyCollection WindowRenderApis => windowRenderApis; + public IGraphicsDevice GraphicsDevice { get; private set; } public IVulkanContext VulkanContext { get; private set; } - IReadOnlyCollection IVulkanRenderApi.WindowRenderApis => - windowRenderApis.Cast().ToList(); + IReadOnlyCollection IVulkanRenderApi.WindowRenderApis => + windowRenderApis.Cast().ToList(); public VulkanRenderApi() { @@ -18,26 +20,46 @@ public VulkanRenderApi() public VulkanRenderApi(IVulkanContext vulkanContext) { VulkanContext = vulkanContext; + GraphicsDevice = CreateGraphicsDevice(vulkanContext); } - public IWindowRenderApi CreateWindowRenderApi() + public IHostViewRenderApi CreateWindowRenderApi() { - VulkanWindowRenderApi windowRenderApi; + VulkanHostViewRenderApi hostViewRenderApi; if (windowRenderApis.Count == 0) { var context = new VulkanWindowContext(); VulkanContext = context; + + hostViewRenderApi = new VulkanHostViewRenderApi(context); - windowRenderApi = new VulkanWindowRenderApi(context); - windowRenderApis.Add(windowRenderApi); - return windowRenderApi; + hostViewRenderApi.Initialized += () => { GraphicsDevice = CreateGraphicsDevice(context); }; + windowRenderApis.Add(hostViewRenderApi); + return hostViewRenderApi; } - var existingWindowRenderApi = windowRenderApis.First() as VulkanWindowRenderApi; + var existingWindowRenderApi = windowRenderApis.First() as VulkanHostViewRenderApi; - windowRenderApi = new VulkanWindowRenderApi(existingWindowRenderApi.Context); + hostViewRenderApi = new VulkanHostViewRenderApi(existingWindowRenderApi.Context); - windowRenderApis.Add(windowRenderApi); - return windowRenderApi; + windowRenderApis.Add(hostViewRenderApi); + return hostViewRenderApi; + } + + private static IGraphicsDevice CreateGraphicsDevice(IVulkanContext context) + { + if (context is not VulkanContext vulkanContext || vulkanContext.Api is null) + throw new InvalidOperationException("Vulkan graphics device is available only after the Vulkan context is initialized."); + + return new VulkanGraphicsDevice(vulkanContext); + } + + public void Dispose() + { + GraphicsDevice?.Dispose(); + GraphicsDevice = null; + + if(VulkanContext is IDisposable disposable) + disposable.Dispose(); } } diff --git a/src/Drawie.RenderApi.Vulkan/VulkanRenderTarget.cs b/src/Drawie.RenderApi.Vulkan/VulkanRenderTarget.cs new file mode 100644 index 0000000..530bf21 --- /dev/null +++ b/src/Drawie.RenderApi.Vulkan/VulkanRenderTarget.cs @@ -0,0 +1,65 @@ +using Drawie.Numerics; +using Drawie.RenderApi.Abstraction.RenderTargets; +using Drawie.RenderApi.Vulkan.Extensions; +using Silk.NET.Vulkan; + +namespace Drawie.RenderApi.Vulkan; + +internal sealed class VulkanRenderTarget(VulkanContext context, Buffers.VulkanTexture texture, VecI size) : IRenderTarget, IDisposable +{ + public VulkanContext Context { get; } = context; + public ulong SurfaceId => Texture.ImageHandle; + public VecI Size { get; } = size; + public Buffers.VulkanTexture Texture { get; } = texture; + + public Framebuffer? Framebuffer { get; private set; } + + private RenderPass? lastCreatedRenderPass; + + public unsafe void CreateFramebufferFor(RenderPass renderPass) + { + if (lastCreatedRenderPass != null && lastCreatedRenderPass.Value.Handle == renderPass.Handle) return; + + DestroyFramebuffer(); + ImageView* attachments = stackalloc ImageView[(int)Texture.Attachments]; + + attachments[0] = Texture.ColorAttachment.View; + int index = 1; + if (Texture.DepthAttachment != null) + { + attachments[index] = Texture.DepthAttachment.View; + index++; + } + + if (Texture.MsaaResolvedColorAttachment != null) + { + attachments[index] = Texture.MsaaResolvedColorAttachment.View; + } + + FramebufferCreateInfo framebufferCreateInfo = new() + { + SType = StructureType.FramebufferCreateInfo, + Width = (uint)Size.X, + Height = (uint)Size.Y, + RenderPass = renderPass, + AttachmentCount = Texture.Attachments, + PAttachments = attachments, + Layers = 1 + }; + + Context.Api.CreateFramebuffer(Context.LogicalDevice.Device, &framebufferCreateInfo, null, out var framebuffer) + .ThrowOnError("Failed to create framebuffer"); + Framebuffer = framebuffer; + } + + private unsafe void DestroyFramebuffer() + { + if(Framebuffer != null) + Context.Api.DestroyFramebuffer(Context.LogicalDevice.Device, Framebuffer.Value, null); + } + + public void Dispose() + { + Texture.Dispose(); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.Vulkan/VulkanSampler.cs b/src/Drawie.RenderApi.Vulkan/VulkanSampler.cs new file mode 100644 index 0000000..9ca8129 --- /dev/null +++ b/src/Drawie.RenderApi.Vulkan/VulkanSampler.cs @@ -0,0 +1,66 @@ +using Drawie.RenderApi.Abstraction.Textures; +using Drawie.RenderApi.Vulkan.Exceptions; +using Silk.NET.Vulkan; + +namespace Drawie.RenderApi.Vulkan; + +internal sealed class VulkanSampler : ISampler, IDisposable +{ + private readonly VulkanContext context; + private readonly Sampler _vkSampler; + + public uint Handle => unchecked((uint)_vkSampler.Handle); + public Sampler VkSampler => _vkSampler; + + public VulkanSampler(VulkanContext context, SamplerDesc desc) + { + this.context = context; + _vkSampler = CreateSampler(desc); + } + + public unsafe void Dispose() + { + context.Api!.DestroySampler( + context.LogicalDevice.Device, + _vkSampler, + null); + } + + private unsafe Sampler CreateSampler(SamplerDesc desc) + { + SamplerCreateInfo info = new() + { + SType = StructureType.SamplerCreateInfo, + + MagFilter = Filter.Linear, + MinFilter = Filter.Linear, + + AddressModeU = SamplerAddressMode.Repeat, + AddressModeV = SamplerAddressMode.Repeat, + AddressModeW = SamplerAddressMode.Repeat, + + AnisotropyEnable = false, + MaxAnisotropy = 1, + + BorderColor = BorderColor.IntOpaqueBlack, + + UnnormalizedCoordinates = false, + + CompareEnable = false, + CompareOp = CompareOp.Always, + + MipmapMode = SamplerMipmapMode.Linear + }; + + if (context.Api!.CreateSampler( + context.LogicalDevice.Device, + &info, + null, + out var result) != Result.Success) + { + throw new VulkanException("Failed to create Vulkan sampler."); + } + + return result; + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.Vulkan/VulkanShaderProgram.cs b/src/Drawie.RenderApi.Vulkan/VulkanShaderProgram.cs new file mode 100644 index 0000000..18d95b1 --- /dev/null +++ b/src/Drawie.RenderApi.Vulkan/VulkanShaderProgram.cs @@ -0,0 +1,142 @@ +using System.Numerics; +using System.Runtime.InteropServices; +using Drawie.Backend.Shaders.Common; +using Drawie.RenderApi.Abstraction.Shaders; +using Drawie.RenderApi.Vulkan.Buffers; +using Drawie.RenderApi.Vulkan.Exceptions; +using Drawie.RenderApi.Vulkan.Extensions; +using Drawie.RenderApi.Vulkan.Stages.Builders; +using Silk.NET.Vulkan; +using Buffer = System.Buffer; + +namespace Drawie.RenderApi.Vulkan; + +internal sealed class VulkanShaderProgram : IShaderProgram, IDisposable +{ + public VulkanContext Context { get; } + public ShaderProgramDesc Description { get; } + + public GraphicsPipelineStageBuilder VertexStageBuilder { get; } + public GraphicsPipelineStageBuilder FragmentStageBuilder { get; } + public DescriptorSetLayout DescriptorSetLayout { get; set; } + + public VulkanDescriptorPool DescriptorPool { get; set; } + + + public VulkanShaderProgram(VulkanContext context, ShaderProgramDesc desc) + { + Context = context; + Description = desc; + + foreach (var shader in desc.Shaders) + { + GraphicsPipelineStageBuilder stageBuilder = + new GraphicsPipelineStageBuilder(Context.Api, Context.LogicalDevice.Device); + + stageBuilder.ShaderBytes = shader.Bytes; + stageBuilder.EntryName = shader.EntryName; + if (shader.Type == ShaderType.Vertex) + { + VertexStageBuilder = stageBuilder; + stageBuilder.Type = GraphicsPipelineStageType.Vertex; + } + else if (shader.Type == ShaderType.Fragment) + { + FragmentStageBuilder = stageBuilder; + stageBuilder.Type = GraphicsPipelineStageType.Fragment; + } + else + { + throw new NotImplementedException("Unsupported shader type."); + } + } + + DescriptorSetLayout = CreateDescriptorSetLayout(); + DescriptorPool = CreateDescriptorPool(); + } + + public void Use() + { + } + + private unsafe VulkanDescriptorPool CreateDescriptorPool() + { + DescriptorPoolSize poolSize = new DescriptorPoolSize() + { + Type = DescriptorType.UniformBuffer, + DescriptorCount = 1, + }; + + DescriptorPoolSize poolFragSize = new DescriptorPoolSize() + { + Type = DescriptorType.CombinedImageSampler, + DescriptorCount = 1, + }; + + DescriptorPoolSize* poolSizes = stackalloc DescriptorPoolSize[2]; + poolSizes[0] = poolSize; + poolSizes[1] = poolFragSize; + + DescriptorPoolCreateInfo poolInfo = new() + { + SType = StructureType.DescriptorPoolCreateInfo, + PoolSizeCount = 2, + PPoolSizes = poolSizes, + MaxSets = 10 + }; + + Context.Api.CreateDescriptorPool(Context.LogicalDevice.Device, &poolInfo, null, out var descriptorPool).ThrowOnError("Failed to create descriptor pool."); + + return new VulkanDescriptorPool(Context, descriptorPool, [DescriptorSetLayout]); + } + + private unsafe DescriptorSetLayout CreateDescriptorSetLayout() + { + DescriptorSetLayoutBinding[] bindings = + [ + new() + { + Binding = 0, + DescriptorType = DescriptorType.UniformBuffer, + DescriptorCount = 1, + StageFlags = ShaderStageFlags.VertexBit + }, + + new() + { + Binding = 1, + DescriptorType = DescriptorType.CombinedImageSampler, + DescriptorCount = 1, + StageFlags = ShaderStageFlags.FragmentBit + } + ]; + + fixed (DescriptorSetLayoutBinding* bindingPtr = bindings) + { + DescriptorSetLayoutCreateInfo info = new() + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + BindingCount = (uint)bindings.Length, + PBindings = bindingPtr + }; + + if (Context.Api!.CreateDescriptorSetLayout( + Context.LogicalDevice.Device, + in info, + null, + out var layout) != Result.Success) + { + throw new VulkanException( + "Failed to create Vulkan descriptor set layout."); + } + + return layout; + } + } + + public unsafe void Dispose() + { + Context.Api.DestroyDescriptorSetLayout(Context.LogicalDevice.Device, DescriptorSetLayout, null); + DescriptorPool.Dispose(); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.Vulkan/VulkanStorageBuffer.cs b/src/Drawie.RenderApi.Vulkan/VulkanStorageBuffer.cs new file mode 100644 index 0000000..2ae1b7f --- /dev/null +++ b/src/Drawie.RenderApi.Vulkan/VulkanStorageBuffer.cs @@ -0,0 +1,14 @@ +using Drawie.RenderApi.Abstraction.Buffers; +using Drawie.RenderApi.Vulkan.Buffers; +using Silk.NET.Vulkan; + +namespace Drawie.RenderApi.Vulkan; + +internal sealed class VulkanStorageBuffer : BufferObject +{ + public VulkanStorageBuffer(VulkanContext context, ulong size) + : base(context.Api!, context.LogicalDevice.Device, context.PhysicalDevice, size, BufferUsageFlags.StorageBufferBit, + MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit, BufferUsage.Storage) + { + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.Vulkan/VulkanWindowContext.cs b/src/Drawie.RenderApi.Vulkan/VulkanWindowContext.cs index 866ec1f..03213ca 100644 --- a/src/Drawie.RenderApi.Vulkan/VulkanWindowContext.cs +++ b/src/Drawie.RenderApi.Vulkan/VulkanWindowContext.cs @@ -4,6 +4,7 @@ using Drawie.RenderApi.Vulkan.Helpers; using Silk.NET.Core.Native; using Silk.NET.Vulkan; +using Silk.NET.Vulkan.Extensions.EXT; using Silk.NET.Vulkan.Extensions.KHR; namespace Drawie.RenderApi.Vulkan; @@ -30,12 +31,12 @@ private unsafe void CreateSurface(IVulkanContextInfo vkContext) public override void Initialize(IVulkanContextInfo contextInfo) { Api = Vk.GetApi(); - TryAddValidationLayer("VK_LAYER_KHRONOS_validation"); deviceExtensions.Add(KhrSwapchain.ExtensionName); SetupInstance(contextInfo); + SetupDebugMessenger(); if (contextInfo.HasSurface) @@ -75,7 +76,8 @@ protected override unsafe void CreateLogicalDevice() PhysicalDeviceFeatures deviceFeatures = new() { - SamplerAnisotropy = false + SamplerAnisotropy = false, + FillModeNonSolid = true }; DeviceCreateInfo createInfo = new() diff --git a/src/Drawie.RenderApi.Web.Common/Drawie.RenderApi.Web.Common.csproj b/src/Drawie.RenderApi.Web.Common/Drawie.RenderApi.Web.Common.csproj index e43df41..5990a50 100644 --- a/src/Drawie.RenderApi.Web.Common/Drawie.RenderApi.Web.Common.csproj +++ b/src/Drawie.RenderApi.Web.Common/Drawie.RenderApi.Web.Common.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable diff --git a/src/Drawie.RenderApi.Web.Common/HtmlCanvas.cs b/src/Drawie.RenderApi.Web.Common/HtmlCanvas.cs index e743346..1fa120e 100644 --- a/src/Drawie.RenderApi.Web.Common/HtmlCanvas.cs +++ b/src/Drawie.RenderApi.Web.Common/HtmlCanvas.cs @@ -1,8 +1,9 @@ using Drawie.JSInterop; -namespace Drawie.RenderApi.Html5Canvas; +namespace Drawie.RenderApi.Web.Common; public class HtmlCanvas() : HtmlObject("canvas"), ICanvasTexture { - public string CanvasId => Id; + public string CanvasId => Id; + public ulong TextureId { get; } = ulong.MaxValue; } diff --git a/src/Drawie.RenderApi.WebGl/Drawie.RenderApi.WebGl.csproj b/src/Drawie.RenderApi.WebGl/Drawie.RenderApi.WebGl.csproj index 96fb79b..cceed28 100644 --- a/src/Drawie.RenderApi.WebGl/Drawie.RenderApi.WebGl.csproj +++ b/src/Drawie.RenderApi.WebGl/Drawie.RenderApi.WebGl.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable true diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlBindings.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlBindings.cs new file mode 100644 index 0000000..0bb8106 --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/Enums/WebGlBindings.cs @@ -0,0 +1,6 @@ +namespace Drawie.RenderApi.WebGl.Enums; + +public enum WebGlBindings +{ + FramebufferBinding = 0x8CA6 +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlBufferMask.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlBufferMask.cs index c03766c..90c1c86 100644 --- a/src/Drawie.RenderApi.WebGl/Enums/WebGlBufferMask.cs +++ b/src/Drawie.RenderApi.WebGl/Enums/WebGlBufferMask.cs @@ -1,6 +1,9 @@ namespace Drawie.RenderApi.WebGl; +[Flags] public enum WebGlBufferMask { ColorBufferBit = 0x00004000, + DepthBufferBit = 0x00000100, + StencilBufferBit = 0x00000400 } diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlBufferType.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlBufferType.cs index 9c280c3..62bb795 100644 --- a/src/Drawie.RenderApi.WebGl/Enums/WebGlBufferType.cs +++ b/src/Drawie.RenderApi.WebGl/Enums/WebGlBufferType.cs @@ -1,6 +1,8 @@ -namespace Drawie.RenderApi.WebGl; +namespace Drawie.RenderApi.WebGl.Enums; public enum WebGlBufferType { Array = 0x8892, + ElementArray = 0x8893, + Uniform = 0x8A11 } diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlBufferUsage.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlBufferUsage.cs index ccd8f69..843691a 100644 --- a/src/Drawie.RenderApi.WebGl/Enums/WebGlBufferUsage.cs +++ b/src/Drawie.RenderApi.WebGl/Enums/WebGlBufferUsage.cs @@ -1,6 +1,7 @@ -namespace Drawie.RenderApi.WebGl; +namespace Drawie.RenderApi.WebGl.Enums; internal enum WebGlBufferUsage { - StaticDraw = 0x88E4 -} + StaticDraw = 0x88E4, + DynamicDraw = 0x88E8 +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlCap.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlCap.cs new file mode 100644 index 0000000..53ae8ce --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/Enums/WebGlCap.cs @@ -0,0 +1,14 @@ +namespace Drawie.RenderApi.WebGl.Enums; + +public enum WebGlCap +{ + Blend = 0x0BE2, + DepthTest = 0x0B71, + Dither = 0x0BD0, + PolygonOffsetFill = 0x8037, + SampleAlphaToCoverage = 0x809E, + SampleCoverage = 0x80A0, + ScissorTest = 0x0C11, + StencilTest = 0x0B90, + CullFace = 0x0B44, +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlCommandList.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlCommandList.cs new file mode 100644 index 0000000..ed176d9 --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/Enums/WebGlCommandList.cs @@ -0,0 +1,87 @@ +using Drawie.JSInterop; +using Drawie.RenderApi.Abstraction.Buffers; +using Drawie.RenderApi.Abstraction.CommandRecording; +using Drawie.RenderApi.Abstraction.Pipeline; +using Drawie.RenderApi.Abstraction.RenderTargets; +using Drawie.RenderApi.Abstraction.Textures; + +namespace Drawie.RenderApi.WebGl.Enums; + +public class WebGlCommandList : CommandList +{ + public int Gl { get; } + private IRenderTarget source; + + private int originalFb; + + private int lastBoundTextureSlot = 0; + + public WebGlCommandList(int gl) + { + Gl = gl; + } + + public override void BeginRenderPass(IRenderTarget fb) + { + source = fb; + lastBoundTextureSlot = 0; + ClearInstructions(); + RecordInstruction(() => + { + originalFb = JSRuntime.GetParameter(Gl, (int)WebGlBindings.FramebufferBinding); + JSRuntime.BindFramebuffer(Gl, (int)WebGlFramebufferTarget.Framebuffer, (int)fb.SurfaceId); + }); + } + + public override void SetPipeline(IPipeline pipeline) + { + RecordInstruction(() => pipeline.Apply()); + } + + public override void SetBuffers(IBufferGroup bufferGroup) + { + RecordInstruction(() => { JSRuntime.BindVertexArray(Gl, (int)bufferGroup.Handle); }); + } + + public override void BindTexture(ITexture texture, ISampler sampler) + { + RecordInstruction(() => + { + JSRuntime.ActiveTexture(Gl, (int)WebGlTextureUnit.Texture0 + lastBoundTextureSlot); + JSRuntime.BindTexture(Gl, (int)WebGlTextureType.Texture2D, (int)texture.TextureId); + JSRuntime.BindSampler(Gl, lastBoundTextureSlot, (int)sampler.Handle); + lastBoundTextureSlot++; + }); + } + + public override void DrawIndexed(int indexCount) + { + RecordInstruction(() => + { + JSRuntime.DrawElements(Gl, (int)WebGlPrimitiveType.Triangles, indexCount, + (int)WebGlDataType.UnsignedInt, 0); + }); + } + + public override RecordedRenderPass EndRenderPass(IRenderTarget blitTo) + { + RecordInstruction(() => + { + JSRuntime.BindFramebuffer(Gl, (int)WebGlFramebufferTarget.ReadFramebuffer, (int)source.SurfaceId); + JSRuntime.BindFramebuffer(Gl, (int)WebGlFramebufferTarget.DrawFramebuffer, (int)blitTo.SurfaceId); + JSRuntime.BlitFramebuffer(Gl, + 0, 0, source.Size.X, source.Size.Y, + 0, 0, blitTo.Size.X, blitTo.Size.Y, + (int)WebGlBufferMask.ColorBufferBit, + (int)WebGlTextureFilter.Nearest); + JSRuntime.BindFramebuffer(Gl, (int)WebGlFramebufferTarget.Framebuffer, originalFb); + }); + return ToRenderPass(); + } + + public override RecordedRenderPass EndRenderPass() + { + RecordInstruction(() => JSRuntime.BindFramebuffer(Gl, (int)WebGlFramebufferTarget.Framebuffer, originalFb)); + return ToRenderPass(); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlDataType.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlDataType.cs new file mode 100644 index 0000000..a2d280b --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/Enums/WebGlDataType.cs @@ -0,0 +1,9 @@ +namespace Drawie.RenderApi.WebGl.Enums; + +public enum WebGlDataType +{ + UnsignedByte = 0x1401, + UnsignedShort = 0x1403, + UnsignedInt = 0x1405, + Float = 0x1406 +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlDepthFunc.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlDepthFunc.cs new file mode 100644 index 0000000..038d83c --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/Enums/WebGlDepthFunc.cs @@ -0,0 +1,13 @@ +namespace Drawie.RenderApi.WebGl.Enums; + +public enum WebGlDepthFunc +{ + Never = 0x0200, + Less = 0x0201, + Equal = 0x0202, + LEqual = 0x0203, + Greater = 0x0204, + NotEqual = 0x0205, + GEqual = 0x0206, + Always = 0x0207 +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlDrawMode.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlDrawMode.cs deleted file mode 100644 index ed5ba88..0000000 --- a/src/Drawie.RenderApi.WebGl/Enums/WebGlDrawMode.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Drawie.RenderApi.WebGl; - -public enum WebGlDrawMode -{ - TriangleStrip = 0x0005, -} diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlError.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlError.cs new file mode 100644 index 0000000..55798ab --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/Enums/WebGlError.cs @@ -0,0 +1,11 @@ +namespace Drawie.RenderApi.WebGl.Enums; + +public enum WebGlError +{ + NoError = 0, + InvalidEnum = 0x0500, + InvalidValue = 0x0501, + InvalidOperation = 0x0502, + OutOfMemory = 0x0505, + ContextLostWebGl = 0x9242 +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlFramebufferAttachment.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlFramebufferAttachment.cs new file mode 100644 index 0000000..424bfbf --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/Enums/WebGlFramebufferAttachment.cs @@ -0,0 +1,24 @@ +namespace Drawie.RenderApi.WebGl.Enums; + +public enum WebGlFramebufferAttachment +{ + ColorAttachment0 = 0x8CE0, + DepthAttachment = 0x8D00, + StencilAttachment = 0x8D20, + DepthStencilAttachment = 0x821A, + ColorAttachment1 = 0x8CE1, + ColorAttachment2 = 0x8CE2, + ColorAttachment3 = 0x8CE3, + ColorAttachment4 = 0x8CE4, + ColorAttachment5 = 0x8CE5, + ColorAttachment6 = 0x8CE6, + ColorAttachment7 = 0x8CE7, + ColorAttachment8 = 0x8CE8, + ColorAttachment9 = 0x8CE9, + ColorAttachment10 = 0x8CEA, + ColorAttachment11 = 0x8CEB, + ColorAttachment12 = 0x8CEC, + ColorAttachment13 = 0x8CED, + ColorAttachment14 = 0x8CEE, + ColorAttachment15 = 0x8CEF +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlFramebufferStatus.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlFramebufferStatus.cs new file mode 100644 index 0000000..518d95d --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/Enums/WebGlFramebufferStatus.cs @@ -0,0 +1,10 @@ +namespace Drawie.RenderApi.WebGl.Enums; + +public enum WebGlFramebufferStatus +{ + FramebufferComplete = 0x8CD5, + FramebufferIncompleteAttachment = 0x8CD6, + FramebufferIncompleteMissingAttachment = 0x8CD7, + FramebufferIncompleteDimensions = 0x8CD9, + FramebufferUnsupported = 0x8CDD +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlFramebufferTarget.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlFramebufferTarget.cs new file mode 100644 index 0000000..df80e18 --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/Enums/WebGlFramebufferTarget.cs @@ -0,0 +1,8 @@ +namespace Drawie.RenderApi.WebGl.Enums; + +public enum WebGlFramebufferTarget +{ + Framebuffer = 0x8D40, + DrawFramebuffer = 0x8CA9, + ReadFramebuffer = 0x8CA8 +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlPrimitiveType.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlPrimitiveType.cs new file mode 100644 index 0000000..ba88c1e --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/Enums/WebGlPrimitiveType.cs @@ -0,0 +1,12 @@ +namespace Drawie.RenderApi.WebGl.Enums; + +public class WebGlPrimitiveType +{ + public const int Points = 0x0000; + public const int Lines = 0x0001; + public const int LineLoop = 0x0002; + public const int LineStrip = 0x0003; + public const int Triangles = 0x0004; + public const int TriangleStrip = 0x0005; + public const int TriangleFan = 0x0006; +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlRenderbufferFormat.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlRenderbufferFormat.cs new file mode 100644 index 0000000..bf81262 --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/Enums/WebGlRenderbufferFormat.cs @@ -0,0 +1,6 @@ +namespace Drawie.RenderApi.WebGl.Enums; + +public enum WebGlRenderbufferFormat +{ + Depth24Stencil8 = 0x88F0 +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlRenderbufferTarget.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlRenderbufferTarget.cs new file mode 100644 index 0000000..1e83873 --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/Enums/WebGlRenderbufferTarget.cs @@ -0,0 +1,6 @@ +namespace Drawie.RenderApi.WebGl.Enums; + +public enum WebGlRenderbufferTarget +{ + Renderbuffer = 0x8D41 +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlTextureFilter.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlTextureFilter.cs index 6e46f2f..475a2f0 100644 --- a/src/Drawie.RenderApi.WebGl/Enums/WebGlTextureFilter.cs +++ b/src/Drawie.RenderApi.WebGl/Enums/WebGlTextureFilter.cs @@ -3,4 +3,5 @@ public enum WebGlTextureFilter { Nearest = 0x2600, + Linear = 0x2601 } diff --git a/src/Drawie.RenderApi.WebGl/Enums/WebGlTextureUnit.cs b/src/Drawie.RenderApi.WebGl/Enums/WebGlTextureUnit.cs new file mode 100644 index 0000000..1ec27b7 --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/Enums/WebGlTextureUnit.cs @@ -0,0 +1,7 @@ +namespace Drawie.RenderApi.WebGl.Enums; + +public enum WebGlTextureUnit +{ + Texture0 = 0x84C0, + // for more just add a number to Texture0 +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/WebGlBuffer.cs b/src/Drawie.RenderApi.WebGl/WebGlBuffer.cs new file mode 100644 index 0000000..0cf62ac --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/WebGlBuffer.cs @@ -0,0 +1,66 @@ +using System.ComponentModel; +using System.Runtime.InteropServices; +using Drawie.JSInterop; +using Drawie.RenderApi.Abstraction.Buffers; +using Drawie.RenderApi.WebGl.Enums; + +namespace Drawie.RenderApi.WebGl; + +public class WebGlBuffer : IBuffer where TData : unmanaged +{ + public int Id { get; } + public int Gl { get; } + public BufferUsage Usage { get; } + public ulong Size { get; } + + public WebGlBuffer(int gl, BufferUsage usage, TData[]? data = null) + { + Usage = usage; + Gl = gl; + + Id = JSRuntime.CreateBuffer(gl); + if (data != null) + { + Size = (uint)data.Length; + WebGlBufferType bufferType = ToBufferType(); + JSRuntime.BindBuffer(Gl, (int)bufferType, Id); + var bytes = MemoryMarshal.AsBytes(data).ToArray(); + JSRuntime.BufferData(Gl, (int)bufferType, bytes, (int)WebGlBufferUsage.StaticDraw); + } + + if (usage == BufferUsage.Vertex) + { + VertexAttributePointer(0, 3, 8, 0); + VertexAttributePointer(1, 3, 8, 3); + VertexAttributePointer(2, 2, 8, 6); + } + } + + private WebGlBufferType ToBufferType() + { + return Usage switch + { + BufferUsage.Vertex => WebGlBufferType.Array, + BufferUsage.Index => WebGlBufferType.ElementArray, + BufferUsage.Uniform => WebGlBufferType.Uniform, + BufferUsage.Storage => + throw new InvalidEnumArgumentException("Storage buffers are not supported in WebGL."), + _ => throw new ArgumentOutOfRangeException() + }; + } + + private void VertexAttributePointer(int index, int count, int vertexSize, + int offset) + { + int vTypeSize = sizeof(float); + JSRuntime.VertexAttribPointer(Gl, index, count, (int)WebGlDataType.Float, false, vertexSize * vTypeSize, + (offset * vTypeSize)); + JSRuntime.EnableVertexAttribArray(Gl, index); + } + + public void Dispose() + { + //TODO: + //JSRuntime.DeleteBuffer(Gl, Id); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/WebGlContext.cs b/src/Drawie.RenderApi.WebGl/WebGlContext.cs index 9fdf62f..1105e8b 100644 --- a/src/Drawie.RenderApi.WebGl/WebGlContext.cs +++ b/src/Drawie.RenderApi.WebGl/WebGlContext.cs @@ -5,11 +5,11 @@ namespace Drawie.RenderApi.WebGl; public class WebGlContext : IWebGlContext { - public WebGlWindowRenderApi WebGlWindowRenderApi { get; } + public WebGlHostViewRenderApi WebGlHostViewRenderApi { get; } - public WebGlContext(WebGlWindowRenderApi webGlWindowRenderApi) + public WebGlContext(WebGlHostViewRenderApi webGlHostViewRenderApi) { - WebGlWindowRenderApi = webGlWindowRenderApi; + WebGlHostViewRenderApi = webGlHostViewRenderApi; } public IntPtr GetGlInterface(string name) diff --git a/src/Drawie.RenderApi.WebGl/WebGlDepthBuffer.cs b/src/Drawie.RenderApi.WebGl/WebGlDepthBuffer.cs new file mode 100644 index 0000000..95a750a --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/WebGlDepthBuffer.cs @@ -0,0 +1,52 @@ +using Drawie.JSInterop; +using Drawie.RenderApi.Abstraction.Textures; +using Drawie.RenderApi.WebGl.Enums; + +namespace Drawie.RenderApi.WebGl; + +public class WebGlDepthBuffer : IDisposable +{ + public uint RenderbufferId { get; } + + public int Width { get; } + public int Height { get; } + + private int Gl { get; } + + public WebGlDepthBuffer(int api, int width, int height, DepthFormat depth) + { + Gl = api; + + Width = width; + Height = height; + + RenderbufferId = (uint)JSRuntime.CreateRenderbuffer(api); + + JSRuntime.BindRenderbuffer(api, (int)WebGlRenderbufferTarget.Renderbuffer, (int)RenderbufferId); + + JSRuntime.RenderbufferStorage( + api, + (int)WebGlRenderbufferTarget.Renderbuffer, + (int)ToOpenglDepth(depth), width, height); + + JSRuntime.BindRenderbuffer(api, (int)WebGlRenderbufferTarget.Renderbuffer, 0); + } + + private WebGlRenderbufferFormat ToOpenglDepth(DepthFormat depth) + { + switch (depth) + { + case DepthFormat.NoDepth: + throw new ArgumentException("Cannot create depth with NoDepth format"); + case DepthFormat.Depth24Stencil8: + return WebGlRenderbufferFormat.Depth24Stencil8; + default: + throw new ArgumentOutOfRangeException(nameof(depth), depth, null); + } + } + + public void Dispose() + { + JSRuntime.DeleteRenderbuffer(Gl, (int)RenderbufferId); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/WebGlGraphicsContext.cs b/src/Drawie.RenderApi.WebGl/WebGlGraphicsContext.cs new file mode 100644 index 0000000..dbbe594 --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/WebGlGraphicsContext.cs @@ -0,0 +1,41 @@ +using Drawie.JSInterop; +using Drawie.RenderApi.Abstraction.Textures; +using Drawie.RenderApi.WebGl.Enums; + +namespace Drawie.RenderApi.WebGl; + +public class WebGlGraphicsContext(int Gl) : IGraphicsContext +{ + private HashSet ownedTextures = new HashSet(); + + public bool OwnsTexture(ITexture nativeTexture) + { + return ownedTextures.Contains(nativeTexture.TextureId); + } + + public void MakeCurrent() + { + JSRuntime.MakeContextCurrent(Gl); + } + + public WebGlRenderTarget CreateRenderTarget(int handle, int width, int height) + { + var texture = new WebGlRenderTarget(handle, width, height, DepthFormat.NoDepth); + ownedTextures.Add(texture.TextureId); + return texture; + } + + public void DisposeTexture(WebGlRenderTarget texture) + { + if (ownedTextures.Contains(texture.TextureId)) + { + texture.Dispose(); + ownedTextures.Remove(texture.TextureId); + } + } + + public override string ToString() + { + return string.Join(", ", ownedTextures); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/WebGlGraphicsDevice.cs b/src/Drawie.RenderApi.WebGl/WebGlGraphicsDevice.cs new file mode 100644 index 0000000..7c51b7b --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/WebGlGraphicsDevice.cs @@ -0,0 +1,140 @@ +using System.Text; +using Drawie.Backend.Shaders.Common; +using Drawie.JSInterop; +using Drawie.RenderApi.Abstraction; +using Drawie.RenderApi.Abstraction.Buffers; +using Drawie.RenderApi.Abstraction.CommandRecording; +using Drawie.RenderApi.Abstraction.Pipeline; +using Drawie.RenderApi.Abstraction.RenderTargets; +using Drawie.RenderApi.Abstraction.Shaders; +using Drawie.RenderApi.Abstraction.Textures; +using Drawie.RenderApi.WebGl.Enums; + +namespace Drawie.RenderApi.WebGl; + +public class WebGlGraphicsDevice : IGraphicsDevice +{ + public int GlHandle { get; } + + public WebGlGraphicsDevice(int gl) + { + GlHandle = gl; + } + + public IBuffer CreateBuffer(BufferUsage usage, TData[]? data) where TData : unmanaged + { + return new WebGlBuffer(GlHandle, usage, data); + } + + public ITexture CreateTexture(TextureDesc desc) + { + return new WebGlTexture(GlHandle, desc.Width, desc.Height); + } + + public IPipeline CreatePipeline(PipelineDesc desc) + { + return new WebGlPipeline(desc, GlHandle); + } + + public ICommandList CreateCommandList() + { + return new WebGlCommandList(GlHandle); + } + + public ISampler CreateSampler(SamplerDesc desc) + { + return new WebGlSampler(GlHandle, desc); + } + + public IShaderProgram CreateShaderProgram(ShaderProgramDesc desc) + { + WebGlShaderProgram webGlProgram = new WebGlShaderProgram(GlHandle); + int program = webGlProgram.ProgramHandle; + + int[] shaders = new int[desc.Shaders.Count]; + + try + { + for (int i = 0; i < desc.Shaders.Count; i++) + { + var shader = desc.Shaders[i]; + + int shaderHandle = JSRuntime.CreateShader( + GlHandle, + ToWebGlShaderType(shader.Type)); + + shaders[i] = shaderHandle; + + string source = Encoding.UTF8.GetString(shader.Bytes); + + JSRuntime.ShaderSource(GlHandle, shaderHandle, source); + string? result = JSRuntime.CompileShader(GlHandle, shaderHandle); + + if(result != null) + { + throw new Exception($"Shader compilation failed: {result}"); + } + + JSRuntime.AttachShader(GlHandle, program, shaderHandle); + } + + string? error = JSRuntime.LinkProgram(GlHandle, program); + if (error != null) + { + throw new Exception($"Program linking failed: {error}"); + } + + return webGlProgram; + } + finally + { + for (int i = 0; i < shaders.Length; i++) + { + int shader = shaders[i]; + + if (shader == 0) + continue; + + // TODO: + /*JSRuntime.DetachShader( + GlHandle, + program, + shader); + + JSRuntime.DeleteShader( + GlHandle, + shader);*/ + } + } + } + + private static int ToWebGlShaderType(ShaderType type) + { + return type switch + { + ShaderType.Vertex => (int)WebGlShaderType.Vertex, + ShaderType.Fragment => (int)WebGlShaderType.Fragment, + _ => throw new NotSupportedException( + $"Unsupported WebGL shader type: {type}") + }; + } + + public IRenderTarget CreateRenderTarget(TextureDesc textureDesc) + { + return new WebGlRenderTarget(GlHandle, textureDesc.Width, textureDesc.Height, textureDesc.Depth); + } + + public IBufferGroup CreateBufferGroup() + { + return new WebGlVertexArray(GlHandle); + } + + public void Submit(RecordedRenderPass cmdList) + { + for (var index = 0; index < cmdList.Instructions.Length; index++) + { + var instruction = cmdList.Instructions[index]; + instruction.Invoke(); + } + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/WebGlHostViewRenderApi.cs b/src/Drawie.RenderApi.WebGl/WebGlHostViewRenderApi.cs new file mode 100644 index 0000000..23c2569 --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/WebGlHostViewRenderApi.cs @@ -0,0 +1,68 @@ +using Drawie.JSInterop; +using Drawie.Numerics; +using Drawie.RenderApi.Abstraction.Textures; +using Drawie.RenderApi.Web.Common; +using Drawie.RenderApi.WebGl.Enums; +using Drawie.RenderApi.WebGl.Exceptions; + +namespace Drawie.RenderApi.WebGl; + +public class WebGlHostViewRenderApi : IHostViewRenderApi +{ + private HtmlCanvas canvasObject; + public event Action? FramebufferResized; + public ITexture RenderTexture => texture; + + public string CanvasId { get; private set; } + + public event Action InstanceCreated; + + public int gl; + + private WebGlRenderTarget texture; + private int framebuffer; + + private VecI fbSize; + + public IGraphicsContext GraphicsContext => webglGraphicsContext; + private WebGlGraphicsContext webglGraphicsContext; + + public void CreateInstance(object contextObject, VecI framebufferSize) + { + if(contextObject is not HtmlCanvas canvas) throw new ArgumentException("Canvas not found", nameof(contextObject)); + + canvasObject = canvas; + CanvasId = canvasObject.Id; + canvasObject.SetAttribute("width", framebufferSize.X.ToString()); + canvasObject.SetAttribute("height", framebufferSize.Y.ToString()); + + gl = JSRuntime.OpenSkiaContext(canvasObject.Id); + webglGraphicsContext = new WebGlGraphicsContext(gl); + + JSRuntime.MakeContextCurrent(gl); + + texture = webglGraphicsContext.CreateRenderTarget(gl, framebufferSize.X, framebufferSize.Y); + fbSize = framebufferSize; + InstanceCreated?.Invoke(); + } + + public void DestroyInstance() + { + } + + public void UpdateFramebufferSize(int width, int height) + { + canvasObject.SetAttribute("width", width.ToString()); + canvasObject.SetAttribute("height", height.ToString()); + fbSize = new VecI(width, height); + FramebufferResized?.Invoke(); + } + + public void PrepareTextureToWrite() + { + } + + public void Render(double deltaTime) + { + } +} diff --git a/src/Drawie.RenderApi.WebGl/WebGlPipeline.cs b/src/Drawie.RenderApi.WebGl/WebGlPipeline.cs new file mode 100644 index 0000000..708c595 --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/WebGlPipeline.cs @@ -0,0 +1,66 @@ +using Drawie.Backend.Vertie.Core; +using Drawie.JSInterop; +using Drawie.RenderApi.Abstraction.Pipeline; +using Drawie.RenderApi.WebGl.Enums; + +namespace Drawie.RenderApi.WebGl; + +public class WebGlPipeline : IPipeline +{ + public PipelineDesc Description { get; } + public int Gl { get; } + + public WebGlPipeline(PipelineDesc description, int gl) + { + Description = description; + Gl = gl; + } + + public void Apply(VulkanCommandList vulkanCommandList) + { + JSRuntime.Viewport(Gl, Description.Viewport.X, Description.Viewport.Y, Description.Viewport.Width, Description.Viewport.Height); + + if (Description.Blend.Enabled) + { + JSRuntime.Enable(Gl, (int)WebGlCap.Blend); + } + else + { + JSRuntime.Disable(Gl, (int)WebGlCap.Blend); + } + + if (Description.Depth.Enabled) + { + JSRuntime.Enable(Gl, (int)WebGlCap.DepthTest); + JSRuntime.DepthFunc(Gl, ToWebGlDesc(Description.Depth.DepthCompare)); + JSRuntime.DepthMask(Gl, true); + + JSRuntime.ClearDepth(Gl, 1.0); + } + else + { + JSRuntime.Disable(Gl, (int)WebGlCap.DepthTest); + } + + JSRuntime.ClearColor(Gl, 0, 0, 0, 1); + JSRuntime.Clear(Gl, (int)(WebGlBufferMask.ColorBufferBit | WebGlBufferMask.DepthBufferBit | WebGlBufferMask.StencilBufferBit)); + + Description.ShaderProgram?.Use(); + } + + private int ToWebGlDesc(DepthCompareType depthDepthCompare) + { + return depthDepthCompare switch + { + DepthCompareType.Never => (int)WebGlDepthFunc.Never, + DepthCompareType.Less => (int)WebGlDepthFunc.Less, + DepthCompareType.Equal => (int)WebGlDepthFunc.Equal, + DepthCompareType.LessEqual => (int)WebGlDepthFunc.LEqual, + DepthCompareType.Greater => (int)WebGlDepthFunc.Greater, + DepthCompareType.NotEqual => (int)WebGlDepthFunc.NotEqual, + DepthCompareType.GreaterEqual => (int)WebGlDepthFunc.GEqual, + DepthCompareType.Always => (int)WebGlDepthFunc.Always, + _ => throw new ArgumentOutOfRangeException(nameof(depthDepthCompare), depthDepthCompare, null) + }; + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/WebGlRenderApi.cs b/src/Drawie.RenderApi.WebGl/WebGlRenderApi.cs index c27427b..16c761b 100644 --- a/src/Drawie.RenderApi.WebGl/WebGlRenderApi.cs +++ b/src/Drawie.RenderApi.WebGl/WebGlRenderApi.cs @@ -1,25 +1,39 @@ -namespace Drawie.RenderApi.WebGl; +using Drawie.RenderApi.Abstraction; + +namespace Drawie.RenderApi.WebGl; public class WebGlRenderApi : IWebGlRenderApi { public IWebGlContext WebGlContext { get; private set; } - public WebGlWindowRenderApi WindowRenderApi { get; private set; } - - IReadOnlyCollection IRenderApi.WindowRenderApis => new List { WindowRenderApi }; + public WebGlHostViewRenderApi HostViewRenderApi { get; private set; } + IReadOnlyCollection IRenderApi.WindowRenderApis => new List { HostViewRenderApi }; + public IGraphicsDevice GraphicsDevice { get; private set; } + public WebGlRenderApi() { } - public IWindowRenderApi CreateWindowRenderApi() + public IHostViewRenderApi CreateWindowRenderApi() { - if (WindowRenderApi != null) + if (HostViewRenderApi != null) { throw new InvalidOperationException("Window render API was already created."); - } + } - WindowRenderApi = new WebGlWindowRenderApi(); - WebGlContext = new WebGlContext(WindowRenderApi); - return WindowRenderApi; + HostViewRenderApi = new WebGlHostViewRenderApi(); + + if (GraphicsDevice == null) + { + HostViewRenderApi.InstanceCreated += () => CreateGraphicsDevice(HostViewRenderApi.gl); + } + + WebGlContext = new WebGlContext(HostViewRenderApi); + return HostViewRenderApi; + } + + private void CreateGraphicsDevice(int context) + { + GraphicsDevice = new WebGlGraphicsDevice(context); } } diff --git a/src/Drawie.RenderApi.WebGl/WebGlRenderTarget.cs b/src/Drawie.RenderApi.WebGl/WebGlRenderTarget.cs new file mode 100644 index 0000000..98c695a --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/WebGlRenderTarget.cs @@ -0,0 +1,74 @@ +using Drawie.JSInterop; +using Drawie.Numerics; +using Drawie.RenderApi.Abstraction.RenderTargets; +using Drawie.RenderApi.Abstraction.Textures; +using Drawie.RenderApi.WebGl.Enums; + +namespace Drawie.RenderApi.WebGl; + +public class WebGlRenderTarget : IRenderTarget, IWebGlTexture, IDisposable +{ + public int Gl { get; } + public uint FramebufferId { get; } + public VecI Size { get; } + public ulong TextureId => texture.TextureId; + + private WebGlTexture texture; + private WebGlDepthBuffer depthBuffer; + + public WebGlRenderTarget(int gl, int id, VecI framebufferSize) + { + Gl = gl; + FramebufferId = (uint)id; + Size = framebufferSize; + } + + public WebGlRenderTarget(int gl, int width, int height, DepthFormat depth) + { + Gl = gl; + Size = new VecI(width, height); + texture = new WebGlTexture(gl, width, height); + FramebufferId = (uint)JSRuntime.CreateFramebuffer(gl); + + if (depth != DepthFormat.NoDepth) + { + depthBuffer = new WebGlDepthBuffer(gl, width, height, depth); + } + + JSRuntime.BindFramebuffer(gl, (int)WebGlFramebufferTarget.Framebuffer, (int)FramebufferId); + + JSRuntime.FramebufferTexture2D(gl, + (int)WebGlFramebufferTarget.Framebuffer, + (int)WebGlFramebufferAttachment.ColorAttachment0, + (int)WebGlTextureType.Texture2D, + (int)texture.TextureId, + 0); + + if (depthBuffer != null) + { + JSRuntime.FramebufferRenderbuffer( + gl, + (int)WebGlFramebufferTarget.Framebuffer, + (int)WebGlFramebufferAttachment.DepthStencilAttachment, + (int)WebGlRenderbufferTarget.Renderbuffer, + (int)depthBuffer.RenderbufferId); + } + + var status = JSRuntime.CheckFramebufferStatus(gl, (int)WebGlFramebufferTarget.Framebuffer); + + if (status != (int)WebGlFramebufferStatus.FramebufferComplete) + { + WebGlError error = (WebGlError)JSRuntime.GetError(gl); + throw new Exception($"Framebuffer invalid: {status}, Error: {error}"); + } + } + + public void Dispose() + { + texture.Dispose(); + depthBuffer.Dispose(); + JSRuntime.DeleteFramebuffer(Gl, (int)FramebufferId); + } + + uint IWebGlTexture.TextureId => (uint)texture.TextureId; +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/WebGlSampler.cs b/src/Drawie.RenderApi.WebGl/WebGlSampler.cs new file mode 100644 index 0000000..bf6261a --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/WebGlSampler.cs @@ -0,0 +1,16 @@ +using Drawie.JSInterop; +using Drawie.RenderApi.Abstraction.Textures; + +namespace Drawie.RenderApi.WebGl; + +public class WebGlSampler : ISampler +{ + public int Gl { get; } + public uint Handle { get; } + + public WebGlSampler(int glHandle, SamplerDesc desc) + { + Gl = glHandle; + Handle = (uint)JSRuntime.CreateSampler(glHandle); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/WebGlShaderProgram.cs b/src/Drawie.RenderApi.WebGl/WebGlShaderProgram.cs new file mode 100644 index 0000000..da52217 --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/WebGlShaderProgram.cs @@ -0,0 +1,126 @@ +using System.Numerics; +using System.Runtime.InteropServices; +using Drawie.Backend.Shaders.Common; +using Drawie.JSInterop; +using Drawie.RenderApi.Abstraction.Shaders; +using Drawie.RenderApi.WebGl.Enums; + +namespace Drawie.RenderApi.WebGl; + +public class WebGlShaderProgram : IShaderProgram +{ + public int ProgramHandle { get; } + public int Gl { get; } + + private readonly Dictionary uniformBlockUbos = new(); + + public WebGlShaderProgram(int gl) + { + Gl = gl; + ProgramHandle = JSRuntime.CreateProgram(gl); + } + + public void Use() + { + JSRuntime.UseProgram(Gl, ProgramHandle); + } + + public void UpdateUniforms(List blocks) + { + int bindingPoint = 0; + + foreach (var uniformBlock in blocks) + { + int blockIndex = uniformBlock.ShaderLayout.Index; + int blockSize = uniformBlock.ShaderLayout.Size; + + if (blockIndex == int.MaxValue) + continue; + + JSRuntime.UniformBlockBinding( + Gl, + ProgramHandle, + blockIndex, + bindingPoint); + + if (!uniformBlockUbos.TryGetValue( + uniformBlock.Name, + out int ubo)) + { + ubo = JSRuntime.CreateBuffer(Gl); + + uniformBlockUbos.Add( + uniformBlock.Name, + ubo); + } + + JSRuntime.BindBuffer( + Gl, + (int)WebGlBufferType.Uniform, + ubo); + + JSRuntime.BufferData( + Gl, + (int)WebGlBufferType.Uniform, + blockSize, + (int)WebGlBufferUsage.DynamicDraw); + + JSRuntime.BindBufferBase( + Gl, + (int)WebGlBufferType.Uniform, + bindingPoint, + ubo); + + for (int i = 0; i < uniformBlock.Properties.Count; i++) + { + var property = uniformBlock.Properties[i]; + + UploadProperty( + property.ObjValue, + uniformBlock.ShaderLayout.UniformProperties[i]); + } + + JSRuntime.BindBuffer( + Gl, + (int)WebGlBufferType.Uniform, + 0); + + bindingPoint++; + } + } + + private void UploadProperty( + object value, + PropertyLayout layout) + { + byte[] data = UniformValueToBytes(value); + + JSRuntime.BufferSubData( + Gl, + (int)WebGlBufferType.Uniform, + layout.Offset, + data); + } + + private static byte[] UniformValueToBytes(object value) + { + return value switch + { + float v => BitConverter.GetBytes(v), + int v => BitConverter.GetBytes(v), + uint v => BitConverter.GetBytes(v), + float[] v => MemoryMarshal.Cast(v.AsSpan()).ToArray(), + int[] v => MemoryMarshal.Cast(v.AsSpan()).ToArray(), + uint[] v => MemoryMarshal.Cast(v.AsSpan()).ToArray(), + Matrix4x4 v => CreateFromMatrix(v), + _ => throw new NotSupportedException($"Unsupported uniform value type: {value.GetType()}") + }; + } + + private static byte[] CreateFromMatrix(Matrix4x4 matrix) + { + return MemoryMarshal + .AsBytes(MemoryMarshal.CreateSpan(ref matrix, 1)) + .ToArray(); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/WebGlTexture.cs b/src/Drawie.RenderApi.WebGl/WebGlTexture.cs index 6c4752d..be41c34 100644 --- a/src/Drawie.RenderApi.WebGl/WebGlTexture.cs +++ b/src/Drawie.RenderApi.WebGl/WebGlTexture.cs @@ -1,22 +1,36 @@ using Drawie.JSInterop; +using Drawie.RenderApi.WebGl.Enums; namespace Drawie.RenderApi.WebGl; public class WebGlTexture : IWebGlTexture, IDisposable { public int Gl { get; private set; } - public int TextureId { get; private set; } + public ulong TextureId { get; private set; } uint IWebGlTexture.TextureId => (uint)TextureId; - public WebGlTexture(int gl, int textureId) + public WebGlTexture(int gl, ulong textureId) { TextureId = textureId; Gl = gl; } + public WebGlTexture(int gl, int width, int height) + { + Gl = gl; + TextureId = (ulong)JSRuntime.CreateTexture(gl); + + JSRuntime.BindTexture(gl, (int)WebGlTextureType.Texture2D, (int)TextureId); + JSRuntime.TexImage2D(gl, (int)WebGlTextureType.Texture2D, 0, (int)WebGlTextureFormat.Rgba, width, height, 0, (int)WebGlTextureFormat.Rgba, (int)WebGlArrayType.UnsignedByte, 0); + JSRuntime.TexParameteri(gl, (int)WebGlTextureType.Texture2D, (int)WebGlTextureParameterName.TextureMinFilter, (int)WebGlTextureFilter.Nearest); + JSRuntime.TexParameteri(gl, (int)WebGlTextureType.Texture2D, (int)WebGlTextureParameterName.TextureMagFilter, (int)WebGlTextureFilter.Nearest); + JSRuntime.TexParameteri(gl, (int)WebGlTextureType.Texture2D, (int)WebGlTextureParameterName.TextureWrapS, (int)WebGlTextureWrap.ClampToEdge); + JSRuntime.TexParameteri(gl, (int)WebGlTextureType.Texture2D, (int)WebGlTextureParameterName.TextureWrapT, (int)WebGlTextureWrap.ClampToEdge); + } + public void Dispose() { - JSRuntime.DeleteTexture(Gl, TextureId); + JSRuntime.DeleteTexture(Gl, (int)TextureId); } } diff --git a/src/Drawie.RenderApi.WebGl/WebGlVertexArray.cs b/src/Drawie.RenderApi.WebGl/WebGlVertexArray.cs new file mode 100644 index 0000000..305beb2 --- /dev/null +++ b/src/Drawie.RenderApi.WebGl/WebGlVertexArray.cs @@ -0,0 +1,30 @@ +using Drawie.JSInterop; +using Drawie.RenderApi.Abstraction.Buffers; + +namespace Drawie.RenderApi.WebGl; + +public class WebGlVertexArray : IBufferGroup +{ + public uint Handle { get; } + public int Gl { get; } + + private WebGlBufferGroupList bufferList = new WebGlBufferGroupList(); + + public WebGlVertexArray(int glHandle) + { + Gl = glHandle; + Handle = (uint)JSRuntime.CreateVertexArray(Gl); + } + + public void Open(Action list) + { + JSRuntime.BindVertexArray(Gl, (int)Handle); + list(bufferList); + //JSRuntime.BindVertexArray(Gl, 0); + } +} + +public class WebGlBufferGroupList : IBufferGroupList +{ + public List Buffers { get; } = new List(); +} \ No newline at end of file diff --git a/src/Drawie.RenderApi.WebGl/WebGlWindowRenderApi.cs b/src/Drawie.RenderApi.WebGl/WebGlWindowRenderApi.cs deleted file mode 100644 index ad219cd..0000000 --- a/src/Drawie.RenderApi.WebGl/WebGlWindowRenderApi.cs +++ /dev/null @@ -1,187 +0,0 @@ -using Drawie.JSInterop; -using Drawie.Numerics; -using Drawie.RenderApi.Html5Canvas; -using Drawie.RenderApi.WebGl.Enums; -using Drawie.RenderApi.WebGl.Exceptions; - -namespace Drawie.RenderApi.WebGl; - -public class WebGlWindowRenderApi : IWindowRenderApi -{ - private const string vertexSource = """ - #version 300 es - in vec4 position; - in vec2 aTextureCoord; - - out highp vec2 vTextureCoord; - void main() { - gl_Position = position; - vTextureCoord = aTextureCoord; - } - """; - - private const string fragSource = """ - #version 300 es - precision highp float; - in highp vec2 vTextureCoord; - - uniform sampler2D uSampler; - out vec4 fragColor; - - void main(void) { - fragColor = texture(uSampler, vTextureCoord); - } - """; - - private HtmlCanvas canvasObject; - public event Action? FramebufferResized; - public ITexture RenderTexture => texture; - - public string CanvasId { get; private set; } - - private int posBuffer; - private int program; - public int gl; - - private WebGlTexture texture; - - private int vertexPosAttrib; - private int texCoordAttrib; - private int uSamplerUniform; - - public void CreateInstance(object contextObject, VecI framebufferSize) - { - JSRuntime.InterceptGLObject(); - canvasObject = JSRuntime.CreateElement(); - CanvasId = canvasObject.Id; - canvasObject.SetAttribute("width", framebufferSize.X.ToString()); - canvasObject.SetAttribute("height", framebufferSize.Y.ToString()); - - gl = JSRuntime.OpenSkiaContext(canvasObject.Id); - - JSRuntime.MakeContextCurrent(gl); - - var vertexShader = LoadShader(gl, vertexSource, WebGlShaderType.Vertex); - var fragmentShader = LoadShader(gl, fragSource, WebGlShaderType.Fragment); - - program = InitProgram(gl, vertexShader, fragmentShader); - - posBuffer = InitBuffers(gl); - CreateTexture(gl, framebufferSize.X, framebufferSize.Y); - InitTextureBuffer(gl); - - vertexPosAttrib = JSRuntime.GetAttribLocation(gl, program, "position"); - texCoordAttrib = JSRuntime.GetAttribLocation(gl, program, "aTextureCoord"); - uSamplerUniform = JSRuntime.GetUniformLocation(gl, program, "uSampler"); - } - - public void DestroyInstance() - { - } - - public void UpdateFramebufferSize(int width, int height) - { - canvasObject.SetAttribute("width", width.ToString()); - canvasObject.SetAttribute("height", height.ToString()); - DisposeTexture(); - CreateTexture(gl, width, height); - FramebufferResized?.Invoke(); - } - - public void PrepareTextureToWrite() - { - } - - public void Render(double deltaTime) - { - JSRuntime.ClearColor(gl, 0.0f, 0.0f, 0.0f, 1.0f); - JSRuntime.Clear(gl, (int)WebGlBufferMask.ColorBufferBit); - - JSRuntime.BindBuffer(gl, (int)WebGlBufferType.Array, posBuffer); - JSRuntime.VertexAttribPointer(gl, vertexPosAttrib, 2, (int)WebGlArrayType.Float, false, 0, 0); - JSRuntime.EnableVertexAttribArray(gl, vertexPosAttrib); - - SetTextureData(); - - JSRuntime.UseProgram(gl, program); - - JSRuntime.ActiveTexture(gl, 0); - JSRuntime.BindTexture(gl, (int)WebGlTextureType.Texture2D, texture.TextureId); - JSRuntime.Uniform1i(gl, uSamplerUniform, 0); - - JSRuntime.DrawArrays(gl, (int)WebGlDrawMode.TriangleStrip, 0, 4); - } - - private int LoadShader(int handle, string shader, WebGlShaderType type) - { - int shaderHandle = JSRuntime.CreateShader(handle, (int)type); - JSRuntime.ShaderSource(handle, shaderHandle, shader); - string? error = JSRuntime.CompileShader(handle, shaderHandle); - - if (error != null) - { - Console.WriteLine(error); - throw new ShaderCompilationException(type, shader, error); - } - - return shaderHandle; - } - - private int InitProgram(int handle, int vertexShader, int fragmentShader) - { - int program = JSRuntime.CreateProgram(handle); - JSRuntime.AttachShader(handle, program, vertexShader); - JSRuntime.AttachShader(handle, program, fragmentShader); - string? error = JSRuntime.LinkProgram(handle, program); - - if (error != null) - { - throw new WebGlException(error); - } - - return program; - } - - private int InitBuffers(int handle) - { - int positionBuffer = JSRuntime.CreateBuffer(handle); - JSRuntime.BindBuffer(handle, (int)WebGlBufferType.Array, positionBuffer); - double[] vertices = new double[] { 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f }; - - JSRuntime.BufferData(handle, (int)WebGlBufferType.Array, vertices, (int)WebGlBufferUsage.StaticDraw); - - return positionBuffer; - } - - private void CreateTexture(int handle, int width, int height) - { - texture = new WebGlTexture(gl, JSRuntime.CreateTexture(handle)); - JSRuntime.BindTexture(handle, (int)WebGlTextureType.Texture2D, texture.TextureId); - JSRuntime.TexImage2D(handle, (int)WebGlTextureType.Texture2D, 0, (int)WebGlTextureFormat.Rgba, width, height, 0, (int)WebGlTextureFormat.Rgba, (int)WebGlArrayType.UnsignedByte, 0); - JSRuntime.TexParameteri(handle, (int)WebGlTextureType.Texture2D, (int)WebGlTextureParameterName.TextureMinFilter, (int)WebGlTextureFilter.Nearest); - JSRuntime.TexParameteri(handle, (int)WebGlTextureType.Texture2D, (int)WebGlTextureParameterName.TextureMagFilter, (int)WebGlTextureFilter.Nearest); - JSRuntime.TexParameteri(handle, (int)WebGlTextureType.Texture2D, (int)WebGlTextureParameterName.TextureWrapS, (int)WebGlTextureWrap.ClampToEdge); - JSRuntime.TexParameteri(handle, (int)WebGlTextureType.Texture2D, (int)WebGlTextureParameterName.TextureWrapT, (int)WebGlTextureWrap.ClampToEdge); - } - - private void DisposeTexture() - { - texture?.Dispose(); - texture = null; - } - - private void InitTextureBuffer(int handle) - { - int texCoordBuffer = JSRuntime.CreateBuffer(handle); - JSRuntime.BindBuffer(handle, (int)WebGlBufferType.Array, texCoordBuffer); - double[] texCoords = new double[] { 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f }; - JSRuntime.BufferData(handle, (int)WebGlBufferType.Array, texCoords, (int)WebGlBufferUsage.StaticDraw); - } - - private void SetTextureData() - { - JSRuntime.BindBuffer(gl, (int)WebGlBufferType.Array, texCoordAttrib); - JSRuntime.VertexAttribPointer(gl, texCoordAttrib, 2, (int)WebGlArrayType.Float, false, 0, 0); - JSRuntime.EnableVertexAttribArray(gl, texCoordAttrib); - } -} diff --git a/src/Drawie.RenderApi/Abstraction/Buffers/BufferUsage.cs b/src/Drawie.RenderApi/Abstraction/Buffers/BufferUsage.cs new file mode 100644 index 0000000..80d6cf0 --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Buffers/BufferUsage.cs @@ -0,0 +1,11 @@ +namespace Drawie.RenderApi.Abstraction.Buffers; + +public enum BufferUsage +{ + Vertex, + Index, + Uniform, + Storage, + Transfer, + Other, +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Buffers/IBuffer.cs b/src/Drawie.RenderApi/Abstraction/Buffers/IBuffer.cs new file mode 100644 index 0000000..253a18c --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Buffers/IBuffer.cs @@ -0,0 +1,11 @@ +namespace Drawie.RenderApi.Abstraction.Buffers; + +public interface IBuffer +{ + public BufferUsage Usage { get; } +} + +public interface IBuffer : IBuffer where T : unmanaged +{ + public ulong Size { get; } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Buffers/IBufferGroup.cs b/src/Drawie.RenderApi/Abstraction/Buffers/IBufferGroup.cs new file mode 100644 index 0000000..7099fa3 --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Buffers/IBufferGroup.cs @@ -0,0 +1,7 @@ +namespace Drawie.RenderApi.Abstraction.Buffers; + +public interface IBufferGroup +{ + public uint Handle { get; } + void Open(Action list); +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Buffers/IBufferGroupList.cs b/src/Drawie.RenderApi/Abstraction/Buffers/IBufferGroupList.cs new file mode 100644 index 0000000..44d09c2 --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Buffers/IBufferGroupList.cs @@ -0,0 +1,6 @@ +namespace Drawie.RenderApi.Abstraction.Buffers; + +public interface IBufferGroupList +{ + public List Buffers { get; } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/CommandRecording/CommandList.cs b/src/Drawie.RenderApi/Abstraction/CommandRecording/CommandList.cs new file mode 100644 index 0000000..854cd68 --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/CommandRecording/CommandList.cs @@ -0,0 +1,48 @@ +using Drawie.RenderApi.Abstraction.Buffers; +using Drawie.RenderApi.Abstraction.Pipeline; +using Drawie.RenderApi.Abstraction.RenderTargets; +using Drawie.RenderApi.Abstraction.Shaders; +using Drawie.RenderApi.Abstraction.Textures; + +namespace Drawie.RenderApi.Abstraction.CommandRecording; + +public abstract class CommandList : ICommandList +{ + private List instructions = new List(); + + protected void RecordInstruction(Action instruction) + { + instructions.Add(instruction); + } + + protected RecordedRenderPass ToRenderPass() + { + var execute = () => + { + foreach (var instruction in instructions) + { + instruction.Invoke(); + } + }; + return new RecordedRenderPass(execute); + } + + protected void ClearInstructions() + { + instructions.Clear(); + } + + public abstract void BeginRenderPass(IRenderTarget fb); + public abstract void SetPipeline(IPipeline pipeline); + public abstract void SetBuffers(IBufferGroup bufferGroup); + public abstract void BindTexture(PreparedTexture texture, ISampler sampler); + public abstract void DrawIndexed(int indexCount); + public abstract RecordedRenderPass EndRenderPass(IRenderTarget blitTo); + public abstract RecordedRenderPass EndRenderPass(); + public abstract void BindPipeline(); + public abstract PreparedTexture PrepareTexture(ITexture texture); + public abstract void UpdateUniforms(List blocks, List textures, + List samplers); + + public abstract void RestoreTexture(PreparedTexture preparedTextureValue); +} diff --git a/src/Drawie.RenderApi/Abstraction/CommandRecording/ICommandList.cs b/src/Drawie.RenderApi/Abstraction/CommandRecording/ICommandList.cs new file mode 100644 index 0000000..81bc423 --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/CommandRecording/ICommandList.cs @@ -0,0 +1,22 @@ +using Drawie.RenderApi.Abstraction.Buffers; +using Drawie.RenderApi.Abstraction.Pipeline; +using Drawie.RenderApi.Abstraction.RenderTargets; +using Drawie.RenderApi.Abstraction.Shaders; +using Drawie.RenderApi.Abstraction.Textures; + +namespace Drawie.RenderApi.Abstraction.CommandRecording; + +public interface ICommandList +{ + void BeginRenderPass(IRenderTarget fb); + void SetPipeline(IPipeline pipeline); + void SetBuffers(IBufferGroup bufferGroup); + void BindTexture(PreparedTexture texture, ISampler sampler); + void DrawIndexed(int indexCount); + RecordedRenderPass EndRenderPass(IRenderTarget blitTo); + RecordedRenderPass EndRenderPass(); + void BindPipeline(); + PreparedTexture PrepareTexture(ITexture texture); + void UpdateUniforms(List blocks, List textures, List samplers); + void RestoreTexture(PreparedTexture preparedTextureValue); +} diff --git a/src/Drawie.RenderApi/Abstraction/CommandRecording/RecordedRenderPass.cs b/src/Drawie.RenderApi/Abstraction/CommandRecording/RecordedRenderPass.cs new file mode 100644 index 0000000..0f08ec3 --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/CommandRecording/RecordedRenderPass.cs @@ -0,0 +1,16 @@ +namespace Drawie.RenderApi.Abstraction.CommandRecording; + +public class RecordedRenderPass +{ + public Action Execute { get; init; } + + public RecordedRenderPass() + { + + } + + public RecordedRenderPass(Action execute) + { + Execute = execute; + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/IGraphicsDevice.cs b/src/Drawie.RenderApi/Abstraction/IGraphicsDevice.cs new file mode 100644 index 0000000..21b223b --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/IGraphicsDevice.cs @@ -0,0 +1,24 @@ +using Drawie.RenderApi.Abstraction.Buffers; +using Drawie.RenderApi.Abstraction.CommandRecording; +using Drawie.RenderApi.Abstraction.Pipeline; +using Drawie.RenderApi.Abstraction.RenderTargets; +using Drawie.RenderApi.Abstraction.Shaders; +using Drawie.RenderApi.Abstraction.Textures; + +namespace Drawie.RenderApi.Abstraction; + +public interface IGraphicsDevice : IDisposable +{ + IBuffer CreateBuffer(BufferUsage usage, TData[]? data) where TData : unmanaged; + ITexture CreateTexture(TextureDesc desc); + IPipeline CreatePipeline(PipelineDesc desc); + ICommandList CreateCommandList(); + + ISampler CreateSampler(SamplerDesc desc); + + void Submit(RecordedRenderPass cmdList); + IShaderProgram CreateShaderProgram(ShaderProgramDesc shaderProgramDesc); + IRenderTarget CreateRenderTarget(TextureDesc textureDesc); + IBufferGroup CreateBufferGroup(); + void DisposeTexture(ulong textureHandle); +} diff --git a/src/Drawie.RenderApi/Abstraction/Pipeline/IPipeline.cs b/src/Drawie.RenderApi/Abstraction/Pipeline/IPipeline.cs new file mode 100644 index 0000000..07ba7ca --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Pipeline/IPipeline.cs @@ -0,0 +1,10 @@ +using System.Collections; +using Drawie.RenderApi.Abstraction.CommandRecording; + +namespace Drawie.RenderApi.Abstraction.Pipeline; + +public interface IPipeline +{ + PipelineDesc Description { get; } + void Apply(ICommandList cmdList); +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Pipeline/PipelineDesc.cs b/src/Drawie.RenderApi/Abstraction/Pipeline/PipelineDesc.cs new file mode 100644 index 0000000..2a8b34e --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Pipeline/PipelineDesc.cs @@ -0,0 +1,53 @@ +using Drawie.Numerics; +using Drawie.RenderApi.Abstraction.Shaders; +using Drawie.RenderApi.Abstraction.Textures; + +namespace Drawie.RenderApi.Abstraction.Pipeline; + +public struct PipelineDesc : IEquatable +{ + public IShaderProgram? ShaderProgram { get; set; } + public DepthDesc Depth { get; set; } + public BlendDesc Blend { get; set; } + public RasterizerDesc Rasterizer { get; set; } + public RectI Viewport { get; set; } + + public bool Equals(PipelineDesc other) + { + return Equals(ShaderProgram, other.ShaderProgram) && Depth.Equals(other.Depth) && Blend.Equals(other.Blend) && Rasterizer.Equals(other.Rasterizer) && Viewport.Equals(other.Viewport); + } + + public override bool Equals(object? obj) + { + return obj is PipelineDesc other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(ShaderProgram, Depth, Blend, Rasterizer, Viewport); + } +} + +public record struct DepthDesc +{ + public bool Enabled { get; set; } + public DepthCompareType DepthCompare { get; set; } + public DepthFormat Format { get; set; } +} + +public enum DepthCompareType +{ + Never, + Less, + LessEqual, + Equal, + Greater, + GreaterEqual, + NotEqual, + Always +} + +public record struct BlendDesc +{ + public bool Enabled; +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Pipeline/RasterizerDesc.cs b/src/Drawie.RenderApi/Abstraction/Pipeline/RasterizerDesc.cs new file mode 100644 index 0000000..f9e4003 --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Pipeline/RasterizerDesc.cs @@ -0,0 +1,15 @@ +using Drawie.Backend.Vertie.Core; + +namespace Drawie.RenderApi.Abstraction.Pipeline; + +public record struct RasterizerDesc +{ + public RenderMode RenderMode { get; set; } + public int Samples { get; set; } + + public RasterizerDesc() + { + RenderMode = RenderMode.Default; + Samples = 1; + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Pipeline/RenderMode.cs b/src/Drawie.RenderApi/Abstraction/Pipeline/RenderMode.cs new file mode 100644 index 0000000..112d1c5 --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Pipeline/RenderMode.cs @@ -0,0 +1,7 @@ +namespace Drawie.Backend.Vertie.Core; + +public enum RenderMode +{ + Default, + Wireframe +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/RenderTargets/IRenderTarget.cs b/src/Drawie.RenderApi/Abstraction/RenderTargets/IRenderTarget.cs new file mode 100644 index 0000000..d8478ac --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/RenderTargets/IRenderTarget.cs @@ -0,0 +1,9 @@ +using Drawie.Numerics; + +namespace Drawie.RenderApi.Abstraction.RenderTargets; + +public interface IRenderTarget +{ + ulong SurfaceId { get; } + VecI Size { get; } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Shaders/IShaderProgram.cs b/src/Drawie.RenderApi/Abstraction/Shaders/IShaderProgram.cs new file mode 100644 index 0000000..72f7b2e --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Shaders/IShaderProgram.cs @@ -0,0 +1,6 @@ +namespace Drawie.RenderApi.Abstraction.Shaders; + +public interface IShaderProgram +{ + public void Use(); +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Shaders/ShaderProgramDesc.cs b/src/Drawie.RenderApi/Abstraction/Shaders/ShaderProgramDesc.cs new file mode 100644 index 0000000..fae846d --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Shaders/ShaderProgramDesc.cs @@ -0,0 +1,27 @@ +using Drawie.Backend.Shaders.Common; + +namespace Drawie.RenderApi.Abstraction.Shaders; + +public struct ShaderProgramDesc +{ + public List Shaders { get; } + + public ShaderProgramDesc(IEnumerable desc) + { + Shaders = new List(desc); + } +} + +public struct ShaderDesc +{ + public string EntryName { get; set; } + public byte[] Bytes { get; } + public ShaderType Type { get; } + + public ShaderDesc(string entryName, byte[] bytes, ShaderType type) + { + EntryName = entryName; + Bytes = bytes; + Type = type; + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Shaders/ShaderProperty.cs b/src/Drawie.RenderApi/Abstraction/Shaders/ShaderProperty.cs new file mode 100644 index 0000000..8279a11 --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Shaders/ShaderProperty.cs @@ -0,0 +1,29 @@ +namespace Drawie.RenderApi.Abstraction.Shaders; + +public class ShaderProperty +{ + public string UniformName { get; set; } + public object? ObjValue { get; set; } + public Type Type { get; set; } + + public ShaderProperty(string name) + { + UniformName = name; + } +} + +public class ShaderProperty : ShaderProperty where T : unmanaged +{ + public T Value + { + get => (T)ObjValue; + set => ObjValue = value; + } + + public ShaderProperty(string uniformName, T value) : base(uniformName) + { + UniformName = uniformName; + ObjValue = value; + Type = typeof(T); + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Shaders/UniformBlock.cs b/src/Drawie.RenderApi/Abstraction/Shaders/UniformBlock.cs new file mode 100644 index 0000000..53a6e10 --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Shaders/UniformBlock.cs @@ -0,0 +1,37 @@ +using System.Numerics; +using Drawie.Backend.Shaders.Common; + +namespace Drawie.RenderApi.Abstraction.Shaders; + +public class UniformBlock +{ + public string Name { get; set; } + public List Properties { get; set; } = new List(); + public UniformBlockLayout ShaderLayout { get; set; } + + public Guid UniformBlockId { get; } = Guid.NewGuid(); + + public UniformBlock AddProperty(ShaderProperty property) + { + Properties.Add(property); + return this; + } + + public UniformBlock(string name) + { + Name = name; + } + + public void SetProperty(string name, object value) + { + var property = Properties.FirstOrDefault(p => p.UniformName == name); + property?.ObjValue = value; + } +} + +public struct UniformBlockLayout +{ + public int Index { get; set; } + public int Size { get; set; } + public List UniformProperties { get; set; } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Textures/DepthFormat.cs b/src/Drawie.RenderApi/Abstraction/Textures/DepthFormat.cs new file mode 100644 index 0000000..97edaa0 --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Textures/DepthFormat.cs @@ -0,0 +1,7 @@ +namespace Drawie.RenderApi.Abstraction.Textures; + +public enum DepthFormat +{ + NoDepth, + Depth24Stencil8, +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Textures/ILazyExternallyAccessibleTexture.cs b/src/Drawie.RenderApi/Abstraction/Textures/ILazyExternallyAccessibleTexture.cs new file mode 100644 index 0000000..9f4d367 --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Textures/ILazyExternallyAccessibleTexture.cs @@ -0,0 +1,6 @@ +namespace Drawie.RenderApi.Abstraction.Textures; + +public interface ILazyExternallyAccessibleTexture : ITexture +{ + void EnsureExternallyAccessible(); +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Textures/ISampler.cs b/src/Drawie.RenderApi/Abstraction/Textures/ISampler.cs new file mode 100644 index 0000000..6231cf4 --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Textures/ISampler.cs @@ -0,0 +1,6 @@ +namespace Drawie.RenderApi.Abstraction.Textures; + +public interface ISampler +{ + public uint Handle { get; } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Textures/ITexture.cs b/src/Drawie.RenderApi/Abstraction/Textures/ITexture.cs new file mode 100644 index 0000000..249db5e --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Textures/ITexture.cs @@ -0,0 +1,6 @@ +namespace Drawie.RenderApi.Abstraction.Textures; + +public interface ITexture +{ + public ulong TextureId { get; } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Textures/PreparedTexture.cs b/src/Drawie.RenderApi/Abstraction/Textures/PreparedTexture.cs new file mode 100644 index 0000000..30899d2 --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Textures/PreparedTexture.cs @@ -0,0 +1,10 @@ +namespace Drawie.RenderApi.Abstraction.Textures; + +public struct PreparedTexture +{ + public ulong Handle { get; } + public PreparedTexture(ulong handle) + { + Handle = handle; + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Textures/SamplerDesc.cs b/src/Drawie.RenderApi/Abstraction/Textures/SamplerDesc.cs new file mode 100644 index 0000000..0675844 --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Textures/SamplerDesc.cs @@ -0,0 +1,6 @@ +namespace Drawie.RenderApi.Abstraction.Textures; + +public struct SamplerDesc +{ + +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Textures/TextureDesc.cs b/src/Drawie.RenderApi/Abstraction/Textures/TextureDesc.cs new file mode 100644 index 0000000..0bd9b53 --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Textures/TextureDesc.cs @@ -0,0 +1,20 @@ +namespace Drawie.RenderApi.Abstraction.Textures; + +public struct TextureDesc +{ + public int Width { get; set; } + public int Height { get; set; } + + public TextureFormat Format { get; set; } + public DepthFormat Depth { get; set; } + public int Samples { get; set; } + + public TextureDesc() + { + Width = 0; + Height = 0; + Format = TextureFormat.RGBA8_Unorm; + Depth = DepthFormat.NoDepth; + Samples = 1; + } +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Textures/TextureFormat.cs b/src/Drawie.RenderApi/Abstraction/Textures/TextureFormat.cs new file mode 100644 index 0000000..5990c4a --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Textures/TextureFormat.cs @@ -0,0 +1,6 @@ +namespace Drawie.RenderApi.Abstraction.Textures; + +public enum TextureFormat +{ + RGBA8_Unorm +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Abstraction/Textures/TextureUsage.cs b/src/Drawie.RenderApi/Abstraction/Textures/TextureUsage.cs new file mode 100644 index 0000000..6c35de1 --- /dev/null +++ b/src/Drawie.RenderApi/Abstraction/Textures/TextureUsage.cs @@ -0,0 +1,9 @@ +namespace Drawie.RenderApi.Abstraction.Textures; + +[Flags] +public enum TextureUsage +{ + Sampled, + RenderTarget, + DepthStencil +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/Drawie.RenderApi.csproj b/src/Drawie.RenderApi/Drawie.RenderApi.csproj index fffbb83..a177957 100644 --- a/src/Drawie.RenderApi/Drawie.RenderApi.csproj +++ b/src/Drawie.RenderApi/Drawie.RenderApi.csproj @@ -1,19 +1,14 @@  - net8.0 + net10.0 enable enable - - - ..\..\..\..\.nuget\packages\silk.net.core\2.21.0\lib\net6.0\Silk.NET.Core.dll - - - + diff --git a/src/Drawie.RenderApi/GpuInfo.cs b/src/Drawie.RenderApi/GpuInfo.cs index 36bbe36..4888e61 100644 --- a/src/Drawie.RenderApi/GpuInfo.cs +++ b/src/Drawie.RenderApi/GpuInfo.cs @@ -1,9 +1,10 @@ namespace Drawie.RenderApi; -public class GpuInfo(string deviceName, string vendor) +public class GpuInfo(string deviceName, string vendor, bool? isDiscreteGpu = null) { public string Name { get; } = deviceName; public string Vendor { get; } = vendor; + public bool? IsDiscreteGpu { get; set; } = isDiscreteGpu; public override string ToString() { diff --git a/src/Drawie.RenderApi/IBrowserWindowRenderApi.cs b/src/Drawie.RenderApi/IBrowserHostViewRenderApi.cs similarity index 52% rename from src/Drawie.RenderApi/IBrowserWindowRenderApi.cs rename to src/Drawie.RenderApi/IBrowserHostViewRenderApi.cs index af9f5d0..f9bfb12 100644 --- a/src/Drawie.RenderApi/IBrowserWindowRenderApi.cs +++ b/src/Drawie.RenderApi/IBrowserHostViewRenderApi.cs @@ -1,6 +1,6 @@ namespace Drawie.RenderApi; -public interface IBrowserWindowRenderApi : IWindowRenderApi +public interface IBrowserHostViewRenderApi : IHostViewRenderApi { public string CanvasId { get; } } \ No newline at end of file diff --git a/src/Drawie.RenderApi/ICanvasTexture.cs b/src/Drawie.RenderApi/ICanvasTexture.cs index 0301a00..9282889 100644 --- a/src/Drawie.RenderApi/ICanvasTexture.cs +++ b/src/Drawie.RenderApi/ICanvasTexture.cs @@ -1,4 +1,6 @@ -namespace Drawie.RenderApi; +using Drawie.RenderApi.Abstraction.Textures; + +namespace Drawie.RenderApi; public interface ICanvasTexture : ITexture { diff --git a/src/Drawie.RenderApi/IGraphicsContext.cs b/src/Drawie.RenderApi/IGraphicsContext.cs new file mode 100644 index 0000000..1230ed0 --- /dev/null +++ b/src/Drawie.RenderApi/IGraphicsContext.cs @@ -0,0 +1,8 @@ +using Drawie.RenderApi.Abstraction.Textures; + +namespace Drawie.RenderApi; + +public interface IGraphicsContext +{ + public void MakeCurrent(); +} \ No newline at end of file diff --git a/src/Drawie.RenderApi/IWindowRenderApi.cs b/src/Drawie.RenderApi/IHostViewRenderApi.cs similarity index 75% rename from src/Drawie.RenderApi/IWindowRenderApi.cs rename to src/Drawie.RenderApi/IHostViewRenderApi.cs index baf7858..38a8a13 100644 --- a/src/Drawie.RenderApi/IWindowRenderApi.cs +++ b/src/Drawie.RenderApi/IHostViewRenderApi.cs @@ -1,9 +1,11 @@ using Drawie.Numerics; +using Drawie.RenderApi.Abstraction.Textures; namespace Drawie.RenderApi; -public interface IWindowRenderApi +public interface IHostViewRenderApi { + public IGraphicsContext GraphicsContext { get; } public void CreateInstance(object contextObject, VecI framebufferSize); public void DestroyInstance(); diff --git a/src/Drawie.RenderApi/IOpenGlContext.cs b/src/Drawie.RenderApi/IOpenGlContext.cs index 1a6a84b..8296d0b 100644 --- a/src/Drawie.RenderApi/IOpenGlContext.cs +++ b/src/Drawie.RenderApi/IOpenGlContext.cs @@ -4,4 +4,7 @@ public interface IOpenGlContext { public IntPtr GetGlInterface(string name); public bool IsGlViaAngle { get; } + void AddManagedTexture(IOpenGlTexture texture); + IOpenGlTexture? GetManagedTexture(ulong textureId); + void RemoveManagedTexture(ulong textureId); } diff --git a/src/Drawie.RenderApi/IOpenGlHostViewRenderApi.cs b/src/Drawie.RenderApi/IOpenGlHostViewRenderApi.cs new file mode 100644 index 0000000..4b5685f --- /dev/null +++ b/src/Drawie.RenderApi/IOpenGlHostViewRenderApi.cs @@ -0,0 +1,6 @@ +namespace Drawie.RenderApi; + +public interface IOpenGlHostViewRenderApi : IHostViewRenderApi +{ + Func GetGlInterface(); +} diff --git a/src/Drawie.RenderApi/IOpenGlTexture.cs b/src/Drawie.RenderApi/IOpenGlTexture.cs index eea4d83..4360c5f 100644 --- a/src/Drawie.RenderApi/IOpenGlTexture.cs +++ b/src/Drawie.RenderApi/IOpenGlTexture.cs @@ -1,6 +1,7 @@ -namespace Drawie.RenderApi; +using Drawie.RenderApi.Abstraction.Textures; + +namespace Drawie.RenderApi; public interface IOpenGlTexture : ITexture { - public uint TextureId { get; } } diff --git a/src/Drawie.RenderApi/IOpenGlWindowRenderApi.cs b/src/Drawie.RenderApi/IOpenGlWindowRenderApi.cs deleted file mode 100644 index 9f30f3d..0000000 --- a/src/Drawie.RenderApi/IOpenGlWindowRenderApi.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Drawie.RenderApi; - -public interface IOpenGlWindowRenderApi : IWindowRenderApi -{ - -} diff --git a/src/Drawie.RenderApi/IRenderApi.cs b/src/Drawie.RenderApi/IRenderApi.cs index 821424e..5a76307 100644 --- a/src/Drawie.RenderApi/IRenderApi.cs +++ b/src/Drawie.RenderApi/IRenderApi.cs @@ -1,7 +1,10 @@ +using Drawie.RenderApi.Abstraction; + namespace Drawie.RenderApi; -public interface IRenderApi +public interface IRenderApi : IDisposable { - public IReadOnlyCollection WindowRenderApis { get; } - public IWindowRenderApi CreateWindowRenderApi(); + public IReadOnlyCollection WindowRenderApis { get; } + public IGraphicsDevice GraphicsDevice { get; } + public IHostViewRenderApi CreateWindowRenderApi(); } \ No newline at end of file diff --git a/src/Drawie.RenderApi/ITexture.cs b/src/Drawie.RenderApi/ITexture.cs deleted file mode 100644 index 3637c88..0000000 --- a/src/Drawie.RenderApi/ITexture.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Drawie.RenderApi; - -public interface ITexture -{ -} \ No newline at end of file diff --git a/src/Drawie.RenderApi/IVkTexture.cs b/src/Drawie.RenderApi/IVkTexture.cs index e41c7c9..0694363 100644 --- a/src/Drawie.RenderApi/IVkTexture.cs +++ b/src/Drawie.RenderApi/IVkTexture.cs @@ -1,3 +1,5 @@ +using Drawie.RenderApi.Abstraction.Textures; + namespace Drawie.RenderApi; public interface IVkTexture : ITexture @@ -11,4 +13,5 @@ public interface IVkTexture : ITexture public uint Tiling { get; } public void MakeReadOnly(); public void MakeWriteable(); -} \ No newline at end of file + event Action Disposing; +} diff --git a/src/Drawie.RenderApi/IVulkanWindowRenderApi.cs b/src/Drawie.RenderApi/IVulkanHostViewRenderApi.cs similarity index 54% rename from src/Drawie.RenderApi/IVulkanWindowRenderApi.cs rename to src/Drawie.RenderApi/IVulkanHostViewRenderApi.cs index 537106b..8929d72 100644 --- a/src/Drawie.RenderApi/IVulkanWindowRenderApi.cs +++ b/src/Drawie.RenderApi/IVulkanHostViewRenderApi.cs @@ -1,6 +1,6 @@ namespace Drawie.RenderApi; -public interface IVulkanWindowRenderApi : IWindowRenderApi +public interface IVulkanHostViewRenderApi : IHostViewRenderApi { public IVulkanContext Context { get; } } \ No newline at end of file diff --git a/src/Drawie.RenderApi/IVulkanRenderApi.cs b/src/Drawie.RenderApi/IVulkanRenderApi.cs index 2194d0c..33b6c31 100644 --- a/src/Drawie.RenderApi/IVulkanRenderApi.cs +++ b/src/Drawie.RenderApi/IVulkanRenderApi.cs @@ -1,7 +1,9 @@ +using Drawie.RenderApi.Abstraction; + namespace Drawie.RenderApi; public interface IVulkanRenderApi : IRenderApi { - public new IReadOnlyCollection WindowRenderApis { get; } + public new IReadOnlyCollection WindowRenderApis { get; } public IVulkanContext VulkanContext { get; } } \ No newline at end of file diff --git a/src/Drawie.RenderApi/IWebGlTexture.cs b/src/Drawie.RenderApi/IWebGlTexture.cs index 0ad1c1b..53284be 100644 --- a/src/Drawie.RenderApi/IWebGlTexture.cs +++ b/src/Drawie.RenderApi/IWebGlTexture.cs @@ -1,4 +1,6 @@ -namespace Drawie.RenderApi; +using Drawie.RenderApi.Abstraction.Textures; + +namespace Drawie.RenderApi; public interface IWebGlTexture : ITexture { diff --git a/src/Drawie.Rendering/Drawie.Rendering.csproj b/src/Drawie.Rendering/Drawie.Rendering.csproj new file mode 100644 index 0000000..926e672 --- /dev/null +++ b/src/Drawie.Rendering/Drawie.Rendering.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/src/Drawie.Rendering/Exceptions/FramebufferNotOpenException.cs b/src/Drawie.Rendering/Exceptions/FramebufferNotOpenException.cs new file mode 100644 index 0000000..825329b --- /dev/null +++ b/src/Drawie.Rendering/Exceptions/FramebufferNotOpenException.cs @@ -0,0 +1,7 @@ +namespace Drawie.Rendering.Exceptions; + +public class FramebufferNotOpenException : Exception +{ + public FramebufferNotOpenException(string message) : base(message) + {} +} \ No newline at end of file diff --git a/src/Drawie.Rendering/RenderingContext.cs b/src/Drawie.Rendering/RenderingContext.cs new file mode 100644 index 0000000..158a658 --- /dev/null +++ b/src/Drawie.Rendering/RenderingContext.cs @@ -0,0 +1,48 @@ +using Drawie.Backend.Core; +using Drawie.Backend.Core.Bridge; +using Drawie.RenderApi; + +namespace Drawie.Rendering; + +/// +/// Represents a context for rendering operations, allowing modifications to textures and other graphics resources. +/// +public class RenderingContext : IDisposable +{ + public IGraphicsContext? GraphicsContext { get; } + public bool IsOpen { get; private set; } = false; + private List ownedFramebuffers = new List(); + + public RenderingContext(IGraphicsContext? ctx) + { + GraphicsContext = ctx; + } + + public TextureFramebuffer Edit(Texture texture) + { + if (!IsOpen) throw new InvalidOperationException("Rendering Context is not open."); + var fbo = new TextureFramebuffer(texture); + fbo.Open(); + ownedFramebuffers.Add(fbo); + return ownedFramebuffers[^1]; + } + + public IDisposable Open() + { + if (IsOpen) throw new InvalidOperationException("Rendering Context is already open."); + IsOpen = true; + GraphicsContext?.MakeCurrent(); + return this; + } + + public void Dispose() + { + foreach (var textureFramebuffer in ownedFramebuffers) + { + if (textureFramebuffer.IsOpen) throw new InvalidOperationException("Framebuffer is in use"); + } + + IsOpen = false; + ownedFramebuffers.Clear(); + } +} diff --git a/src/Drawie.Rendering/TextureFramebuffer.cs b/src/Drawie.Rendering/TextureFramebuffer.cs new file mode 100644 index 0000000..c6d730a --- /dev/null +++ b/src/Drawie.Rendering/TextureFramebuffer.cs @@ -0,0 +1,68 @@ +using Drawie.Backend.Core; +using Drawie.Backend.Core.Bridge; +using Drawie.Backend.Core.ColorsImpl; +using Drawie.Backend.Core.ColorsImpl.Paintables; +using Drawie.Backend.Core.Surfaces; +using Drawie.Backend.Core.Surfaces.PaintImpl; +using Drawie.Numerics; +using Drawie.RenderApi.Abstraction.RenderTargets; +using Drawie.Rendering.Exceptions; + +namespace Drawie.Rendering; + +public class TextureFramebuffer : IDisposable, IRenderTarget +{ + internal Texture UnderlyingTexture { get; } + public bool IsOpen { get; private set; } + public VecI Size { get; } + public Canvas? Canvas => IsOpen ? UnderlyingTexture?.DrawingSurface?.Canvas : null; + + internal TextureFramebuffer(Texture texture) + { + UnderlyingTexture = texture; + Size = texture.Size; + } + + internal IDisposable Open() + { + IsOpen = true; + return this; + } + + public void Clear() + { + ThrowIfNotOpen(); + UnderlyingTexture.DrawingSurface.Canvas.Clear(); + } + + public void Clear(Color color) + { + ThrowIfNotOpen(); + UnderlyingTexture.DrawingSurface.Canvas.Clear(color); + } + + public void DrawRectangle(float x, float y, float width, float height, Paintable paintable) + { + ThrowIfNotOpen(); + + using var paint = new Paint() { Paintable = paintable }; + UnderlyingTexture.DrawingSurface.Canvas.DrawRect(x, y, width, height, paint); + } + + private void ThrowIfNotOpen() + { + if (!IsOpen) + { + throw new FramebufferNotOpenException("Cannot edit closed framebuffer"); + } + } + + public void Dispose() + { + if (!IsOpen) return; + + IsOpen = false; + } + + ulong IRenderTarget.SurfaceId => UnderlyingTexture.SurfaceId; +} diff --git a/src/Drawie.ShaderCompiler/Compilation/CrossCompiler.cs b/src/Drawie.ShaderCompiler/Compilation/CrossCompiler.cs new file mode 100644 index 0000000..1805c5a --- /dev/null +++ b/src/Drawie.ShaderCompiler/Compilation/CrossCompiler.cs @@ -0,0 +1,54 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using Drawie.ShaderCompiler; +using Silk.NET.SPIRV.Cross; + +namespace Drawie.Backend.Vertie.Compilation; + +public class CrossCompiler +{ + private Cross api; + private SpvContext spvContext; + + public unsafe CrossCompiler() + { + LoadNativeLibs(); + api = Cross.GetApi(); + spvContext = new SpvContext(api); + } + + private void LoadNativeLibs() + { + var location = Path.GetDirectoryName(typeof(ShaderCompilerTask).Assembly.Location); + string pathToNative = Path.Combine(location, "runtimes", RuntimeInformation.RuntimeIdentifier, "native"); + string pathToLib = Path.Combine(pathToNative, GetLibName()); + + NativeLibrary.Load(pathToLib); + } + + private string GetLibName() + { + if (RuntimeInformation.RuntimeIdentifier.StartsWith("win")) + { + return "spirv-cross.dll"; + } + + if (RuntimeInformation.RuntimeIdentifier.StartsWith("linux")) + { + return "libspirv-cross.so"; + } + + if (RuntimeInformation.RuntimeIdentifier.StartsWith("osx")) + { + return "libspirv-cross.dylib"; + } + throw new PlatformNotSupportedException(); + } + + public string CompileToGlslEs3(byte[] spirv) + { + var compiler = spvContext.CreateCompiler(Silk.NET.SPIRV.Cross.Backend.Glsl, spirv); + compiler.ConfigureGlslVersion(GlslVersion.GlslEs300); + return compiler.Compile(); + } +} \ No newline at end of file diff --git a/src/Drawie.ShaderCompiler/Compilation/GlslVersion.cs b/src/Drawie.ShaderCompiler/Compilation/GlslVersion.cs new file mode 100644 index 0000000..af17efd --- /dev/null +++ b/src/Drawie.ShaderCompiler/Compilation/GlslVersion.cs @@ -0,0 +1,9 @@ +namespace Drawie.Backend.Vertie.Compilation; + +public enum GlslVersion +{ + GlslEs100 = 100, + GlslEs300 = 300, + GlslEs310 = 310, + GlslEs320 = 320 +} \ No newline at end of file diff --git a/src/Drawie.ShaderCompiler/Compilation/ShaderCompiler.cs b/src/Drawie.ShaderCompiler/Compilation/ShaderCompiler.cs new file mode 100644 index 0000000..d4cb3a3 --- /dev/null +++ b/src/Drawie.ShaderCompiler/Compilation/ShaderCompiler.cs @@ -0,0 +1,188 @@ +using System.Text; +using System.Text.Json; +using Drawie.Backend.Shaders.Common; +using Drawie.Backend.Vertie.Compilation; +using Slangc.NET; + +namespace Drawie.ShaderCompiler.Compilation; + +public class ShaderCompiler +{ + public string OutputPath { get; set; } + public string ShaderName { get; set; } + + public ShaderCompiler(string outputPath, string shaderName) + { + OutputPath = outputPath; + ShaderName = shaderName; + } + + public void Compile(string sourceCode, CompilationTarget target) + { + var bytes = SlangCompiler.CompileWithReflection( + sourceCode, + [ + "-matrix-layout-column-major", + "-fvk-use-entrypoint-name", + "-target", "spirv", + "-profile", "spirv_1_3" + ], + out var reflection); + + File.WriteAllBytes(Path.Combine(OutputPath, $"{ShaderName}.spv"), bytes); + + if (target == CompilationTarget.GlslEs3) + { + var compiler = new CrossCompiler(); + + var glslCode = compiler.CompileToGlslEs3(bytes); + + bytes = Encoding.UTF8.GetBytes(glslCode); + } + + var outputName = + Path.GetFileNameWithoutExtension(ShaderName) + ".shader"; + + var outputReflectionName = Path.GetFileNameWithoutExtension(ShaderName) + ".reflection.json"; + + var outputPath = + Path.Combine(OutputPath, outputName); + + var outputReflectionPath = Path.Combine(OutputPath, outputReflectionName); + + File.WriteAllBytes(outputPath, bytes); + File.WriteAllText(outputReflectionPath, ToReflectionJson(reflection)); + } + + + private string ToReflectionJson(SlangReflection reflection) + { + var shaderReflection = new ShaderReflection(); + + foreach (var entryPoint in reflection.EntryPoints) + { + shaderReflection.EntryPoints.Add(new EntryPoint + { + Name = entryPoint.Name, + Type = StageToType(entryPoint.Stage) + }); + } + + foreach (var parameter in reflection.Parameters) + { + var binding = parameter.Bindings.FirstOrDefault(); + + if (binding == null) + continue; + + var shaderParameter = new ShaderParameter + { + Name = parameter.Name, + Index = (int)binding.Index, + Var = CreateShaderVar(parameter) + }; + // TODO Validate? + shaderParameter.Size = shaderParameter.Var.Layout.Size; + + shaderReflection.Parameters.Add(shaderParameter); + } + + shaderReflection.RawReflectionJson = reflection.Json; + + return JsonSerializer.Serialize(shaderReflection); + } + + private static ShaderVar CreateShaderVar(SlangParameter parameter) + { + var type = parameter.Type; + + var shaderVar = new ShaderVar + { + Layout = new PropertyLayout + { + Name = parameter.Name + }, + Fields = new List() + }; + + // ConstantBuffer -> ElementVarLayout + var elementVarLayout = type.ConstantBuffer?.ElementVarLayout; + + if (elementVarLayout != null) + { + shaderVar.Layout = CreatePropertyLayout( + elementVarLayout, + parameter.Name); + + var fields = elementVarLayout.Type.Struct?.Fields; + + if (fields != null) + { + foreach (var field in fields) + { + if (field.Binding == null) + continue; + + shaderVar.Fields.Add( + CreatePropertyLayout( + field, + field.Name)); + } + } + } + else + { + // Non-constant-buffer parameter. + // Use the parameter's own binding information. + var binding = parameter.Bindings.FirstOrDefault(); + + if (binding != null) + { + shaderVar.Layout = new PropertyLayout + { + Name = parameter.Name, + Offset = (int)binding.Offset, + Size = (int)binding.Size + }; + } + } + + return shaderVar; + } + + private static PropertyLayout CreatePropertyLayout( + SlangVar variable, + string name) + { + var binding = variable.Binding; + + return new PropertyLayout + { + Name = name, + Offset = binding != null + ? (int)binding.Offset + : 0, + Size = binding != null + ? (int)binding.Size + : 0 + }; + } + + private static ShaderType StageToType(SlangStage stage) + { + return stage switch + { + SlangStage.Vertex => ShaderType.Vertex, + SlangStage.Fragment => ShaderType.Fragment, + SlangStage.Compute => ShaderType.Compute, + _ => throw new ArgumentException( + $"Unsupported shader stage: {stage}") + }; + } +} + +public enum CompilationTarget +{ + SpirV, + GlslEs3 +} diff --git a/src/Drawie.ShaderCompiler/Compilation/SpvCompiler.cs b/src/Drawie.ShaderCompiler/Compilation/SpvCompiler.cs new file mode 100644 index 0000000..de6b81c --- /dev/null +++ b/src/Drawie.ShaderCompiler/Compilation/SpvCompiler.cs @@ -0,0 +1,87 @@ +using System.Runtime.InteropServices; +using Drawie.Backend.Vertie.Compilation; +using Silk.NET.SPIRV.Cross; + +namespace Drawie.ShaderCompiler.Compilation; + +public class SpvCompiler +{ + private unsafe Compiler* compiler; + private unsafe CompilerOptions* options; + + private Cross api; + + public unsafe SpvCompiler(Cross api, Context* spvContext, Silk.NET.SPIRV.Cross.Backend backend, byte[] spirv) + { + this.api = api; + Compiler* crossCompiler = null; + Compiler** crossCompilerPtr = &crossCompiler; + + if (spirv.Length == 0) + throw new ArgumentException( + "SPIR-V cannot be empty.", + nameof(spirv)); + + if (spirv.Length % sizeof(uint) != 0) + throw new ArgumentException( + "SPIR-V byte length must be a multiple of 4.", + nameof(spirv)); + + + ParsedIr* parsedIr = null; + + var words = MemoryMarshal.Cast(spirv); + + var parseResult = api.ContextParseSpirv( + spvContext, + words, + (nuint)words.Length, + &parsedIr); + + if (parseResult != Result.Success) + throw new Exception( + $"SPIR-V parsing failed: {parseResult}"); + + var result = api.ContextCreateCompiler(spvContext, backend, parsedIr, + CaptureMode.TakeOwnership, crossCompilerPtr); + + if (result != Result.Success) + throw new Exception($"GLSL compiler creation failed: {result}"); + + compiler = crossCompiler; + + CompilerOptions* options = null; + var optionsResult = api.CompilerCreateCompilerOptions(crossCompiler, &options); + + if (optionsResult != Result.Success) + throw new Exception($"Failed to create compiler options: {optionsResult}"); + + this.options = options; + } + + public unsafe void ConfigureGlslVersion(GlslVersion version) + { + api.CompilerOptionsSetUint(options, CompilerOption.GlslVersion, (uint)version); + api.CompilerOptionsSetBool(options, CompilerOption.GlslES, 1); + } + + public unsafe string Compile() + { + var installResult = api.CompilerInstallCompilerOptions(compiler, options); + if (installResult != Result.Success) + { + throw new Exception($"Failed to install compiler options: {installResult}"); + } + + byte* source = null; + + var result = api.CompilerCompile(compiler, &source); + if (result != Result.Success) + { + throw new Exception($"Compilation failed: {result}"); + } + + string code = Marshal.PtrToStringUTF8((nint)source)!; + return code; + } +} \ No newline at end of file diff --git a/src/Drawie.ShaderCompiler/Compilation/SpvContext.cs b/src/Drawie.ShaderCompiler/Compilation/SpvContext.cs new file mode 100644 index 0000000..bb61060 --- /dev/null +++ b/src/Drawie.ShaderCompiler/Compilation/SpvContext.cs @@ -0,0 +1,29 @@ +using Drawie.ShaderCompiler.Compilation; +using Silk.NET.SPIRV.Cross; + +namespace Drawie.Backend.Vertie.Compilation; + +public class SpvContext +{ + private unsafe Context* spvContext; + private Cross api; + + public unsafe SpvContext(Cross api) + { + this.api = api; + Context* context = null; + + var result = api.ContextCreate(&context); + if (result != Result.Success) + { + throw new Exception($"SPIRV-Cross context creation failed: {result}"); + } + + spvContext = context; + } + + public unsafe SpvCompiler CreateCompiler(Silk.NET.SPIRV.Cross.Backend backend, byte[] spirv) + { + return new SpvCompiler(api, spvContext, backend, spirv); + } +} \ No newline at end of file diff --git a/src/Drawie.ShaderCompiler/Drawie.ShaderCompiler.csproj b/src/Drawie.ShaderCompiler/Drawie.ShaderCompiler.csproj new file mode 100644 index 0000000..6d0011a --- /dev/null +++ b/src/Drawie.ShaderCompiler/Drawie.ShaderCompiler.csproj @@ -0,0 +1,26 @@ + + + + net10.0 + enable + enable + true + true + + + + + + + + + + + + + + + + + diff --git a/src/Drawie.ShaderCompiler/Program.cs b/src/Drawie.ShaderCompiler/Program.cs new file mode 100644 index 0000000..bffaee0 --- /dev/null +++ b/src/Drawie.ShaderCompiler/Program.cs @@ -0,0 +1,67 @@ +using System.Runtime.InteropServices; +using Drawie.ShaderCompiler.Compilation; +using Microsoft.Build.Framework; +using Task = Microsoft.Build.Utilities.Task; + +namespace Drawie.ShaderCompiler; + +public sealed class ShaderCompilerTask : Task +{ + [Required] public string OutputDirectory { get; set; } = ""; + + [Required] public string ShaderRoot { get; set; } = ""; + + public bool Browser { get; set; } + + public override bool Execute() + { + try + { + Directory.CreateDirectory(OutputDirectory); + + foreach (var source in Directory.GetFiles(ShaderRoot, "*.slang", SearchOption.AllDirectories)) + { + try + { + CompileShader(source); + } + catch (Exception ex) + { + Log.LogError( + $"Failed to compile shader '{source}': {ex}"); + + return false; + } + } + + return !Log.HasLoggedErrors; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex, true); + return false; + } + } + + private void CompileShader(string sourcePath) + { + var target = Browser + ? CompilationTarget.GlslEs3 + : CompilationTarget.SpirV; + + Log.LogMessage( + MessageImportance.High, + $"Compiling shader: {sourcePath}, target: {target}"); + + var sourceCode = File.ReadAllText(sourcePath); + + + Compilation.ShaderCompiler compiler = + new Compilation.ShaderCompiler(OutputDirectory, Path.GetFileNameWithoutExtension(sourcePath)); + compiler.Compile(sourceCode, target); + + Log.LogMessage( + MessageImportance.High, + $"Generated: {Path.Combine(OutputDirectory, Path.GetFileNameWithoutExtension(sourcePath) + ".shader")} and {Path.Combine(OutputDirectory, Path.GetFileNameWithoutExtension(sourcePath) + ".reflection.json")}"); + } +} \ No newline at end of file diff --git a/src/Drawie.Shaders.Common/Drawie.Shaders.Common.csproj b/src/Drawie.Shaders.Common/Drawie.Shaders.Common.csproj new file mode 100644 index 0000000..52955e5 --- /dev/null +++ b/src/Drawie.Shaders.Common/Drawie.Shaders.Common.csproj @@ -0,0 +1,10 @@ + + + + net10.0 + enable + enable + Drawie.Backend.Shaders.Common + + + diff --git a/src/Drawie.Shaders.Common/PropertyLayout.cs b/src/Drawie.Shaders.Common/PropertyLayout.cs new file mode 100644 index 0000000..7524808 --- /dev/null +++ b/src/Drawie.Shaders.Common/PropertyLayout.cs @@ -0,0 +1,8 @@ +namespace Drawie.Backend.Shaders.Common; + +public struct PropertyLayout +{ + public string Name { get; set; } + public int Offset { get; set; } + public int Size { get; set; } +} \ No newline at end of file diff --git a/src/Drawie.Shaders.Common/ShaderReflection.cs b/src/Drawie.Shaders.Common/ShaderReflection.cs new file mode 100644 index 0000000..500d4a7 --- /dev/null +++ b/src/Drawie.Shaders.Common/ShaderReflection.cs @@ -0,0 +1,33 @@ +namespace Drawie.Backend.Shaders.Common; + +[Serializable] +public class ShaderReflection +{ + public List Parameters { get; set; } = new List(); + public List EntryPoints { get; set; } = new List(); + + public string RawReflectionJson { get; set; } +} + +[Serializable] +public class EntryPoint +{ + public string Name { get; set; } + public ShaderType Type { get; set; } +} + +[Serializable] +public class ShaderParameter +{ + public string Name { get; set; } + public int Index { get; set; } + public int Size { get; set; } + public ShaderVar Var { get; set; } +} + +[Serializable] +public class ShaderVar +{ + public PropertyLayout Layout { get; set; } + public List Fields { get; set; } +} \ No newline at end of file diff --git a/src/Drawie.Shaders.Common/ShaderReflectionContext.cs b/src/Drawie.Shaders.Common/ShaderReflectionContext.cs new file mode 100644 index 0000000..afe9b1c --- /dev/null +++ b/src/Drawie.Shaders.Common/ShaderReflectionContext.cs @@ -0,0 +1,10 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Drawie.Backend.Shaders.Common; + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(ShaderReflection))] +public partial class ShaderReflectionContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/src/Drawie.Shaders.Common/ShaderType.cs b/src/Drawie.Shaders.Common/ShaderType.cs new file mode 100644 index 0000000..6e94c77 --- /dev/null +++ b/src/Drawie.Shaders.Common/ShaderType.cs @@ -0,0 +1,8 @@ +namespace Drawie.Backend.Shaders.Common; + +public enum ShaderType +{ + Vertex, + Fragment, + Compute +} \ No newline at end of file diff --git a/src/Drawie.Tests/Drawie.Tests.csproj b/src/Drawie.Tests/Drawie.Tests.csproj index 6379522..e6f41ea 100644 --- a/src/Drawie.Tests/Drawie.Tests.csproj +++ b/src/Drawie.Tests/Drawie.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable @@ -10,7 +10,6 @@ - diff --git a/src/Drawie.Tests/SkiaBackendFixture.cs b/src/Drawie.Tests/SkiaBackendFixture.cs index 40fde1c..57952ac 100644 --- a/src/Drawie.Tests/SkiaBackendFixture.cs +++ b/src/Drawie.Tests/SkiaBackendFixture.cs @@ -1,10 +1,4 @@ using Drawie.Backend.Core.Bridge; -using Drawie.RenderApi.OpenGL; -using Drawie.RenderApi.Vulkan; -using Drawie.Skia; -using Drawie.Windowing; -using DrawiEngine; -using DrawiEngine.Desktop; namespace Drawie.Tests; diff --git a/src/Drawie.Windowing.Browser/BrowserWindow.cs b/src/Drawie.Windowing.Browser/BrowserWindow.cs deleted file mode 100644 index f865881..0000000 --- a/src/Drawie.Windowing.Browser/BrowserWindow.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Drawie.Backend.Core; -using Drawie.Backend.Core.Bridge; -using Drawie.Numerics; -using Drawie.RenderApi; -using Drawie.Windowing.Browser.Input; -using Drawie.Windowing.Input; - -namespace Drawie.Windowing.Browser; - -public class BrowserWindow(IWindowRenderApi windowRenderApi) : IWindow -{ - public string Name - { - get => BrowserInterop.GetTitle(); - set => BrowserInterop.SetTitle(value); - } - - public VecI Size - { - get => UsableWindowSize; - } - - public VecI UsableWindowSize => BrowserInterop.GetWindowSize(); - - public IWindowRenderApi RenderApi { get; set; } = windowRenderApi; - - public InputController InputController { get; private set; } - - public bool ShowOnTop - { - get => false; - set { } - } - - public bool IsVisible - { - get => true; - set - { - throw new NotSupportedException("Browser windows cannot be hidden."); - } - } - - public event Action? Update; - public event Action? Render; - - private Texture renderTexture; - - public void Initialize() - { - RenderApi.CreateInstance(null, UsableWindowSize); - RenderApi.FramebufferResized += FramebufferResized; - - InputController = new InputController(new [] { new BrowserKeyboard() }, []); - } - - private void FramebufferResized() - { - renderTexture?.Dispose(); - renderTexture = CreateRenderTexture(); - } - - public void Show() - { - renderTexture = CreateRenderTexture(); - OnRender(0); - BrowserInterop.SubscribeWindowResize(OnWindowResized); - } - - private void OnRender(double dt) - { - double deltaTime = dt / 1000.0; - Update?.Invoke(deltaTime); - RenderApi.PrepareTextureToWrite(); - renderTexture.DrawingSurface?.Canvas.Clear(); - Render?.Invoke(renderTexture, deltaTime); - renderTexture.DrawingSurface?.Flush(); - BrowserInterop.RequestAnimationFrame(OnRender); - } - - public void Close() - { - } - - private void OnWindowResized(int width, int height) - { - RenderApi?.UpdateFramebufferSize(width, height); - BrowserInterop.RequestAnimationFrame(OnRender); - } - - private Texture CreateRenderTexture() - { - var drawingSurface = - DrawingBackendApi.Current.CreateRenderSurface(UsableWindowSize, RenderApi.RenderTexture, - SurfaceOrigin.BottomLeft); - return Texture.FromExisting(drawingSurface); - } -} diff --git a/src/Drawie.Windowing.Glfw/GlfwWindowingPlatform.cs b/src/Drawie.Windowing.Glfw/GlfwWindowingPlatform.cs deleted file mode 100644 index ce980f9..0000000 --- a/src/Drawie.Windowing.Glfw/GlfwWindowingPlatform.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Drawie.Numerics; -using Drawie.RenderApi; -using Drawie.Windowing; -using Drawie.Windowing.Input; -using Silk.NET.Input; -using IWindow = Drawie.Windowing.IWindow; - -namespace Drawie.Silk; - -public class GlfwWindowingPlatform : IWindowingPlatform -{ - private readonly List _windows = new(); - - public IReadOnlyCollection Windows => _windows; - public IRenderApi RenderApi { get; } - - public GlfwWindowingPlatform(IRenderApi renderApi) - { - RenderApi = renderApi; - } - - public IWindow CreateWindow(string name) - { - return CreateWindow(name, VecI.Zero); - } - - public IWindow CreateWindow(string name, VecI size) - { - GlfwWindow window = new(name, size, RenderApi.CreateWindowRenderApi()); - _windows.Add(window); - return window; - } - - public override string ToString() - { - return "Glfw"; - } -} \ No newline at end of file diff --git a/src/Drawie.Windowing/IWindow.cs b/src/Drawie.Windowing/IWindow.cs deleted file mode 100644 index 47fcbaa..0000000 --- a/src/Drawie.Windowing/IWindow.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Drawie.Backend.Core; -using Drawie.Numerics; -using Drawie.RenderApi; -using Drawie.Windowing.Input; - -namespace Drawie.Windowing; - -public interface IWindow -{ - public string Name { get; set; } - public VecI Size { get; } - - public IWindowRenderApi RenderApi { get; set; } - - public InputController InputController { get; } - public bool ShowOnTop { get; set; } - - public bool IsVisible { get; set; } - - public event Action Update; - public event Action Render; - - public void Initialize(); - public void Show(); - public void Close(); -} diff --git a/src/Drawie.Windowing/IWindowingPlatform.cs b/src/Drawie.Windowing/IWindowingPlatform.cs deleted file mode 100644 index b6f636a..0000000 --- a/src/Drawie.Windowing/IWindowingPlatform.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Drawie.Numerics; -using Drawie.RenderApi; -using Drawie.Windowing.Input; - -namespace Drawie.Windowing; - -public interface IWindowingPlatform -{ - public IRenderApi RenderApi { get; } - public IReadOnlyCollection Windows { get; } - public IWindow CreateWindow(string name); - public IWindow CreateWindow(string name, VecI size); -} \ No newline at end of file diff --git a/src/Drawie.sln b/src/Drawie.sln deleted file mode 100644 index 11b1e85..0000000 --- a/src/Drawie.sln +++ /dev/null @@ -1,190 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.Backend.Skia", "Drawie.Backend.Skia\Drawie.Backend.Skia.csproj", "{F549B8B4-B40C-4E79-B9D9-272FF9C648F0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.Backend.Core", "Drawie.Backend.Core\Drawie.Backend.Core.csproj", "{860067DF-D78D-4459-BD5F-96AEC70C281B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DrawieSample.Desktop", "DrawieSample.Desktop\DrawieSample.Desktop.csproj", "{645AF54D-D58F-492E-A2A9-12F86EA8C00A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.Numerics", "Drawie.Numerics\Drawie.Numerics.csproj", "{2C84A8E2-9DAF-43CF-AFCB-030922AEA412}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DrawiEngine", "DrawiEngine\DrawiEngine.csproj", "{C91E2EF2-BF05-49E6-91A0-53B847B38FC0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.Windowing", "Drawie.Windowing\Drawie.Windowing.csproj", "{503FADEB-B803-4B27-BE37-DDAA37C5CDA9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.Windowing.Glfw", "Drawie.Windowing.Glfw\Drawie.Windowing.Glfw.csproj", "{D4927AD1-8A67-4358-B867-EA19771D47C8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.RenderApi", "Drawie.RenderApi\Drawie.RenderApi.csproj", "{D6C22861-1F82-4032-8108-06744D3DB33D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.RenderApi.Vulkan", "Drawie.RenderApi.Vulkan\Drawie.RenderApi.Vulkan.csproj", "{C66A9ACB-904D-42B4-8FFE-6ED36E855A11}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DrawieSample.Browser", "DrawieSample.Browser\DrawieSample.Browser.csproj", "{16A45076-AAE6-497E-BBA6-E8CA974E19CD}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Samples", "_Samples", "{239DB1B4-E66F-444A-87DA-B19775F40974}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.Windowing.Browser", "Drawie.Windowing.Browser\Drawie.Windowing.Browser.csproj", "{14896E27-47BA-48E9-81D1-612582C8FF5B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RenderApis", "RenderApis", "{9A805C39-3EC4-478F-B7F2-5D54F9F35CB8}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Backends", "Backends", "{BE35DF1E-91E8-4F16-841D-70EDDE31CC9F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Windowing", "Windowing", "{823F63C1-3DD7-4DE7-A41E-B4E0159FBCB4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.JSInterop", "Drawie.JSInterop\Drawie.JSInterop.csproj", "{6B57DDAF-FE9A-496C-B290-D019004B6B11}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DrawieSample", "DrawieSample\DrawieSample.csproj", "{6E2E0A40-4156-43C2-B0BD-2100FE4B0E4A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DrawiEngine.Browser", "DrawiEngine.Browser\DrawiEngine.Browser.csproj", "{6E4896DE-D6A3-433F-B350-2B35D0320F53}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DrawiEngine.Desktop", "DrawiEngine.Desktop\DrawiEngine.Desktop.csproj", "{88620A08-0FC3-4A60-835C-2FB516141375}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.AvaloniaInterop", "Drawie.AvaloniaInterop\Drawie.AvaloniaInterop.csproj", "{0CF0D692-9EEC-43C2-9064-E59609A134E6}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interops", "Interops", "{844D01F5-5C74-4635-9DBB-12303702D4A6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.Interop.Avalonia.Vulkan", "Drawie.Interop.Avalonia.Vulkan\Drawie.Interop.Avalonia.Vulkan.csproj", "{43F3D61C-5FA8-4F24-ABDC-186E287EC882}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.RenderApi.WebGl", "Drawie.RenderApi.WebGl\Drawie.RenderApi.WebGl.csproj", "{B5E4E80D-C248-4565-90F9-B3ECDF450EC6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.RenderApi.Web.Common", "Drawie.RenderApi.Web.Common\Drawie.RenderApi.Web.Common.csproj", "{9ADAF199-8AEA-4831-B8D3-8FC33A2F4F04}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.RenderApi.OpenGl", "Drawie.RenderApi.OpenGL\Drawie.RenderApi.OpenGl.csproj", "{0AD65A3C-895C-4358-AD3F-B2FC1CED531E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.Interop.Avalonia.Core", "Drawie.Interop.Avalonia.Core\Drawie.Interop.Avalonia.Core.csproj", "{D10009DC-D4F1-4A50-A36D-BF38ABBCC148}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.Interop.Avalonia", "Drawie.Interop.Avalonia\Drawie.Interop.Avalonia.csproj", "{88F7130B-C1B0-4383-8503-CB7A3960C6A4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.Interop.Avalonia.OpenGl", "Drawie.Interop.Avalonia.OpenGl\Drawie.Interop.Avalonia.OpenGl.csproj", "{EF944C77-D86B-4D6B-B88E-FE06131C70D7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.Tests", "Drawie.Tests\Drawie.Tests.csproj", "{5222843B-3E7F-4AF9-9394-4BDBC2D2ABD6}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F549B8B4-B40C-4E79-B9D9-272FF9C648F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F549B8B4-B40C-4E79-B9D9-272FF9C648F0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F549B8B4-B40C-4E79-B9D9-272FF9C648F0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F549B8B4-B40C-4E79-B9D9-272FF9C648F0}.Release|Any CPU.Build.0 = Release|Any CPU - {860067DF-D78D-4459-BD5F-96AEC70C281B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {860067DF-D78D-4459-BD5F-96AEC70C281B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {860067DF-D78D-4459-BD5F-96AEC70C281B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {860067DF-D78D-4459-BD5F-96AEC70C281B}.Release|Any CPU.Build.0 = Release|Any CPU - {645AF54D-D58F-492E-A2A9-12F86EA8C00A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {645AF54D-D58F-492E-A2A9-12F86EA8C00A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {645AF54D-D58F-492E-A2A9-12F86EA8C00A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {645AF54D-D58F-492E-A2A9-12F86EA8C00A}.Release|Any CPU.Build.0 = Release|Any CPU - {2C84A8E2-9DAF-43CF-AFCB-030922AEA412}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2C84A8E2-9DAF-43CF-AFCB-030922AEA412}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2C84A8E2-9DAF-43CF-AFCB-030922AEA412}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2C84A8E2-9DAF-43CF-AFCB-030922AEA412}.Release|Any CPU.Build.0 = Release|Any CPU - {C91E2EF2-BF05-49E6-91A0-53B847B38FC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C91E2EF2-BF05-49E6-91A0-53B847B38FC0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C91E2EF2-BF05-49E6-91A0-53B847B38FC0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C91E2EF2-BF05-49E6-91A0-53B847B38FC0}.Release|Any CPU.Build.0 = Release|Any CPU - {503FADEB-B803-4B27-BE37-DDAA37C5CDA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {503FADEB-B803-4B27-BE37-DDAA37C5CDA9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {503FADEB-B803-4B27-BE37-DDAA37C5CDA9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {503FADEB-B803-4B27-BE37-DDAA37C5CDA9}.Release|Any CPU.Build.0 = Release|Any CPU - {D4927AD1-8A67-4358-B867-EA19771D47C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D4927AD1-8A67-4358-B867-EA19771D47C8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D4927AD1-8A67-4358-B867-EA19771D47C8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D4927AD1-8A67-4358-B867-EA19771D47C8}.Release|Any CPU.Build.0 = Release|Any CPU - {D6C22861-1F82-4032-8108-06744D3DB33D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D6C22861-1F82-4032-8108-06744D3DB33D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D6C22861-1F82-4032-8108-06744D3DB33D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D6C22861-1F82-4032-8108-06744D3DB33D}.Release|Any CPU.Build.0 = Release|Any CPU - {C66A9ACB-904D-42B4-8FFE-6ED36E855A11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C66A9ACB-904D-42B4-8FFE-6ED36E855A11}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C66A9ACB-904D-42B4-8FFE-6ED36E855A11}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C66A9ACB-904D-42B4-8FFE-6ED36E855A11}.Release|Any CPU.Build.0 = Release|Any CPU - {16A45076-AAE6-497E-BBA6-E8CA974E19CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {16A45076-AAE6-497E-BBA6-E8CA974E19CD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {16A45076-AAE6-497E-BBA6-E8CA974E19CD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {16A45076-AAE6-497E-BBA6-E8CA974E19CD}.Release|Any CPU.Build.0 = Release|Any CPU - {14896E27-47BA-48E9-81D1-612582C8FF5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {14896E27-47BA-48E9-81D1-612582C8FF5B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {14896E27-47BA-48E9-81D1-612582C8FF5B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {14896E27-47BA-48E9-81D1-612582C8FF5B}.Release|Any CPU.Build.0 = Release|Any CPU - {6B57DDAF-FE9A-496C-B290-D019004B6B11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6B57DDAF-FE9A-496C-B290-D019004B6B11}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6B57DDAF-FE9A-496C-B290-D019004B6B11}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6B57DDAF-FE9A-496C-B290-D019004B6B11}.Release|Any CPU.Build.0 = Release|Any CPU - {6E2E0A40-4156-43C2-B0BD-2100FE4B0E4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6E2E0A40-4156-43C2-B0BD-2100FE4B0E4A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6E2E0A40-4156-43C2-B0BD-2100FE4B0E4A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6E2E0A40-4156-43C2-B0BD-2100FE4B0E4A}.Release|Any CPU.Build.0 = Release|Any CPU - {6E4896DE-D6A3-433F-B350-2B35D0320F53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6E4896DE-D6A3-433F-B350-2B35D0320F53}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6E4896DE-D6A3-433F-B350-2B35D0320F53}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6E4896DE-D6A3-433F-B350-2B35D0320F53}.Release|Any CPU.Build.0 = Release|Any CPU - {88620A08-0FC3-4A60-835C-2FB516141375}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88620A08-0FC3-4A60-835C-2FB516141375}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88620A08-0FC3-4A60-835C-2FB516141375}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88620A08-0FC3-4A60-835C-2FB516141375}.Release|Any CPU.Build.0 = Release|Any CPU - {0CF0D692-9EEC-43C2-9064-E59609A134E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0CF0D692-9EEC-43C2-9064-E59609A134E6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0CF0D692-9EEC-43C2-9064-E59609A134E6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0CF0D692-9EEC-43C2-9064-E59609A134E6}.Release|Any CPU.Build.0 = Release|Any CPU - {43F3D61C-5FA8-4F24-ABDC-186E287EC882}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {43F3D61C-5FA8-4F24-ABDC-186E287EC882}.Debug|Any CPU.Build.0 = Debug|Any CPU - {43F3D61C-5FA8-4F24-ABDC-186E287EC882}.Release|Any CPU.ActiveCfg = Release|Any CPU - {43F3D61C-5FA8-4F24-ABDC-186E287EC882}.Release|Any CPU.Build.0 = Release|Any CPU - {B5E4E80D-C248-4565-90F9-B3ECDF450EC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B5E4E80D-C248-4565-90F9-B3ECDF450EC6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B5E4E80D-C248-4565-90F9-B3ECDF450EC6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B5E4E80D-C248-4565-90F9-B3ECDF450EC6}.Release|Any CPU.Build.0 = Release|Any CPU - {9ADAF199-8AEA-4831-B8D3-8FC33A2F4F04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9ADAF199-8AEA-4831-B8D3-8FC33A2F4F04}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9ADAF199-8AEA-4831-B8D3-8FC33A2F4F04}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9ADAF199-8AEA-4831-B8D3-8FC33A2F4F04}.Release|Any CPU.Build.0 = Release|Any CPU - {0AD65A3C-895C-4358-AD3F-B2FC1CED531E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0AD65A3C-895C-4358-AD3F-B2FC1CED531E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0AD65A3C-895C-4358-AD3F-B2FC1CED531E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0AD65A3C-895C-4358-AD3F-B2FC1CED531E}.Release|Any CPU.Build.0 = Release|Any CPU - {D10009DC-D4F1-4A50-A36D-BF38ABBCC148}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D10009DC-D4F1-4A50-A36D-BF38ABBCC148}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D10009DC-D4F1-4A50-A36D-BF38ABBCC148}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D10009DC-D4F1-4A50-A36D-BF38ABBCC148}.Release|Any CPU.Build.0 = Release|Any CPU - {88F7130B-C1B0-4383-8503-CB7A3960C6A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88F7130B-C1B0-4383-8503-CB7A3960C6A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88F7130B-C1B0-4383-8503-CB7A3960C6A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88F7130B-C1B0-4383-8503-CB7A3960C6A4}.Release|Any CPU.Build.0 = Release|Any CPU - {EF944C77-D86B-4D6B-B88E-FE06131C70D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EF944C77-D86B-4D6B-B88E-FE06131C70D7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EF944C77-D86B-4D6B-B88E-FE06131C70D7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EF944C77-D86B-4D6B-B88E-FE06131C70D7}.Release|Any CPU.Build.0 = Release|Any CPU - {5222843B-3E7F-4AF9-9394-4BDBC2D2ABD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5222843B-3E7F-4AF9-9394-4BDBC2D2ABD6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5222843B-3E7F-4AF9-9394-4BDBC2D2ABD6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5222843B-3E7F-4AF9-9394-4BDBC2D2ABD6}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {645AF54D-D58F-492E-A2A9-12F86EA8C00A} = {239DB1B4-E66F-444A-87DA-B19775F40974} - {16A45076-AAE6-497E-BBA6-E8CA974E19CD} = {239DB1B4-E66F-444A-87DA-B19775F40974} - {D6C22861-1F82-4032-8108-06744D3DB33D} = {9A805C39-3EC4-478F-B7F2-5D54F9F35CB8} - {C66A9ACB-904D-42B4-8FFE-6ED36E855A11} = {9A805C39-3EC4-478F-B7F2-5D54F9F35CB8} - {860067DF-D78D-4459-BD5F-96AEC70C281B} = {BE35DF1E-91E8-4F16-841D-70EDDE31CC9F} - {F549B8B4-B40C-4E79-B9D9-272FF9C648F0} = {BE35DF1E-91E8-4F16-841D-70EDDE31CC9F} - {503FADEB-B803-4B27-BE37-DDAA37C5CDA9} = {823F63C1-3DD7-4DE7-A41E-B4E0159FBCB4} - {14896E27-47BA-48E9-81D1-612582C8FF5B} = {823F63C1-3DD7-4DE7-A41E-B4E0159FBCB4} - {D4927AD1-8A67-4358-B867-EA19771D47C8} = {823F63C1-3DD7-4DE7-A41E-B4E0159FBCB4} - {6E2E0A40-4156-43C2-B0BD-2100FE4B0E4A} = {239DB1B4-E66F-444A-87DA-B19775F40974} - {0CF0D692-9EEC-43C2-9064-E59609A134E6} = {239DB1B4-E66F-444A-87DA-B19775F40974} - {43F3D61C-5FA8-4F24-ABDC-186E287EC882} = {844D01F5-5C74-4635-9DBB-12303702D4A6} - {B5E4E80D-C248-4565-90F9-B3ECDF450EC6} = {9A805C39-3EC4-478F-B7F2-5D54F9F35CB8} - {9ADAF199-8AEA-4831-B8D3-8FC33A2F4F04} = {9A805C39-3EC4-478F-B7F2-5D54F9F35CB8} - {0AD65A3C-895C-4358-AD3F-B2FC1CED531E} = {9A805C39-3EC4-478F-B7F2-5D54F9F35CB8} - {D10009DC-D4F1-4A50-A36D-BF38ABBCC148} = {844D01F5-5C74-4635-9DBB-12303702D4A6} - {88F7130B-C1B0-4383-8503-CB7A3960C6A4} = {844D01F5-5C74-4635-9DBB-12303702D4A6} - {EF944C77-D86B-4D6B-B88E-FE06131C70D7} = {844D01F5-5C74-4635-9DBB-12303702D4A6} - EndGlobalSection -EndGlobal diff --git a/src/Drawie.sln.DotSettings.user b/src/Drawie.sln.DotSettings.user index 77a6070..743c8d7 100644 --- a/src/Drawie.sln.DotSettings.user +++ b/src/Drawie.sln.DotSettings.user @@ -1,79 +1,3 @@  - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - ForceIncluded - <AssemblyExplorer> - <PhysicalFolder Path="C:\Users\flubb\.nuget\packages\silk.net.shaderc.native\2.21.0" Loaded="True" /> -</AssemblyExplorer> - C:\Program Files\dotnet\sdk\8.0.405\MSBuild.dll - C:\Program Files\dotnet\dotnet.exe - <SessionState ContinuousTestingMode="0" IsActive="True" Name="TestThatDrawingBackendHasGraphicsContext" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <TestAncestor> - <TestId>xUnit::5222843B-3E7F-4AF9-9394-4BDBC2D2ABD6::net8.0::Drawie.Tests.SkiaDrawingBackendTests</TestId> - </TestAncestor> -</SessionState> - - - - - - - - - - - - - - - - - - - - - - True - True \ No newline at end of file + ForceIncluded + ForceIncluded \ No newline at end of file diff --git a/src/Drawie.slnx b/src/Drawie.slnx new file mode 100644 index 0000000..e0aeb40 --- /dev/null +++ b/src/Drawie.slnx @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Drawie2Sample/Assets/shiba.fbx b/src/Drawie2Sample/Assets/shiba.fbx new file mode 100644 index 0000000..620dd55 Binary files /dev/null and b/src/Drawie2Sample/Assets/shiba.fbx differ diff --git a/src/Drawie2Sample/Assets/teapot.obj b/src/Drawie2Sample/Assets/teapot.obj new file mode 100644 index 0000000..cedb196 --- /dev/null +++ b/src/Drawie2Sample/Assets/teapot.obj @@ -0,0 +1,4663 @@ +# Blender v2.61 (sub 0) OBJ File: '' +# www.blender.org +v 0.605903 0.005903 -0.000000 +v 0.000000 0.000000 0.000000 +v 0.584584 0.005902 -0.162696 +v 0.524218 0.005902 -0.307888 +v 0.430191 0.005901 -0.430191 +v 0.307888 0.005901 -0.524218 +v 0.162696 0.005901 -0.584584 +v 0.000000 0.005901 -0.605903 +v -0.162696 0.005901 -0.584584 +v -0.307888 0.005901 -0.524218 +v -0.430191 0.005901 -0.430191 +v -0.524218 0.005902 -0.307888 +v -0.584584 0.005902 -0.162696 +v -0.605903 0.005903 -0.000000 +v -0.584584 0.005904 0.162696 +v -0.524218 0.005904 0.307888 +v -0.430191 0.005905 0.430191 +v -0.307888 0.005905 0.524218 +v -0.162696 0.005905 0.584584 +v 0.000000 0.005905 0.605903 +v 0.162696 0.005905 0.584584 +v 0.307888 0.005905 0.524218 +v 0.430191 0.005905 0.430191 +v 0.524218 0.005904 0.307888 +v 0.584584 0.005904 0.162696 +v 1.400000 2.400000 -0.000008 +v 1.350740 2.400000 0.375917 +v 1.332760 2.454690 0.370913 +v 1.381370 2.454690 -0.000009 +v 1.384260 2.487500 -0.000009 +v 1.335550 2.487500 0.371690 +v 1.403120 2.498440 -0.000009 +v 1.353760 2.498440 0.376756 +v 1.382010 2.487500 0.384619 +v 1.432410 2.487500 -0.000009 +v 1.414950 2.454690 0.393787 +v 1.466550 2.454690 -0.000009 +v 1.447220 2.400000 0.402769 +v 1.500000 2.400000 -0.000008 +v 1.211260 2.400000 0.711398 +v 1.195140 2.454690 0.701929 +v 1.197640 2.487500 0.703400 +v 1.213960 2.498440 0.712986 +v 1.239300 2.487500 0.727866 +v 1.268840 2.454690 0.745216 +v 1.297780 2.400000 0.762213 +v 0.994000 2.400000 0.993991 +v 0.980770 2.454690 0.980761 +v 0.982824 2.487500 0.982815 +v 0.996219 2.498440 0.996210 +v 1.017010 2.487500 1.017000 +v 1.041250 2.454690 1.041240 +v 1.065000 2.400000 1.064990 +v 0.711407 2.400000 1.211250 +v 0.701938 2.454690 1.195130 +v 0.703409 2.487500 1.197630 +v 0.712995 2.498440 1.213950 +v 0.727875 2.487500 1.239290 +v 0.745225 2.454690 1.268830 +v 0.762222 2.400000 1.297770 +v 0.375926 2.400010 1.350730 +v 0.370922 2.454690 1.332750 +v 0.371699 2.487500 1.335540 +v 0.376765 2.498450 1.353750 +v 0.384628 2.487500 1.382000 +v 0.393796 2.454700 1.414940 +v 0.402778 2.400010 1.447210 +v 0.000000 2.400010 1.399990 +v 0.000000 2.454690 1.381360 +v 0.000000 2.487500 1.384250 +v 0.000000 2.498450 1.403110 +v 0.000000 2.487510 1.432400 +v 0.000000 2.454700 1.466540 +v 0.000000 2.400010 1.499990 +v -0.375926 2.400010 1.350730 +v -0.370922 2.454690 1.332750 +v -0.371699 2.487500 1.335540 +v -0.376765 2.498450 1.353750 +v -0.384628 2.487500 1.382000 +v -0.393796 2.454700 1.414940 +v -0.402778 2.400010 1.447210 +v -0.711407 2.400000 1.211250 +v -0.701938 2.454690 1.195130 +v -0.703409 2.487500 1.197630 +v -0.712995 2.498440 1.213950 +v -0.727875 2.487500 1.239290 +v -0.745225 2.454690 1.268830 +v -0.762222 2.400000 1.297770 +v -0.994000 2.400000 0.993991 +v -0.980770 2.454690 0.980761 +v -0.982824 2.487500 0.982815 +v -0.996219 2.498440 0.996210 +v -1.017010 2.487500 1.017000 +v -1.041250 2.454690 1.041240 +v -1.065000 2.400000 1.064990 +v -1.211260 2.400000 0.711398 +v -1.195140 2.454690 0.701929 +v -1.197640 2.487500 0.703400 +v -1.213960 2.498440 0.712986 +v -1.239300 2.487500 0.727866 +v -1.268840 2.454690 0.745216 +v -1.297780 2.400000 0.762213 +v -1.350740 2.400000 0.375917 +v -1.332760 2.454690 0.370913 +v -1.335550 2.487500 0.371690 +v -1.353760 2.498440 0.376756 +v -1.382010 2.487500 0.384619 +v -1.414950 2.454690 0.393787 +v -1.447220 2.400000 0.402769 +v -1.400000 2.400000 -0.000008 +v -1.381370 2.454690 -0.000009 +v -1.384260 2.487500 -0.000009 +v -1.403120 2.498440 -0.000009 +v -1.432410 2.487500 -0.000009 +v -1.466550 2.454690 -0.000009 +v -1.500000 2.400000 -0.000008 +v -1.350740 2.400000 -0.375935 +v -1.332760 2.454690 -0.370931 +v -1.335550 2.487500 -0.371708 +v -1.353760 2.498440 -0.376774 +v -1.382010 2.487500 -0.384637 +v -1.414950 2.454690 -0.393805 +v -1.447220 2.400000 -0.402787 +v -1.211260 2.400000 -0.711416 +v -1.195140 2.454690 -0.701947 +v -1.197640 2.487500 -0.703418 +v -1.213960 2.498440 -0.713004 +v -1.239300 2.487500 -0.727884 +v -1.268840 2.454690 -0.745234 +v -1.297780 2.400000 -0.762231 +v -0.994000 2.400000 -0.994009 +v -0.980770 2.454690 -0.980779 +v -0.982824 2.487500 -0.982833 +v -0.996219 2.498440 -0.996228 +v -1.017010 2.487500 -1.017020 +v -1.041250 2.454690 -1.041260 +v -1.065000 2.400000 -1.065010 +v -0.711407 2.400000 -1.211270 +v -0.701938 2.454690 -1.195150 +v -0.703409 2.487500 -1.197650 +v -0.712995 2.498440 -1.213970 +v -0.727875 2.487500 -1.239310 +v -0.745225 2.454690 -1.268850 +v -0.762222 2.400000 -1.297790 +v -0.375926 2.400000 -1.350750 +v -0.370922 2.454680 -1.332770 +v -0.371699 2.487490 -1.335560 +v -0.376765 2.498440 -1.353770 +v -0.384628 2.487490 -1.382020 +v -0.393796 2.454680 -1.414960 +v -0.402778 2.399990 -1.447230 +v 0.000000 2.399990 -1.400010 +v 0.000000 2.454680 -1.381380 +v 0.000000 2.487490 -1.384270 +v 0.000000 2.498430 -1.403130 +v 0.000000 2.487490 -1.432420 +v 0.000000 2.454680 -1.466560 +v 0.000000 2.399990 -1.500010 +v 0.375926 2.400000 -1.350750 +v 0.370922 2.454680 -1.332770 +v 0.371699 2.487490 -1.335560 +v 0.376765 2.498440 -1.353770 +v 0.384628 2.487490 -1.382020 +v 0.393796 2.454680 -1.414960 +v 0.402778 2.399990 -1.447230 +v 0.711407 2.400000 -1.211270 +v 0.701938 2.454690 -1.195150 +v 0.703409 2.487500 -1.197650 +v 0.712995 2.498440 -1.213970 +v 0.727875 2.487500 -1.239310 +v 0.745225 2.454690 -1.268850 +v 0.762222 2.400000 -1.297790 +v 0.994000 2.400000 -0.994009 +v 0.980770 2.454690 -0.980779 +v 0.982824 2.487500 -0.982833 +v 0.996219 2.498440 -0.996228 +v 1.017010 2.487500 -1.017020 +v 1.041250 2.454690 -1.041260 +v 1.065000 2.400000 -1.065010 +v 1.211260 2.400000 -0.711416 +v 1.195140 2.454690 -0.701947 +v 1.197640 2.487500 -0.703418 +v 1.213960 2.498440 -0.713004 +v 1.239300 2.487500 -0.727884 +v 1.268840 2.454690 -0.745234 +v 1.297780 2.400000 -0.762231 +v 1.350740 2.400000 -0.375935 +v 1.332760 2.454690 -0.370931 +v 1.335550 2.487500 -0.371708 +v 1.353760 2.498440 -0.376774 +v 1.382010 2.487500 -0.384637 +v 1.414950 2.454690 -0.393805 +v 1.447220 2.400000 -0.402787 +v 1.566710 2.137850 0.436024 +v 1.623840 2.137850 -0.000008 +v 1.679490 1.877780 0.467414 +v 1.740740 1.877780 -0.000007 +v 1.778880 1.621880 0.495075 +v 1.843750 1.621870 -0.000006 +v 1.858160 1.372220 0.517142 +v 1.925930 1.372220 -0.000005 +v 1.910650 1.130900 0.531750 +v 1.980320 1.130900 -0.000004 +v 1.929630 0.900002 0.537034 +v 2.000000 0.900000 -0.000003 +v 1.404920 2.137850 0.825145 +v 1.506060 1.877780 0.884547 +v 1.595190 1.621880 0.936892 +v 1.666280 1.372220 0.978651 +v 1.713350 1.130900 1.006300 +v 1.730370 0.900004 1.016300 +v 1.152930 2.137850 1.152920 +v 1.235930 1.877780 1.235920 +v 1.309060 1.621870 1.309050 +v 1.367410 1.372230 1.367400 +v 1.406030 1.130910 1.406030 +v 1.420000 0.900005 1.420000 +v 0.825153 2.137860 1.404910 +v 0.884554 1.877790 1.506050 +v 0.936898 1.621890 1.595180 +v 0.978656 1.372230 1.666270 +v 1.006300 1.130910 1.713350 +v 1.016300 0.900006 1.730370 +v 0.436032 2.137860 1.566700 +v 0.467421 1.877790 1.679480 +v 0.495081 1.621880 1.778870 +v 0.517147 1.372230 1.858150 +v 0.531754 1.130910 1.910650 +v 0.537037 0.900007 1.929630 +v 0.000000 2.137860 1.623830 +v 0.000000 1.877790 1.740730 +v 0.000000 1.621880 1.843740 +v 0.000000 1.372230 1.925920 +v 0.000000 1.130910 1.980320 +v 0.000000 0.900007 2.000000 +v -0.436032 2.137860 1.566700 +v -0.467421 1.877790 1.679480 +v -0.495081 1.621890 1.778870 +v -0.517147 1.372230 1.858150 +v -0.531754 1.130910 1.910650 +v -0.537037 0.900007 1.929630 +v -0.825153 2.137860 1.404910 +v -0.884554 1.877790 1.506050 +v -0.936898 1.621890 1.595180 +v -0.978656 1.372230 1.666270 +v -1.006300 1.130910 1.713350 +v -1.016300 0.900006 1.730370 +v -1.152930 2.137850 1.152920 +v -1.235930 1.877780 1.235920 +v -1.309060 1.621870 1.309050 +v -1.367410 1.372230 1.367400 +v -1.406030 1.130910 1.406030 +v -1.420000 0.900005 1.420000 +v -1.404920 2.137850 0.825145 +v -1.506060 1.877780 0.884547 +v -1.595190 1.621880 0.936892 +v -1.666280 1.372220 0.978651 +v -1.713350 1.130900 1.006300 +v -1.730370 0.900004 1.016300 +v -1.566710 2.137850 0.436024 +v -1.679490 1.877780 0.467414 +v -1.778880 1.621870 0.495075 +v -1.858160 1.372220 0.517142 +v -1.910650 1.130900 0.531750 +v -1.929630 0.900002 0.537034 +v -1.623840 2.137850 -0.000008 +v -1.740740 1.877780 -0.000007 +v -1.843750 1.621870 -0.000006 +v -1.925930 1.372220 -0.000005 +v -1.980320 1.130900 -0.000004 +v -2.000000 0.900000 -0.000003 +v -1.566710 2.137850 -0.436040 +v -1.679490 1.877780 -0.467428 +v -1.778880 1.621880 -0.495087 +v -1.858160 1.372220 -0.517152 +v -1.910650 1.130900 -0.531758 +v -1.929630 0.899998 -0.537040 +v -1.404920 2.137850 -0.825161 +v -1.506060 1.877780 -0.884561 +v -1.595190 1.621880 -0.936904 +v -1.666280 1.372220 -0.978661 +v -1.713350 1.130900 -1.006300 +v -1.730370 0.899996 -1.016300 +v -1.152930 2.137850 -1.152940 +v -1.235930 1.877780 -1.235940 +v -1.309060 1.621870 -1.309070 +v -1.367410 1.372220 -1.367420 +v -1.406030 1.130890 -1.406030 +v -1.420000 0.899995 -1.420000 +v -0.825153 2.137840 -1.404930 +v -0.884554 1.877770 -1.506070 +v -0.936898 1.621870 -1.595200 +v -0.978656 1.372210 -1.666290 +v -1.006300 1.130890 -1.713350 +v -1.016300 0.899994 -1.730370 +v -0.436032 2.137840 -1.566720 +v -0.467421 1.877770 -1.679500 +v -0.495081 1.621860 -1.778890 +v -0.517147 1.372210 -1.858170 +v -0.531754 1.130890 -1.910650 +v -0.537037 0.899993 -1.929630 +v 0.000000 2.137840 -1.623850 +v 0.000000 1.877770 -1.740750 +v 0.000000 1.621860 -1.843760 +v 0.000000 1.372210 -1.925940 +v 0.000000 1.130890 -1.980320 +v 0.000000 0.899993 -2.000000 +v 0.436032 2.137840 -1.566720 +v 0.467421 1.877770 -1.679500 +v 0.495081 1.621870 -1.778890 +v 0.517147 1.372210 -1.858170 +v 0.531754 1.130890 -1.910650 +v 0.537037 0.899993 -1.929630 +v 0.825153 2.137840 -1.404930 +v 0.884554 1.877770 -1.506070 +v 0.936898 1.621870 -1.595200 +v 0.978656 1.372210 -1.666290 +v 1.006300 1.130890 -1.713350 +v 1.016300 0.899994 -1.730370 +v 1.152930 2.137850 -1.152940 +v 1.235930 1.877780 -1.235940 +v 1.309060 1.621870 -1.309070 +v 1.367410 1.372220 -1.367420 +v 1.406030 1.130890 -1.406030 +v 1.420000 0.899995 -1.420000 +v 1.404920 2.137850 -0.825161 +v 1.506060 1.877780 -0.884561 +v 1.595190 1.621880 -0.936904 +v 1.666280 1.372220 -0.978661 +v 1.713350 1.130900 -1.006300 +v 1.730370 0.899996 -1.016300 +v 1.566710 2.137850 -0.436040 +v 1.679490 1.877780 -0.467428 +v 1.778880 1.621870 -0.495087 +v 1.858160 1.372220 -0.517152 +v 1.910650 1.130900 -0.531758 +v 1.929630 0.899998 -0.537040 +v 1.893900 0.693405 0.527089 +v 1.962960 0.693403 -0.000002 +v 1.804560 0.522224 0.502227 +v 1.870370 0.522222 -0.000002 +v 1.688430 0.384377 0.469906 +v 1.750000 0.384375 -0.000001 +v 1.572290 0.277780 0.437585 +v 1.629630 0.277778 -0.000001 +v 1.482960 0.200349 0.412722 +v 1.537040 0.200347 -0.000001 +v 1.447220 0.150001 0.402777 +v 1.500000 0.150000 -0.000001 +v 1.698330 0.693407 0.997473 +v 1.618220 0.522225 0.950423 +v 1.514070 0.384378 0.889258 +v 1.409930 0.277781 0.828092 +v 1.329820 0.200350 0.781042 +v 1.297780 0.150003 0.762221 +v 1.393700 0.693408 1.393700 +v 1.327960 0.522227 1.327960 +v 1.242500 0.384380 1.242500 +v 1.157040 0.277782 1.157040 +v 1.091300 0.200351 1.091300 +v 1.065000 0.150004 1.065000 +v 0.997476 0.693409 1.698330 +v 0.950425 0.522228 1.618220 +v 0.889259 0.384381 1.514070 +v 0.828093 0.277783 1.409930 +v 0.781043 0.200352 1.329820 +v 0.762222 0.150005 1.297780 +v 0.527092 0.693410 1.893900 +v 0.502229 0.522229 1.804560 +v 0.469907 0.384381 1.688430 +v 0.437586 0.277784 1.572290 +v 0.412723 0.200352 1.482960 +v 0.402778 0.150005 1.447220 +v 0.000000 0.693410 1.962960 +v 0.000000 0.522229 1.870370 +v 0.000000 0.384381 1.750000 +v 0.000000 0.277784 1.629630 +v 0.000000 0.200353 1.537040 +v 0.000000 0.150006 1.500000 +v -0.527092 0.693410 1.893900 +v -0.502229 0.522229 1.804560 +v -0.469907 0.384381 1.688430 +v -0.437586 0.277784 1.572290 +v -0.412723 0.200352 1.482960 +v -0.402778 0.150005 1.447220 +v -0.997476 0.693409 1.698330 +v -0.950425 0.522228 1.618220 +v -0.889259 0.384381 1.514070 +v -0.828093 0.277783 1.409930 +v -0.781043 0.200352 1.329820 +v -0.762222 0.150005 1.297780 +v -1.393700 0.693408 1.393700 +v -1.327960 0.522227 1.327960 +v -1.242500 0.384380 1.242500 +v -1.157040 0.277782 1.157040 +v -1.091300 0.200351 1.091300 +v -1.065000 0.150004 1.065000 +v -1.698330 0.693407 0.997473 +v -1.618220 0.522225 0.950423 +v -1.514070 0.384378 0.889258 +v -1.409930 0.277781 0.828092 +v -1.329820 0.200350 0.781042 +v -1.297780 0.150003 0.762221 +v -1.893900 0.693405 0.527089 +v -1.804560 0.522224 0.502227 +v -1.688430 0.384377 0.469906 +v -1.572290 0.277780 0.437585 +v -1.482960 0.200349 0.412722 +v -1.447220 0.150001 0.402777 +v -1.962960 0.693403 -0.000002 +v -1.870370 0.522222 -0.000002 +v -1.750000 0.384375 -0.000001 +v -1.629630 0.277778 -0.000001 +v -1.537040 0.200347 -0.000001 +v -1.500000 0.150000 -0.000001 +v -1.893900 0.693401 -0.527095 +v -1.804560 0.522220 -0.502231 +v -1.688430 0.384373 -0.469908 +v -1.572290 0.277776 -0.437587 +v -1.482960 0.200345 -0.412724 +v -1.447220 0.149999 -0.402779 +v -1.698330 0.693399 -0.997479 +v -1.618220 0.522218 -0.950427 +v -1.514070 0.384372 -0.889260 +v -1.409930 0.277775 -0.828094 +v -1.329820 0.200344 -0.781044 +v -1.297780 0.149997 -0.762223 +v -1.393700 0.693398 -1.393700 +v -1.327960 0.522217 -1.327960 +v -1.242500 0.384370 -1.242500 +v -1.157040 0.277774 -1.157040 +v -1.091300 0.200343 -1.091300 +v -1.065000 0.149996 -1.065000 +v -0.997476 0.693397 -1.698330 +v -0.950425 0.522216 -1.618220 +v -0.889259 0.384369 -1.514070 +v -0.828093 0.277773 -1.409930 +v -0.781043 0.200342 -1.329820 +v -0.762222 0.149995 -1.297780 +v -0.527092 0.693396 -1.893900 +v -0.502229 0.522215 -1.804560 +v -0.469907 0.384369 -1.688430 +v -0.437586 0.277772 -1.572290 +v -0.412723 0.200342 -1.482960 +v -0.402778 0.149995 -1.447220 +v 0.000000 0.693396 -1.962960 +v 0.000000 0.522215 -1.870370 +v 0.000000 0.384369 -1.750000 +v 0.000000 0.277772 -1.629630 +v 0.000000 0.200341 -1.537040 +v 0.000000 0.149994 -1.500000 +v 0.527092 0.693396 -1.893900 +v 0.502229 0.522215 -1.804560 +v 0.469907 0.384369 -1.688430 +v 0.437586 0.277772 -1.572290 +v 0.412723 0.200342 -1.482960 +v 0.402778 0.149995 -1.447220 +v 0.997476 0.693397 -1.698330 +v 0.950425 0.522216 -1.618220 +v 0.889259 0.384369 -1.514070 +v 0.828093 0.277773 -1.409930 +v 0.781043 0.200342 -1.329820 +v 0.762222 0.149995 -1.297780 +v 1.393700 0.693398 -1.393700 +v 1.327960 0.522217 -1.327960 +v 1.242500 0.384370 -1.242500 +v 1.157040 0.277774 -1.157040 +v 1.091300 0.200343 -1.091300 +v 1.065000 0.149996 -1.065000 +v 1.698330 0.693399 -0.997479 +v 1.618220 0.522218 -0.950427 +v 1.514070 0.384372 -0.889260 +v 1.409930 0.277775 -0.828094 +v 1.329820 0.200344 -0.781044 +v 1.297780 0.149997 -0.762223 +v 1.893900 0.693401 -0.527095 +v 1.804560 0.522220 -0.502231 +v 1.688430 0.384373 -0.469908 +v 1.572290 0.277776 -0.437587 +v 1.482960 0.200345 -0.412724 +v 1.447220 0.149999 -0.402779 +v 1.022220 0.022222 -0.000000 +v 0.986255 0.022221 -0.274486 +v 1.284370 0.046875 -0.000000 +v 1.239180 0.046874 -0.344878 +v 1.427780 0.077778 -0.000000 +v 1.377540 0.077777 -0.383385 +v 1.487850 0.112847 -0.000000 +v 1.435500 0.112846 -0.399515 +v 0.884412 0.022220 -0.519440 +v 1.111220 0.046873 -0.652653 +v 1.235290 0.077775 -0.725523 +v 1.287260 0.112844 -0.756047 +v 0.725778 0.022219 -0.725778 +v 0.911906 0.046872 -0.911906 +v 1.013720 0.077774 -1.013720 +v 1.056370 0.112843 -1.056370 +v 0.519440 0.022219 -0.884412 +v 0.652653 0.046871 -1.111220 +v 0.725523 0.077774 -1.235290 +v 0.756047 0.112842 -1.287260 +v 0.274486 0.022219 -0.986255 +v 0.344878 0.046871 -1.239180 +v 0.383385 0.077773 -1.377540 +v 0.399515 0.112842 -1.435500 +v 0.000000 0.022218 -1.022220 +v 0.000000 0.046871 -1.284370 +v 0.000000 0.077773 -1.427780 +v 0.000000 0.112842 -1.487850 +v -0.274486 0.022219 -0.986255 +v -0.344878 0.046871 -1.239180 +v -0.383385 0.077773 -1.377540 +v -0.399515 0.112842 -1.435500 +v -0.519440 0.022219 -0.884412 +v -0.652653 0.046871 -1.111220 +v -0.725523 0.077774 -1.235290 +v -0.756047 0.112842 -1.287260 +v -0.725778 0.022219 -0.725778 +v -0.911906 0.046872 -0.911906 +v -1.013720 0.077774 -1.013720 +v -1.056370 0.112843 -1.056370 +v -0.884412 0.022220 -0.519440 +v -1.111220 0.046873 -0.652653 +v -1.235290 0.077775 -0.725523 +v -1.287260 0.112844 -0.756047 +v -0.986255 0.022221 -0.274486 +v -1.239180 0.046874 -0.344878 +v -1.377540 0.077777 -0.383385 +v -1.435500 0.112846 -0.399515 +v -1.022220 0.022222 -0.000000 +v -1.284370 0.046875 -0.000000 +v -1.427780 0.077778 -0.000000 +v -1.487850 0.112847 -0.000000 +v -0.986255 0.022223 0.274486 +v -1.239180 0.046876 0.344878 +v -1.377540 0.077779 0.383385 +v -1.435500 0.112848 0.399515 +v -0.884412 0.022224 0.519440 +v -1.111220 0.046877 0.652653 +v -1.235290 0.077781 0.725523 +v -1.287260 0.112850 0.756047 +v -0.725778 0.022225 0.725778 +v -0.911906 0.046878 0.911906 +v -1.013720 0.077782 1.013720 +v -1.056370 0.112851 1.056370 +v -0.519440 0.022225 0.884412 +v -0.652653 0.046879 1.111220 +v -0.725523 0.077782 1.235290 +v -0.756047 0.112852 1.287260 +v -0.274486 0.022225 0.986255 +v -0.344878 0.046879 1.239180 +v -0.383385 0.077783 1.377540 +v -0.399515 0.112852 1.435500 +v 0.000000 0.022226 1.022220 +v 0.000000 0.046879 1.284370 +v 0.000000 0.077783 1.427780 +v 0.000000 0.112852 1.487850 +v 0.274486 0.022225 0.986255 +v 0.344878 0.046879 1.239180 +v 0.383385 0.077783 1.377540 +v 0.399515 0.112852 1.435500 +v 0.519440 0.022225 0.884412 +v 0.652653 0.046879 1.111220 +v 0.725523 0.077782 1.235290 +v 0.756047 0.112852 1.287260 +v 0.725778 0.022225 0.725778 +v 0.911906 0.046878 0.911906 +v 1.013720 0.077782 1.013720 +v 1.056370 0.112851 1.056370 +v 0.884412 0.022224 0.519440 +v 1.111220 0.046877 0.652653 +v 1.235290 0.077781 0.725523 +v 1.287260 0.112850 0.756047 +v 0.986255 0.022223 0.274486 +v 1.239180 0.046876 0.344878 +v 1.377540 0.077779 0.383385 +v 1.435500 0.112848 0.399515 +v 0.192963 2.700000 0.053694 +v 0.200000 2.700000 -0.000010 +v 0.165279 2.785420 0.046035 +v 0.171296 2.785420 -0.000010 +v 0.173037 2.700000 0.101620 +v 0.148234 2.785420 0.087096 +v 0.142000 2.700000 0.141990 +v 0.121672 2.785420 0.121662 +v 0.101630 2.700000 0.173027 +v 0.087106 2.785420 0.148224 +v 0.053704 2.700000 0.192953 +v 0.046045 2.785420 0.165269 +v 0.000000 2.700000 0.199990 +v 0.000000 2.785420 0.171286 +v -0.053704 2.700000 0.192953 +v -0.046045 2.785420 0.165269 +v -0.101630 2.700000 0.173027 +v -0.087106 2.785420 0.148224 +v -0.142000 2.700000 0.141990 +v -0.121672 2.785420 0.121662 +v -0.173037 2.700000 0.101620 +v -0.148234 2.785420 0.087096 +v -0.192963 2.700000 0.053694 +v -0.165279 2.785420 0.046035 +v -0.200000 2.700000 -0.000010 +v -0.171296 2.785420 -0.000010 +v -0.192963 2.700000 -0.053714 +v -0.165279 2.785420 -0.046055 +v -0.173037 2.700000 -0.101640 +v -0.148234 2.785420 -0.087116 +v -0.142000 2.700000 -0.142010 +v -0.121672 2.785420 -0.121682 +v -0.101630 2.700000 -0.173047 +v -0.087106 2.785420 -0.148244 +v -0.053704 2.700000 -0.192973 +v -0.046045 2.785420 -0.165289 +v 0.000000 2.700000 -0.200010 +v 0.000000 2.785420 -0.171306 +v 0.053704 2.700000 -0.192973 +v 0.046045 2.785420 -0.165289 +v 0.101630 2.700000 -0.173047 +v 0.087106 2.785420 -0.148244 +v 0.142000 2.700000 -0.142010 +v 0.121672 2.785420 -0.121682 +v 0.173037 2.700000 -0.101640 +v 0.148234 2.785420 -0.087116 +v 0.192963 2.700000 -0.053714 +v 0.165279 2.785420 -0.046055 +v 0.338579 2.636110 0.094221 +v 0.350926 2.636110 -0.000009 +v 0.553875 2.588890 0.154140 +v 0.574074 2.588890 -0.000009 +v 0.795972 2.550000 0.221519 +v 0.825000 2.550000 -0.000009 +v 1.021990 2.511110 0.284422 +v 1.059260 2.511110 -0.000009 +v 1.189040 2.463890 0.330915 +v 1.232410 2.463890 -0.000009 +v 1.254260 2.400000 0.349065 +v 1.300000 2.400000 -0.000008 +v 0.303616 2.636110 0.178312 +v 0.496680 2.588890 0.291705 +v 0.713778 2.550000 0.419213 +v 0.916455 2.511110 0.538252 +v 1.066260 2.463890 0.626237 +v 1.124740 2.400000 0.660584 +v 0.249157 2.636110 0.249147 +v 0.407593 2.588890 0.407583 +v 0.585750 2.550000 0.585741 +v 0.752074 2.511110 0.752065 +v 0.875009 2.463890 0.875000 +v 0.923000 2.400000 0.922991 +v 0.178322 2.636110 0.303606 +v 0.291715 2.588890 0.496670 +v 0.419222 2.550000 0.713769 +v 0.538261 2.511110 0.916446 +v 0.626246 2.463890 1.066250 +v 0.660593 2.400000 1.124730 +v 0.094230 2.636110 0.338569 +v 0.154150 2.588890 0.553865 +v 0.221528 2.550000 0.795963 +v 0.284431 2.511110 1.021980 +v 0.330924 2.463890 1.189030 +v 0.349074 2.400000 1.254250 +v 0.000000 2.636110 0.350916 +v 0.000000 2.588890 0.574064 +v 0.000000 2.550000 0.824991 +v 0.000000 2.511110 1.059250 +v 0.000000 2.463890 1.232400 +v 0.000000 2.400000 1.299990 +v -0.094230 2.636110 0.338569 +v -0.154150 2.588890 0.553865 +v -0.221528 2.550000 0.795963 +v -0.284431 2.511110 1.021980 +v -0.330924 2.463890 1.189030 +v -0.349074 2.400000 1.254250 +v -0.178322 2.636110 0.303606 +v -0.291715 2.588890 0.496670 +v -0.419222 2.550000 0.713769 +v -0.538261 2.511110 0.916446 +v -0.626246 2.463890 1.066250 +v -0.660593 2.400000 1.124730 +v -0.249157 2.636110 0.249147 +v -0.407593 2.588890 0.407583 +v -0.585750 2.550000 0.585741 +v -0.752074 2.511110 0.752065 +v -0.875009 2.463890 0.875000 +v -0.923000 2.400000 0.922991 +v -0.303616 2.636110 0.178312 +v -0.496680 2.588890 0.291705 +v -0.713778 2.550000 0.419213 +v -0.916455 2.511110 0.538252 +v -1.066260 2.463890 0.626237 +v -1.124740 2.400000 0.660584 +v -0.338579 2.636110 0.094221 +v -0.553875 2.588890 0.154140 +v -0.795972 2.550000 0.221519 +v -1.021990 2.511110 0.284422 +v -1.189040 2.463890 0.330915 +v -1.254260 2.400000 0.349065 +v -0.350926 2.636110 -0.000009 +v -0.574074 2.588890 -0.000009 +v -0.825000 2.550000 -0.000009 +v -1.059260 2.511110 -0.000009 +v -1.232410 2.463890 -0.000009 +v -1.300000 2.400000 -0.000008 +v -0.338579 2.636110 -0.094239 +v -0.553875 2.588890 -0.154160 +v -0.795972 2.550000 -0.221537 +v -1.021990 2.511110 -0.284440 +v -1.189040 2.463890 -0.330933 +v -1.254260 2.400000 -0.349083 +v -0.303616 2.636110 -0.178332 +v -0.496680 2.588890 -0.291725 +v -0.713778 2.550000 -0.419231 +v -0.916455 2.511110 -0.538270 +v -1.066260 2.463890 -0.626255 +v -1.124740 2.400000 -0.660602 +v -0.249157 2.636110 -0.249167 +v -0.407593 2.588890 -0.407603 +v -0.585750 2.550000 -0.585759 +v -0.752074 2.511110 -0.752083 +v -0.875009 2.463890 -0.875018 +v -0.923000 2.400000 -0.923009 +v -0.178322 2.636110 -0.303626 +v -0.291715 2.588890 -0.496690 +v -0.419222 2.550000 -0.713787 +v -0.538261 2.511110 -0.916464 +v -0.626246 2.463890 -1.066270 +v -0.660593 2.400000 -1.124750 +v -0.094230 2.636110 -0.338589 +v -0.154150 2.588890 -0.553885 +v -0.221528 2.550000 -0.795981 +v -0.284431 2.511110 -1.022000 +v -0.330924 2.463890 -1.189050 +v -0.349074 2.400000 -1.254270 +v 0.000000 2.636110 -0.350936 +v 0.000000 2.588890 -0.574084 +v 0.000000 2.550000 -0.825009 +v 0.000000 2.511110 -1.059270 +v 0.000000 2.463890 -1.232420 +v 0.000000 2.400000 -1.300010 +v 0.094230 2.636110 -0.338589 +v 0.154150 2.588890 -0.553885 +v 0.221528 2.550000 -0.795981 +v 0.284431 2.511110 -1.022000 +v 0.330924 2.463890 -1.189050 +v 0.349074 2.400000 -1.254270 +v 0.178322 2.636110 -0.303626 +v 0.291715 2.588890 -0.496690 +v 0.419222 2.550000 -0.713787 +v 0.538261 2.511110 -0.916464 +v 0.626246 2.463890 -1.066270 +v 0.660593 2.400000 -1.124750 +v 0.249157 2.636110 -0.249167 +v 0.407593 2.588890 -0.407603 +v 0.585750 2.550000 -0.585759 +v 0.752074 2.511110 -0.752083 +v 0.875009 2.463890 -0.875018 +v 0.923000 2.400000 -0.923009 +v 0.303616 2.636110 -0.178332 +v 0.496680 2.588890 -0.291725 +v 0.713778 2.550000 -0.419231 +v 0.916455 2.511110 -0.538270 +v 1.066260 2.463890 -0.626255 +v 1.124740 2.400000 -0.660602 +v 0.338579 2.636110 -0.094239 +v 0.553875 2.588890 -0.154160 +v 0.795972 2.550000 -0.221537 +v 1.021990 2.511110 -0.284440 +v 1.189040 2.463890 -0.330933 +v 1.254260 2.400000 -0.349083 +v -1.924540 2.023960 -0.000007 +v -1.600000 2.025000 -0.000007 +v -1.927040 2.040550 0.124992 +v -1.592590 2.041670 0.124992 +v -2.196300 2.016670 -0.000007 +v -2.206450 2.032720 0.124992 +v -2.428240 2.011460 0.124993 +v -2.412500 1.996870 -0.000007 +v -2.589850 1.970060 0.124993 +v -2.570370 1.958330 -0.000007 +v -2.688700 1.901810 0.124993 +v -2.667130 1.894790 -0.000007 +v -2.722220 1.800000 0.124993 +v -2.700000 1.800000 -0.000006 +v -1.933300 2.082020 0.199992 +v -1.574070 2.083330 0.199992 +v -2.231820 2.072840 0.199992 +v -2.467590 2.047920 0.199992 +v -2.638550 1.999380 0.199993 +v -2.742630 1.919370 0.199993 +v -2.777780 1.800000 0.199993 +v -1.941440 2.135940 0.224992 +v -1.550000 2.137500 0.224992 +v -2.264810 2.125000 0.224992 +v -2.518750 2.095310 0.224992 +v -2.701850 2.037500 0.224992 +v -2.812730 1.942190 0.224993 +v -2.850000 1.800000 0.224993 +v -1.949570 2.189850 0.199992 +v -1.525930 2.191670 0.199992 +v -2.297810 2.177160 0.199992 +v -2.569910 2.142710 0.199992 +v -2.765160 2.075620 0.199992 +v -2.882840 1.965010 0.199993 +v -2.922220 1.800000 0.199993 +v -1.955830 2.231330 0.124992 +v -1.507410 2.233330 0.124992 +v -2.323180 2.217280 0.124992 +v -2.609260 2.179170 0.124992 +v -2.813850 2.104940 0.124992 +v -2.936760 1.982560 0.124993 +v -2.977780 1.800000 0.124993 +v -1.958330 2.247920 -0.000008 +v -1.500000 2.250000 -0.000008 +v -2.333330 2.233330 -0.000008 +v -2.625000 2.193750 -0.000008 +v -2.833330 2.116670 -0.000007 +v -2.958330 1.989580 -0.000007 +v -3.000000 1.800000 -0.000006 +v -1.507410 2.233330 -0.125008 +v -1.955830 2.231330 -0.125008 +v -2.323180 2.217280 -0.125008 +v -2.609260 2.179170 -0.125008 +v -2.813850 2.104940 -0.125008 +v -2.936760 1.982560 -0.125007 +v -2.977780 1.800000 -0.125007 +v -1.525930 2.191670 -0.200008 +v -1.949570 2.189850 -0.200008 +v -2.297810 2.177160 -0.200008 +v -2.569910 2.142710 -0.200008 +v -2.765160 2.075620 -0.200008 +v -2.882840 1.965010 -0.200007 +v -2.922220 1.800000 -0.200007 +v -1.550000 2.137500 -0.225008 +v -1.941440 2.135940 -0.225008 +v -2.264810 2.125000 -0.225008 +v -2.518750 2.095310 -0.225008 +v -2.701850 2.037500 -0.225008 +v -2.812730 1.942190 -0.225007 +v -2.850000 1.800000 -0.225007 +v -1.574070 2.083330 -0.200008 +v -1.933300 2.082020 -0.200008 +v -2.231820 2.072840 -0.200008 +v -2.467590 2.047920 -0.200008 +v -2.638550 1.999380 -0.200007 +v -2.742630 1.919370 -0.200007 +v -2.777780 1.800000 -0.200007 +v -1.592590 2.041670 -0.125008 +v -1.927040 2.040550 -0.125008 +v -2.206450 2.032720 -0.125008 +v -2.428240 2.011460 -0.125007 +v -2.589850 1.970060 -0.125007 +v -2.688700 1.901810 -0.125007 +v -2.722220 1.800000 -0.125007 +v -2.704180 1.663980 0.124994 +v -2.682870 1.670830 -0.000006 +v -2.648290 1.505350 0.124994 +v -2.629630 1.516670 -0.000005 +v -2.551850 1.335760 0.124995 +v -2.537500 1.350000 -0.000005 +v -2.412210 1.166870 0.124996 +v -2.403700 1.183330 -0.000004 +v -2.226680 1.010330 0.124996 +v -2.225460 1.029170 -0.000004 +v -1.992590 0.877778 0.124997 +v -2.000000 0.900000 -0.000003 +v -2.757470 1.646840 0.199994 +v -2.694920 1.477060 0.199995 +v -2.587730 1.300170 0.199995 +v -2.433470 1.125720 0.199996 +v -2.229720 0.963228 0.199996 +v -1.974070 0.822223 0.199997 +v -2.826740 1.624570 0.224994 +v -2.755560 1.440280 0.224995 +v -2.634370 1.253910 0.224995 +v -2.461110 1.072220 0.224996 +v -2.233680 0.901998 0.224997 +v -1.950000 0.750001 0.224997 +v -2.896000 1.602290 0.199994 +v -2.816190 1.403500 0.199995 +v -2.681020 1.207640 0.199996 +v -2.488750 1.018720 0.199996 +v -2.237640 0.840767 0.199997 +v -1.925930 0.677779 0.199997 +v -2.949290 1.585150 0.124994 +v -2.862830 1.375210 0.124995 +v -2.716900 1.172050 0.124996 +v -2.510010 0.977573 0.124996 +v -2.240680 0.793666 0.124997 +v -1.907410 0.622222 0.124998 +v -2.970600 1.578300 -0.000006 +v -2.881480 1.363890 -0.000005 +v -2.731250 1.157810 -0.000004 +v -2.518520 0.961111 -0.000003 +v -2.241900 0.774826 -0.000003 +v -1.900000 0.600000 -0.000002 +v -2.949290 1.585150 -0.125006 +v -2.862830 1.375210 -0.125005 +v -2.716900 1.172050 -0.125004 +v -2.510010 0.977572 -0.125004 +v -2.240680 0.793666 -0.125003 +v -1.907410 0.622222 -0.125002 +v -2.896000 1.602290 -0.200006 +v -2.816190 1.403500 -0.200005 +v -2.681020 1.207640 -0.200004 +v -2.488750 1.018720 -0.200004 +v -2.237640 0.840765 -0.200003 +v -1.925930 0.677777 -0.200003 +v -2.826740 1.624570 -0.225006 +v -2.755560 1.440280 -0.225005 +v -2.634370 1.253910 -0.225005 +v -2.461110 1.072220 -0.225004 +v -2.233680 0.901996 -0.225003 +v -1.950000 0.749999 -0.225003 +v -2.757470 1.646840 -0.200006 +v -2.694920 1.477060 -0.200005 +v -2.587730 1.300170 -0.200005 +v -2.433470 1.125720 -0.200004 +v -2.229720 0.963226 -0.200004 +v -1.974070 0.822221 -0.200003 +v -2.704180 1.663980 -0.125006 +v -2.648290 1.505350 -0.125006 +v -2.551850 1.335760 -0.125005 +v -2.412210 1.166870 -0.125004 +v -2.226680 1.010330 -0.125004 +v -1.992590 0.877778 -0.125003 +v 1.700000 1.425000 -0.000005 +v 1.700000 1.363890 0.274995 +v 2.072380 1.425210 0.262341 +v 2.058800 1.476390 -0.000005 +v 2.290120 1.572020 0.230704 +v 2.270370 1.611110 -0.000006 +v 2.409720 1.773610 0.189576 +v 2.387500 1.800000 -0.000006 +v 2.487650 1.999280 0.148450 +v 2.462960 2.013890 -0.000007 +v 2.580400 2.218310 0.116813 +v 2.549540 2.223610 -0.000008 +v 2.700000 2.400000 -0.000008 +v 2.744440 2.400000 0.104158 +v 1.700000 1.211110 0.439996 +v 2.106330 1.297250 0.419748 +v 2.339510 1.474280 0.369131 +v 2.465280 1.707640 0.303327 +v 2.549380 1.962760 0.237524 +v 2.657560 2.205070 0.186906 +v 2.855560 2.400000 0.166658 +v 1.700000 1.012500 0.494996 +v 2.150460 1.130900 0.472218 +v 2.403700 1.347220 0.415273 +v 2.537500 1.621870 0.341244 +v 2.629630 1.915280 0.267215 +v 2.757870 2.187850 0.210270 +v 3.000000 2.400000 0.187491 +v 1.700000 0.813891 0.439997 +v 2.194600 0.964560 0.419749 +v 2.467900 1.220160 0.369132 +v 2.609720 1.536110 0.303327 +v 2.709880 1.867800 0.237524 +v 2.858180 2.170630 0.186906 +v 3.144440 2.400000 0.166658 +v 1.700000 0.661112 0.274998 +v 2.228550 0.836601 0.262343 +v 2.517280 1.122430 0.230706 +v 2.665280 1.470140 0.189578 +v 2.771600 1.831280 0.148450 +v 2.935340 2.157380 0.116813 +v 3.255560 2.400000 0.104158 +v 1.700000 0.600000 -0.000002 +v 2.242130 0.785417 -0.000003 +v 2.537040 1.083330 -0.000004 +v 2.687500 1.443750 -0.000005 +v 2.796300 1.816670 -0.000006 +v 2.966200 2.152080 -0.000008 +v 3.300000 2.400000 -0.000008 +v 1.700000 0.661110 -0.275002 +v 2.228550 0.836599 -0.262349 +v 2.517280 1.122430 -0.230714 +v 2.665280 1.470140 -0.189588 +v 2.771600 1.831280 -0.148464 +v 2.935340 2.157380 -0.116829 +v 3.255560 2.400000 -0.104176 +v 1.700000 0.813887 -0.440003 +v 2.194600 0.964556 -0.419757 +v 2.467900 1.220160 -0.369141 +v 2.609720 1.536110 -0.303339 +v 2.709880 1.867800 -0.237538 +v 2.858180 2.170630 -0.186922 +v 3.144440 2.400000 -0.166676 +v 1.700000 1.012500 -0.495004 +v 2.150460 1.130900 -0.472226 +v 2.403700 1.347220 -0.415283 +v 2.537500 1.621870 -0.341256 +v 2.629630 1.915280 -0.267229 +v 2.757870 2.187850 -0.210286 +v 3.000000 2.400000 -0.187509 +v 1.700000 1.211110 -0.440004 +v 2.106330 1.297250 -0.419758 +v 2.339510 1.474280 -0.369141 +v 2.465280 1.707640 -0.303339 +v 2.549380 1.962760 -0.237538 +v 2.657560 2.205070 -0.186922 +v 2.855560 2.400000 -0.166676 +v 1.700000 1.363890 -0.275005 +v 2.072380 1.425210 -0.262351 +v 2.290120 1.572020 -0.230716 +v 2.409720 1.773610 -0.189590 +v 2.487650 1.999280 -0.148464 +v 2.580400 2.218310 -0.116829 +v 2.744440 2.400000 -0.104176 +v 2.749070 2.431250 -0.000009 +v 2.796410 2.431930 0.101023 +v 2.792590 2.450000 -0.000009 +v 2.839780 2.451230 0.092969 +v 2.825000 2.456250 -0.000009 +v 2.869680 2.457810 0.082022 +v 2.881210 2.451540 0.070207 +v 2.840740 2.450000 -0.000009 +v 2.869490 2.432310 0.059549 +v 2.834260 2.431250 -0.000009 +v 2.829630 2.400000 0.052074 +v 2.800000 2.400000 -0.000008 +v 2.914740 2.433610 0.161565 +v 2.957750 2.454320 0.148139 +v 2.981370 2.461720 0.129158 +v 2.982370 2.455400 0.107398 +v 2.957560 2.434960 0.085639 +v 2.903700 2.400000 0.066658 +v 3.068580 2.435810 0.181675 +v 3.111110 2.458330 0.165963 +v 3.126560 2.466800 0.142960 +v 3.113890 2.460420 0.115269 +v 3.072050 2.438410 0.085495 +v 3.000000 2.400000 0.056241 +v 3.222410 2.438000 0.161411 +v 3.264470 2.462350 0.146905 +v 3.271760 2.471870 0.124991 +v 3.245400 2.465430 0.097522 +v 3.186540 2.441860 0.066349 +v 3.096300 2.400000 0.033324 +v 3.340750 2.439690 0.100830 +v 3.382440 2.465430 0.091426 +v 3.383450 2.475780 0.076814 +v 3.346570 2.469290 0.057861 +v 3.274610 2.444510 0.035437 +v 3.170370 2.400000 0.010408 +v 3.388080 2.440360 -0.000009 +v 3.429630 2.466670 -0.000009 +v 3.428130 2.477340 -0.000009 +v 3.387040 2.470830 -0.000009 +v 3.309840 2.445570 -0.000009 +v 3.200000 2.400000 -0.000008 +v 3.340750 2.439690 -0.101089 +v 3.382440 2.465430 -0.093373 +v 3.383450 2.475780 -0.083342 +v 3.346570 2.469290 -0.073312 +v 3.274610 2.444510 -0.065595 +v 3.170370 2.400000 -0.062509 +v 3.222410 2.438000 -0.161737 +v 3.264470 2.462350 -0.149392 +v 3.271760 2.471870 -0.133342 +v 3.245400 2.465430 -0.117293 +v 3.186540 2.441860 -0.104947 +v 3.096300 2.400000 -0.100009 +v 3.068580 2.435810 -0.181953 +v 3.111110 2.458330 -0.168065 +v 3.126560 2.466800 -0.150009 +v 3.113890 2.460420 -0.131953 +v 3.072050 2.438410 -0.118065 +v 3.000000 2.400000 -0.112509 +v 2.914740 2.433610 -0.161737 +v 2.957750 2.454320 -0.149392 +v 2.981370 2.461720 -0.133342 +v 2.982370 2.455400 -0.117293 +v 2.957560 2.434960 -0.104947 +v 2.903700 2.400000 -0.100009 +v 2.796410 2.431930 -0.101089 +v 2.839780 2.451230 -0.093373 +v 2.869680 2.457810 -0.083342 +v 2.881210 2.451540 -0.073312 +v 2.869490 2.432310 -0.065595 +v 2.829630 2.400000 -0.062509 +v 0.278704 3.127080 -0.000011 +v 0.000000 3.150000 -0.000011 +v 0.268946 3.127080 0.075067 +v 0.241285 3.127080 0.141920 +v 0.198140 3.127080 0.198129 +v 0.141931 3.127080 0.241274 +v 0.075078 3.127080 0.268935 +v 0.000000 3.127080 0.278693 +v -0.075078 3.127080 0.268935 +v -0.141931 3.127080 0.241274 +v -0.198140 3.127080 0.198129 +v -0.241285 3.127080 0.141920 +v -0.268946 3.127080 0.075067 +v -0.278704 3.127080 -0.000011 +v -0.268946 3.127080 -0.075089 +v -0.241285 3.127080 -0.141942 +v -0.198140 3.127080 -0.198151 +v -0.141931 3.127080 -0.241296 +v -0.075078 3.127080 -0.268957 +v 0.000000 3.127080 -0.278715 +v 0.075078 3.127080 -0.268957 +v 0.141931 3.127080 -0.241296 +v 0.198140 3.127080 -0.198151 +v 0.241285 3.127080 -0.141942 +v 0.268946 3.127080 -0.075089 +v 0.350254 3.066670 0.097760 +v 0.362963 3.066670 -0.000011 +v 0.313617 2.981250 0.087518 +v 0.325000 2.981250 -0.000011 +v 0.228728 2.883330 0.063793 +v 0.237037 2.883330 -0.000010 +v 0.165279 2.785420 0.046035 +v 0.171296 2.785420 -0.000010 +v 0.314228 3.066670 0.184824 +v 0.281352 2.981250 0.165470 +v 0.205180 2.883330 0.120636 +v 0.148234 2.785420 0.087096 +v 0.258037 3.066670 0.258027 +v 0.231031 2.981250 0.231020 +v 0.168463 2.883330 0.168452 +v 0.121672 2.785420 0.121662 +v 0.184834 3.066670 0.314218 +v 0.165481 2.981250 0.281341 +v 0.120647 2.883330 0.205169 +v 0.087106 2.785420 0.148224 +v 0.097771 3.066670 0.350244 +v 0.087529 2.981250 0.313606 +v 0.063803 2.883330 0.228717 +v 0.046045 2.785420 0.165269 +v 0.000000 3.066670 0.362953 +v 0.000000 2.981250 0.324989 +v 0.000000 2.883330 0.237026 +v 0.000000 2.785420 0.171286 +v -0.097771 3.066670 0.350244 +v -0.087529 2.981250 0.313606 +v -0.063803 2.883330 0.228717 +v -0.046045 2.785420 0.165269 +v -0.184834 3.066670 0.314218 +v -0.165481 2.981250 0.281341 +v -0.120647 2.883330 0.205169 +v -0.087106 2.785420 0.148224 +v -0.258037 3.066670 0.258027 +v -0.231031 2.981250 0.231020 +v -0.168463 2.883330 0.168452 +v -0.121672 2.785420 0.121662 +v -0.314228 3.066670 0.184824 +v -0.281352 2.981250 0.165470 +v -0.205180 2.883330 0.120636 +v -0.148234 2.785420 0.087096 +v -0.350254 3.066670 0.097760 +v -0.313617 2.981250 0.087518 +v -0.228728 2.883330 0.063793 +v -0.165279 2.785420 0.046035 +v -0.362963 3.066670 -0.000011 +v -0.325000 2.981250 -0.000011 +v -0.237037 2.883330 -0.000010 +v -0.171296 2.785420 -0.000010 +v -0.350254 3.066670 -0.097782 +v -0.313617 2.981250 -0.087540 +v -0.228728 2.883330 -0.063813 +v -0.165279 2.785420 -0.046055 +v -0.314228 3.066670 -0.184844 +v -0.281352 2.981250 -0.165492 +v -0.205180 2.883330 -0.120658 +v -0.148234 2.785420 -0.087116 +v -0.258037 3.066670 -0.258047 +v -0.231031 2.981250 -0.231042 +v -0.168463 2.883330 -0.168474 +v -0.121672 2.785420 -0.121682 +v -0.184834 3.066670 -0.314238 +v -0.165481 2.981250 -0.281363 +v -0.120647 2.883330 -0.205191 +v -0.087106 2.785420 -0.148244 +v -0.097771 3.066670 -0.350264 +v -0.087529 2.981250 -0.313628 +v -0.063803 2.883330 -0.228739 +v -0.046045 2.785420 -0.165289 +v 0.000000 3.066670 -0.362973 +v 0.000000 2.981250 -0.325011 +v 0.000000 2.883330 -0.237048 +v 0.000000 2.785420 -0.171306 +v 0.097771 3.066670 -0.350264 +v 0.087529 2.981250 -0.313628 +v 0.063803 2.883330 -0.228739 +v 0.046045 2.785420 -0.165289 +v 0.184834 3.066670 -0.314238 +v 0.165481 2.981250 -0.281363 +v 0.120647 2.883330 -0.205191 +v 0.087106 2.785420 -0.148244 +v 0.258037 3.066670 -0.258047 +v 0.231031 2.981250 -0.231042 +v 0.168463 2.883330 -0.168474 +v 0.121672 2.785420 -0.121682 +v 0.314228 3.066670 -0.184844 +v 0.281352 2.981250 -0.165492 +v 0.205180 2.883330 -0.120658 +v 0.148234 2.785420 -0.087116 +v 0.350254 3.066670 -0.097782 +v 0.313617 2.981250 -0.087540 +v 0.228728 2.883330 -0.063813 +v 0.165279 2.785420 -0.046055 +vn 0.025666 -0.999664 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.024781 -0.999664 -0.006623 +vn 0.022156 -0.999664 -0.012787 +vn 0.018067 -0.999664 -0.018067 +vn 0.012787 -0.999664 -0.022126 +vn 0.006623 -0.999664 -0.024751 +vn 0.000000 -0.999664 -0.025666 +vn -0.006623 -0.999664 -0.024751 +vn -0.012787 -0.999664 -0.022126 +vn -0.018067 -0.999664 -0.018067 +vn -0.022156 -0.999664 -0.012787 +vn -0.024781 -0.999664 -0.006623 +vn -0.025666 -0.999664 0.000000 +vn -0.024781 -0.999664 0.006623 +vn -0.022156 -0.999664 0.012787 +vn -0.018067 -0.999664 0.018067 +vn -0.012787 -0.999664 0.022156 +vn -0.006623 -0.999664 0.024781 +vn 0.000000 -0.999664 0.025666 +vn 0.006623 -0.999664 0.024781 +vn 0.012787 -0.999664 0.022156 +vn 0.018067 -0.999664 0.018067 +vn 0.022156 -0.999664 0.012787 +vn 0.024781 -0.999664 0.006623 +vn -0.946562 -0.322459 0.000000 +vn -0.913999 -0.322947 -0.245491 +vn -0.958617 -0.122227 -0.257057 +vn -0.992523 -0.122013 0.000000 +vn -0.832057 0.554674 0.000000 +vn -0.803217 0.555376 -0.215308 +vn -0.048616 0.998810 0.000000 +vn -0.046205 0.998840 -0.012726 +vn 0.525376 0.839106 0.140843 +vn 0.544267 0.838893 0.000000 +vn 0.756340 0.621845 0.202918 +vn 0.783471 0.621387 0.000000 +vn 0.850551 0.473769 0.228217 +vn 0.880886 0.473281 0.000000 +vn -0.818842 -0.323435 -0.474166 +vn -0.859004 -0.122410 -0.497085 +vn -0.719657 0.555559 -0.416425 +vn -0.041749 0.998810 -0.024415 +vn 0.470107 0.839625 0.272011 +vn 0.677236 0.622608 0.391980 +vn 0.761803 0.474471 0.440962 +vn -0.669027 -0.323679 -0.669027 +vn -0.701773 -0.122440 -0.701773 +vn -0.587878 0.555650 -0.587878 +vn -0.034272 0.998810 -0.034272 +vn 0.383831 0.839808 0.383831 +vn 0.553148 0.622913 0.553148 +vn 0.622303 0.474776 0.622303 +vn -0.474166 -0.323435 -0.818842 +vn -0.497085 -0.122410 -0.859004 +vn -0.416425 0.555528 -0.719657 +vn -0.024415 0.998810 -0.041749 +vn 0.272011 0.839625 0.470077 +vn 0.392010 0.622608 0.677236 +vn 0.440962 0.474502 0.761803 +vn -0.245460 -0.322977 -0.913999 +vn -0.257057 -0.122257 -0.958617 +vn -0.215339 0.555193 -0.803308 +vn -0.012726 0.998840 -0.046236 +vn 0.140873 0.839076 0.525437 +vn 0.202918 0.621906 0.756310 +vn 0.228217 0.473769 0.850551 +vn 0.000000 -0.322489 -0.946562 +vn 0.000000 -0.122044 -0.992523 +vn 0.000000 0.554491 -0.832179 +vn 0.000000 0.998779 -0.048799 +vn 0.000000 0.838893 0.544267 +vn 0.000000 0.621387 0.783471 +vn 0.000000 0.473281 0.880886 +vn 0.245460 -0.322977 -0.913999 +vn 0.257057 -0.122257 -0.958617 +vn 0.215339 0.555193 -0.803308 +vn 0.012726 0.998840 -0.046236 +vn -0.140873 0.839076 0.525437 +vn -0.202918 0.621906 0.756310 +vn -0.228217 0.473769 0.850551 +vn 0.474166 -0.323435 -0.818842 +vn 0.497085 -0.122410 -0.859004 +vn 0.416425 0.555528 -0.719657 +vn 0.024415 0.998810 -0.041749 +vn -0.272011 0.839625 0.470077 +vn -0.392010 0.622608 0.677236 +vn -0.440962 0.474502 0.761803 +vn 0.669027 -0.323679 -0.669027 +vn 0.701773 -0.122440 -0.701773 +vn 0.587878 0.555650 -0.587878 +vn 0.034272 0.998810 -0.034272 +vn -0.383831 0.839808 0.383831 +vn -0.553148 0.622913 0.553148 +vn -0.622303 0.474776 0.622303 +vn 0.818842 -0.323435 -0.474166 +vn 0.859004 -0.122410 -0.497085 +vn 0.719657 0.555559 -0.416425 +vn 0.041749 0.998810 -0.024415 +vn -0.470107 0.839625 0.272011 +vn -0.677236 0.622608 0.391980 +vn -0.761803 0.474471 0.440962 +vn 0.913999 -0.322947 -0.245491 +vn 0.958617 -0.122227 -0.257057 +vn 0.803217 0.555376 -0.215308 +vn 0.046205 0.998840 -0.012726 +vn -0.525376 0.839106 0.140843 +vn -0.756340 0.621845 0.202918 +vn -0.850551 0.473769 0.228217 +vn 0.946562 -0.322459 0.000000 +vn 0.992523 -0.122013 0.000000 +vn 0.832057 0.554674 0.000000 +vn 0.048616 0.998810 0.000000 +vn -0.544267 0.838893 0.000000 +vn -0.783471 0.621387 0.000000 +vn -0.880886 0.473281 0.000000 +vn 0.913999 -0.322947 0.245491 +vn 0.958617 -0.122227 0.257057 +vn 0.803217 0.555376 0.215308 +vn 0.046205 0.998840 0.012726 +vn -0.525376 0.839106 -0.140843 +vn -0.756340 0.621845 -0.202918 +vn -0.850551 0.473769 -0.228217 +vn 0.818842 -0.323435 0.474166 +vn 0.859004 -0.122410 0.497085 +vn 0.719657 0.555559 0.416425 +vn 0.041749 0.998810 0.024415 +vn -0.470107 0.839625 -0.272011 +vn -0.677236 0.622608 -0.391980 +vn -0.761803 0.474471 -0.440962 +vn 0.669027 -0.323679 0.669027 +vn 0.701773 -0.122440 0.701773 +vn 0.587878 0.555650 0.587878 +vn 0.034272 0.998810 0.034272 +vn -0.383831 0.839808 -0.383831 +vn -0.553148 0.622913 -0.553148 +vn -0.622303 0.474776 -0.622303 +vn 0.474166 -0.323435 0.818842 +vn 0.497085 -0.122410 0.859004 +vn 0.416425 0.555559 0.719657 +vn 0.024415 0.998810 0.041749 +vn -0.272011 0.839625 -0.470107 +vn -0.391980 0.622608 -0.677236 +vn -0.440962 0.474471 -0.761803 +vn 0.245460 -0.322977 0.913999 +vn 0.257027 -0.122257 0.958617 +vn 0.215369 0.555193 0.803308 +vn 0.012726 0.998840 0.046236 +vn -0.140873 0.839045 -0.525498 +vn -0.202918 0.621845 -0.756371 +vn -0.228187 0.473769 -0.850551 +vn 0.000000 -0.322459 0.946562 +vn 0.000000 -0.122013 0.992523 +vn 0.000000 0.554674 0.832057 +vn 0.000000 0.998810 0.048616 +vn 0.000000 0.838893 -0.544267 +vn 0.000000 0.621387 -0.783471 +vn 0.000000 0.473281 -0.880886 +vn -0.245460 -0.322977 0.913999 +vn -0.257027 -0.122257 0.958617 +vn -0.215369 0.555193 0.803308 +vn -0.012726 0.998840 0.046236 +vn 0.140873 0.839045 -0.525498 +vn 0.202918 0.621845 -0.756371 +vn 0.228187 0.473769 -0.850551 +vn -0.474166 -0.323435 0.818842 +vn -0.497085 -0.122410 0.859004 +vn -0.416425 0.555559 0.719657 +vn -0.024415 0.998810 0.041749 +vn 0.272011 0.839625 -0.470107 +vn 0.391980 0.622608 -0.677236 +vn 0.440962 0.474471 -0.761803 +vn -0.669027 -0.323679 0.669027 +vn -0.701773 -0.122440 0.701773 +vn -0.587878 0.555650 0.587878 +vn -0.034272 0.998810 0.034272 +vn 0.383831 0.839808 -0.383831 +vn 0.553148 0.622913 -0.553148 +vn 0.622303 0.474776 -0.622303 +vn -0.818842 -0.323435 0.474166 +vn -0.859004 -0.122410 0.497085 +vn -0.719657 0.555559 0.416425 +vn -0.041749 0.998810 0.024415 +vn 0.470107 0.839625 -0.272011 +vn 0.677236 0.622608 -0.391980 +vn 0.761803 0.474471 -0.440962 +vn -0.913999 -0.322947 0.245491 +vn -0.958617 -0.122227 0.257057 +vn -0.803217 0.555376 0.215308 +vn -0.046205 0.998840 0.012726 +vn 0.525376 0.839106 -0.140843 +vn 0.756340 0.621845 -0.202918 +vn 0.850551 0.473769 -0.228217 +vn 0.877041 0.418744 0.235298 +vn 0.908292 0.418256 0.000000 +vn 0.888668 0.391644 0.238441 +vn 0.920286 0.391156 0.000000 +vn 0.907315 0.342753 0.243446 +vn 0.939543 0.342357 0.000000 +vn 0.931028 0.265908 0.249855 +vn 0.964080 0.265542 0.000000 +vn 0.954558 0.152104 0.256172 +vn 0.988372 0.151891 0.000000 +vn 0.964782 -0.045717 0.258980 +vn 0.998932 -0.045656 0.000000 +vn 0.785638 0.419416 0.454756 +vn 0.796075 0.392285 0.460799 +vn 0.812830 0.343333 0.470504 +vn 0.834162 0.266366 0.482864 +vn 0.855312 0.152409 0.495132 +vn 0.864498 -0.045808 0.500504 +vn 0.641804 0.419691 0.641804 +vn 0.650349 0.392529 0.650349 +vn 0.664052 0.343577 0.664052 +vn 0.681509 0.266579 0.681509 +vn 0.698813 0.152501 0.698813 +vn 0.706351 -0.045869 0.706351 +vn 0.454756 0.419416 0.785638 +vn 0.460799 0.392285 0.796075 +vn 0.470504 0.343333 0.812830 +vn 0.482864 0.266396 0.834162 +vn 0.495132 0.152409 0.855312 +vn 0.500504 -0.045808 0.864498 +vn 0.235298 0.418744 0.877041 +vn 0.238441 0.391644 0.888668 +vn 0.243446 0.342753 0.907315 +vn 0.249825 0.265908 0.931028 +vn 0.256172 0.152135 0.954558 +vn 0.258980 -0.045717 0.964782 +vn 0.000000 0.418256 0.908292 +vn 0.000000 0.391156 0.920286 +vn 0.000000 0.342357 0.939543 +vn 0.000000 0.265572 0.964080 +vn 0.000000 0.151921 0.988372 +vn 0.000000 -0.045656 0.998932 +vn -0.235298 0.418744 0.877041 +vn -0.238441 0.391644 0.888668 +vn -0.243446 0.342753 0.907315 +vn -0.249825 0.265908 0.931028 +vn -0.256172 0.152135 0.954558 +vn -0.258980 -0.045717 0.964782 +vn -0.454756 0.419416 0.785638 +vn -0.460799 0.392285 0.796075 +vn -0.470504 0.343333 0.812830 +vn -0.482864 0.266396 0.834162 +vn -0.495132 0.152409 0.855312 +vn -0.500504 -0.045808 0.864498 +vn -0.641804 0.419691 0.641804 +vn -0.650349 0.392529 0.650349 +vn -0.664052 0.343577 0.664052 +vn -0.681509 0.266579 0.681509 +vn -0.698813 0.152501 0.698813 +vn -0.706351 -0.045869 0.706351 +vn -0.785638 0.419416 0.454756 +vn -0.796075 0.392285 0.460799 +vn -0.812830 0.343333 0.470504 +vn -0.834162 0.266366 0.482864 +vn -0.855312 0.152409 0.495132 +vn -0.864498 -0.045808 0.500504 +vn -0.877041 0.418744 0.235298 +vn -0.888668 0.391644 0.238441 +vn -0.907315 0.342753 0.243446 +vn -0.931028 0.265908 0.249825 +vn -0.954558 0.152104 0.256172 +vn -0.964782 -0.045717 0.258980 +vn -0.908292 0.418256 0.000000 +vn -0.920286 0.391156 0.000000 +vn -0.939543 0.342357 0.000000 +vn -0.964080 0.265542 0.000000 +vn -0.988372 0.151891 0.000000 +vn -0.998932 -0.045656 0.000000 +vn -0.877041 0.418744 -0.235298 +vn -0.888668 0.391644 -0.238441 +vn -0.907315 0.342753 -0.243446 +vn -0.931028 0.265877 -0.249855 +vn -0.954558 0.152104 -0.256172 +vn -0.964782 -0.045717 -0.258980 +vn -0.785638 0.419416 -0.454756 +vn -0.796075 0.392285 -0.460799 +vn -0.812830 0.343333 -0.470504 +vn -0.834162 0.266366 -0.482864 +vn -0.855312 0.152379 -0.495132 +vn -0.864498 -0.045808 -0.500504 +vn -0.641804 0.419691 -0.641804 +vn -0.650349 0.392529 -0.650349 +vn -0.664052 0.343547 -0.664052 +vn -0.681509 0.266549 -0.681509 +vn -0.698813 0.152470 -0.698813 +vn -0.706351 -0.045869 -0.706351 +vn -0.454756 0.419416 -0.785638 +vn -0.460768 0.392285 -0.796075 +vn -0.470504 0.343333 -0.812830 +vn -0.482864 0.266366 -0.834162 +vn -0.495132 0.152379 -0.855312 +vn -0.500504 -0.045808 -0.864498 +vn -0.235298 0.418744 -0.877041 +vn -0.238441 0.391644 -0.888668 +vn -0.243446 0.342753 -0.907315 +vn -0.249855 0.265877 -0.931059 +vn -0.256172 0.152074 -0.954558 +vn -0.258980 -0.045717 -0.964782 +vn 0.000000 0.418256 -0.908292 +vn 0.000000 0.391156 -0.920286 +vn 0.000000 0.342357 -0.939543 +vn 0.000000 0.265511 -0.964080 +vn 0.000000 0.151891 -0.988372 +vn 0.000000 -0.045656 -0.998932 +vn 0.235298 0.418744 -0.877041 +vn 0.238441 0.391644 -0.888668 +vn 0.243446 0.342753 -0.907315 +vn 0.249855 0.265877 -0.931059 +vn 0.256172 0.152074 -0.954558 +vn 0.258980 -0.045717 -0.964782 +vn 0.454756 0.419416 -0.785638 +vn 0.460768 0.392285 -0.796075 +vn 0.470504 0.343333 -0.812830 +vn 0.482864 0.266366 -0.834162 +vn 0.495132 0.152379 -0.855312 +vn 0.500504 -0.045808 -0.864498 +vn 0.641804 0.419691 -0.641804 +vn 0.650349 0.392529 -0.650349 +vn 0.664052 0.343547 -0.664052 +vn 0.681509 0.266549 -0.681509 +vn 0.698813 0.152470 -0.698813 +vn 0.706351 -0.045869 -0.706351 +vn 0.785638 0.419416 -0.454756 +vn 0.796075 0.392285 -0.460799 +vn 0.812830 0.343333 -0.470504 +vn 0.834162 0.266366 -0.482864 +vn 0.855312 0.152379 -0.495132 +vn 0.864498 -0.045808 -0.500504 +vn 0.877041 0.418744 -0.235298 +vn 0.888668 0.391644 -0.238441 +vn 0.907315 0.342753 -0.243446 +vn 0.931028 0.265908 -0.249825 +vn 0.954558 0.152104 -0.256172 +vn 0.964782 -0.045717 -0.258980 +vn 0.912839 -0.326609 0.245003 +vn 0.945250 -0.326273 0.000000 +vn 0.795892 -0.566485 0.213538 +vn 0.824396 -0.565996 0.000000 +vn 0.687399 -0.702445 0.184393 +vn 0.712180 -0.701987 0.000000 +vn 0.630146 -0.757805 0.169012 +vn 0.652974 -0.757347 0.000000 +vn 0.698752 -0.690329 0.187445 +vn 0.724021 -0.689749 0.000000 +vn 0.855861 -0.463454 0.229530 +vn 0.886380 -0.462905 0.000000 +vn 0.817774 -0.327158 0.473434 +vn 0.712729 -0.567248 0.412549 +vn 0.615375 -0.703146 0.356151 +vn 0.564043 -0.758446 0.326456 +vn 0.625660 -0.690939 0.362102 +vn 0.766625 -0.464125 0.443678 +vn 0.668111 -0.327403 0.668111 +vn 0.582171 -0.567522 0.582171 +vn 0.502579 -0.703421 0.502579 +vn 0.460646 -0.758660 0.460646 +vn 0.510971 -0.691183 0.510971 +vn 0.626209 -0.464370 0.626209 +vn 0.473434 -0.327158 0.817774 +vn 0.412549 -0.567248 0.712729 +vn 0.356151 -0.703146 0.615375 +vn 0.326456 -0.758446 0.564043 +vn 0.362102 -0.690939 0.625660 +vn 0.443678 -0.464125 0.766625 +vn 0.245003 -0.326609 0.912839 +vn 0.213538 -0.566485 0.795892 +vn 0.184393 -0.702445 0.687399 +vn 0.169012 -0.757805 0.630146 +vn 0.187414 -0.690329 0.698752 +vn 0.229530 -0.463454 0.855831 +vn 0.000000 -0.326273 0.945250 +vn 0.000000 -0.565996 0.824396 +vn 0.000000 -0.701987 0.712180 +vn 0.000000 -0.757347 0.652974 +vn 0.000000 -0.689749 0.724021 +vn 0.000000 -0.462905 0.886380 +vn -0.245003 -0.326609 0.912839 +vn -0.213538 -0.566485 0.795892 +vn -0.184393 -0.702445 0.687399 +vn -0.169012 -0.757805 0.630146 +vn -0.187414 -0.690329 0.698752 +vn -0.229530 -0.463454 0.855831 +vn -0.473434 -0.327158 0.817774 +vn -0.412549 -0.567248 0.712729 +vn -0.356151 -0.703146 0.615375 +vn -0.326456 -0.758446 0.564043 +vn -0.362102 -0.690939 0.625660 +vn -0.443678 -0.464125 0.766625 +vn -0.668111 -0.327403 0.668111 +vn -0.582171 -0.567522 0.582171 +vn -0.502579 -0.703421 0.502579 +vn -0.460646 -0.758660 0.460646 +vn -0.510971 -0.691183 0.510971 +vn -0.626209 -0.464370 0.626209 +vn -0.817774 -0.327158 0.473434 +vn -0.712729 -0.567248 0.412549 +vn -0.615375 -0.703146 0.356151 +vn -0.564043 -0.758446 0.326456 +vn -0.625660 -0.690939 0.362102 +vn -0.766625 -0.464125 0.443678 +vn -0.912839 -0.326609 0.245003 +vn -0.795892 -0.566485 0.213538 +vn -0.687399 -0.702445 0.184393 +vn -0.630146 -0.757805 0.169012 +vn -0.698752 -0.690329 0.187445 +vn -0.855861 -0.463454 0.229530 +vn -0.945250 -0.326273 0.000000 +vn -0.824396 -0.565996 0.000000 +vn -0.712180 -0.701987 0.000000 +vn -0.652974 -0.757347 0.000000 +vn -0.724021 -0.689749 0.000000 +vn -0.886380 -0.462905 0.000000 +vn -0.912839 -0.326609 -0.245003 +vn -0.795892 -0.566485 -0.213538 +vn -0.687399 -0.702445 -0.184393 +vn -0.630146 -0.757805 -0.169012 +vn -0.698752 -0.690329 -0.187414 +vn -0.855831 -0.463454 -0.229530 +vn -0.817774 -0.327158 -0.473434 +vn -0.712729 -0.567248 -0.412549 +vn -0.615375 -0.703146 -0.356151 +vn -0.564043 -0.758446 -0.326456 +vn -0.625660 -0.690939 -0.362102 +vn -0.766625 -0.464125 -0.443678 +vn -0.668111 -0.327403 -0.668111 +vn -0.582171 -0.567522 -0.582171 +vn -0.502579 -0.703421 -0.502579 +vn -0.460646 -0.758660 -0.460646 +vn -0.510971 -0.691183 -0.510971 +vn -0.626209 -0.464370 -0.626209 +vn -0.473434 -0.327158 -0.817774 +vn -0.412549 -0.567248 -0.712729 +vn -0.356151 -0.703146 -0.615375 +vn -0.326456 -0.758446 -0.564043 +vn -0.362102 -0.690939 -0.625660 +vn -0.443678 -0.464125 -0.766625 +vn -0.245003 -0.326609 -0.912839 +vn -0.213538 -0.566485 -0.795892 +vn -0.184393 -0.702445 -0.687399 +vn -0.169012 -0.757805 -0.630146 +vn -0.187414 -0.690329 -0.698752 +vn -0.229530 -0.463454 -0.855831 +vn 0.000000 -0.326273 -0.945250 +vn 0.000000 -0.565996 -0.824396 +vn 0.000000 -0.701987 -0.712149 +vn 0.000000 -0.757347 -0.652974 +vn 0.000000 -0.689749 -0.724021 +vn 0.000000 -0.462905 -0.886380 +vn 0.245003 -0.326609 -0.912839 +vn 0.213538 -0.566485 -0.795892 +vn 0.184393 -0.702445 -0.687399 +vn 0.169012 -0.757805 -0.630146 +vn 0.187414 -0.690329 -0.698752 +vn 0.229530 -0.463454 -0.855831 +vn 0.473434 -0.327158 -0.817774 +vn 0.412549 -0.567248 -0.712729 +vn 0.356151 -0.703146 -0.615375 +vn 0.326456 -0.758446 -0.564043 +vn 0.362102 -0.690939 -0.625660 +vn 0.443678 -0.464125 -0.766625 +vn 0.668111 -0.327403 -0.668111 +vn 0.582171 -0.567522 -0.582171 +vn 0.502579 -0.703421 -0.502579 +vn 0.460646 -0.758660 -0.460646 +vn 0.510971 -0.691183 -0.510971 +vn 0.626209 -0.464370 -0.626209 +vn 0.817774 -0.327158 -0.473434 +vn 0.712729 -0.567248 -0.412549 +vn 0.615375 -0.703146 -0.356151 +vn 0.564043 -0.758446 -0.326456 +vn 0.625660 -0.690939 -0.362102 +vn 0.766625 -0.464125 -0.443678 +vn 0.912839 -0.326609 -0.245003 +vn 0.795892 -0.566485 -0.213538 +vn 0.687399 -0.702445 -0.184393 +vn 0.630146 -0.757805 -0.169012 +vn 0.698752 -0.690329 -0.187414 +vn 0.855831 -0.463454 -0.229530 +vn 0.068667 -0.997620 0.000000 +vn 0.066256 -0.997620 -0.017731 +vn 0.157170 -0.987548 0.000000 +vn 0.151677 -0.987579 -0.040620 +vn 0.373150 -0.927763 0.000000 +vn 0.360149 -0.927885 -0.096469 +vn 0.789148 -0.614154 0.000000 +vn 0.762017 -0.614399 -0.204474 +vn 0.059236 -0.997650 -0.034242 +vn 0.135624 -0.987640 -0.078463 +vn 0.322153 -0.928129 -0.186346 +vn 0.682333 -0.615131 -0.394971 +vn 0.048341 -0.997650 -0.048341 +vn 0.110691 -0.987640 -0.110691 +vn 0.262947 -0.928251 -0.262947 +vn 0.557329 -0.615375 -0.557329 +vn 0.034272 -0.997650 -0.059236 +vn 0.078463 -0.987640 -0.135624 +vn 0.186377 -0.928129 -0.322153 +vn 0.394971 -0.615131 -0.682333 +vn 0.017731 -0.997620 -0.066256 +vn 0.040620 -0.987579 -0.151677 +vn 0.096469 -0.927885 -0.360118 +vn 0.204505 -0.614399 -0.762017 +vn 0.000000 -0.997620 -0.068667 +vn 0.000000 -0.987548 -0.157170 +vn 0.000000 -0.927763 -0.373150 +vn 0.000000 -0.614154 -0.789148 +vn -0.017731 -0.997620 -0.066256 +vn -0.040620 -0.987579 -0.151677 +vn -0.096469 -0.927885 -0.360118 +vn -0.204505 -0.614399 -0.762017 +vn -0.034272 -0.997650 -0.059236 +vn -0.078463 -0.987640 -0.135624 +vn -0.186377 -0.928129 -0.322153 +vn -0.394971 -0.615131 -0.682333 +vn -0.048341 -0.997650 -0.048341 +vn -0.110691 -0.987640 -0.110691 +vn -0.262947 -0.928251 -0.262947 +vn -0.557329 -0.615375 -0.557329 +vn -0.059236 -0.997650 -0.034242 +vn -0.135624 -0.987640 -0.078463 +vn -0.322153 -0.928129 -0.186346 +vn -0.682333 -0.615131 -0.394971 +vn -0.066256 -0.997620 -0.017731 +vn -0.151677 -0.987579 -0.040620 +vn -0.360149 -0.927885 -0.096469 +vn -0.762017 -0.614399 -0.204474 +vn -0.068667 -0.997620 0.000000 +vn -0.157170 -0.987548 0.000000 +vn -0.373150 -0.927763 0.000000 +vn -0.789148 -0.614154 0.000000 +vn -0.066256 -0.997620 0.017731 +vn -0.151677 -0.987579 0.040620 +vn -0.360118 -0.927885 0.096469 +vn -0.762017 -0.614399 0.204505 +vn -0.059236 -0.997650 0.034272 +vn -0.135624 -0.987640 0.078463 +vn -0.322153 -0.928129 0.186377 +vn -0.682333 -0.615131 0.394971 +vn -0.048341 -0.997650 0.048341 +vn -0.110691 -0.987640 0.110691 +vn -0.262947 -0.928251 0.262947 +vn -0.557329 -0.615375 0.557329 +vn -0.034272 -0.997650 0.059236 +vn -0.078463 -0.987640 0.135624 +vn -0.186377 -0.928129 0.322153 +vn -0.394971 -0.615131 0.682333 +vn -0.017731 -0.997620 0.066256 +vn -0.040620 -0.987579 0.151677 +vn -0.096469 -0.927885 0.360149 +vn -0.204474 -0.614399 0.762017 +vn 0.000000 -0.997620 0.068667 +vn 0.000000 -0.987548 0.157170 +vn 0.000000 -0.927763 0.373150 +vn 0.000000 -0.614154 0.789148 +vn 0.017731 -0.997620 0.066256 +vn 0.040620 -0.987579 0.151677 +vn 0.096469 -0.927885 0.360149 +vn 0.204474 -0.614399 0.762017 +vn 0.034272 -0.997650 0.059236 +vn 0.078463 -0.987640 0.135624 +vn 0.186377 -0.928129 0.322153 +vn 0.394971 -0.615131 0.682333 +vn 0.048341 -0.997650 0.048341 +vn 0.110691 -0.987640 0.110691 +vn 0.262947 -0.928251 0.262947 +vn 0.557329 -0.615375 0.557329 +vn 0.059236 -0.997650 0.034272 +vn 0.135624 -0.987640 0.078463 +vn 0.322153 -0.928129 0.186377 +vn 0.682333 -0.615101 0.394971 +vn 0.066256 -0.997620 0.017731 +vn 0.151677 -0.987579 0.040620 +vn 0.360118 -0.927885 0.096469 +vn 0.762017 -0.614399 0.204505 +vn 0.692129 0.697470 0.185583 +vn 0.717063 0.696982 0.000000 +vn 0.915586 0.318766 0.245064 +vn 0.947905 0.318522 0.000000 +vn 0.620045 0.697653 0.358837 +vn 0.820429 0.318979 0.474410 +vn 0.506546 0.697684 0.506546 +vn 0.670125 0.319041 0.670125 +vn 0.358837 0.697653 0.620045 +vn 0.474410 0.318979 0.820429 +vn 0.185583 0.697470 0.692129 +vn 0.245064 0.318766 0.915586 +vn 0.000000 0.696982 0.717063 +vn 0.000000 0.318522 0.947905 +vn -0.185583 0.697470 0.692129 +vn -0.245064 0.318766 0.915586 +vn -0.358837 0.697653 0.620045 +vn -0.474410 0.318979 0.820429 +vn -0.506546 0.697684 0.506546 +vn -0.670125 0.319041 0.670125 +vn -0.620045 0.697653 0.358837 +vn -0.820429 0.318979 0.474410 +vn -0.692129 0.697470 0.185583 +vn -0.915586 0.318766 0.245064 +vn -0.717063 0.696982 0.000000 +vn -0.947905 0.318522 0.000000 +vn -0.692129 0.697470 -0.185583 +vn -0.915586 0.318766 -0.245064 +vn -0.620045 0.697653 -0.358837 +vn -0.820429 0.318979 -0.474410 +vn -0.506546 0.697684 -0.506546 +vn -0.670125 0.319041 -0.670125 +vn -0.358837 0.697653 -0.620045 +vn -0.474410 0.318979 -0.820429 +vn -0.185583 0.697470 -0.692129 +vn -0.245064 0.318766 -0.915586 +vn 0.000000 0.696982 -0.717063 +vn 0.000000 0.318522 -0.947905 +vn 0.185583 0.697470 -0.692129 +vn 0.245064 0.318766 -0.915586 +vn 0.358837 0.697653 -0.620045 +vn 0.474410 0.318979 -0.820429 +vn 0.506546 0.697684 -0.506546 +vn 0.670125 0.319041 -0.670125 +vn 0.620045 0.697653 -0.358837 +vn 0.820429 0.318979 -0.474410 +vn 0.692129 0.697470 -0.185583 +vn 0.915586 0.318766 -0.245064 +vn 0.282083 0.956389 0.075686 +vn 0.292520 0.956236 0.000000 +vn 0.171606 0.984069 0.046022 +vn 0.177953 0.984008 0.000000 +vn 0.153264 0.987304 0.041078 +vn 0.158879 0.987274 0.000000 +vn 0.210059 0.976043 0.056276 +vn 0.217719 0.975982 0.000000 +vn 0.487197 0.863460 0.130558 +vn 0.504715 0.863277 0.000000 +vn 0.662801 0.727226 0.178198 +vn 0.686911 0.726707 0.000000 +vn 0.252388 0.956511 0.146092 +vn 0.153508 0.984130 0.088839 +vn 0.137059 0.987365 0.079318 +vn 0.187872 0.976135 0.108676 +vn 0.435926 0.863887 0.252205 +vn 0.593310 0.727866 0.343730 +vn 0.206091 0.956572 0.206091 +vn 0.125340 0.984161 0.125340 +vn 0.111911 0.987396 0.111911 +vn 0.153356 0.976196 0.153356 +vn 0.355907 0.864071 0.355907 +vn 0.484664 0.728111 0.484664 +vn 0.146092 0.956511 0.252388 +vn 0.088839 0.984130 0.153508 +vn 0.079318 0.987365 0.137059 +vn 0.108676 0.976135 0.187872 +vn 0.252205 0.863887 0.435926 +vn 0.343730 0.727866 0.593310 +vn 0.075686 0.956389 0.282083 +vn 0.046022 0.984069 0.171606 +vn 0.041078 0.987304 0.153264 +vn 0.056276 0.976043 0.210059 +vn 0.130558 0.863460 0.487197 +vn 0.178198 0.727226 0.662801 +vn 0.000000 0.956236 0.292520 +vn 0.000000 0.984008 0.177953 +vn 0.000000 0.987274 0.158879 +vn 0.000000 0.975982 0.217719 +vn 0.000000 0.863277 0.504715 +vn 0.000000 0.726707 0.686911 +vn -0.075686 0.956389 0.282083 +vn -0.046022 0.984069 0.171606 +vn -0.041078 0.987304 0.153264 +vn -0.056276 0.976043 0.210059 +vn -0.130558 0.863460 0.487197 +vn -0.178198 0.727226 0.662801 +vn -0.146092 0.956511 0.252388 +vn -0.088839 0.984130 0.153508 +vn -0.079318 0.987365 0.137059 +vn -0.108676 0.976135 0.187872 +vn -0.252205 0.863887 0.435926 +vn -0.343730 0.727866 0.593310 +vn -0.206091 0.956572 0.206091 +vn -0.125340 0.984161 0.125340 +vn -0.111911 0.987396 0.111911 +vn -0.153356 0.976196 0.153356 +vn -0.355907 0.864071 0.355907 +vn -0.484664 0.728111 0.484664 +vn -0.252388 0.956511 0.146092 +vn -0.153508 0.984130 0.088839 +vn -0.137059 0.987365 0.079318 +vn -0.187872 0.976135 0.108676 +vn -0.435926 0.863887 0.252205 +vn -0.593310 0.727866 0.343730 +vn -0.282083 0.956389 0.075686 +vn -0.171606 0.984069 0.046022 +vn -0.153264 0.987304 0.041078 +vn -0.210059 0.976043 0.056276 +vn -0.487197 0.863460 0.130558 +vn -0.662801 0.727226 0.178198 +vn -0.292520 0.956236 0.000000 +vn -0.177953 0.984008 0.000000 +vn -0.158879 0.987274 0.000000 +vn -0.217719 0.975982 0.000000 +vn -0.504715 0.863277 0.000000 +vn -0.686911 0.726707 0.000000 +vn -0.282083 0.956389 -0.075686 +vn -0.171606 0.984069 -0.046022 +vn -0.153264 0.987304 -0.041078 +vn -0.210059 0.976043 -0.056276 +vn -0.487197 0.863460 -0.130558 +vn -0.662801 0.727226 -0.178198 +vn -0.252388 0.956511 -0.146092 +vn -0.153508 0.984130 -0.088839 +vn -0.137059 0.987365 -0.079318 +vn -0.187872 0.976135 -0.108676 +vn -0.435926 0.863887 -0.252205 +vn -0.593310 0.727866 -0.343730 +vn -0.206091 0.956572 -0.206091 +vn -0.125340 0.984161 -0.125340 +vn -0.111911 0.987396 -0.111911 +vn -0.153356 0.976196 -0.153356 +vn -0.355907 0.864071 -0.355907 +vn -0.484664 0.728111 -0.484664 +vn -0.146092 0.956511 -0.252388 +vn -0.088839 0.984130 -0.153508 +vn -0.079318 0.987365 -0.137059 +vn -0.108676 0.976135 -0.187872 +vn -0.252205 0.863887 -0.435926 +vn -0.343730 0.727866 -0.593310 +vn -0.075686 0.956389 -0.282083 +vn -0.046022 0.984069 -0.171606 +vn -0.041078 0.987304 -0.153264 +vn -0.056276 0.976043 -0.210059 +vn -0.130558 0.863460 -0.487197 +vn -0.178198 0.727226 -0.662801 +vn 0.000000 0.956236 -0.292520 +vn 0.000000 0.984008 -0.177953 +vn 0.000000 0.987274 -0.158879 +vn 0.000000 0.975982 -0.217719 +vn 0.000000 0.863277 -0.504715 +vn 0.000000 0.726707 -0.686911 +vn 0.075686 0.956389 -0.282083 +vn 0.046022 0.984069 -0.171606 +vn 0.041078 0.987304 -0.153264 +vn 0.056276 0.976043 -0.210059 +vn 0.130558 0.863460 -0.487197 +vn 0.178198 0.727226 -0.662801 +vn 0.146092 0.956511 -0.252388 +vn 0.088839 0.984130 -0.153508 +vn 0.079318 0.987365 -0.137059 +vn 0.108676 0.976135 -0.187872 +vn 0.252205 0.863887 -0.435926 +vn 0.343730 0.727866 -0.593310 +vn 0.206091 0.956572 -0.206091 +vn 0.125340 0.984161 -0.125340 +vn 0.111911 0.987396 -0.111911 +vn 0.153356 0.976196 -0.153356 +vn 0.355907 0.864071 -0.355907 +vn 0.484664 0.728111 -0.484664 +vn 0.252388 0.956511 -0.146092 +vn 0.153508 0.984130 -0.088839 +vn 0.137059 0.987365 -0.079318 +vn 0.187872 0.976135 -0.108676 +vn 0.435926 0.863887 -0.252205 +vn 0.593310 0.727866 -0.343730 +vn 0.282083 0.956389 -0.075686 +vn 0.171606 0.984069 -0.046022 +vn 0.153264 0.987304 -0.041078 +vn 0.210059 0.976043 -0.056276 +vn 0.487197 0.863460 -0.130558 +vn 0.662801 0.727226 -0.178198 +vn 0.015290 -0.999878 0.000000 +vn 0.003296 -0.999969 0.000000 +vn 0.015168 -0.949339 0.313852 +vn 0.003265 -0.944395 0.328715 +vn 0.058870 -0.998260 0.000000 +vn 0.058046 -0.947630 0.314005 +vn 0.158361 -0.934690 0.318155 +vn 0.159764 -0.987152 0.000000 +vn 0.373943 -0.860958 0.344798 +vn 0.391583 -0.920103 0.000000 +vn 0.726829 -0.553880 0.406049 +vn 0.784570 -0.620014 0.000000 +vn 0.908139 -0.082766 0.410321 +vn 0.994995 -0.099796 0.000000 +vn 0.011902 -0.679403 0.733634 +vn 0.002380 -0.636219 0.771477 +vn 0.046449 -0.674398 0.736869 +vn 0.125980 -0.648946 0.750298 +vn 0.270089 -0.562120 0.781671 +vn 0.460067 -0.316263 0.829615 +vn 0.563036 -0.041200 0.825373 +vn 0.000153 0.004242 0.999969 +vn -0.000519 0.113254 0.993561 +vn 0.003510 0.014008 0.999878 +vn 0.005921 0.035951 0.999329 +vn -0.007813 0.058840 0.998230 +vn -0.046510 0.041536 0.998047 +vn -0.039155 0.003113 0.999207 +vn -0.014161 0.682394 0.730796 +vn -0.003204 0.727744 0.685812 +vn -0.055361 0.680074 0.731010 +vn -0.150029 0.655660 0.739952 +vn -0.322520 0.565203 0.759239 +vn -0.537645 0.315806 0.781762 +vn -0.611530 0.029939 0.790613 +vn -0.020569 0.949400 0.313334 +vn -0.004273 0.954772 0.297281 +vn -0.082705 0.944945 0.316507 +vn -0.229591 0.914548 0.332926 +vn -0.502335 0.785943 0.360454 +vn -0.810633 0.443220 0.382611 +vn -0.921232 0.039705 0.386944 +vn -0.021851 0.999756 0.000000 +vn -0.004517 0.999969 0.000000 +vn -0.087649 0.996124 0.000000 +vn -0.246223 0.969207 0.000000 +vn -0.549211 0.835658 0.000000 +vn -0.881039 0.472976 0.000000 +vn -0.999115 0.041444 0.000000 +vn -0.004273 0.954772 -0.297281 +vn -0.020569 0.949400 -0.313334 +vn -0.082705 0.944945 -0.316507 +vn -0.229591 0.914548 -0.332926 +vn -0.502335 0.785943 -0.360454 +vn -0.810633 0.443220 -0.382611 +vn -0.921232 0.039705 -0.386944 +vn -0.003204 0.727744 -0.685812 +vn -0.014161 0.682394 -0.730796 +vn -0.055361 0.680074 -0.731010 +vn -0.150029 0.655660 -0.739952 +vn -0.322520 0.565203 -0.759239 +vn -0.537645 0.315806 -0.781762 +vn -0.611530 0.029939 -0.790613 +vn -0.000519 0.113254 -0.993561 +vn 0.000153 0.004242 -0.999969 +vn 0.003510 0.014008 -0.999878 +vn 0.005921 0.035951 -0.999329 +vn -0.007813 0.058809 -0.998230 +vn -0.046510 0.041536 -0.998047 +vn -0.039155 0.003113 -0.999207 +vn 0.002380 -0.636219 -0.771477 +vn 0.011902 -0.679403 -0.733634 +vn 0.046449 -0.674398 -0.736869 +vn 0.125980 -0.648946 -0.750298 +vn 0.270089 -0.562151 -0.781671 +vn 0.460067 -0.316263 -0.829615 +vn 0.563036 -0.041231 -0.825373 +vn 0.003265 -0.944395 -0.328715 +vn 0.015168 -0.949339 -0.313852 +vn 0.058046 -0.947630 -0.314005 +vn 0.158361 -0.934690 -0.318155 +vn 0.373943 -0.860958 -0.344798 +vn 0.726829 -0.553880 -0.406049 +vn 0.908139 -0.082766 -0.410321 +vn 0.890500 0.214759 0.401044 +vn 0.972930 0.231025 0.000000 +vn 0.836634 0.384075 0.390515 +vn 0.912503 0.408979 0.000000 +vn 0.765191 0.530198 0.365123 +vn 0.828791 0.559496 0.000000 +vn 0.671041 0.663228 0.331339 +vn 0.718955 0.695029 0.000000 +vn 0.549455 0.776238 0.309000 +vn 0.580859 0.813990 0.000000 +vn 0.461165 0.821528 0.335215 +vn 0.497085 0.867672 0.000000 +vn 0.559679 0.139714 0.816828 +vn 0.528581 0.255501 0.809473 +vn 0.494888 0.359783 0.790948 +vn 0.445143 0.467879 0.763451 +vn 0.376049 0.559984 0.738212 +vn 0.287332 0.527940 0.799188 +vn -0.024537 -0.005737 0.999664 +vn -0.020844 -0.012207 0.999695 +vn -0.014466 -0.014466 0.999786 +vn -0.009796 -0.013276 0.999847 +vn -0.014771 -0.013886 0.999786 +vn -0.101779 -0.196661 0.975158 +vn -0.585437 -0.154668 0.795801 +vn -0.538499 -0.291696 0.790490 +vn -0.487228 -0.408918 0.771599 +vn -0.428327 -0.511948 0.744560 +vn -0.360820 -0.584735 0.726524 +vn -0.357311 -0.691549 0.627735 +vn -0.889126 -0.238868 0.390332 +vn -0.807001 -0.448500 0.384075 +vn -0.700980 -0.613392 0.363750 +vn -0.590442 -0.733757 0.336009 +vn -0.486190 -0.814966 0.315256 +vn -0.440138 -0.855586 0.272439 +vn -0.965453 -0.260506 0.000000 +vn -0.872097 -0.489273 0.000000 +vn -0.748253 -0.663381 0.000000 +vn -0.621784 -0.783166 0.000000 +vn -0.507614 -0.861568 0.000000 +vn -0.456954 -0.889462 0.000000 +vn -0.889126 -0.238868 -0.390332 +vn -0.807001 -0.448531 -0.384075 +vn -0.700980 -0.613392 -0.363750 +vn -0.590442 -0.733757 -0.336009 +vn -0.486190 -0.814966 -0.315256 +vn -0.440138 -0.855586 -0.272439 +vn -0.585437 -0.154668 -0.795801 +vn -0.538499 -0.291696 -0.790490 +vn -0.487228 -0.408918 -0.771599 +vn -0.428327 -0.511948 -0.744560 +vn -0.360820 -0.584735 -0.726524 +vn -0.357311 -0.691549 -0.627705 +vn -0.024537 -0.005737 -0.999664 +vn -0.020844 -0.012238 -0.999695 +vn -0.014466 -0.014466 -0.999786 +vn -0.009766 -0.013276 -0.999847 +vn -0.014771 -0.013916 -0.999786 +vn -0.101779 -0.196661 -0.975158 +vn 0.559679 0.139714 -0.816828 +vn 0.528581 0.255501 -0.809473 +vn 0.494888 0.359783 -0.790948 +vn 0.445143 0.467879 -0.763451 +vn 0.376049 0.559984 -0.738212 +vn 0.287332 0.527940 -0.799188 +vn 0.890500 0.214759 -0.401044 +vn 0.836634 0.384075 -0.390515 +vn 0.765191 0.530198 -0.365123 +vn 0.671041 0.663228 -0.331339 +vn 0.549455 0.776238 -0.309000 +vn 0.461165 0.821528 -0.335215 +vn -0.149937 0.988678 0.000000 +vn -0.137028 0.872402 0.469131 +vn -0.297769 0.840358 0.452895 +vn -0.350505 0.936552 0.000000 +vn -0.617512 0.663961 0.421613 +vn -0.715506 0.698569 0.000000 +vn -0.801324 0.450209 0.393872 +vn -0.900845 0.434065 0.000000 +vn -0.828028 0.379803 0.412397 +vn -0.929289 0.369274 0.000000 +vn -0.729179 0.503464 0.463393 +vn -0.857875 0.513810 0.000000 +vn -0.663076 0.748527 0.000000 +vn -0.531449 0.686514 0.496170 +vn -0.066713 0.491440 0.868313 +vn -0.117893 0.503159 0.856105 +vn -0.254341 0.474349 0.842769 +vn -0.411115 0.399182 0.819483 +vn -0.459395 0.346446 0.817835 +vn -0.385876 0.395734 0.833338 +vn -0.270669 0.487838 0.829890 +vn 0.062716 -0.043458 0.997070 +vn 0.135929 -0.002472 0.990692 +vn 0.247963 0.095187 0.964049 +vn 0.209296 0.170660 0.962828 +vn 0.096194 0.178625 0.979186 +vn 0.009552 0.154332 0.987945 +vn -0.000122 0.151952 0.988372 +vn 0.202582 -0.542894 0.814966 +vn 0.360088 -0.479232 0.800378 +vn 0.611988 -0.282235 0.738762 +vn 0.679220 -0.106754 0.726096 +vn 0.583911 -0.078524 0.807978 +vn 0.402722 -0.205237 0.891995 +vn 0.279519 -0.338694 0.898404 +vn 0.294107 -0.855037 0.427015 +vn 0.488418 -0.768700 0.412915 +vn 0.784570 -0.501511 0.364544 +vn 0.893918 -0.279611 0.350291 +vn 0.861415 -0.285287 0.420179 +vn 0.679373 -0.540422 0.496323 +vn 0.458357 -0.754540 0.469588 +vn 0.320780 -0.947142 0.000000 +vn 0.525101 -0.851009 0.000000 +vn 0.827570 -0.561327 0.000000 +vn 0.943419 -0.331523 0.000000 +vn 0.933561 -0.358409 0.000000 +vn 0.756340 -0.654134 0.000000 +vn 0.491928 -0.870602 0.000092 +vn 0.294107 -0.855037 -0.427015 +vn 0.488418 -0.768700 -0.412915 +vn 0.784570 -0.501511 -0.364544 +vn 0.893918 -0.279611 -0.350291 +vn 0.861385 -0.285287 -0.420179 +vn 0.679373 -0.540422 -0.496323 +vn 0.457839 -0.755608 -0.468368 +vn 0.202582 -0.542894 -0.814966 +vn 0.360088 -0.479232 -0.800378 +vn 0.611988 -0.282235 -0.738762 +vn 0.679220 -0.106754 -0.726096 +vn 0.583911 -0.078524 -0.807978 +vn 0.402722 -0.205237 -0.891995 +vn 0.279153 -0.342235 -0.897153 +vn 0.062716 -0.043458 -0.997070 +vn 0.135929 -0.002472 -0.990692 +vn 0.247963 0.095187 -0.964049 +vn 0.209296 0.170629 -0.962828 +vn 0.096194 0.178625 -0.979186 +vn 0.009552 0.154332 -0.987945 +vn -0.000458 0.149358 -0.988769 +vn -0.066713 0.491440 -0.868313 +vn -0.117893 0.503159 -0.856105 +vn -0.254341 0.474319 -0.842769 +vn -0.411115 0.399182 -0.819514 +vn -0.459395 0.346446 -0.817835 +vn -0.385876 0.395734 -0.833338 +vn -0.271035 0.487136 -0.830164 +vn -0.137028 0.872402 -0.469131 +vn -0.297769 0.840358 -0.452895 +vn -0.617512 0.663961 -0.421613 +vn -0.801324 0.450209 -0.393872 +vn -0.828028 0.379803 -0.412397 +vn -0.729209 0.503464 -0.463393 +vn -0.531541 0.686453 -0.496200 +vn -0.480697 0.876858 0.000000 +vn -0.394635 0.815363 0.423536 +vn -0.320750 0.947142 0.000092 +vn -0.255287 0.921964 0.291086 +vn 0.002686 0.999969 -0.000732 +vn -0.007172 0.999939 -0.007599 +vn 0.366832 0.704398 -0.607624 +vn 0.853236 0.521226 -0.016388 +vn 0.567492 -0.154088 -0.808802 +vn 0.803766 -0.594409 -0.024964 +vn 0.580920 -0.584490 -0.566424 +vn 0.673757 -0.738639 -0.020966 +vn -0.206824 0.638203 0.741539 +vn -0.129490 0.862056 0.489944 +vn -0.034486 0.999023 0.026704 +vn 0.041597 0.871334 -0.488876 +vn 0.103488 0.553880 -0.826136 +vn 0.189642 0.174200 -0.966247 +vn 0.020112 0.322611 0.946287 +vn 0.021943 0.748894 0.662282 +vn -0.025697 0.995392 0.092166 +vn -0.056551 0.931608 -0.358989 +vn -0.070711 0.782006 -0.619190 +vn -0.066408 0.651509 -0.755699 +vn 0.281747 -0.174993 0.943388 +vn 0.303903 0.444136 0.842830 +vn 0.035279 0.983856 0.175329 +vn -0.109928 0.953551 -0.280435 +vn -0.149571 0.847682 -0.508927 +vn -0.145634 0.777520 -0.611744 +vn 0.467238 -0.683218 0.561144 +vn 0.699515 0.004364 0.714560 +vn 0.354900 0.892758 0.277444 +vn -0.174383 0.969054 -0.174596 +vn -0.252998 0.894314 -0.368938 +vn -0.191443 0.807947 -0.557237 +vn 0.495346 -0.868679 0.001587 +vn 0.933897 -0.357311 0.011017 +vn 0.704215 0.709830 0.013337 +vn -0.205634 0.978576 -0.006623 +vn -0.322367 0.945708 -0.041169 +vn -0.314951 0.916288 -0.247322 +vn 0.459120 -0.703757 -0.542100 +vn 0.693655 -0.096530 -0.713767 +vn 0.408673 0.848415 -0.336344 +vn -0.198248 0.963439 0.180151 +vn -0.306833 0.888516 0.341075 +vn -0.335978 0.808863 0.482498 +vn 0.277047 -0.215796 -0.936277 +vn 0.306192 0.349864 -0.885311 +vn 0.056246 0.971099 -0.231819 +vn -0.146733 0.912168 0.382611 +vn -0.202612 0.700797 0.683950 +vn -0.159368 0.444777 0.881314 +vn 0.016907 0.300088 -0.953734 +vn 0.019349 0.701865 -0.712027 +vn -0.023713 0.995361 -0.093081 +vn -0.047395 0.829371 0.556658 +vn -0.015259 0.386608 0.922086 +vn 0.080721 0.001404 0.996704 +vn -0.209967 0.632160 -0.745811 +vn -0.137394 0.849117 -0.509995 +vn -0.023438 0.999695 -0.004883 +vn 0.120426 0.724540 0.678579 +vn 0.260750 0.006531 0.965361 +vn 0.341502 -0.385510 0.857143 +vn -0.395489 0.814814 -0.423841 +vn -0.257942 0.920621 -0.293039 +vn 0.005005 0.999908 0.011628 +vn 0.466628 0.599811 0.649983 +vn 0.621937 -0.400861 0.672628 +vn 0.584826 -0.661397 0.469558 +vn 0.363842 0.931455 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.351451 0.931516 0.093509 +vn 0.314432 0.931791 0.181280 +vn 0.256386 0.931913 0.256386 +vn 0.181280 0.931791 0.314432 +vn 0.093509 0.931516 0.351451 +vn 0.000000 0.931455 0.363842 +vn -0.093509 0.931516 0.351451 +vn -0.181280 0.931791 0.314432 +vn -0.256386 0.931913 0.256386 +vn -0.314432 0.931791 0.181280 +vn -0.351451 0.931516 0.093509 +vn -0.363842 0.931455 0.000000 +vn -0.351451 0.931516 -0.093509 +vn -0.314432 0.931791 -0.181280 +vn -0.256417 0.931913 -0.256417 +vn -0.181280 0.931791 -0.314432 +vn -0.093509 0.931516 -0.351451 +vn 0.000000 0.931455 -0.363842 +vn 0.093509 0.931516 -0.351451 +vn 0.181280 0.931791 -0.314432 +vn 0.256417 0.931913 -0.256417 +vn 0.314432 0.931791 -0.181280 +vn 0.351451 0.931516 -0.093509 +vn 0.935423 0.249763 0.250160 +vn 0.968261 0.249916 0.000000 +vn 0.813959 -0.538713 0.217322 +vn 0.842860 -0.538102 0.000000 +vn 0.759484 -0.618030 0.202887 +vn 0.786767 -0.617206 0.000000 +vn 0.801569 -0.558184 0.214148 +vn 0.830195 -0.557421 0.000000 +vn 0.838404 0.249825 0.484359 +vn 0.729026 -0.539720 0.420881 +vn 0.680013 -0.619098 0.392712 +vn 0.717765 -0.559343 0.414594 +vn 0.684652 0.249886 0.684652 +vn 0.595050 -0.540147 0.595050 +vn 0.555040 -0.619526 0.555040 +vn 0.585894 -0.559862 0.585894 +vn 0.484359 0.249825 0.838404 +vn 0.420881 -0.539720 0.729026 +vn 0.392712 -0.619098 0.680013 +vn 0.414594 -0.559343 0.717765 +vn 0.250160 0.249763 0.935423 +vn 0.217322 -0.538713 0.813959 +vn 0.202887 -0.618030 0.759514 +vn 0.214148 -0.558184 0.801569 +vn 0.000000 0.249916 0.968261 +vn 0.000000 -0.538102 0.842860 +vn 0.000000 -0.617206 0.786767 +vn 0.000000 -0.557421 0.830195 +vn -0.250160 0.249763 0.935423 +vn -0.217322 -0.538713 0.813959 +vn -0.202887 -0.618030 0.759514 +vn -0.214148 -0.558184 0.801569 +vn -0.484359 0.249825 0.838404 +vn -0.420881 -0.539720 0.729026 +vn -0.392712 -0.619098 0.680013 +vn -0.414594 -0.559343 0.717765 +vn -0.684652 0.249886 0.684652 +vn -0.595050 -0.540147 0.595050 +vn -0.555040 -0.619526 0.555040 +vn -0.585894 -0.559862 0.585894 +vn -0.838404 0.249825 0.484359 +vn -0.729026 -0.539720 0.420881 +vn -0.680013 -0.619098 0.392712 +vn -0.717765 -0.559343 0.414594 +vn -0.935423 0.249763 0.250160 +vn -0.813959 -0.538713 0.217322 +vn -0.759484 -0.618030 0.202887 +vn -0.801569 -0.558184 0.214148 +vn -0.968261 0.249916 0.000000 +vn -0.842860 -0.538102 0.000000 +vn -0.786767 -0.617206 0.000000 +vn -0.830195 -0.557421 0.000000 +vn -0.935423 0.249763 -0.250160 +vn -0.813959 -0.538713 -0.217322 +vn -0.759484 -0.618030 -0.202887 +vn -0.801569 -0.558184 -0.214148 +vn -0.838404 0.249825 -0.484359 +vn -0.729026 -0.539720 -0.420881 +vn -0.680013 -0.619098 -0.392712 +vn -0.717765 -0.559343 -0.414594 +vn -0.684652 0.249886 -0.684652 +vn -0.595050 -0.540147 -0.595050 +vn -0.555040 -0.619526 -0.555040 +vn -0.585864 -0.559862 -0.585894 +vn -0.484359 0.249825 -0.838404 +vn -0.420881 -0.539720 -0.729026 +vn -0.392712 -0.619098 -0.680013 +vn -0.414594 -0.559343 -0.717765 +vn -0.250160 0.249763 -0.935423 +vn -0.217322 -0.538713 -0.813959 +vn -0.202887 -0.618030 -0.759484 +vn -0.214148 -0.558214 -0.801569 +vn 0.000000 0.249916 -0.968261 +vn 0.000000 -0.538102 -0.842860 +vn 0.000000 -0.617206 -0.786767 +vn 0.000000 -0.557421 -0.830195 +vn 0.250160 0.249763 -0.935423 +vn 0.217322 -0.538713 -0.813959 +vn 0.202887 -0.618030 -0.759484 +vn 0.214148 -0.558214 -0.801569 +vn 0.484359 0.249825 -0.838404 +vn 0.420881 -0.539720 -0.729026 +vn 0.392712 -0.619098 -0.680013 +vn 0.414594 -0.559343 -0.717765 +vn 0.684652 0.249886 -0.684652 +vn 0.595050 -0.540147 -0.595050 +vn 0.555040 -0.619526 -0.555040 +vn 0.585864 -0.559862 -0.585894 +vn 0.838404 0.249825 -0.484359 +vn 0.729026 -0.539720 -0.420881 +vn 0.680013 -0.619098 -0.392712 +vn 0.717765 -0.559343 -0.414594 +vn 0.935423 0.249763 -0.250160 +vn 0.813959 -0.538713 -0.217322 +vn 0.759484 -0.618030 -0.202887 +vn 0.801569 -0.558184 -0.214148 +s 1 +f 1//1 2//2 3//3 +f 3//3 2//2 4//4 +f 4//4 2//2 5//5 +f 5//5 2//2 6//6 +f 6//6 2//2 7//7 +f 7//7 2//2 8//8 +f 8//8 2//2 9//9 +f 9//9 2//2 10//10 +f 10//10 2//2 11//11 +f 11//11 2//2 12//12 +f 12//12 2//2 13//13 +f 13//13 2//2 14//14 +f 14//14 2//2 15//15 +f 15//15 2//2 16//16 +f 16//16 2//2 17//17 +f 17//17 2//2 18//18 +f 18//18 2//2 19//19 +f 19//19 2//2 20//20 +f 20//20 2//2 21//21 +f 21//21 2//2 22//22 +f 22//22 2//2 23//23 +f 23//23 2//2 24//24 +f 24//24 2//2 25//25 +f 2//2 1//1 25//25 +f 26//26 27//27 28//28 +f 28//28 29//29 26//26 +f 29//29 28//28 30//30 +f 31//31 30//30 28//28 +f 30//30 31//31 32//32 +f 33//33 32//32 31//31 +f 34//34 35//35 33//33 +f 32//32 33//33 35//35 +f 36//36 37//37 34//34 +f 35//35 34//34 37//37 +f 38//38 39//39 36//36 +f 37//37 36//36 39//39 +f 27//27 40//40 41//41 +f 41//41 28//28 27//27 +f 28//28 41//41 31//31 +f 42//42 31//31 41//41 +f 31//31 42//42 33//33 +f 43//43 33//33 42//42 +f 44//44 34//34 43//43 +f 33//33 43//43 34//34 +f 45//45 36//36 44//44 +f 34//34 44//44 36//36 +f 46//46 38//38 45//45 +f 36//36 45//45 38//38 +f 40//40 47//47 48//48 +f 48//48 41//41 40//40 +f 41//41 48//48 42//42 +f 49//49 42//42 48//48 +f 42//42 49//49 43//43 +f 50//50 43//43 49//49 +f 51//51 44//44 50//50 +f 43//43 50//50 44//44 +f 52//52 45//45 51//51 +f 44//44 51//51 45//45 +f 53//53 46//46 52//52 +f 45//45 52//52 46//46 +f 47//47 54//54 48//48 +f 55//55 48//48 54//54 +f 48//48 55//55 49//49 +f 56//56 49//49 55//55 +f 49//49 56//56 57//57 +f 57//57 50//50 49//49 +f 58//58 51//51 50//50 +f 50//50 57//57 58//58 +f 59//59 52//52 51//51 +f 51//51 58//58 59//59 +f 60//60 53//53 52//52 +f 52//52 59//59 60//60 +f 54//54 61//61 55//55 +f 62//62 55//55 61//61 +f 55//55 62//62 63//63 +f 63//63 56//56 55//55 +f 56//56 63//63 64//64 +f 64//64 57//57 56//56 +f 65//65 58//58 57//57 +f 57//57 64//64 65//65 +f 66//66 59//59 58//58 +f 58//58 65//65 66//66 +f 67//67 60//60 59//59 +f 59//59 66//66 67//67 +f 61//61 68//68 62//62 +f 69//69 62//62 68//68 +f 62//62 69//69 70//70 +f 70//70 63//63 62//62 +f 63//63 70//70 71//71 +f 71//71 64//64 63//63 +f 72//72 65//65 64//64 +f 64//64 71//71 72//72 +f 73//73 66//66 65//65 +f 65//65 72//72 73//73 +f 74//74 67//67 66//66 +f 66//66 73//73 74//74 +f 68//68 75//75 76//76 +f 76//76 69//69 68//68 +f 69//69 76//76 70//70 +f 77//77 70//70 76//76 +f 70//70 77//77 71//71 +f 78//78 71//71 77//77 +f 79//79 72//72 78//78 +f 71//71 78//78 72//72 +f 80//80 73//73 79//79 +f 72//72 79//79 73//73 +f 81//81 74//74 80//80 +f 73//73 80//80 74//74 +f 75//75 82//82 83//83 +f 83//83 76//76 75//75 +f 76//76 83//83 77//77 +f 84//84 77//77 83//83 +f 77//77 84//84 78//78 +f 85//85 78//78 84//84 +f 86//86 79//79 85//85 +f 78//78 85//85 79//79 +f 87//87 80//80 86//86 +f 79//79 86//86 80//80 +f 88//88 81//81 87//87 +f 80//80 87//87 81//81 +f 82//82 89//89 90//90 +f 90//90 83//83 82//82 +f 83//83 90//90 91//91 +f 91//91 84//84 83//83 +f 84//84 91//91 85//85 +f 92//92 85//85 91//91 +f 93//93 86//86 92//92 +f 85//85 92//92 86//86 +f 94//94 87//87 93//93 +f 86//86 93//93 87//87 +f 95//95 88//88 94//94 +f 87//87 94//94 88//88 +f 89//89 96//96 90//90 +f 97//97 90//90 96//96 +f 90//90 97//97 98//98 +f 98//98 91//91 90//90 +f 91//91 98//98 99//99 +f 99//99 92//92 91//91 +f 100//100 93//93 92//92 +f 92//92 99//99 100//100 +f 101//101 94//94 93//93 +f 93//93 100//100 101//101 +f 102//102 95//95 94//94 +f 94//94 101//101 102//102 +f 96//96 103//103 97//97 +f 104//104 97//97 103//103 +f 97//97 104//104 105//105 +f 105//105 98//98 97//97 +f 98//98 105//105 106//106 +f 106//106 99//99 98//98 +f 107//107 100//100 99//99 +f 99//99 106//106 107//107 +f 108//108 101//101 100//100 +f 100//100 107//107 108//108 +f 109//109 102//102 101//101 +f 101//101 108//108 109//109 +f 103//103 110//110 104//104 +f 111//111 104//104 110//110 +f 104//104 111//111 112//112 +f 112//112 105//105 104//104 +f 105//105 112//112 113//113 +f 113//113 106//106 105//105 +f 114//114 107//107 106//106 +f 106//106 113//113 114//114 +f 115//115 108//108 107//107 +f 107//107 114//114 115//115 +f 116//116 109//109 108//108 +f 108//108 115//115 116//116 +f 110//110 117//117 118//118 +f 118//118 111//111 110//110 +f 111//111 118//118 119//119 +f 119//119 112//112 111//111 +f 112//112 119//119 113//113 +f 120//120 113//113 119//119 +f 121//121 114//114 120//120 +f 113//113 120//120 114//114 +f 122//122 115//115 121//121 +f 114//114 121//121 115//115 +f 123//123 116//116 122//122 +f 115//115 122//122 116//116 +f 117//117 124//124 125//125 +f 125//125 118//118 117//117 +f 118//118 125//125 119//119 +f 126//126 119//119 125//125 +f 119//119 126//126 120//120 +f 127//127 120//120 126//126 +f 128//128 121//121 127//127 +f 120//120 127//127 121//121 +f 129//129 122//122 128//128 +f 121//121 128//128 122//122 +f 130//130 123//123 129//129 +f 122//122 129//129 123//123 +f 124//124 131//131 132//132 +f 132//132 125//125 124//124 +f 125//125 132//132 133//133 +f 133//133 126//126 125//125 +f 126//126 133//133 127//127 +f 134//134 127//127 133//133 +f 135//135 128//128 134//134 +f 127//127 134//134 128//128 +f 136//136 129//129 135//135 +f 128//128 135//135 129//129 +f 137//137 130//130 136//136 +f 129//129 136//136 130//130 +f 131//131 138//138 132//132 +f 139//139 132//132 138//138 +f 132//132 139//139 140//140 +f 140//140 133//133 132//132 +f 133//133 140//140 141//141 +f 141//141 134//134 133//133 +f 142//142 135//135 134//134 +f 134//134 141//141 142//142 +f 143//143 136//136 135//135 +f 135//135 142//142 143//143 +f 144//144 137//137 136//136 +f 136//136 143//143 144//144 +f 138//138 145//145 139//139 +f 146//146 139//139 145//145 +f 139//139 146//146 147//147 +f 147//147 140//140 139//139 +f 140//140 147//147 148//148 +f 148//148 141//141 140//140 +f 149//149 142//142 141//141 +f 141//141 148//148 149//149 +f 150//150 143//143 142//142 +f 142//142 149//149 150//150 +f 151//151 144//144 143//143 +f 143//143 150//150 151//151 +f 145//145 152//152 146//146 +f 153//153 146//146 152//152 +f 146//146 153//153 154//154 +f 154//154 147//147 146//146 +f 147//147 154//154 155//155 +f 155//155 148//148 147//147 +f 156//156 149//149 148//148 +f 148//148 155//155 156//156 +f 157//157 150//150 149//149 +f 149//149 156//156 157//157 +f 158//158 151//151 150//150 +f 150//150 157//157 158//158 +f 152//152 159//159 160//160 +f 160//160 153//153 152//152 +f 153//153 160//160 154//154 +f 161//161 154//154 160//160 +f 154//154 161//161 155//155 +f 162//162 155//155 161//161 +f 163//163 156//156 162//162 +f 155//155 162//162 156//156 +f 164//164 157//157 163//163 +f 156//156 163//163 157//157 +f 165//165 158//158 164//164 +f 157//157 164//164 158//158 +f 159//159 166//166 167//167 +f 167//167 160//160 159//159 +f 160//160 167//167 161//161 +f 168//168 161//161 167//167 +f 161//161 168//168 162//162 +f 169//169 162//162 168//168 +f 170//170 163//163 169//169 +f 162//162 169//169 163//163 +f 171//171 164//164 170//170 +f 163//163 170//170 164//164 +f 172//172 165//165 171//171 +f 164//164 171//171 165//165 +f 166//166 173//173 174//174 +f 174//174 167//167 166//166 +f 167//167 174//174 168//168 +f 175//175 168//168 174//174 +f 168//168 175//175 169//169 +f 176//176 169//169 175//175 +f 177//177 170//170 176//176 +f 169//169 176//176 170//170 +f 178//178 171//171 177//177 +f 170//170 177//177 171//171 +f 179//179 172//172 178//178 +f 171//171 178//178 172//172 +f 173//173 180//180 174//174 +f 181//181 174//174 180//180 +f 174//174 181//181 175//175 +f 182//182 175//175 181//181 +f 175//175 182//182 183//183 +f 183//183 176//176 175//175 +f 184//184 177//177 176//176 +f 176//176 183//183 184//184 +f 185//185 178//178 177//177 +f 177//177 184//184 185//185 +f 186//186 179//179 178//178 +f 178//178 185//185 186//186 +f 180//180 187//187 181//181 +f 188//188 181//181 187//187 +f 181//181 188//188 189//189 +f 189//189 182//182 181//181 +f 182//182 189//189 190//190 +f 190//190 183//183 182//182 +f 191//191 184//184 183//183 +f 183//183 190//190 191//191 +f 192//192 185//185 184//184 +f 184//184 191//191 192//192 +f 193//193 186//186 185//185 +f 185//185 192//192 193//193 +f 187//187 26//26 188//188 +f 29//29 188//188 26//26 +f 188//188 29//29 189//189 +f 30//30 189//189 29//29 +f 189//189 30//30 32//32 +f 32//32 190//190 189//189 +f 35//35 191//191 190//190 +f 190//190 32//32 35//35 +f 37//37 192//192 191//191 +f 191//191 35//35 37//37 +f 39//39 193//193 192//192 +f 192//192 37//37 39//39 +f 194//194 195//195 38//38 +f 39//39 38//38 195//195 +f 196//196 197//197 194//194 +f 195//195 194//194 197//197 +f 198//198 199//199 196//196 +f 197//197 196//196 199//199 +f 200//200 201//201 198//198 +f 199//199 198//198 201//201 +f 202//202 203//203 200//200 +f 201//201 200//200 203//203 +f 204//204 205//205 202//202 +f 203//203 202//202 205//205 +f 206//206 194//194 46//46 +f 38//38 46//46 194//194 +f 207//207 196//196 206//206 +f 194//194 206//206 196//196 +f 208//208 198//198 207//207 +f 196//196 207//207 198//198 +f 209//209 200//200 208//208 +f 198//198 208//208 200//200 +f 210//210 202//202 209//209 +f 200//200 209//209 202//202 +f 211//211 204//204 210//210 +f 202//202 210//210 204//204 +f 212//212 206//206 53//53 +f 46//46 53//53 206//206 +f 213//213 207//207 212//212 +f 206//206 212//212 207//207 +f 214//214 208//208 213//213 +f 207//207 213//213 208//208 +f 215//215 209//209 214//214 +f 208//208 214//214 209//209 +f 216//216 210//210 215//215 +f 209//209 215//215 210//210 +f 217//217 211//211 216//216 +f 210//210 216//216 211//211 +f 218//218 212//212 53//53 +f 53//53 60//60 218//218 +f 219//219 213//213 212//212 +f 212//212 218//218 219//219 +f 220//220 214//214 213//213 +f 213//213 219//219 220//220 +f 221//221 215//215 214//214 +f 214//214 220//220 221//221 +f 222//222 216//216 215//215 +f 215//215 221//221 222//222 +f 223//223 217//217 216//216 +f 216//216 222//222 223//223 +f 224//224 218//218 60//60 +f 60//60 67//67 224//224 +f 225//225 219//219 218//218 +f 218//218 224//224 225//225 +f 226//226 220//220 219//219 +f 219//219 225//225 226//226 +f 227//227 221//221 220//220 +f 220//220 226//226 227//227 +f 228//228 222//222 221//221 +f 221//221 227//227 228//228 +f 229//229 223//223 222//222 +f 222//222 228//228 229//229 +f 230//230 224//224 67//67 +f 67//67 74//74 230//230 +f 231//231 225//225 224//224 +f 224//224 230//230 231//231 +f 232//232 226//226 225//225 +f 225//225 231//231 232//232 +f 233//233 227//227 226//226 +f 226//226 232//232 233//233 +f 234//234 228//228 227//227 +f 227//227 233//233 234//234 +f 235//235 229//229 228//228 +f 228//228 234//234 235//235 +f 236//236 230//230 81//81 +f 74//74 81//81 230//230 +f 237//237 231//231 236//236 +f 230//230 236//236 231//231 +f 238//238 232//232 237//237 +f 231//231 237//237 232//232 +f 239//239 233//233 238//238 +f 232//232 238//238 233//233 +f 240//240 234//234 239//239 +f 233//233 239//239 234//234 +f 241//241 235//235 240//240 +f 234//234 240//240 235//235 +f 242//242 236//236 88//88 +f 81//81 88//88 236//236 +f 243//243 237//237 242//242 +f 236//236 242//242 237//237 +f 244//244 238//238 243//243 +f 237//237 243//243 238//238 +f 245//245 239//239 244//244 +f 238//238 244//244 239//239 +f 246//246 240//240 245//245 +f 239//239 245//245 240//240 +f 247//247 241//241 246//246 +f 240//240 246//246 241//241 +f 248//248 242//242 95//95 +f 88//88 95//95 242//242 +f 249//249 243//243 248//248 +f 242//242 248//248 243//243 +f 250//250 244//244 249//249 +f 243//243 249//249 244//244 +f 251//251 245//245 250//250 +f 244//244 250//250 245//245 +f 252//252 246//246 251//251 +f 245//245 251//251 246//246 +f 253//253 247//247 252//252 +f 246//246 252//252 247//247 +f 254//254 248//248 95//95 +f 95//95 102//102 254//254 +f 255//255 249//249 248//248 +f 248//248 254//254 255//255 +f 256//256 250//250 249//249 +f 249//249 255//255 256//256 +f 257//257 251//251 250//250 +f 250//250 256//256 257//257 +f 258//258 252//252 251//251 +f 251//251 257//257 258//258 +f 259//259 253//253 252//252 +f 252//252 258//258 259//259 +f 260//260 254//254 102//102 +f 102//102 109//109 260//260 +f 261//261 255//255 254//254 +f 254//254 260//260 261//261 +f 262//262 256//256 255//255 +f 255//255 261//261 262//262 +f 263//263 257//257 256//256 +f 256//256 262//262 263//263 +f 264//264 258//258 257//257 +f 257//257 263//263 264//264 +f 265//265 259//259 258//258 +f 258//258 264//264 265//265 +f 266//266 260//260 109//109 +f 109//109 116//116 266//266 +f 267//267 261//261 260//260 +f 260//260 266//266 267//267 +f 268//268 262//262 261//261 +f 261//261 267//267 268//268 +f 269//269 263//263 262//262 +f 262//262 268//268 269//269 +f 270//270 264//264 263//263 +f 263//263 269//269 270//270 +f 271//271 265//265 264//264 +f 264//264 270//270 271//271 +f 272//272 266//266 123//123 +f 116//116 123//123 266//266 +f 273//273 267//267 272//272 +f 266//266 272//272 267//267 +f 274//274 268//268 273//273 +f 267//267 273//273 268//268 +f 275//275 269//269 274//274 +f 268//268 274//274 269//269 +f 276//276 270//270 275//275 +f 269//269 275//275 270//270 +f 277//277 271//271 276//276 +f 270//270 276//276 271//271 +f 278//278 272//272 130//130 +f 123//123 130//130 272//272 +f 279//279 273//273 278//278 +f 272//272 278//278 273//273 +f 280//280 274//274 279//279 +f 273//273 279//279 274//274 +f 281//281 275//275 280//280 +f 274//274 280//280 275//275 +f 282//282 276//276 281//281 +f 275//275 281//281 276//276 +f 283//283 277//277 282//282 +f 276//276 282//282 277//277 +f 284//284 278//278 137//137 +f 130//130 137//137 278//278 +f 285//285 279//279 284//284 +f 278//278 284//284 279//279 +f 286//286 280//280 285//285 +f 279//279 285//285 280//280 +f 287//287 281//281 286//286 +f 280//280 286//286 281//281 +f 288//288 282//282 287//287 +f 281//281 287//287 282//282 +f 289//289 283//283 288//288 +f 282//282 288//288 283//283 +f 290//290 284//284 137//137 +f 137//137 144//144 290//290 +f 291//291 285//285 284//284 +f 284//284 290//290 291//291 +f 292//292 286//286 285//285 +f 285//285 291//291 292//292 +f 293//293 287//287 286//286 +f 286//286 292//292 293//293 +f 294//294 288//288 287//287 +f 287//287 293//293 294//294 +f 295//295 289//289 288//288 +f 288//288 294//294 295//295 +f 296//296 290//290 144//144 +f 144//144 151//151 296//296 +f 297//297 291//291 290//290 +f 290//290 296//296 297//297 +f 298//298 292//292 291//291 +f 291//291 297//297 298//298 +f 299//299 293//293 292//292 +f 292//292 298//298 299//299 +f 300//300 294//294 293//293 +f 293//293 299//299 300//300 +f 301//301 295//295 294//294 +f 294//294 300//300 301//301 +f 302//302 296//296 151//151 +f 151//151 158//158 302//302 +f 303//303 297//297 296//296 +f 296//296 302//302 303//303 +f 304//304 298//298 297//297 +f 297//297 303//303 304//304 +f 305//305 299//299 298//298 +f 298//298 304//304 305//305 +f 306//306 300//300 299//299 +f 299//299 305//305 306//306 +f 307//307 301//301 300//300 +f 300//300 306//306 307//307 +f 308//308 302//302 165//165 +f 158//158 165//165 302//302 +f 309//309 303//303 308//308 +f 302//302 308//308 303//303 +f 310//310 304//304 309//309 +f 303//303 309//309 304//304 +f 311//311 305//305 310//310 +f 304//304 310//310 305//305 +f 312//312 306//306 311//311 +f 305//305 311//311 306//306 +f 313//313 307//307 312//312 +f 306//306 312//312 307//307 +f 314//314 308//308 172//172 +f 165//165 172//172 308//308 +f 315//315 309//309 314//314 +f 308//308 314//314 309//309 +f 316//316 310//310 315//315 +f 309//309 315//315 310//310 +f 317//317 311//311 316//316 +f 310//310 316//316 311//311 +f 318//318 312//312 317//317 +f 311//311 317//317 312//312 +f 319//319 313//313 318//318 +f 312//312 318//318 313//313 +f 320//320 314//314 179//179 +f 172//172 179//179 314//314 +f 321//321 315//315 320//320 +f 314//314 320//320 315//315 +f 322//322 316//316 321//321 +f 315//315 321//321 316//316 +f 323//323 317//317 322//322 +f 316//316 322//322 317//317 +f 324//324 318//318 323//323 +f 317//317 323//323 318//318 +f 325//325 319//319 324//324 +f 318//318 324//324 319//319 +f 326//326 320//320 179//179 +f 179//179 186//186 326//326 +f 327//327 321//321 320//320 +f 320//320 326//326 327//327 +f 328//328 322//322 321//321 +f 321//321 327//327 328//328 +f 329//329 323//323 322//322 +f 322//322 328//328 329//329 +f 330//330 324//324 323//323 +f 323//323 329//329 330//330 +f 331//331 325//325 324//324 +f 324//324 330//330 331//331 +f 332//332 326//326 186//186 +f 186//186 193//193 332//332 +f 333//333 327//327 326//326 +f 326//326 332//332 333//333 +f 334//334 328//328 327//327 +f 327//327 333//333 334//334 +f 335//335 329//329 328//328 +f 328//328 334//334 335//335 +f 336//336 330//330 329//329 +f 329//329 335//335 336//336 +f 337//337 331//331 330//330 +f 330//330 336//336 337//337 +f 195//195 332//332 193//193 +f 193//193 39//39 195//195 +f 197//197 333//333 332//332 +f 332//332 195//195 197//197 +f 199//199 334//334 333//333 +f 333//333 197//197 199//199 +f 201//201 335//335 334//334 +f 334//334 199//199 201//201 +f 203//203 336//336 335//335 +f 335//335 201//201 203//203 +f 205//205 337//337 336//336 +f 336//336 203//203 205//205 +f 338//338 339//339 205//205 +f 205//205 204//204 338//338 +f 340//340 341//341 339//339 +f 339//339 338//338 340//340 +f 342//342 343//343 341//341 +f 341//341 340//340 342//342 +f 344//344 345//345 343//343 +f 343//343 342//342 344//344 +f 346//346 347//347 345//345 +f 345//345 344//344 346//346 +f 348//348 349//349 347//347 +f 347//347 346//346 348//348 +f 350//350 338//338 204//204 +f 204//204 211//211 350//350 +f 351//351 340//340 338//338 +f 338//338 350//350 351//351 +f 352//352 342//342 340//340 +f 340//340 351//351 352//352 +f 353//353 344//344 342//342 +f 342//342 352//352 353//353 +f 354//354 346//346 344//344 +f 344//344 353//353 354//354 +f 355//355 348//348 346//346 +f 346//346 354//354 355//355 +f 356//356 350//350 211//211 +f 211//211 217//217 356//356 +f 357//357 351//351 350//350 +f 350//350 356//356 357//357 +f 358//358 352//352 351//351 +f 351//351 357//357 358//358 +f 359//359 353//353 352//352 +f 352//352 358//358 359//359 +f 360//360 354//354 353//353 +f 353//353 359//359 360//360 +f 361//361 355//355 354//354 +f 354//354 360//360 361//361 +f 362//362 356//356 223//223 +f 217//217 223//223 356//356 +f 363//363 357//357 362//362 +f 356//356 362//362 357//357 +f 364//364 358//358 363//363 +f 357//357 363//363 358//358 +f 365//365 359//359 364//364 +f 358//358 364//364 359//359 +f 366//366 360//360 365//365 +f 359//359 365//365 360//360 +f 367//367 361//361 366//366 +f 360//360 366//366 361//361 +f 368//368 362//362 229//229 +f 223//223 229//229 362//362 +f 369//369 363//363 368//368 +f 362//362 368//368 363//363 +f 370//370 364//364 369//369 +f 363//363 369//369 364//364 +f 371//371 365//365 370//370 +f 364//364 370//370 365//365 +f 372//372 366//366 371//371 +f 365//365 371//371 366//366 +f 373//373 367//367 372//372 +f 366//366 372//372 367//367 +f 374//374 368//368 235//235 +f 229//229 235//235 368//368 +f 375//375 369//369 374//374 +f 368//368 374//374 369//369 +f 376//376 370//370 375//375 +f 369//369 375//375 370//370 +f 377//377 371//371 376//376 +f 370//370 376//376 371//371 +f 378//378 372//372 377//377 +f 371//371 377//377 372//372 +f 379//379 373//373 378//378 +f 372//372 378//378 373//373 +f 380//380 374//374 235//235 +f 235//235 241//241 380//380 +f 381//381 375//375 374//374 +f 374//374 380//380 381//381 +f 382//382 376//376 375//375 +f 375//375 381//381 382//382 +f 383//383 377//377 376//376 +f 376//376 382//382 383//383 +f 384//384 378//378 377//377 +f 377//377 383//383 384//384 +f 385//385 379//379 378//378 +f 378//378 384//384 385//385 +f 386//386 380//380 241//241 +f 241//241 247//247 386//386 +f 387//387 381//381 380//380 +f 380//380 386//386 387//387 +f 388//388 382//382 381//381 +f 381//381 387//387 388//388 +f 389//389 383//383 382//382 +f 382//382 388//388 389//389 +f 390//390 384//384 383//383 +f 383//383 389//389 390//390 +f 391//391 385//385 384//384 +f 384//384 390//390 391//391 +f 392//392 386//386 247//247 +f 247//247 253//253 392//392 +f 393//393 387//387 386//386 +f 386//386 392//392 393//393 +f 394//394 388//388 387//387 +f 387//387 393//393 394//394 +f 395//395 389//389 388//388 +f 388//388 394//394 395//395 +f 396//396 390//390 389//389 +f 389//389 395//395 396//396 +f 397//397 391//391 390//390 +f 390//390 396//396 397//397 +f 398//398 392//392 259//259 +f 253//253 259//259 392//392 +f 399//399 393//393 398//398 +f 392//392 398//398 393//393 +f 400//400 394//394 399//399 +f 393//393 399//399 394//394 +f 401//401 395//395 400//400 +f 394//394 400//400 395//395 +f 402//402 396//396 401//401 +f 395//395 401//401 396//396 +f 403//403 397//397 402//402 +f 396//396 402//402 397//397 +f 404//404 398//398 265//265 +f 259//259 265//265 398//398 +f 405//405 399//399 404//404 +f 398//398 404//404 399//399 +f 406//406 400//400 405//405 +f 399//399 405//405 400//400 +f 407//407 401//401 406//406 +f 400//400 406//406 401//401 +f 408//408 402//402 407//407 +f 401//401 407//407 402//402 +f 409//409 403//403 408//408 +f 402//402 408//408 403//403 +f 410//410 404//404 271//271 +f 265//265 271//271 404//404 +f 411//411 405//405 410//410 +f 404//404 410//410 405//405 +f 412//412 406//406 411//411 +f 405//405 411//411 406//406 +f 413//413 407//407 412//412 +f 406//406 412//412 407//407 +f 414//414 408//408 413//413 +f 407//407 413//413 408//408 +f 415//415 409//409 414//414 +f 408//408 414//414 409//409 +f 416//416 410//410 271//271 +f 271//271 277//277 416//416 +f 417//417 411//411 410//410 +f 410//410 416//416 417//417 +f 418//418 412//412 411//411 +f 411//411 417//417 418//418 +f 419//419 413//413 412//412 +f 412//412 418//418 419//419 +f 420//420 414//414 413//413 +f 413//413 419//419 420//420 +f 421//421 415//415 414//414 +f 414//414 420//420 421//421 +f 422//422 416//416 277//277 +f 277//277 283//283 422//422 +f 423//423 417//417 416//416 +f 416//416 422//422 423//423 +f 424//424 418//418 417//417 +f 417//417 423//423 424//424 +f 425//425 419//419 418//418 +f 418//418 424//424 425//425 +f 426//426 420//420 419//419 +f 419//419 425//425 426//426 +f 427//427 421//421 420//420 +f 420//420 426//426 427//427 +f 428//428 422//422 283//283 +f 283//283 289//289 428//428 +f 429//429 423//423 422//422 +f 422//422 428//428 429//429 +f 430//430 424//424 423//423 +f 423//423 429//429 430//430 +f 431//431 425//425 424//424 +f 424//424 430//430 431//431 +f 432//432 426//426 425//425 +f 425//425 431//431 432//432 +f 433//433 427//427 426//426 +f 426//426 432//432 433//433 +f 434//434 428//428 295//295 +f 289//289 295//295 428//428 +f 435//435 429//429 434//434 +f 428//428 434//434 429//429 +f 436//436 430//430 435//435 +f 429//429 435//435 430//430 +f 437//437 431//431 436//436 +f 430//430 436//436 431//431 +f 438//438 432//432 437//437 +f 431//431 437//437 432//432 +f 439//439 433//433 438//438 +f 432//432 438//438 433//433 +f 440//440 434//434 301//301 +f 295//295 301//301 434//434 +f 441//441 435//435 440//440 +f 434//434 440//440 435//435 +f 442//442 436//436 441//441 +f 435//435 441//441 436//436 +f 443//443 437//437 442//442 +f 436//436 442//442 437//437 +f 444//444 438//438 443//443 +f 437//437 443//443 438//438 +f 445//445 439//439 444//444 +f 438//438 444//444 439//439 +f 446//446 440//440 307//307 +f 301//301 307//307 440//440 +f 447//447 441//441 446//446 +f 440//440 446//446 441//441 +f 448//448 442//442 447//447 +f 441//441 447//447 442//442 +f 449//449 443//443 448//448 +f 442//442 448//448 443//443 +f 450//450 444//444 449//449 +f 443//443 449//449 444//444 +f 451//451 445//445 450//450 +f 444//444 450//450 445//445 +f 452//452 446//446 307//307 +f 307//307 313//313 452//452 +f 453//453 447//447 446//446 +f 446//446 452//452 453//453 +f 454//454 448//448 447//447 +f 447//447 453//453 454//454 +f 455//455 449//449 448//448 +f 448//448 454//454 455//455 +f 456//456 450//450 449//449 +f 449//449 455//455 456//456 +f 457//457 451//451 450//450 +f 450//450 456//456 457//457 +f 458//458 452//452 313//313 +f 313//313 319//319 458//458 +f 459//459 453//453 452//452 +f 452//452 458//458 459//459 +f 460//460 454//454 453//453 +f 453//453 459//459 460//460 +f 461//461 455//455 454//454 +f 454//454 460//460 461//461 +f 462//462 456//456 455//455 +f 455//455 461//461 462//462 +f 463//463 457//457 456//456 +f 456//456 462//462 463//463 +f 464//464 458//458 319//319 +f 319//319 325//325 464//464 +f 465//465 459//459 458//458 +f 458//458 464//464 465//465 +f 466//466 460//460 459//459 +f 459//459 465//465 466//466 +f 467//467 461//461 460//460 +f 460//460 466//466 467//467 +f 468//468 462//462 461//461 +f 461//461 467//467 468//468 +f 469//469 463//463 462//462 +f 462//462 468//468 469//469 +f 470//470 464//464 331//331 +f 325//325 331//331 464//464 +f 471//471 465//465 470//470 +f 464//464 470//470 465//465 +f 472//472 466//466 471//471 +f 465//465 471//471 466//466 +f 473//473 467//467 472//472 +f 466//466 472//472 467//467 +f 474//474 468//468 473//473 +f 467//467 473//473 468//468 +f 475//475 469//469 474//474 +f 468//468 474//474 469//469 +f 476//476 470//470 337//337 +f 331//331 337//337 470//470 +f 477//477 471//471 476//476 +f 470//470 476//476 471//471 +f 478//478 472//472 477//477 +f 471//471 477//477 472//472 +f 479//479 473//473 478//478 +f 472//472 478//478 473//473 +f 480//480 474//474 479//479 +f 473//473 479//479 474//474 +f 481//481 475//475 480//480 +f 474//474 480//480 475//475 +f 339//339 476//476 205//205 +f 337//337 205//205 476//476 +f 341//341 477//477 339//339 +f 476//476 339//339 477//477 +f 343//343 478//478 341//341 +f 477//477 341//341 478//478 +f 345//345 479//479 343//343 +f 478//478 343//343 479//479 +f 347//347 480//480 345//345 +f 479//479 345//345 480//480 +f 349//349 481//481 347//347 +f 480//480 347//347 481//481 +f 1//1 3//3 482//482 +f 483//483 482//482 3//3 +f 482//482 483//483 484//484 +f 485//485 484//484 483//483 +f 484//484 485//485 486//486 +f 487//487 486//486 485//485 +f 486//486 487//487 488//488 +f 489//489 488//488 487//487 +f 488//488 489//489 349//349 +f 481//481 349//349 489//489 +f 3//3 4//4 483//483 +f 490//490 483//483 4//4 +f 483//483 490//490 485//485 +f 491//491 485//485 490//490 +f 485//485 491//491 487//487 +f 492//492 487//487 491//491 +f 487//487 492//492 489//489 +f 493//493 489//489 492//492 +f 489//489 493//493 481//481 +f 475//475 481//481 493//493 +f 4//4 5//5 490//490 +f 494//494 490//490 5//5 +f 490//490 494//494 491//491 +f 495//495 491//491 494//494 +f 491//491 495//495 492//492 +f 496//496 492//492 495//495 +f 492//492 496//496 493//493 +f 497//497 493//493 496//496 +f 493//493 497//497 475//475 +f 469//469 475//475 497//497 +f 5//5 6//6 498//498 +f 498//498 494//494 5//5 +f 494//494 498//498 499//499 +f 499//499 495//495 494//494 +f 495//495 499//499 500//500 +f 500//500 496//496 495//495 +f 496//496 500//500 501//501 +f 501//501 497//497 496//496 +f 497//497 501//501 463//463 +f 463//463 469//469 497//497 +f 6//6 7//7 502//502 +f 502//502 498//498 6//6 +f 498//498 502//502 503//503 +f 503//503 499//499 498//498 +f 499//499 503//503 504//504 +f 504//504 500//500 499//499 +f 500//500 504//504 505//505 +f 505//505 501//501 500//500 +f 501//501 505//505 457//457 +f 457//457 463//463 501//501 +f 7//7 8//8 506//506 +f 506//506 502//502 7//7 +f 502//502 506//506 507//507 +f 507//507 503//503 502//502 +f 503//503 507//507 508//508 +f 508//508 504//504 503//503 +f 504//504 508//508 509//509 +f 509//509 505//505 504//504 +f 505//505 509//509 451//451 +f 451//451 457//457 505//505 +f 8//8 9//9 506//506 +f 510//510 506//506 9//9 +f 506//506 510//510 507//507 +f 511//511 507//507 510//510 +f 507//507 511//511 508//508 +f 512//512 508//508 511//511 +f 508//508 512//512 509//509 +f 513//513 509//509 512//512 +f 509//509 513//513 451//451 +f 445//445 451//451 513//513 +f 9//9 10//10 510//510 +f 514//514 510//510 10//10 +f 510//510 514//514 511//511 +f 515//515 511//511 514//514 +f 511//511 515//515 512//512 +f 516//516 512//512 515//515 +f 512//512 516//516 513//513 +f 517//517 513//513 516//516 +f 513//513 517//517 445//445 +f 439//439 445//445 517//517 +f 10//10 11//11 514//514 +f 518//518 514//514 11//11 +f 514//514 518//518 515//515 +f 519//519 515//515 518//518 +f 515//515 519//519 516//516 +f 520//520 516//516 519//519 +f 516//516 520//520 517//517 +f 521//521 517//517 520//520 +f 517//517 521//521 439//439 +f 433//433 439//439 521//521 +f 11//11 12//12 522//522 +f 522//522 518//518 11//11 +f 518//518 522//522 523//523 +f 523//523 519//519 518//518 +f 519//519 523//523 524//524 +f 524//524 520//520 519//519 +f 520//520 524//524 525//525 +f 525//525 521//521 520//520 +f 521//521 525//525 427//427 +f 427//427 433//433 521//521 +f 12//12 13//13 526//526 +f 526//526 522//522 12//12 +f 522//522 526//526 527//527 +f 527//527 523//523 522//522 +f 523//523 527//527 528//528 +f 528//528 524//524 523//523 +f 524//524 528//528 529//529 +f 529//529 525//525 524//524 +f 525//525 529//529 421//421 +f 421//421 427//427 525//525 +f 13//13 14//14 530//530 +f 530//530 526//526 13//13 +f 526//526 530//530 531//531 +f 531//531 527//527 526//526 +f 527//527 531//531 532//532 +f 532//532 528//528 527//527 +f 528//528 532//532 533//533 +f 533//533 529//529 528//528 +f 529//529 533//533 415//415 +f 415//415 421//421 529//529 +f 14//14 15//15 530//530 +f 534//534 530//530 15//15 +f 530//530 534//534 531//531 +f 535//535 531//531 534//534 +f 531//531 535//535 532//532 +f 536//536 532//532 535//535 +f 532//532 536//536 533//533 +f 537//537 533//533 536//536 +f 533//533 537//537 415//415 +f 409//409 415//415 537//537 +f 15//15 16//16 534//534 +f 538//538 534//534 16//16 +f 534//534 538//538 535//535 +f 539//539 535//535 538//538 +f 535//535 539//539 536//536 +f 540//540 536//536 539//539 +f 536//536 540//540 537//537 +f 541//541 537//537 540//540 +f 537//537 541//541 409//409 +f 403//403 409//409 541//541 +f 16//16 17//17 538//538 +f 542//542 538//538 17//17 +f 538//538 542//542 539//539 +f 543//543 539//539 542//542 +f 539//539 543//543 540//540 +f 544//544 540//540 543//543 +f 540//540 544//544 541//541 +f 545//545 541//541 544//544 +f 541//541 545//545 403//403 +f 397//397 403//403 545//545 +f 17//17 18//18 546//546 +f 546//546 542//542 17//17 +f 542//542 546//546 547//547 +f 547//547 543//543 542//542 +f 543//543 547//547 548//548 +f 548//548 544//544 543//543 +f 544//544 548//548 549//549 +f 549//549 545//545 544//544 +f 545//545 549//549 391//391 +f 391//391 397//397 545//545 +f 18//18 19//19 550//550 +f 550//550 546//546 18//18 +f 546//546 550//550 551//551 +f 551//551 547//547 546//546 +f 547//547 551//551 552//552 +f 552//552 548//548 547//547 +f 548//548 552//552 553//553 +f 553//553 549//549 548//548 +f 549//549 553//553 385//385 +f 385//385 391//391 549//549 +f 19//19 20//20 554//554 +f 554//554 550//550 19//19 +f 550//550 554//554 555//555 +f 555//555 551//551 550//550 +f 551//551 555//555 556//556 +f 556//556 552//552 551//551 +f 552//552 556//556 557//557 +f 557//557 553//553 552//552 +f 553//553 557//557 379//379 +f 379//379 385//385 553//553 +f 20//20 21//21 554//554 +f 558//558 554//554 21//21 +f 554//554 558//558 555//555 +f 559//559 555//555 558//558 +f 555//555 559//559 556//556 +f 560//560 556//556 559//559 +f 556//556 560//560 557//557 +f 561//561 557//557 560//560 +f 557//557 561//561 379//379 +f 373//373 379//379 561//561 +f 21//21 22//22 558//558 +f 562//562 558//558 22//22 +f 558//558 562//562 559//559 +f 563//563 559//559 562//562 +f 559//559 563//563 560//560 +f 564//564 560//560 563//563 +f 560//560 564//564 561//561 +f 565//565 561//561 564//564 +f 561//561 565//565 373//373 +f 367//367 373//373 565//565 +f 22//22 23//23 562//562 +f 566//566 562//562 23//23 +f 562//562 566//566 563//563 +f 567//567 563//563 566//566 +f 563//563 567//567 564//564 +f 568//568 564//564 567//567 +f 564//564 568//568 565//565 +f 569//569 565//565 568//568 +f 565//565 569//569 367//367 +f 361//361 367//367 569//569 +f 23//23 24//24 570//570 +f 570//570 566//566 23//23 +f 566//566 570//570 571//571 +f 571//571 567//567 566//566 +f 567//567 571//571 572//572 +f 572//572 568//568 567//567 +f 568//568 572//572 573//573 +f 573//573 569//569 568//568 +f 569//569 573//573 355//355 +f 355//355 361//361 569//569 +f 24//24 25//25 574//574 +f 574//574 570//570 24//24 +f 570//570 574//574 575//575 +f 575//575 571//571 570//570 +f 571//571 575//575 576//576 +f 576//576 572//572 571//571 +f 572//572 576//576 577//577 +f 577//577 573//573 572//572 +f 573//573 577//577 348//348 +f 348//348 355//355 573//573 +f 25//25 1//1 482//482 +f 482//482 574//574 25//25 +f 574//574 482//482 484//484 +f 484//484 575//575 574//574 +f 575//575 484//484 486//486 +f 486//486 576//576 575//575 +f 576//576 486//486 488//488 +f 488//488 577//577 576//576 +f 577//577 488//488 349//349 +f 349//349 348//348 577//577 +f 578//578 579//579 580//580 +f 581//581 580//580 579//579 +f 582//582 578//578 583//583 +f 580//580 583//583 578//578 +f 584//584 582//582 585//585 +f 583//583 585//585 582//582 +f 586//586 584//584 585//585 +f 585//585 587//587 586//586 +f 588//588 586//586 587//587 +f 587//587 589//589 588//588 +f 590//590 588//588 589//589 +f 589//589 591//591 590//590 +f 592//592 590//590 593//593 +f 591//591 593//593 590//590 +f 594//594 592//592 595//595 +f 593//593 595//595 592//592 +f 596//596 594//594 597//597 +f 595//595 597//597 594//594 +f 598//598 596//596 597//597 +f 597//597 599//599 598//598 +f 600//600 598//598 599//599 +f 599//599 601//601 600//600 +f 602//602 600//600 601//601 +f 601//601 603//603 602//602 +f 604//604 602//602 605//605 +f 603//603 605//605 602//602 +f 606//606 604//604 607//607 +f 605//605 607//607 604//604 +f 608//608 606//606 609//609 +f 607//607 609//609 606//606 +f 610//610 608//608 609//609 +f 609//609 611//611 610//610 +f 612//612 610//610 611//611 +f 611//611 613//613 612//612 +f 614//614 612//612 613//613 +f 613//613 615//615 614//614 +f 616//616 614//614 617//617 +f 615//615 617//617 614//614 +f 618//618 616//616 619//619 +f 617//617 619//619 616//616 +f 620//620 618//618 621//621 +f 619//619 621//621 618//618 +f 622//622 620//620 621//621 +f 621//621 623//623 622//622 +f 624//624 622//622 623//623 +f 623//623 625//625 624//624 +f 579//579 624//624 625//625 +f 625//625 581//581 579//579 +f 626//626 627//627 578//578 +f 579//579 578//578 627//627 +f 628//628 629//629 626//626 +f 627//627 626//626 629//629 +f 630//630 631//631 628//628 +f 629//629 628//628 631//631 +f 632//632 633//633 630//630 +f 631//631 630//630 633//633 +f 634//634 635//635 632//632 +f 633//633 632//632 635//635 +f 636//636 637//637 634//634 +f 635//635 634//634 637//637 +f 638//638 626//626 582//582 +f 578//578 582//582 626//626 +f 639//639 628//628 638//638 +f 626//626 638//638 628//628 +f 640//640 630//630 639//639 +f 628//628 639//639 630//630 +f 641//641 632//632 640//640 +f 630//630 640//640 632//632 +f 642//642 634//634 641//641 +f 632//632 641//641 634//634 +f 643//643 636//636 642//642 +f 634//634 642//642 636//636 +f 644//644 638//638 584//584 +f 582//582 584//584 638//638 +f 645//645 639//639 644//644 +f 638//638 644//644 639//639 +f 646//646 640//640 645//645 +f 639//639 645//645 640//640 +f 647//647 641//641 646//646 +f 640//640 646//646 641//641 +f 648//648 642//642 647//647 +f 641//641 647//647 642//642 +f 649//649 643//643 648//648 +f 642//642 648//648 643//643 +f 650//650 644//644 584//584 +f 584//584 586//586 650//650 +f 651//651 645//645 644//644 +f 644//644 650//650 651//651 +f 652//652 646//646 645//645 +f 645//645 651//651 652//652 +f 653//653 647//647 646//646 +f 646//646 652//652 653//653 +f 654//654 648//648 647//647 +f 647//647 653//653 654//654 +f 655//655 649//649 648//648 +f 648//648 654//654 655//655 +f 656//656 650//650 586//586 +f 586//586 588//588 656//656 +f 657//657 651//651 650//650 +f 650//650 656//656 657//657 +f 658//658 652//652 651//651 +f 651//651 657//657 658//658 +f 659//659 653//653 652//652 +f 652//652 658//658 659//659 +f 660//660 654//654 653//653 +f 653//653 659//659 660//660 +f 661//661 655//655 654//654 +f 654//654 660//660 661//661 +f 662//662 656//656 588//588 +f 588//588 590//590 662//662 +f 663//663 657//657 656//656 +f 656//656 662//662 663//663 +f 664//664 658//658 657//657 +f 657//657 663//663 664//664 +f 665//665 659//659 658//658 +f 658//658 664//664 665//665 +f 666//666 660//660 659//659 +f 659//659 665//665 666//666 +f 667//667 661//661 660//660 +f 660//660 666//666 667//667 +f 668//668 662//662 592//592 +f 590//590 592//592 662//662 +f 669//669 663//663 668//668 +f 662//662 668//668 663//663 +f 670//670 664//664 669//669 +f 663//663 669//669 664//664 +f 671//671 665//665 670//670 +f 664//664 670//670 665//665 +f 672//672 666//666 671//671 +f 665//665 671//671 666//666 +f 673//673 667//667 672//672 +f 666//666 672//672 667//667 +f 674//674 668//668 594//594 +f 592//592 594//594 668//668 +f 675//675 669//669 674//674 +f 668//668 674//674 669//669 +f 676//676 670//670 675//675 +f 669//669 675//675 670//670 +f 677//677 671//671 676//676 +f 670//670 676//676 671//671 +f 678//678 672//672 677//677 +f 671//671 677//677 672//672 +f 679//679 673//673 678//678 +f 672//672 678//678 673//673 +f 680//680 674//674 596//596 +f 594//594 596//596 674//674 +f 681//681 675//675 680//680 +f 674//674 680//680 675//675 +f 682//682 676//676 681//681 +f 675//675 681//681 676//676 +f 683//683 677//677 682//682 +f 676//676 682//682 677//677 +f 684//684 678//678 683//683 +f 677//677 683//683 678//678 +f 685//685 679//679 684//684 +f 678//678 684//684 679//679 +f 686//686 680//680 596//596 +f 596//596 598//598 686//686 +f 687//687 681//681 680//680 +f 680//680 686//686 687//687 +f 688//688 682//682 681//681 +f 681//681 687//687 688//688 +f 689//689 683//683 682//682 +f 682//682 688//688 689//689 +f 690//690 684//684 683//683 +f 683//683 689//689 690//690 +f 691//691 685//685 684//684 +f 684//684 690//690 691//691 +f 692//692 686//686 598//598 +f 598//598 600//600 692//692 +f 693//693 687//687 686//686 +f 686//686 692//692 693//693 +f 694//694 688//688 687//687 +f 687//687 693//693 694//694 +f 695//695 689//689 688//688 +f 688//688 694//694 695//695 +f 696//696 690//690 689//689 +f 689//689 695//695 696//696 +f 697//697 691//691 690//690 +f 690//690 696//696 697//697 +f 698//698 692//692 600//600 +f 600//600 602//602 698//698 +f 699//699 693//693 692//692 +f 692//692 698//698 699//699 +f 700//700 694//694 693//693 +f 693//693 699//699 700//700 +f 701//701 695//695 694//694 +f 694//694 700//700 701//701 +f 702//702 696//696 695//695 +f 695//695 701//701 702//702 +f 703//703 697//697 696//696 +f 696//696 702//702 703//703 +f 704//704 698//698 604//604 +f 602//602 604//604 698//698 +f 705//705 699//699 704//704 +f 698//698 704//704 699//699 +f 706//706 700//700 705//705 +f 699//699 705//705 700//700 +f 707//707 701//701 706//706 +f 700//700 706//706 701//701 +f 708//708 702//702 707//707 +f 701//701 707//707 702//702 +f 709//709 703//703 708//708 +f 702//702 708//708 703//703 +f 710//710 704//704 606//606 +f 604//604 606//606 704//704 +f 711//711 705//705 710//710 +f 704//704 710//710 705//705 +f 712//712 706//706 711//711 +f 705//705 711//711 706//706 +f 713//713 707//707 712//712 +f 706//706 712//712 707//707 +f 714//714 708//708 713//713 +f 707//707 713//713 708//708 +f 715//715 709//709 714//714 +f 708//708 714//714 709//709 +f 716//716 710//710 608//608 +f 606//606 608//608 710//710 +f 717//717 711//711 716//716 +f 710//710 716//716 711//711 +f 718//718 712//712 717//717 +f 711//711 717//717 712//712 +f 719//719 713//713 718//718 +f 712//712 718//718 713//713 +f 720//720 714//714 719//719 +f 713//713 719//719 714//714 +f 721//721 715//715 720//720 +f 714//714 720//720 715//715 +f 722//722 716//716 608//608 +f 608//608 610//610 722//722 +f 723//723 717//717 716//716 +f 716//716 722//722 723//723 +f 724//724 718//718 717//717 +f 717//717 723//723 724//724 +f 725//725 719//719 718//718 +f 718//718 724//724 725//725 +f 726//726 720//720 719//719 +f 719//719 725//725 726//726 +f 727//727 721//721 720//720 +f 720//720 726//726 727//727 +f 728//728 722//722 610//610 +f 610//610 612//612 728//728 +f 729//729 723//723 722//722 +f 722//722 728//728 729//729 +f 730//730 724//724 723//723 +f 723//723 729//729 730//730 +f 731//731 725//725 724//724 +f 724//724 730//730 731//731 +f 732//732 726//726 725//725 +f 725//725 731//731 732//732 +f 733//733 727//727 726//726 +f 726//726 732//732 733//733 +f 734//734 728//728 612//612 +f 612//612 614//614 734//734 +f 735//735 729//729 728//728 +f 728//728 734//734 735//735 +f 736//736 730//730 729//729 +f 729//729 735//735 736//736 +f 737//737 731//731 730//730 +f 730//730 736//736 737//737 +f 738//738 732//732 731//731 +f 731//731 737//737 738//738 +f 739//739 733//733 732//732 +f 732//732 738//738 739//739 +f 740//740 734//734 616//616 +f 614//614 616//616 734//734 +f 741//741 735//735 740//740 +f 734//734 740//740 735//735 +f 742//742 736//736 741//741 +f 735//735 741//741 736//736 +f 743//743 737//737 742//742 +f 736//736 742//742 737//737 +f 744//744 738//738 743//743 +f 737//737 743//743 738//738 +f 745//745 739//739 744//744 +f 738//738 744//744 739//739 +f 746//746 740//740 618//618 +f 616//616 618//618 740//740 +f 747//747 741//741 746//746 +f 740//740 746//746 741//741 +f 748//748 742//742 747//747 +f 741//741 747//747 742//742 +f 749//749 743//743 748//748 +f 742//742 748//748 743//743 +f 750//750 744//744 749//749 +f 743//743 749//749 744//744 +f 751//751 745//745 750//750 +f 744//744 750//750 745//745 +f 752//752 746//746 620//620 +f 618//618 620//620 746//746 +f 753//753 747//747 752//752 +f 746//746 752//752 747//747 +f 754//754 748//748 753//753 +f 747//747 753//753 748//748 +f 755//755 749//749 754//754 +f 748//748 754//754 749//749 +f 756//756 750//750 755//755 +f 749//749 755//755 750//750 +f 757//757 751//751 756//756 +f 750//750 756//756 751//751 +f 758//758 752//752 620//620 +f 620//620 622//622 758//758 +f 759//759 753//753 752//752 +f 752//752 758//758 759//759 +f 760//760 754//754 753//753 +f 753//753 759//759 760//760 +f 761//761 755//755 754//754 +f 754//754 760//760 761//761 +f 762//762 756//756 755//755 +f 755//755 761//761 762//762 +f 763//763 757//757 756//756 +f 756//756 762//762 763//763 +f 764//764 758//758 622//622 +f 622//622 624//624 764//764 +f 765//765 759//759 758//758 +f 758//758 764//764 765//765 +f 766//766 760//760 759//759 +f 759//759 765//765 766//766 +f 767//767 761//761 760//760 +f 760//760 766//766 767//767 +f 768//768 762//762 761//761 +f 761//761 767//767 768//768 +f 769//769 763//763 762//762 +f 762//762 768//768 769//769 +f 627//627 764//764 624//624 +f 624//624 579//579 627//627 +f 629//629 765//765 764//764 +f 764//764 627//627 629//629 +f 631//631 766//766 765//765 +f 765//765 629//629 631//631 +f 633//633 767//767 766//766 +f 766//766 631//631 633//633 +f 635//635 768//768 767//767 +f 767//767 633//633 635//635 +f 637//637 769//769 768//768 +f 768//768 635//635 637//637 +f 770//770 771//771 772//772 +f 773//773 772//772 771//771 +f 774//774 770//770 775//775 +f 772//772 775//775 770//770 +f 776//776 777//777 774//774 +f 774//774 775//775 776//776 +f 778//778 779//779 776//776 +f 777//777 776//776 779//779 +f 780//780 781//781 778//778 +f 779//779 778//778 781//781 +f 782//782 783//783 780//780 +f 781//781 780//780 783//783 +f 772//772 773//773 784//784 +f 785//785 784//784 773//773 +f 775//775 772//772 786//786 +f 784//784 786//786 772//772 +f 776//776 775//775 787//787 +f 786//786 787//787 775//775 +f 788//788 778//778 776//776 +f 776//776 787//787 788//788 +f 789//789 780//780 788//788 +f 778//778 788//788 780//780 +f 790//790 782//782 789//789 +f 780//780 789//789 782//782 +f 784//784 785//785 791//791 +f 792//792 791//791 785//785 +f 786//786 784//784 793//793 +f 791//791 793//793 784//784 +f 787//787 786//786 794//794 +f 793//793 794//794 786//786 +f 795//795 788//788 787//787 +f 787//787 794//794 795//795 +f 796//796 789//789 795//795 +f 788//788 795//795 789//789 +f 797//797 790//790 796//796 +f 789//789 796//796 790//790 +f 791//791 792//792 798//798 +f 799//799 798//798 792//792 +f 793//793 791//791 800//800 +f 798//798 800//800 791//791 +f 794//794 793//793 801//801 +f 800//800 801//801 793//793 +f 802//802 795//795 794//794 +f 794//794 801//801 802//802 +f 803//803 796//796 802//802 +f 795//795 802//802 796//796 +f 804//804 797//797 803//803 +f 796//796 803//803 797//797 +f 798//798 799//799 805//805 +f 806//806 805//805 799//799 +f 800//800 798//798 807//807 +f 805//805 807//807 798//798 +f 801//801 800//800 808//808 +f 807//807 808//808 800//800 +f 809//809 802//802 801//801 +f 801//801 808//808 809//809 +f 810//810 803//803 809//809 +f 802//802 809//809 803//803 +f 811//811 804//804 810//810 +f 803//803 810//810 804//804 +f 805//805 806//806 812//812 +f 813//813 812//812 806//806 +f 807//807 805//805 814//814 +f 812//812 814//814 805//805 +f 815//815 808//808 814//814 +f 807//807 814//814 808//808 +f 816//816 809//809 815//815 +f 808//808 815//815 809//809 +f 817//817 810//810 816//816 +f 809//809 816//816 810//810 +f 818//818 811//811 817//817 +f 810//810 817//817 811//811 +f 819//819 820//820 812//812 +f 812//812 813//813 819//819 +f 820//820 821//821 814//814 +f 814//814 812//812 820//820 +f 822//822 815//815 814//814 +f 814//814 821//821 822//822 +f 823//823 816//816 815//815 +f 815//815 822//822 823//823 +f 824//824 817//817 816//816 +f 816//816 823//823 824//824 +f 825//825 818//818 817//817 +f 817//817 824//824 825//825 +f 826//826 827//827 820//820 +f 820//820 819//819 826//826 +f 827//827 828//828 821//821 +f 821//821 820//820 827//827 +f 828//828 829//829 822//822 +f 822//822 821//821 828//828 +f 830//830 823//823 829//829 +f 822//822 829//829 823//823 +f 831//831 824//824 823//823 +f 823//823 830//830 831//831 +f 832//832 825//825 824//824 +f 824//824 831//831 832//832 +f 833//833 834//834 827//827 +f 827//827 826//826 833//833 +f 834//834 835//835 828//828 +f 828//828 827//827 834//834 +f 835//835 836//836 829//829 +f 829//829 828//828 835//835 +f 837//837 830//830 836//836 +f 829//829 836//836 830//830 +f 838//838 831//831 830//830 +f 830//830 837//837 838//838 +f 839//839 832//832 831//831 +f 831//831 838//838 839//839 +f 840//840 841//841 834//834 +f 834//834 833//833 840//840 +f 841//841 842//842 835//835 +f 835//835 834//834 841//841 +f 842//842 843//843 836//836 +f 836//836 835//835 842//842 +f 844//844 837//837 843//843 +f 836//836 843//843 837//837 +f 845//845 838//838 837//837 +f 837//837 844//844 845//845 +f 846//846 839//839 838//838 +f 838//838 845//845 846//846 +f 847//847 848//848 841//841 +f 841//841 840//840 847//847 +f 848//848 849//849 842//842 +f 842//842 841//841 848//848 +f 849//849 850//850 843//843 +f 843//843 842//842 849//849 +f 851//851 844//844 850//850 +f 843//843 850//850 844//844 +f 852//852 845//845 844//844 +f 844//844 851//851 852//852 +f 853//853 846//846 845//845 +f 845//845 852//852 853//853 +f 771//771 770//770 848//848 +f 848//848 847//847 771//771 +f 770//770 774//774 849//849 +f 849//849 848//848 770//770 +f 777//777 850//850 774//774 +f 849//849 774//774 850//850 +f 779//779 851//851 850//850 +f 850//850 777//777 779//779 +f 781//781 852//852 851//851 +f 851//851 779//779 781//781 +f 783//783 853//853 852//852 +f 852//852 781//781 783//783 +f 854//854 855//855 782//782 +f 783//783 782//782 855//855 +f 856//856 857//857 855//855 +f 855//855 854//854 856//856 +f 858//858 859//859 857//857 +f 857//857 856//856 858//858 +f 860//860 861//861 859//859 +f 859//859 858//858 860//860 +f 862//862 863//863 861//861 +f 861//861 860//860 862//862 +f 864//864 865//865 863//863 +f 863//863 862//862 864//864 +f 866//866 854//854 782//782 +f 782//782 790//790 866//866 +f 867//867 856//856 854//854 +f 854//854 866//866 867//867 +f 868//868 858//858 856//856 +f 856//856 867//867 868//868 +f 869//869 860//860 858//858 +f 858//858 868//868 869//869 +f 870//870 862//862 860//860 +f 860//860 869//869 870//870 +f 871//871 864//864 870//870 +f 862//862 870//870 864//864 +f 872//872 866//866 790//790 +f 790//790 797//797 872//872 +f 873//873 867//867 866//866 +f 866//866 872//872 873//873 +f 874//874 868//868 867//867 +f 867//867 873//873 874//874 +f 875//875 869//869 874//874 +f 868//868 874//874 869//869 +f 876//876 870//870 875//875 +f 869//869 875//875 870//870 +f 877//877 871//871 876//876 +f 870//870 876//876 871//871 +f 878//878 872//872 797//797 +f 797//797 804//804 878//878 +f 879//879 873//873 872//872 +f 872//872 878//878 879//879 +f 880//880 874//874 873//873 +f 873//873 879//879 880//880 +f 881//881 875//875 880//880 +f 874//874 880//880 875//875 +f 882//882 876//876 881//881 +f 875//875 881//881 876//876 +f 883//883 877//877 882//882 +f 876//876 882//882 877//877 +f 884//884 878//878 804//804 +f 804//804 811//811 884//884 +f 885//885 879//879 878//878 +f 878//878 884//884 885//885 +f 886//886 880//880 879//879 +f 879//879 885//885 886//886 +f 887//887 881//881 880//880 +f 880//880 886//886 887//887 +f 888//888 882//882 881//881 +f 881//881 887//887 888//888 +f 889//889 883//883 888//888 +f 882//882 888//888 883//883 +f 890//890 884//884 811//811 +f 811//811 818//818 890//890 +f 891//891 885//885 884//884 +f 884//884 890//890 891//891 +f 892//892 886//886 885//885 +f 885//885 891//891 892//892 +f 893//893 887//887 886//886 +f 886//886 892//892 893//893 +f 894//894 888//888 887//887 +f 887//887 893//893 894//894 +f 895//895 889//889 888//888 +f 888//888 894//894 895//895 +f 896//896 890//890 825//825 +f 818//818 825//825 890//890 +f 897//897 891//891 896//896 +f 890//890 896//896 891//891 +f 898//898 892//892 897//897 +f 891//891 897//897 892//892 +f 899//899 893//893 898//898 +f 892//892 898//898 893//893 +f 900//900 894//894 899//899 +f 893//893 899//899 894//894 +f 901//901 895//895 900//900 +f 894//894 900//900 895//895 +f 902//902 896//896 832//832 +f 825//825 832//832 896//896 +f 903//903 897//897 902//902 +f 896//896 902//902 897//897 +f 904//904 898//898 903//903 +f 897//897 903//903 898//898 +f 905//905 899//899 904//904 +f 898//898 904//904 899//899 +f 906//906 900//900 905//905 +f 899//899 905//905 900//900 +f 907//907 901//901 900//900 +f 900//900 906//906 907//907 +f 908//908 902//902 839//839 +f 832//832 839//839 902//902 +f 909//909 903//903 908//908 +f 902//902 908//908 903//903 +f 910//910 904//904 909//909 +f 903//903 909//909 904//904 +f 911//911 905//905 904//904 +f 904//904 910//910 911//911 +f 912//912 906//906 905//905 +f 905//905 911//911 912//912 +f 913//913 907//907 906//906 +f 906//906 912//912 913//913 +f 914//914 908//908 846//846 +f 839//839 846//846 908//908 +f 915//915 909//909 914//914 +f 908//908 914//914 909//909 +f 916//916 910//910 915//915 +f 909//909 915//915 910//910 +f 917//917 911//911 910//910 +f 910//910 916//916 917//917 +f 918//918 912//912 911//911 +f 911//911 917//917 918//918 +f 919//919 913//913 912//912 +f 912//912 918//918 919//919 +f 920//920 914//914 853//853 +f 846//846 853//853 914//914 +f 921//921 915//915 920//920 +f 914//914 920//920 915//915 +f 922//922 916//916 921//921 +f 915//915 921//921 916//916 +f 923//923 917//917 922//922 +f 916//916 922//922 917//917 +f 924//924 918//918 923//923 +f 917//917 923//923 918//918 +f 925//925 919//919 918//918 +f 918//918 924//924 925//925 +f 855//855 920//920 853//853 +f 853//853 783//783 855//855 +f 857//857 921//921 855//855 +f 920//920 855//855 921//921 +f 859//859 922//922 857//857 +f 921//921 857//857 922//922 +f 861//861 923//923 859//859 +f 922//922 859//859 923//923 +f 863//863 924//924 861//861 +f 923//923 861//861 924//924 +f 865//865 925//925 863//863 +f 924//924 863//863 925//925 +f 926//926 927//927 928//928 +f 928//928 929//929 926//926 +f 929//929 928//928 930//930 +f 930//930 931//931 929//929 +f 931//931 930//930 932//932 +f 932//932 933//933 931//931 +f 933//933 932//932 934//934 +f 934//934 935//935 933//933 +f 935//935 934//934 936//936 +f 936//936 937//937 935//935 +f 937//937 936//936 938//938 +f 939//939 938//938 936//936 +f 940//940 941//941 927//927 +f 928//928 927//927 941//941 +f 928//928 941//941 942//942 +f 942//942 930//930 928//928 +f 930//930 942//942 943//943 +f 943//943 932//932 930//930 +f 932//932 943//943 944//944 +f 944//944 934//934 932//932 +f 934//934 944//944 936//936 +f 945//945 936//936 944//944 +f 936//936 945//945 939//939 +f 946//946 939//939 945//945 +f 947//947 948//948 940//940 +f 941//941 940//940 948//948 +f 941//941 948//948 949//949 +f 949//949 942//942 941//941 +f 942//942 949//949 943//943 +f 950//950 943//943 949//949 +f 943//943 950//950 944//944 +f 951//951 944//944 950//950 +f 944//944 951//951 945//945 +f 952//952 945//945 951//951 +f 945//945 952//952 946//946 +f 953//953 946//946 952//952 +f 954//954 955//955 947//947 +f 948//948 947//947 955//955 +f 948//948 955//955 956//956 +f 956//956 949//949 948//948 +f 949//949 956//956 950//950 +f 957//957 950//950 956//956 +f 950//950 957//957 951//951 +f 958//958 951//951 957//957 +f 951//951 958//958 952//952 +f 959//959 952//952 958//958 +f 952//952 959//959 953//953 +f 960//960 953//953 959//959 +f 954//954 961//961 962//962 +f 962//962 955//955 954//954 +f 955//955 962//962 956//956 +f 963//963 956//956 962//962 +f 956//956 963//963 957//957 +f 964//964 957//957 963//963 +f 957//957 964//964 958//958 +f 965//965 958//958 964//964 +f 958//958 965//965 959//959 +f 966//966 959//959 965//965 +f 959//959 966//966 960//960 +f 967//967 960//960 966//966 +f 961//961 968//968 962//962 +f 969//969 962//962 968//968 +f 962//962 969//969 963//963 +f 970//970 963//963 969//969 +f 963//963 970//970 964//964 +f 971//971 964//964 970//970 +f 964//964 971//971 965//965 +f 972//972 965//965 971//971 +f 965//965 972//972 966//966 +f 973//973 966//966 972//972 +f 966//966 973//973 967//967 +f 974//974 967//967 973//973 +f 968//968 975//975 976//976 +f 976//976 969//969 968//968 +f 969//969 976//976 977//977 +f 977//977 970//970 969//969 +f 970//970 977//977 978//978 +f 978//978 971//971 970//970 +f 971//971 978//978 979//979 +f 979//979 972//972 971//971 +f 972//972 979//979 980//980 +f 980//980 973//973 972//972 +f 973//973 980//980 981//981 +f 981//981 974//974 973//973 +f 975//975 982//982 976//976 +f 983//983 976//976 982//982 +f 976//976 983//983 984//984 +f 984//984 977//977 976//976 +f 977//977 984//984 985//985 +f 985//985 978//978 977//977 +f 978//978 985//985 986//986 +f 986//986 979//979 978//978 +f 979//979 986//986 987//987 +f 987//987 980//980 979//979 +f 980//980 987//987 988//988 +f 988//988 981//981 980//980 +f 983//983 982//982 989//989 +f 989//989 990//990 983//983 +f 983//983 990//990 984//984 +f 991//991 984//984 990//990 +f 984//984 991//991 992//992 +f 992//992 985//985 984//984 +f 985//985 992//992 993//993 +f 993//993 986//986 985//985 +f 986//986 993//993 994//994 +f 994//994 987//987 986//986 +f 987//987 994//994 995//995 +f 995//995 988//988 987//987 +f 990//990 989//989 996//996 +f 996//996 997//997 990//990 +f 990//990 997//997 991//991 +f 998//998 991//991 997//997 +f 991//991 998//998 999//999 +f 999//999 992//992 991//991 +f 992//992 999//999 1000//1000 +f 1000//1000 993//993 992//992 +f 993//993 1000//1000 1001//1001 +f 1001//1001 994//994 993//993 +f 994//994 1001//1001 1002//1002 +f 1002//1002 995//995 994//994 +f 997//997 996//996 1003//1003 +f 1003//1003 1004//1004 997//997 +f 997//997 1004//1004 998//998 +f 1005//1005 998//998 1004//1004 +f 998//998 1005//1005 999//999 +f 1006//1006 999//999 1005//1005 +f 999//999 1006//1006 1000//1000 +f 1007//1007 1000//1000 1006//1006 +f 1000//1000 1007//1007 1008//1008 +f 1008//1008 1001//1001 1000//1000 +f 1001//1001 1008//1008 1009//1009 +f 1009//1009 1002//1002 1001//1001 +f 1003//1003 926//926 1004//1004 +f 929//929 1004//1004 926//926 +f 1004//1004 929//929 1005//1005 +f 931//931 1005//1005 929//929 +f 1005//1005 931//931 1006//1006 +f 933//933 1006//1006 931//931 +f 1006//1006 933//933 1007//1007 +f 935//935 1007//1007 933//933 +f 1007//1007 935//935 1008//1008 +f 937//937 1008//1008 935//935 +f 1008//1008 937//937 938//938 +f 938//938 1009//1009 1008//1008 +f 938//938 939//939 1010//1010 +f 1011//1011 1010//1010 939//939 +f 1010//1010 1011//1011 1012//1012 +f 1013//1013 1012//1012 1011//1011 +f 1012//1012 1013//1013 1014//1014 +f 1015//1015 1014//1014 1013//1013 +f 1016//1016 1017//1017 1014//1014 +f 1014//1014 1015//1015 1016//1016 +f 1018//1018 1019//1019 1017//1017 +f 1017//1017 1016//1016 1018//1018 +f 1020//1020 1021//1021 1019//1019 +f 1019//1019 1018//1018 1020//1020 +f 939//939 946//946 1011//1011 +f 1022//1022 1011//1011 946//946 +f 1011//1011 1022//1022 1013//1013 +f 1023//1023 1013//1013 1022//1022 +f 1013//1013 1023//1023 1015//1015 +f 1024//1024 1015//1015 1023//1023 +f 1025//1025 1016//1016 1015//1015 +f 1015//1015 1024//1024 1025//1025 +f 1026//1026 1018//1018 1016//1016 +f 1016//1016 1025//1025 1026//1026 +f 1027//1027 1020//1020 1018//1018 +f 1018//1018 1026//1026 1027//1027 +f 946//946 953//953 1022//1022 +f 1028//1028 1022//1022 953//953 +f 1022//1022 1028//1028 1023//1023 +f 1029//1029 1023//1023 1028//1028 +f 1023//1023 1029//1029 1024//1024 +f 1030//1030 1024//1024 1029//1029 +f 1031//1031 1025//1025 1024//1024 +f 1024//1024 1030//1030 1031//1031 +f 1032//1032 1026//1026 1025//1025 +f 1025//1025 1031//1031 1032//1032 +f 1033//1033 1027//1027 1026//1026 +f 1026//1026 1032//1032 1033//1033 +f 953//953 960//960 1028//1028 +f 1034//1034 1028//1028 960//960 +f 1028//1028 1034//1034 1029//1029 +f 1035//1035 1029//1029 1034//1034 +f 1029//1029 1035//1035 1030//1030 +f 1036//1036 1030//1030 1035//1035 +f 1037//1037 1031//1031 1030//1030 +f 1030//1030 1036//1036 1037//1037 +f 1038//1038 1032//1032 1031//1031 +f 1031//1031 1037//1037 1038//1038 +f 1039//1039 1033//1033 1032//1032 +f 1032//1032 1038//1038 1039//1039 +f 960//960 967//967 1034//1034 +f 1040//1040 1034//1034 967//967 +f 1034//1034 1040//1040 1035//1035 +f 1041//1041 1035//1035 1040//1040 +f 1035//1035 1041//1041 1036//1036 +f 1042//1042 1036//1036 1041//1041 +f 1043//1043 1037//1037 1036//1036 +f 1036//1036 1042//1042 1043//1043 +f 1044//1044 1038//1038 1037//1037 +f 1037//1037 1043//1043 1044//1044 +f 1045//1045 1039//1039 1038//1038 +f 1038//1038 1044//1044 1045//1045 +f 967//967 974//974 1040//1040 +f 1046//1046 1040//1040 974//974 +f 1040//1040 1046//1046 1041//1041 +f 1047//1047 1041//1041 1046//1046 +f 1041//1041 1047//1047 1042//1042 +f 1048//1048 1042//1042 1047//1047 +f 1049//1049 1043//1043 1042//1042 +f 1042//1042 1048//1048 1049//1049 +f 1050//1050 1044//1044 1043//1043 +f 1043//1043 1049//1049 1050//1050 +f 1051//1051 1045//1045 1044//1044 +f 1044//1044 1050//1050 1051//1051 +f 974//974 981//981 1052//1052 +f 1052//1052 1046//1046 974//974 +f 1046//1046 1052//1052 1053//1053 +f 1053//1053 1047//1047 1046//1046 +f 1047//1047 1053//1053 1054//1054 +f 1054//1054 1048//1048 1047//1047 +f 1055//1055 1049//1049 1054//1054 +f 1048//1048 1054//1054 1049//1049 +f 1056//1056 1050//1050 1055//1055 +f 1049//1049 1055//1055 1050//1050 +f 1057//1057 1051//1051 1056//1056 +f 1050//1050 1056//1056 1051//1051 +f 981//981 988//988 1058//1058 +f 1058//1058 1052//1052 981//981 +f 1052//1052 1058//1058 1059//1059 +f 1059//1059 1053//1053 1052//1052 +f 1053//1053 1059//1059 1060//1060 +f 1060//1060 1054//1054 1053//1053 +f 1061//1061 1055//1055 1060//1060 +f 1054//1054 1060//1060 1055//1055 +f 1062//1062 1056//1056 1061//1061 +f 1055//1055 1061//1061 1056//1056 +f 1063//1063 1057//1057 1062//1062 +f 1056//1056 1062//1062 1057//1057 +f 988//988 995//995 1064//1064 +f 1064//1064 1058//1058 988//988 +f 1058//1058 1064//1064 1065//1065 +f 1065//1065 1059//1059 1058//1058 +f 1059//1059 1065//1065 1066//1066 +f 1066//1066 1060//1060 1059//1059 +f 1067//1067 1061//1061 1066//1066 +f 1060//1060 1066//1066 1061//1061 +f 1068//1068 1062//1062 1067//1067 +f 1061//1061 1067//1067 1062//1062 +f 1069//1069 1063//1063 1068//1068 +f 1062//1062 1068//1068 1063//1063 +f 995//995 1002//1002 1070//1070 +f 1070//1070 1064//1064 995//995 +f 1064//1064 1070//1070 1071//1071 +f 1071//1071 1065//1065 1064//1064 +f 1065//1065 1071//1071 1072//1072 +f 1072//1072 1066//1066 1065//1065 +f 1073//1073 1067//1067 1072//1072 +f 1066//1066 1072//1072 1067//1067 +f 1074//1074 1068//1068 1073//1073 +f 1067//1067 1073//1073 1068//1068 +f 1075//1075 1069//1069 1074//1074 +f 1068//1068 1074//1074 1069//1069 +f 1002//1002 1009//1009 1076//1076 +f 1076//1076 1070//1070 1002//1002 +f 1070//1070 1076//1076 1077//1077 +f 1077//1077 1071//1071 1070//1070 +f 1071//1071 1077//1077 1078//1078 +f 1078//1078 1072//1072 1071//1071 +f 1079//1079 1073//1073 1078//1078 +f 1072//1072 1078//1078 1073//1073 +f 1080//1080 1074//1074 1079//1079 +f 1073//1073 1079//1079 1074//1074 +f 1081//1081 1075//1075 1080//1080 +f 1074//1074 1080//1080 1075//1075 +f 1009//1009 938//938 1010//1010 +f 1010//1010 1076//1076 1009//1009 +f 1076//1076 1010//1010 1012//1012 +f 1012//1012 1077//1077 1076//1076 +f 1077//1077 1012//1012 1014//1014 +f 1014//1014 1078//1078 1077//1077 +f 1017//1017 1079//1079 1014//1014 +f 1078//1078 1014//1014 1079//1079 +f 1019//1019 1080//1080 1017//1017 +f 1079//1079 1017//1017 1080//1080 +f 1021//1021 1081//1081 1019//1019 +f 1080//1080 1019//1019 1081//1081 +f 1082//1082 1083//1083 1084//1084 +f 1084//1084 1083//1083 1085//1085 +f 1085//1085 1083//1083 1086//1086 +f 1086//1086 1083//1083 1087//1087 +f 1087//1087 1083//1083 1088//1088 +f 1088//1088 1083//1083 1089//1089 +f 1089//1089 1083//1083 1090//1090 +f 1090//1090 1083//1083 1091//1091 +f 1091//1091 1083//1083 1092//1092 +f 1092//1092 1083//1083 1093//1093 +f 1093//1093 1083//1083 1094//1094 +f 1094//1094 1083//1083 1095//1095 +f 1095//1095 1083//1083 1096//1096 +f 1096//1096 1083//1083 1097//1097 +f 1097//1097 1083//1083 1098//1098 +f 1098//1098 1083//1083 1099//1099 +f 1099//1099 1083//1083 1100//1100 +f 1100//1100 1083//1083 1101//1101 +f 1101//1101 1083//1083 1102//1102 +f 1102//1102 1083//1083 1103//1103 +f 1103//1103 1083//1083 1104//1104 +f 1104//1104 1083//1083 1105//1105 +f 1105//1105 1083//1083 1106//1106 +f 1106//1106 1083//1083 1082//1082 +f 1107//1107 1108//1108 1084//1084 +f 1082//1082 1084//1084 1108//1108 +f 1109//1109 1110//1110 1108//1108 +f 1108//1108 1107//1107 1109//1109 +f 1111//1111 1112//1112 1110//1110 +f 1110//1110 1109//1109 1111//1111 +f 1113//1113 1114//1114 1112//1112 +f 1112//1112 1111//1111 1113//1113 +f 1115//1115 1107//1107 1085//1085 +f 1084//1084 1085//1085 1107//1107 +f 1116//1116 1109//1109 1107//1107 +f 1107//1107 1115//1115 1116//1116 +f 1117//1117 1111//1111 1109//1109 +f 1109//1109 1116//1116 1117//1117 +f 1118//1118 1113//1113 1111//1111 +f 1111//1111 1117//1117 1118//1118 +f 1119//1119 1115//1115 1086//1086 +f 1085//1085 1086//1086 1115//1115 +f 1120//1120 1116//1116 1115//1115 +f 1115//1115 1119//1119 1120//1120 +f 1121//1121 1117//1117 1116//1116 +f 1116//1116 1120//1120 1121//1121 +f 1122//1122 1118//1118 1117//1117 +f 1117//1117 1121//1121 1122//1122 +f 1123//1123 1119//1119 1086//1086 +f 1086//1086 1087//1087 1123//1123 +f 1124//1124 1120//1120 1123//1123 +f 1119//1119 1123//1123 1120//1120 +f 1125//1125 1121//1121 1124//1124 +f 1120//1120 1124//1124 1121//1121 +f 1126//1126 1122//1122 1125//1125 +f 1121//1121 1125//1125 1122//1122 +f 1127//1127 1123//1123 1087//1087 +f 1087//1087 1088//1088 1127//1127 +f 1128//1128 1124//1124 1127//1127 +f 1123//1123 1127//1127 1124//1124 +f 1129//1129 1125//1125 1128//1128 +f 1124//1124 1128//1128 1125//1125 +f 1130//1130 1126//1126 1129//1129 +f 1125//1125 1129//1129 1126//1126 +f 1131//1131 1127//1127 1088//1088 +f 1088//1088 1089//1089 1131//1131 +f 1132//1132 1128//1128 1131//1131 +f 1127//1127 1131//1131 1128//1128 +f 1133//1133 1129//1129 1132//1132 +f 1128//1128 1132//1132 1129//1129 +f 1134//1134 1130//1130 1133//1133 +f 1129//1129 1133//1133 1130//1130 +f 1135//1135 1131//1131 1090//1090 +f 1089//1089 1090//1090 1131//1131 +f 1136//1136 1132//1132 1131//1131 +f 1131//1131 1135//1135 1136//1136 +f 1137//1137 1133//1133 1132//1132 +f 1132//1132 1136//1136 1137//1137 +f 1138//1138 1134//1134 1133//1133 +f 1133//1133 1137//1137 1138//1138 +f 1139//1139 1135//1135 1091//1091 +f 1090//1090 1091//1091 1135//1135 +f 1140//1140 1136//1136 1135//1135 +f 1135//1135 1139//1139 1140//1140 +f 1141//1141 1137//1137 1136//1136 +f 1136//1136 1140//1140 1141//1141 +f 1142//1142 1138//1138 1137//1137 +f 1137//1137 1141//1141 1142//1142 +f 1143//1143 1139//1139 1092//1092 +f 1091//1091 1092//1092 1139//1139 +f 1144//1144 1140//1140 1139//1139 +f 1139//1139 1143//1143 1144//1144 +f 1145//1145 1141//1141 1140//1140 +f 1140//1140 1144//1144 1145//1145 +f 1146//1146 1142//1142 1141//1141 +f 1141//1141 1145//1145 1146//1146 +f 1147//1147 1143//1143 1092//1092 +f 1092//1092 1093//1093 1147//1147 +f 1148//1148 1144//1144 1147//1147 +f 1143//1143 1147//1147 1144//1144 +f 1149//1149 1145//1145 1148//1148 +f 1144//1144 1148//1148 1145//1145 +f 1150//1150 1146//1146 1149//1149 +f 1145//1145 1149//1149 1146//1146 +f 1151//1151 1147//1147 1093//1093 +f 1093//1093 1094//1094 1151//1151 +f 1152//1152 1148//1148 1151//1151 +f 1147//1147 1151//1151 1148//1148 +f 1153//1153 1149//1149 1152//1152 +f 1148//1148 1152//1152 1149//1149 +f 1154//1154 1150//1150 1153//1153 +f 1149//1149 1153//1153 1150//1150 +f 1155//1155 1151//1151 1094//1094 +f 1094//1094 1095//1095 1155//1155 +f 1156//1156 1152//1152 1155//1155 +f 1151//1151 1155//1155 1152//1152 +f 1157//1157 1153//1153 1156//1156 +f 1152//1152 1156//1156 1153//1153 +f 1158//1158 1154//1154 1157//1157 +f 1153//1153 1157//1157 1154//1154 +f 1159//1159 1155//1155 1096//1096 +f 1095//1095 1096//1096 1155//1155 +f 1160//1160 1156//1156 1155//1155 +f 1155//1155 1159//1159 1160//1160 +f 1161//1161 1157//1157 1156//1156 +f 1156//1156 1160//1160 1161//1161 +f 1162//1162 1158//1158 1157//1157 +f 1157//1157 1161//1161 1162//1162 +f 1163//1163 1159//1159 1097//1097 +f 1096//1096 1097//1097 1159//1159 +f 1164//1164 1160//1160 1159//1159 +f 1159//1159 1163//1163 1164//1164 +f 1165//1165 1161//1161 1160//1160 +f 1160//1160 1164//1164 1165//1165 +f 1166//1166 1162//1162 1161//1161 +f 1161//1161 1165//1165 1166//1166 +f 1167//1167 1163//1163 1098//1098 +f 1097//1097 1098//1098 1163//1163 +f 1168//1168 1164//1164 1163//1163 +f 1163//1163 1167//1167 1168//1168 +f 1169//1169 1165//1165 1164//1164 +f 1164//1164 1168//1168 1169//1169 +f 1170//1170 1166//1166 1165//1165 +f 1165//1165 1169//1169 1170//1170 +f 1171//1171 1167//1167 1098//1098 +f 1098//1098 1099//1099 1171//1171 +f 1172//1172 1168//1168 1171//1171 +f 1167//1167 1171//1171 1168//1168 +f 1173//1173 1169//1169 1172//1172 +f 1168//1168 1172//1172 1169//1169 +f 1174//1174 1170//1170 1173//1173 +f 1169//1169 1173//1173 1170//1170 +f 1175//1175 1171//1171 1099//1099 +f 1099//1099 1100//1100 1175//1175 +f 1176//1176 1172//1172 1175//1175 +f 1171//1171 1175//1175 1172//1172 +f 1177//1177 1173//1173 1176//1176 +f 1172//1172 1176//1176 1173//1173 +f 1178//1178 1174//1174 1177//1177 +f 1173//1173 1177//1177 1174//1174 +f 1179//1179 1175//1175 1100//1100 +f 1100//1100 1101//1101 1179//1179 +f 1180//1180 1176//1176 1179//1179 +f 1175//1175 1179//1179 1176//1176 +f 1181//1181 1177//1177 1180//1180 +f 1176//1176 1180//1180 1177//1177 +f 1182//1182 1178//1178 1181//1181 +f 1177//1177 1181//1181 1178//1178 +f 1183//1183 1179//1179 1102//1102 +f 1101//1101 1102//1102 1179//1179 +f 1184//1184 1180//1180 1179//1179 +f 1179//1179 1183//1183 1184//1184 +f 1185//1185 1181//1181 1180//1180 +f 1180//1180 1184//1184 1185//1185 +f 1186//1186 1182//1182 1181//1181 +f 1181//1181 1185//1185 1186//1186 +f 1187//1187 1183//1183 1103//1103 +f 1102//1102 1103//1103 1183//1183 +f 1188//1188 1184//1184 1183//1183 +f 1183//1183 1187//1187 1188//1188 +f 1189//1189 1185//1185 1184//1184 +f 1184//1184 1188//1188 1189//1189 +f 1190//1190 1186//1186 1185//1185 +f 1185//1185 1189//1189 1190//1190 +f 1191//1191 1187//1187 1104//1104 +f 1103//1103 1104//1104 1187//1187 +f 1192//1192 1188//1188 1187//1187 +f 1187//1187 1191//1191 1192//1192 +f 1193//1193 1189//1189 1188//1188 +f 1188//1188 1192//1192 1193//1193 +f 1194//1194 1190//1190 1189//1189 +f 1189//1189 1193//1193 1194//1194 +f 1195//1195 1191//1191 1104//1104 +f 1104//1104 1105//1105 1195//1195 +f 1196//1196 1192//1192 1195//1195 +f 1191//1191 1195//1195 1192//1192 +f 1197//1197 1193//1193 1196//1196 +f 1192//1192 1196//1196 1193//1193 +f 1198//1198 1194//1194 1197//1197 +f 1193//1193 1197//1197 1194//1194 +f 1199//1199 1195//1195 1105//1105 +f 1105//1105 1106//1106 1199//1199 +f 1200//1200 1196//1196 1199//1199 +f 1195//1195 1199//1199 1196//1196 +f 1201//1201 1197//1197 1200//1200 +f 1196//1196 1200//1200 1197//1197 +f 1202//1202 1198//1198 1201//1201 +f 1197//1197 1201//1201 1198//1198 +f 1108//1108 1199//1199 1106//1106 +f 1106//1106 1082//1082 1108//1108 +f 1110//1110 1200//1200 1108//1108 +f 1199//1199 1108//1108 1200//1200 +f 1112//1112 1201//1201 1110//1110 +f 1200//1200 1110//1110 1201//1201 +f 1114//1114 1202//1202 1112//1112 +f 1201//1201 1112//1112 1202//1202 diff --git a/src/Drawie2Sample/Assets/textures/diffuse.png b/src/Drawie2Sample/Assets/textures/diffuse.png new file mode 100644 index 0000000..22decf0 Binary files /dev/null and b/src/Drawie2Sample/Assets/textures/diffuse.png differ diff --git a/src/Drawie2Sample/Drawie2Sample.csproj b/src/Drawie2Sample/Drawie2Sample.csproj new file mode 100644 index 0000000..eb6ca87 --- /dev/null +++ b/src/Drawie2Sample/Drawie2Sample.csproj @@ -0,0 +1,23 @@ + + + + net10.0 + enable + enable + Drawie2Sample + + + + + + + + + + + + PreserveNewest + + + + diff --git a/src/Drawie2Sample/Drawie2SampleApp.cs b/src/Drawie2Sample/Drawie2SampleApp.cs new file mode 100644 index 0000000..3c1f28c --- /dev/null +++ b/src/Drawie2Sample/Drawie2SampleApp.cs @@ -0,0 +1,176 @@ +using System.Numerics; +using Drawie.Backend.Core.ColorsImpl; +using Drawie.Backend.Core.Surfaces.PaintImpl; +using Drawie.Backend.Vertie.Core; +using Drawie.Backend.Vertie.Helpers; +using Drawie.Host; +using Drawie.Host.Input; +using Drawie.Layer.UI.MiniUi; +using Drawie.Layer.UI.MiniUi.Controls; +using Drawie.Numerics; +using DrawiEngine; +using ImGuiNET; +using Label = Drawie.Layer.UI.MiniUi.Controls.Label; + +namespace Drawie2Sample; + +public class Drawie2SampleApp : DrawieApp +{ + private IHost window; + + private static Camera camera; + private static VecD lastMousePosition; + private int activeRenderMode = 0; + private bool handleMovement; + + private string[] renderModes = new[] + { + "Default", + "Wireframe", + }; + + private RenderOptions renderOptions = new RenderOptions() { MsaaSamples = MsaaSamples.X4 }; + + public override IHost CreateMainWindow() + { + window = Engine.WindowingPlatform.CreateWindow("Drawie 2 Sample", new VecI(1920, 1080)); + //window.AddLayer(new ImGuiLayer(RenderImGui)); + window.AddLayer(new MiniUILayer(RenderMiniUi)); + return window; + } + + private void RenderMiniUi(double dt) + { + if (CollapsableGroup.Begin("debug", "Debug")) + { + Panel.BeginColumn(); + + Panel.BeginRow(); + Label.Show($"FPS: {1f / dt:F1}"); + Panel.EndRow(); + + string text = renderOptions.RenderMode == RenderMode.Default ? "Enable wireframe" : "Enable solid fill"; + if (Button.Show(text)) + { + renderOptions.RenderMode = renderOptions.RenderMode == RenderMode.Default + ? RenderMode.Wireframe + : RenderMode.Default; + } + + Panel.EndColumn(); + CollapsableGroup.End(); + } + } + + private void RenderImGui(double dt) + { + ImGui.BeginGroup(); + if (ImGui.Combo("Render Mode", ref activeRenderMode, renderModes, renderModes.Length)) + { + renderOptions.RenderMode = (RenderMode)activeRenderMode; + } + + ImGui.EndGroup(); + } + + protected override void OnInitialize() + { + handleMovement = true; + + window.InputController.PrimaryPointer.Cursor.State = CursorState.Disabled; + window.InputController.PrimaryKeyboard.KeyPressed += (keyboard, key, code) => + { + if (key == Key.Escape) + { + window.InputController.PrimaryPointer.Cursor.State = + window.InputController.PrimaryPointer.Cursor.State == CursorState.Disabled + ? CursorState.Normal + : CursorState.Disabled; + handleMovement = !handleMovement; + } + }; + + camera = new Camera(new Vector3(0, 0, 5), Vector3.UnitZ, Vector3.UnitY, (float)window.Size.X / window.Size.Y); + + //"Shiba" (https://skfb.ly/6WxVW) by zixisun02 is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/). + Scene scene = new Scene("Assets/shiba.fbx", Path.Combine("Assets", "textures")); + + foreach (var sceneMesh in scene.Meshes) + { + sceneMesh.Transform.Rotation = Quaternion.CreateFromAxisAngle(Vector3.UnitX, float.DegreesToRadians(-90)); + } + + RegisterMouse(window.InputController); + + window.Update += d => + { + camera.AspectRatio = (float)window.Size.X / window.Size.Y; + HandleMovement((float)d, camera, window.InputController.PrimaryKeyboard); + }; + + window.Render += (targetTexture, deltaTime) => + { + targetTexture.Clear(); + targetTexture.DrawScene(scene, camera, renderOptions); + }; + } + + private void RegisterMouse(InputController input) + { + for (int i = 0; i < input.Pointers.Count; i++) + { + var mouse = input.Pointers[i]; + mouse.PointerMoved += OnMouseMove; + mouse.PointerScrolled += OnScroll; + } + } + + private void OnScroll(IPointer pointer, VecD scrollDelta) + { + if (!handleMovement) return; + camera.Zoom = (float)scrollDelta.Y; + } + + private void OnMouseMove(IPointer pointer, VecD position) + { + if (!handleMovement) return; + float lookSensitivity = 0.1f; + if (lastMousePosition == default) + { + lastMousePosition = position; + } + else + { + double offsetX = (position.X - lastMousePosition.X) * lookSensitivity; + double offsetY = (position.Y - lastMousePosition.Y) * lookSensitivity; + lastMousePosition = position; + + camera.SetDirection((float)offsetX, (float)offsetY); + } + } + + private void HandleMovement(float deltaTime, Camera camera, IKeyboard primaryKeyboard) + { + if (!handleMovement) return; + float moveSpeed = 5f * (float)deltaTime; + if (primaryKeyboard.IsKeyPressed(Key.W)) + { + camera.Position += moveSpeed * camera.Forward; + } + + if (primaryKeyboard.IsKeyPressed(Key.S)) + { + camera.Position -= moveSpeed * camera.Forward; + } + + if (primaryKeyboard.IsKeyPressed(Key.A)) + { + camera.Position -= Vector3.Normalize(Vector3.Cross(camera.Forward, camera.Up)) * moveSpeed; + } + + if (primaryKeyboard.IsKeyPressed(Key.D)) + { + camera.Position += Vector3.Normalize(Vector3.Cross(camera.Forward, camera.Up)) * moveSpeed; + } + } +} \ No newline at end of file diff --git a/src/DrawieSample.Browser/DrawieSample.Browser.csproj b/src/DrawieSample.Browser/DrawieSample.Browser.csproj index a5ac3c1..c1a0db7 100644 --- a/src/DrawieSample.Browser/DrawieSample.Browser.csproj +++ b/src/DrawieSample.Browser/DrawieSample.Browser.csproj @@ -1,26 +1,24 @@ - net8.0-browser + net10.0-browser true - true + true $(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)\SkiaSharpGLInterop.js" true true + - - - PreserveNewest - + - + diff --git a/src/DrawieSample.Browser/Program.cs b/src/DrawieSample.Browser/Program.cs index 33fc9b4..5de4216 100644 --- a/src/DrawieSample.Browser/Program.cs +++ b/src/DrawieSample.Browser/Program.cs @@ -1,6 +1,6 @@ +using Drawie2Sample; using DrawiEngine; using DrawiEngine.Browser; -using DrawieSample; public static class Program { @@ -8,7 +8,7 @@ public static void Main() { DrawingEngine engine = BrowserDrawingEngine.CreateDefaultBrowser(); - DrawieSampleApp sampleApp = new DrawieSampleApp(); + Drawie2SampleApp sampleApp = new Drawie2SampleApp(); engine.RunWithApp(sampleApp); } diff --git a/src/DrawieSample.Browser/wwwroot/main.js b/src/DrawieSample.Browser/wwwroot/main.js index bd3aa1c..5cb4690 100644 --- a/src/DrawieSample.Browser/wwwroot/main.js +++ b/src/DrawieSample.Browser/wwwroot/main.js @@ -4,7 +4,7 @@ import {dotnet} from './_framework/dotnet.js' import {Drawie} from "./scripts/drawie.js"; -const {setModuleImports, getAssemblyExports, getConfig} = await dotnet +const {setModuleImports, getAssemblyExports, getConfig, runMain} = await dotnet .withDiagnosticTracing(false) .withApplicationArgumentsFromQuery() .create(); @@ -15,4 +15,4 @@ drawie.addDrawieImports(); await drawie.addDrawieExports(); const config = getConfig(); -await dotnet.run(); \ No newline at end of file +await runMain(); \ No newline at end of file diff --git a/src/DrawieSample.Browser/wwwroot/scripts/drawie.js b/src/DrawieSample.Browser/wwwroot/scripts/drawie.js index c0d595c..35218df 100644 --- a/src/DrawieSample.Browser/wwwroot/scripts/drawie.js +++ b/src/DrawieSample.Browser/wwwroot/scripts/drawie.js @@ -1,5 +1,6 @@ export class Drawie { canvasContextHandles = {}; + canvasContextIds = 0; shaderHandleIds = 0; shaderHandles = {}; @@ -13,9 +14,21 @@ textureHandleIds = 0; textureHandles = {}; + samplerIds = 0; + samplerHandles = {} + + framebufferIds = 0; + framebufferHandles = {} + uniformLocationHandleIds = 0; uniformLocationHandles = {}; + + vertexArrayIds = 0; + vertexArrayHandles = {} + renderbufferIds = 0; + renderbufferHandles = {} + exports = {}; addDrawieImports() { @@ -50,6 +63,44 @@ return null; }, + viewport: (handleId, x, y, width, height) => { + const gl = this.canvasContextHandles[handleId]; + gl.viewport(x, y, width, height); + }, + createFramebuffer: (glHandle) => { + const gl = this.canvasContextHandles[glHandle]; + const webGlFramebuffer = gl.createFramebuffer(); + this.framebufferIds++; + this.framebufferHandles[this.framebufferIds] = webGlFramebuffer; + return this.framebufferIds; + }, + bindFramebuffer: (handleId, target, framebuffer) => { + const gl = this.canvasContextHandles[handleId]; + if(framebuffer === 0) { + gl.bindFramebuffer(target, null); + return; + } + const fb = this.framebufferHandles[framebuffer]; + gl.bindFramebuffer(target, fb); + }, + framebufferTexture2D: (glHandle, target, attachment, textarget, texture, level) => { + const gl = this.canvasContextHandles[glHandle]; + const targetTexture = this.textureHandles[texture] + gl.framebufferTexture2D(target, attachment, textarget, targetTexture, level); + }, + checkFramebufferStatus: (glHandle, target) => { + const gl = this.canvasContextHandles[glHandle]; + return gl.checkFramebufferStatus(target); + }, + getError: (glHandle) => { + const gl = this.canvasContextHandles[glHandle]; + return gl.getError(); + }, + deleteFramebuffer: (glHandle, framebuffer) => { + const gl = this.canvasContextHandles[glHandle]; + gl.deleteFramebuffer(this.framebufferHandles[framebuffer]); + delete this.framebufferHandles[framebuffer]; + }, createProgram: (glHandle) => { const gl = this.canvasContextHandles[glHandle]; @@ -92,9 +143,33 @@ const buffer = this.bufferHandles[bufferId]; gl.bindBuffer(target, buffer); }, - bufferData: (glHandle, target, data, usage) => { + bufferData: (glHandle, target, dataOrSize, usage) => { + const gl = this.canvasContextHandles[glHandle]; + if (typeof dataOrSize === 'number') { + gl.bufferData(target, dataOrSize, usage); + return; + } + + const array = target === 0x8893 ? new Uint8Array(dataOrSize) : new Float32Array(dataOrSize); + gl.bufferData(target, array, usage); + }, + bindBufferBase: (glHandle, target, index, buffer) => { const gl = this.canvasContextHandles[glHandle]; - gl.bufferData(target, new Float32Array(data), usage); + const bufferObj = this.bufferHandles[buffer]; + gl.bindBufferBase(target, index, bufferObj); + }, + bufferSubData: (glHandle, target, dstByteOffset, srcData) => { + const gl = this.canvasContextHandles[glHandle]; + + const data = srcData instanceof Uint8Array + ? srcData + : new Uint8Array(srcData); + + gl.bufferSubData( + target, + dstByteOffset, + data + ); }, clearColor: (glHandle, r, g, b, a) => { const gl = this.canvasContextHandles[glHandle]; @@ -114,7 +189,7 @@ }, useProgram: (glHandle, programId) => { const gl = this.canvasContextHandles[glHandle]; - const program = programHandles[programId]; + const program = this.programHandles[programId]; gl.useProgram(program); }, drawArrays: (glHandle, mode, first, count) => { @@ -126,6 +201,84 @@ const program = this.programHandles[programId]; return gl.getAttribLocation(program, name); }, + enable: (glHandle, cap) => { + const gl = this.canvasContextHandles[glHandle]; + gl.enable(cap); + }, + disable: (glHandle, cap) => { + const gl = this.canvasContextHandles[glHandle]; + gl.disable(cap) + }, + depthFunc: (glHandle, func) => { + const gl = this.canvasContextHandles[glHandle]; + gl.depthFunc(func); + }, + clearDepth: (glHandle, depth) => { + const gl = this.canvasContextHandles[glHandle]; + gl.clearDepth(depth); + }, + depthMask: (glHandle, value) => { + const gl = this.canvasContextHandles[glHandle]; + gl.depthMask(value); + }, + getParameter: (glHandle, param) => { + const gl = this.canvasContextHandles[glHandle]; + const foundParam = gl.getParameter(param); + return foundParam.name; + }, + bindVertexArray: (glHandle, vertexArray) => { + const gl = this.canvasContextHandles[glHandle]; + const vao = this.vertexArrayHandles[vertexArray]; + gl.bindVertexArray(vao); + }, + bindSampler: (glHandle, slot, sampler) => { + const gl = this.canvasContextHandles[glHandle]; + const wglSampler = this.samplerHandles[sampler]; + gl.bindSampler(slot, wglSampler); + }, + uniformBlockBinding: (glHandle, program, blockIndex, bindingPoint) => { + const gl = this.canvasContextHandles[glHandle]; + const wglProgram = this.programHandles[program]; + gl.uniformBlockBinding(wglProgram, blockIndex, bindingPoint); + }, + createRenderbuffer: (glHandle) => { + const gl = this.canvasContextHandles[glHandle]; + const rb = gl.createRenderbuffer() + this.renderbufferIds++; + this.renderbufferHandles[this.renderbufferIds] = rb; + return this.renderbufferIds; + }, + bindRenderbuffer: (glHandle, target, renderbufferId) => { + const gl = this.canvasContextHandles[glHandle]; + const rb = this.renderbufferHandles[renderbufferId]; + gl.bindRenderbuffer(target, rb); + }, + renderbufferStorage: (glHandle, target, internalFormat, width, height) => { + const gl = this.canvasContextHandles[glHandle]; + gl.renderbufferStorage(target, internalFormat, width, height); + }, + deleteRenderbuffer: (glHandle, renderbufferId) => { + const gl = this.canvasContextHandles[glHandle]; + const rb = this.renderbufferHandles[renderbufferId]; + gl.deleteRenderbuffer(rb); + delete this.renderbufferHandles[renderbufferId]; + }, + framebufferRenderbuffer: (glHandle, target, attachment, renderbufferTarget, renderbuffer) => { + const gl = this.canvasContextHandles[glHandle]; + const rb = this.renderbufferHandles[renderbuffer]; + gl.framebufferRenderbuffer(target, attachment, renderbufferTarget, rb); + }, + getContext: (canvasId, contextType) => { + const canvas = document.getElementById(canvasId); + if (!canvas) { + return null; + } + + const handle = canvas.getContext(contextType); + this.canvasContextIds++; + this.canvasContextHandles[this.canvasContextIds] = handle; + return this.canvasContextIds; + }, openSkiaContext: (canvasId) => { const contextAttributes = { alpha: 1, @@ -173,7 +326,7 @@ }, activeTexture: (glHandle, textureUnit) => { const gl = this.canvasContextHandles[glHandle]; - gl.activeTexture(gl.TEXTURE0 + textureUnit); + gl.activeTexture(textureUnit); }, uniform1i: (glHandle, location, value) => { const gl = this.canvasContextHandles[glHandle]; @@ -197,12 +350,46 @@ delete this.textureHandles[textureId]; }, + drawElements: (glHandle, mode, count, type, offset) => { + const gl = this.canvasContextHandles[glHandle]; + gl.drawElements(mode, count, type, offset); + }, + blitFramebuffer: (glHandle, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter) => { + const gl = this.canvasContextHandles[glHandle]; + gl.blitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); + }, + createSampler: (glHandle) => { + const gl = this.canvasContextHandles[glHandle]; + const sampler = gl.createSampler(); + this.samplerIds++; + this.samplerHandles[this.samplerIds] = sampler; + return this.samplerIds; + }, + createVertexArray: (glHandle) => { + const gl = this.canvasContextHandles[glHandle]; + const vao = gl.createVertexArray(); + this.vertexArrayIds++; + this.vertexArrayHandles[this.vertexArrayIds] = vao; + return this.vertexArrayIds; + } }, window: { innerWidth: () => window.innerWidth, innerHeight: () => window.innerHeight, requestAnimationFrame: () => this.invokeRequestAnimationFrame(), subscribeWindowResize: () => window.addEventListener('resize', this.invokeWindowResize) + }, + input: { + subscribeKeyDown: () => { + document.addEventListener('keydown', (event) => { + this.exports.Drawie.JSInterop.JSRuntime.OnKeyDown(event.key); + }); + }, + subscribeKeyUp: () => { + document.addEventListener('keyup', (event) => { + this.exports.Drawie.JSInterop.JSRuntime.OnKeyUp(event.key); + }); + }, } }); } @@ -219,11 +406,12 @@ } invokeWindowResize() { - if(this.exports) { + if (this.exports) { this.exports.Drawie.JSInterop.JSRuntime.WindowResized(window.innerWidth, window.innerHeight); } } async addDrawieExports() { this.exports = await globalThis.getDotnetRuntime(0).getAssemblyExports("Drawie.JSInterop"); - }} \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/DrawieSample.Browser/wwwroot/styles.css b/src/DrawieSample.Browser/wwwroot/styles.css index 30fb6ac..5590d29 100644 --- a/src/DrawieSample.Browser/wwwroot/styles.css +++ b/src/DrawieSample.Browser/wwwroot/styles.css @@ -1,3 +1,5 @@ -body{ - margin:0; +html, body { + margin: 0; + padding: 0; + overflow: hidden; } \ No newline at end of file diff --git a/src/DrawieSample.Desktop/DrawieSample.Desktop.csproj b/src/DrawieSample.Desktop/DrawieSample.Desktop.csproj index 0535ac6..772ff18 100644 --- a/src/DrawieSample.Desktop/DrawieSample.Desktop.csproj +++ b/src/DrawieSample.Desktop/DrawieSample.Desktop.csproj @@ -2,15 +2,15 @@ Exe - net8.0 + net10.0 enable enable DrawieSample + - diff --git a/src/DrawieSample.Desktop/Program.cs b/src/DrawieSample.Desktop/Program.cs index 217953f..2a08667 100644 --- a/src/DrawieSample.Desktop/Program.cs +++ b/src/DrawieSample.Desktop/Program.cs @@ -1,9 +1,11 @@ -using DrawiEngine; +using Drawie2Sample; +using DrawiEngine; using DrawiEngine.Desktop; -using DrawieSample; DrawingEngine engine = DesktopDrawingEngine.CreateDefaultDesktop(); -DrawieSampleApp app = new DrawieSampleApp(); +Drawie2SampleApp app = new Drawie2SampleApp(); engine.RunWithApp(app); + +await engine.Dispose(); \ No newline at end of file diff --git a/src/DrawieSample/DrawieSampleApp.cs b/src/DrawieSample/DrawieSampleApp.cs deleted file mode 100644 index 0397cf6..0000000 --- a/src/DrawieSample/DrawieSampleApp.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Drawie.Backend.Core; -using Drawie.Backend.Core.ColorsImpl; -using Drawie.Backend.Core.Shaders; -using Drawie.Backend.Core.Surfaces; -using Drawie.Backend.Core.Surfaces.ImageData; -using Drawie.Backend.Core.Surfaces.PaintImpl; -using Drawie.Numerics; -using Drawie.Windowing; -using DrawiEngine; - -namespace DrawieSample; - -public class DrawieSampleApp : DrawieApp -{ - private IWindow window; - - public override IWindow CreateMainWindow() - { - window = Engine.WindowingPlatform.CreateWindow("Drawie Sample", new VecI(800, 600)); - return window; - } - - protected override void OnInitialize() - { - Paint paint = new Paint() { IsAntiAliased = true }; - - Texture testTexture = new Texture(new VecI(800, 600)); - DrawHorizontalColorStrips(testTexture, paint); - - DrawBlendTestHorizontalStrips(testTexture, paint); - - DrawingSurface srgbSurface = DrawingSurface.Create(new ImageInfo(testTexture.Size.X, testTexture.Size.Y, - ColorType.Rgba8888, AlphaType.Premul, ColorSpace.CreateSrgb()) { GpuBacked = true }); - - srgbSurface.Canvas.DrawSurface(testTexture.DrawingSurface, 0, 0); - - window.Render += (targetTexture, deltaTime) => - { - targetTexture.DrawingSurface.Canvas.Clear(Colors.White); - targetTexture.DrawingSurface.Canvas.DrawSurface(srgbSurface, 0, 0); - DrawReferenceColors(targetTexture, paint); - }; - } - - private void DrawReferenceColors(Texture targetTexture, Paint paint) - { - using Paint referencePaint = new Paint() { IsAntiAliased = true }; - referencePaint.Color = Colors.Black; - targetTexture.DrawingSurface.Canvas.DrawRect(0, 0, 5, 5, referencePaint); - referencePaint.Color = Colors.White; - targetTexture.DrawingSurface.Canvas.DrawRect(5, 0, 5, 5, referencePaint); - referencePaint.Color = Color.FromRgb(255, 0, 0); - targetTexture.DrawingSurface.Canvas.DrawRect(10, 0, 5, 5, referencePaint); - referencePaint.Color = Color.FromRgb(0, 255, 0); - targetTexture.DrawingSurface.Canvas.DrawRect(15, 0, 5, 5, referencePaint); - referencePaint.Color = Color.FromRgb(0, 0, 255); - targetTexture.DrawingSurface.Canvas.DrawRect(20, 0, 5, 5, referencePaint); - } - - private void DrawHorizontalColorStrips(Texture targetTexture, Paint paint) - { - int stripWidth = targetTexture.Size.X / 4; - int stripHeight = targetTexture.Size.Y; - - int spacing = 10; - - Color[] colors = [Color.FromRgb(0, 255, 0), Colors.Yellow, Colors.Cyan, Colors.Magenta]; - - for (int i = 0; i < 4; i++) - { - paint.Color = colors[i]; - targetTexture.DrawingSurface.Canvas.DrawRect(i * stripWidth + spacing, spacing, stripWidth - 2 * spacing, - stripHeight, paint); - } - } - - private void DrawBlendTestHorizontalStrips(Texture targetTexture, Paint paint) - { - int stripWidth = targetTexture.Size.X; - int stripHeight = targetTexture.Size.Y / 3; - - int spacing = 50; - - Color[] colors = [Colors.Red, Colors.Blue, Colors.Green]; - - for (int i = 0; i < 3; i++) - { - paint.Color = colors[i].WithAlpha(128); - paint.Style = PaintStyle.Fill; - targetTexture.DrawingSurface.Canvas.DrawRect(0, i * stripHeight + spacing, stripWidth, - stripHeight - 2 * spacing, paint); - } - } -} diff --git a/src/ShaderPlayground/Program.cs b/src/ShaderPlayground/Program.cs new file mode 100644 index 0000000..133a9dc --- /dev/null +++ b/src/ShaderPlayground/Program.cs @@ -0,0 +1,59 @@ +using Drawie.ShaderCompiler.Compilation; + +string code = """ + struct VertexInput + { + float3 vPos : POSITION; + float3 vNormal : NORMAL; + float2 vTexCoords : TEXCOORD0; + }; + + struct VertexOutput + { + float4 position : SV_Position; + float3 fNormal : TEXCOORD0; + float3 fPos : TEXCOORD1; + float2 fTexCoords : TEXCOORD2; + }; + + [[vk::binding(0, 0)]] + cbuffer Transform + { + float4x4 uModel; + float4x4 uView; + float4x4 uProjection; + }; + + [shader("vertex")] + VertexOutput VSMain(VertexInput input) + { + VertexOutput output; + + output.position = mul( + mul( + mul(uProjection, uView), + uModel + ), + float4(input.vPos, 1.0) + ); + + output.fPos = mul( + uModel, + float4(input.vPos, 1.0) + ).xyz; + + float3x3 model3x3 = float3x3(uModel); + + output.fNormal = mul( + float3x3(uModel), + input.vNormal + ); + + output.fTexCoords = input.vTexCoords; + + return output; + } + """; + +ShaderCompiler compiler = new ShaderCompiler("", "shader.slang"); +compiler.Compile(code, CompilationTarget.GlslEs3); \ No newline at end of file diff --git a/src/ShaderPlayground/ShaderPlayground.csproj b/src/ShaderPlayground/ShaderPlayground.csproj new file mode 100644 index 0000000..132d643 --- /dev/null +++ b/src/ShaderPlayground/ShaderPlayground.csproj @@ -0,0 +1,15 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + +