diff --git a/Zenith.NET.slnx b/Zenith.NET.slnx index 3e34441f..b277f91f 100644 --- a/Zenith.NET.slnx +++ b/Zenith.NET.slnx @@ -3,6 +3,7 @@ + diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index 44203098..27fc5529 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -5,7 +5,7 @@ - + @@ -19,9 +19,9 @@ - - - + + + \ No newline at end of file diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs index 2097c79d..bbebc9c7 100644 --- a/sources/Experiments/CornellBox/App.cs +++ b/sources/Experiments/CornellBox/App.cs @@ -49,9 +49,9 @@ static App() window = Window.Create(WindowOptions.Default with { + Size = new(1280, 720), API = GraphicsAPI.None, - Title = "Cornell Box - Zenith.NET", - Size = new(1280, 720) + Title = "Cornell Box - Zenith.NET" }); window.Initialize(); window.Center(); @@ -86,8 +86,8 @@ static App() camera = new(input, Matrix4x4.CreateTranslation(278.0f, 273.0f, -800.0f)) { - Speed = 240.0f, - FarPlane = 2000.0f + FarPlane = 2000.0f, + Speed = 240.0f }; rasterizer = new(); diff --git a/sources/Experiments/FluidTank/App.cs b/sources/Experiments/FluidTank/App.cs index 876c53fd..36eb12ba 100644 --- a/sources/Experiments/FluidTank/App.cs +++ b/sources/Experiments/FluidTank/App.cs @@ -44,9 +44,9 @@ static App() window = Window.Create(WindowOptions.Default with { + Size = new(1280, 720), API = GraphicsAPI.None, - Title = "Fluid Tank - Zenith.NET", - Size = new(1280, 720) + Title = "Fluid Tank - Zenith.NET" }); window.Initialize(); window.Center(); @@ -86,10 +86,10 @@ static App() camera = new(input, new(9.2f, 5.3f, -10.8f), new(0.0f, 1.45f, 0.0f)) { - Speed = 4.0f, NearPlane = 0.05f, FarPlane = 80.0f, - Fov = 48.0f + Fov = 48.0f, + Speed = 4.0f }; renderer = new(); diff --git a/sources/Experiments/FluidTank/Assets/Shaders/FluidComposite.slang b/sources/Experiments/FluidTank/Assets/Shaders/FluidComposite.slang index be78fb65..9fd415d7 100644 --- a/sources/Experiments/FluidTank/Assets/Shaders/FluidComposite.slang +++ b/sources/Experiments/FluidTank/Assets/Shaders/FluidComposite.slang @@ -184,9 +184,8 @@ float3 ReconstructNormal(float2 uv, float centerDepth) return normal; } -float3 SceneAt(float2 uv) +float3 SceneAt(float2 uv, float depth) { - float depth = composite.SceneDepth.SampleLevel(composite.Sampler, uv, 0.0).r; if (depth <= 0.0) { float3 rayView = ViewRay(uv); @@ -198,22 +197,62 @@ float3 SceneAt(float2 uv) return composite.SceneColor.SampleLevel(composite.Sampler, uv, 0.0).rgb; } +float3 SceneAt(float2 uv) +{ + float depth = composite.SceneDepth.SampleLevel(composite.Sampler, uv, 0.0).r; + + return SceneAt(uv, depth); +} + +bool FrameOccludesFluid(FullscreenOutput input, float sceneDepth) +{ + if (composite.SceneColor.Load(int3(int2(input.Position.xy), 0)).a < 0.5) + { + return false; + } + + int2 fluidDimensions = int2(composite.Width, composite.Height); + int2 baseFluidPixel = int2(floor(input.UV * float2(fluidDimensions) - 0.5)); + float closestFluidDepth = 0.0; + + for (int y = 0; y < 2; y++) + { + for (int x = 0; x < 2; x++) + { + int2 samplePixel = clamp(baseFluidPixel + int2(x, y), int2(0, 0), fluidDimensions - 1); + float sampleDepth = composite.Attributes.Load(int3(samplePixel, 0)).w; + + if (sampleDepth > 0.0 && (closestFluidDepth <= 0.0 || sampleDepth < closestFluidDepth)) + { + closestFluidDepth = sampleDepth; + } + } + } + + return closestFluidDepth <= 0.0 || closestFluidDepth >= sceneDepth; +} + float4 ShadeWater(FullscreenOutput input) { float2 uv = input.UV; + float sceneDepth = composite.SceneDepth.Load(int3(int2(input.Position.xy), 0)).r; + float3 scene = SceneAt(uv, sceneDepth); if (composite.RenderMode == 1) { - return float4(ToSRGB(ACESFilm(SceneAt(uv))), 1.0); + return float4(ToSRGB(ACESFilm(scene)), 1.0); } float fluidDepth = composite.FluidDepth.SampleLevel(composite.Sampler, uv, 0.0).r; if (fluidDepth <= 0.0) { - float3 background = SceneAt(uv); + return float4(ToSRGB(ACESFilm(scene)), 1.0); + } - return float4(ToSRGB(ACESFilm(background)), 1.0); + if (sceneDepth > 0.0 && (fluidDepth >= sceneDepth || FrameOccludesFluid(input, sceneDepth))) + { + return float4(ToSRGB(ACESFilm(scene)), 1.0); } float thickness = composite.Thickness.SampleLevel(composite.Sampler, uv, 0.0).r; @@ -222,7 +261,7 @@ float4 ShadeWater(FullscreenOutput input) if (surfaceConfidence <= 0.01) { - return float4(ToSRGB(ACESFilm(SceneAt(uv))), 1.0); + return float4(ToSRGB(ACESFilm(scene)), 1.0); } float3 normalView = ReconstructNormal(uv, fluidDepth); @@ -258,7 +297,7 @@ float4 ShadeWater(FullscreenOutput input) color += sunSpecular * float3(1.0, 0.94, 0.78); color = lerp(color, float3(0.76, 0.92, 0.96), foam * 0.72); - color = lerp(SceneAt(uv), color, surfaceConfidence); + color = lerp(scene, color, surfaceConfidence); return float4(ToSRGB(ACESFilm(color)), 1.0); } diff --git a/sources/Experiments/FluidTank/Assets/Shaders/FluidSurface.slang b/sources/Experiments/FluidTank/Assets/Shaders/FluidSurface.slang index f0b7b4fc..cfc68e2b 100644 --- a/sources/Experiments/FluidTank/Assets/Shaders/FluidSurface.slang +++ b/sources/Experiments/FluidTank/Assets/Shaders/FluidSurface.slang @@ -191,15 +191,16 @@ DepthOutput DepthFS(SurfaceVSOutput input) float sphereZ = sqrt(max(1.0 - radiusSquared, 0.0)); float3 viewPosition = input.CenterView + float3(input.Corner * surface.ParticleRadius, surface.ParticleRadius * 0.62); float4 clipPosition = mul(float4(viewPosition, 1.0), surface.Projection); + float linearDepth = -viewPosition.z; - if (OccludedByScene(input, -viewPosition.z)) + if (OccludedByScene(input, linearDepth)) { discard; } DepthOutput output; - output.LinearDepth = -viewPosition.z; - output.Attributes = float4(input.Speed, input.Density, sphereZ, 1.0); + output.LinearDepth = linearDepth; + output.Attributes = float4(input.Speed, input.Density, sphereZ, linearDepth); output.DeviceDepth = clipPosition.z / clipPosition.w; return output; diff --git a/sources/Experiments/FluidTank/Assets/Shaders/Scene.slang b/sources/Experiments/FluidTank/Assets/Shaders/Scene.slang index b69b0367..91908b11 100644 --- a/sources/Experiments/FluidTank/Assets/Shaders/Scene.slang +++ b/sources/Experiments/FluidTank/Assets/Shaders/Scene.slang @@ -1,5 +1,7 @@ #include "SceneCommon.slang" +static const uint FrameMaterialId = 4; + struct SceneConstants { float4x4 View; @@ -115,7 +117,7 @@ FSOutput FSMain(VSOutput input) float3 color = diffuse + specular + environment + material.Albedo * material.Emission; FSOutput output; - output.Color = float4(color, 1.0); + output.Color = float4(color, input.MaterialId == FrameMaterialId ? 1.0 : 0.0); output.LinearDepth = input.ViewDepth; return output; diff --git a/sources/Experiments/FluidTank/FluidSimulation.cs b/sources/Experiments/FluidTank/FluidSimulation.cs index 547be4fd..5198fc10 100644 --- a/sources/Experiments/FluidTank/FluidSimulation.cs +++ b/sources/Experiments/FluidTank/FluidSimulation.cs @@ -48,9 +48,7 @@ public FluidSimulation() ParticleCount = DamDimensions.X * DamDimensions.Y * DamDimensions.Z; Vector3 tankExtent = TankMax - TankMin; - GridDimensions = new((uint)MathF.Ceiling(tankExtent.X / GridSpacing), - (uint)MathF.Ceiling(tankExtent.Y / GridSpacing), - (uint)MathF.Ceiling(tankExtent.Z / GridSpacing)); + GridDimensions = new((uint)MathF.Ceiling(tankExtent.X / GridSpacing), (uint)MathF.Ceiling(tankExtent.Y / GridSpacing), (uint)MathF.Ceiling(tankExtent.Z / GridSpacing)); CellCount = GridDimensions.X * GridDimensions.Y * GridDimensions.Z; GridPointCount = (GridDimensions.X + 1) * (GridDimensions.Y + 1) * (GridDimensions.Z + 1); pressureParityDispatchCount = (GridDimensions.X + 1) / 2 * GridDimensions.Y * GridDimensions.Z; diff --git a/sources/Experiments/FluidTank/FluidTankRenderer.cs b/sources/Experiments/FluidTank/FluidTankRenderer.cs index 068a679d..9feb59a3 100644 --- a/sources/Experiments/FluidTank/FluidTankRenderer.cs +++ b/sources/Experiments/FluidTank/FluidTankRenderer.cs @@ -10,6 +10,7 @@ namespace FluidTank; internal enum FluidViewMode { Water, + Particles } @@ -109,7 +110,7 @@ public FluidTankRenderer() fluidDepthPipeline = GraphicsHelper.CreateGraphicsPipeline(surfaceVertexShader, "FluidSurface.slang", "DepthFS", [], new() { - ColorFormats = [PixelFormat.R32Float, PixelFormat.R16G16B16A16Float], + ColorFormats = [PixelFormat.R32Float, PixelFormat.R32G32B32A32Float], DepthStencilFormat = PixelFormat.D32FloatS8UInt, SampleCount = SampleCount.Count1 }, RasterizerState.CullNone(), DepthStencilState.DepthReadWrite(), BlendState.Opaque(), PrimitiveTopology.TriangleStrip); @@ -302,11 +303,7 @@ public void RenderScene(CommandBuffer commandBuffer) commandBuffer.Transition(sceneLinearDepth, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); commandBuffer.Transition(DepthStencil, default, TextureLayout.Undefined, TextureLayout.DepthStencilAttachment); - commandBuffer.BeginRenderPass( - [ - ColorAttachment.Clear(sceneColor, new(0.0f, 0.0f, 0.0f, 1.0f)), - ColorAttachment.Clear(sceneLinearDepth, Vector4.Zero) - ], DepthStencilAttachment.Clear(DepthStencil, 1.0f, 0)); + commandBuffer.BeginRenderPass([ColorAttachment.Clear(sceneColor, Vector4.Zero), ColorAttachment.Clear(sceneLinearDepth, Vector4.Zero)], DepthStencilAttachment.Clear(DepthStencil, 1.0f, 0)); commandBuffer.SetPipeline(scenePipeline); commandBuffer.SetVertexBuffer(sceneVertexBuffer, 0, 0); commandBuffer.SetIndexBuffer(sceneIndexBuffer, 0, IndexFormat.UInt32); @@ -339,11 +336,7 @@ public void RenderFluid(CommandBuffer commandBuffer) commandBuffer.Transition(smoothDepthA, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); commandBuffer.Transition(fluidAttributes, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); - commandBuffer.BeginRenderPass( - [ - ColorAttachment.Clear(smoothDepthA, Vector4.Zero), - ColorAttachment.Clear(fluidAttributes, Vector4.Zero) - ], DepthStencilAttachment.Clear(reconstructionDepth, 1.0f, 0)); + commandBuffer.BeginRenderPass([ColorAttachment.Clear(smoothDepthA, Vector4.Zero), ColorAttachment.Clear(fluidAttributes, Vector4.Zero)], DepthStencilAttachment.Clear(reconstructionDepth, 1.0f, 0)); commandBuffer.SetPipeline(fluidDepthPipeline); commandBuffer.SetConstantBuffer(surfaceConstantBuffer, 0); commandBuffer.Draw(4, simulation.ParticleCount, 0, 0); @@ -423,14 +416,12 @@ public void Resize(uint width, uint height) uint reconstructionWidth = Math.Max((width + 2) / 3, 1u); uint reconstructionHeight = Math.Max((height + 2) / 3, 1u); reconstructionDepth = GraphicsHelper.CreateTexture(PixelFormat.D32FloatS8UInt, reconstructionWidth, reconstructionHeight, TextureUsages.DepthStencilAttachment); - fluidAttributes = GraphicsHelper.CreateTexture(PixelFormat.R16G16B16A16Float, reconstructionWidth, reconstructionHeight, TextureUsages.Sampled | TextureUsages.ColorAttachment); smoothDepthA = GraphicsHelper.CreateTexture(PixelFormat.R32Float, reconstructionWidth, reconstructionHeight, TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment); + fluidAttributes = GraphicsHelper.CreateTexture(PixelFormat.R32G32B32A32Float, reconstructionWidth, reconstructionHeight, TextureUsages.Sampled | TextureUsages.ColorAttachment); smoothDepthB = GraphicsHelper.CreateTexture(PixelFormat.R32Float, reconstructionWidth, reconstructionHeight, TextureUsages.Sampled | TextureUsages.Storage); smoothThicknessA = GraphicsHelper.CreateTexture(PixelFormat.R16Float, reconstructionWidth, reconstructionHeight, TextureUsages.Sampled | TextureUsages.Storage | TextureUsages.ColorAttachment); smoothThicknessB = GraphicsHelper.CreateTexture(PixelFormat.R16Float, reconstructionWidth, reconstructionHeight, TextureUsages.Sampled | TextureUsages.Storage); - reflection = App.Context.Capabilities.RayTracingSupported - ? GraphicsHelper.CreateTexture(PixelFormat.R16G16B16A16Float, reconstructionWidth, reconstructionHeight, TextureUsages.Sampled | TextureUsages.Storage) - : null; + reflection = App.Context.Capabilities.RayTracingSupported ? GraphicsHelper.CreateTexture(PixelFormat.R16G16B16A16Float, reconstructionWidth, reconstructionHeight, TextureUsages.Sampled | TextureUsages.Storage) : null; } public void Dispose() diff --git a/sources/Experiments/InkCanvas/App.cs b/sources/Experiments/InkCanvas/App.cs new file mode 100644 index 00000000..7917a439 --- /dev/null +++ b/sources/Experiments/InkCanvas/App.cs @@ -0,0 +1,135 @@ +using System.Numerics; +using InkCanvas.Drawing; +using InkCanvas.Helpers; +using Silk.NET.Input; +using Silk.NET.Windowing; +using Zenith.NET; +using Zenith.NET.DirectX12; +using Zenith.NET.Metal; +using Zenith.NET.Vulkan; + +namespace InkCanvas; + +internal static class App +{ + private static readonly IWindow window; + private static readonly IInputContext input; + private static readonly SwapChain swapChain; + private static readonly CanvasController canvas; + + static App() + { + if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS() && !OperatingSystem.IsLinux()) + { + throw new PlatformNotSupportedException("This application only supports Windows, macOS, and Linux."); + } + + if (OperatingSystem.IsWindows()) + { + Context = GraphicsContext.CreateDirectX12(useValidationLayer: true); + } + else if (OperatingSystem.IsMacOS()) + { + Context = GraphicsContext.CreateMetal(useValidationLayer: true); + } + else + { + Context = GraphicsContext.CreateVulkan(useValidationLayer: true); + } + + Context.ValidationMessage += static (_, args) => + { + // These validation messages are caused by known issues in Skia's D3D12 backend. + if (args.Message.StartsWith("ID3D12DescriptorHeap::GetGPUDescriptorHandleForHeapStart:", StringComparison.Ordinal) + || args.Message.StartsWith("ID3D12Device::CreateSampler2:", StringComparison.Ordinal) + || args.Message.StartsWith("ID3D12CommandList::ClearRenderTargetView:", StringComparison.Ordinal)) + { + return; + } + + Console.WriteLine($"[{args.Severity}] {args.Message}"); + }; + + window = Window.Create(WindowOptions.Default with + { + Size = new(1280, 800), + API = GraphicsAPI.None, + Title = "Ink Canvas - Zenith.NET" + }); + window.Initialize(); + window.Center(); + + input = window.CreateInput(); + + Surface surface; + if (OperatingSystem.IsWindows()) + { + surface = Surface.Win32(window.Native!.Win32!.Value.Hwnd, Width, Height); + } + else if (OperatingSystem.IsMacOS()) + { + surface = Surface.Apple(CocoaHelper.CreateLayer(window.Native!.Cocoa!.Value), Width, Height); + } + else + { + surface = Surface.Xlib(window.Native!.X11!.Value.Display, (nint)window.Native.X11.Value.Window, Width, Height); + } + + swapChain = Context.CreateSwapChain(new() + { + Surface = surface, + Format = PixelFormat.B8G8R8A8UNorm + }); + + canvas = new(Context, input, Width, Height); + } + + public static GraphicsContext Context { get; } + + public static uint Width => (uint)window.FramebufferSize.X; + + public static uint Height => (uint)window.FramebufferSize.Y; + + public static Vector2 DpiScale => (Vector2)window.FramebufferSize / (Vector2)window.Size; + + public static void Run() + { + window.Render += _ => + { + if (Width is 0 || Height is 0) + { + return; + } + + CommandBuffer commandBuffer = Context.GraphicsQueue.CommandBuffer(); + + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.Undefined, TextureLayout.CopyDst); + canvas.Render(commandBuffer, swapChain.Drawable, DpiScale); + commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.CopyDst, TextureLayout.Present); + + commandBuffer.Submit().Wait(); + + swapChain.Present(); + }; + + window.FramebufferResize += _ => + { + if (Width is 0 || Height is 0) + { + return; + } + + canvas.Resize(Width, Height); + swapChain.Resize(Width, Height); + }; + + window.Run(); + + canvas.Dispose(); + swapChain.Dispose(); + input.Dispose(); + window.Dispose(); + + Context.Dispose(); + } +} \ No newline at end of file diff --git a/sources/Experiments/InkCanvas/Drawing/Canvas.cs b/sources/Experiments/InkCanvas/Drawing/Canvas.cs new file mode 100644 index 00000000..01100288 --- /dev/null +++ b/sources/Experiments/InkCanvas/Drawing/Canvas.cs @@ -0,0 +1,273 @@ +using SkiaSharp; + +namespace InkCanvas.Drawing; + +internal class Canvas : IDisposable +{ + private const float EraserRadius = 22.0f; + + private static readonly SKColor Surface = new(22, 24, 30); + private static readonly SKColor Grid = new(32, 35, 43); + private static readonly SKColor Cursor = new(150, 158, 172, 200); + + private readonly Toolbar toolbar = new(); + private readonly List strokes = []; + + private readonly SKPathBuilder eraserBuilder = new(); + private readonly SKPaint fillPaint = new(); + private readonly SKPaint strokePaint = new() + { + Style = SKPaintStyle.Stroke, + StrokeCap = SKStrokeCap.Round, + StrokeJoin = SKStrokeJoin.Round + }; + + private SKPicture? cachedPicture; + private int nodeCount; + private bool pictureDirty = true; + + private Stroke? activeStroke; + private SKPoint pointer; + private bool erasing; + private bool erasePending; + + private SKRect drawingArea; + private SKSize size; + + public bool MSAA => toolbar.MSAA; + + private bool CanClear => strokes.Count > 0 || activeStroke is not null || erasing || erasePending; + + public void Draw(SKCanvas canvas, float width, float height) + { + EnsureLayout(width, height); + + if (erasePending) + { + ApplyEraser(); + } + + fillPaint.Color = Surface; + canvas.DrawRect(0.0f, 0.0f, width, height, fillPaint); + + if (pictureDirty) + { + cachedPicture?.Dispose(); + cachedPicture = RecordCanvas(); + pictureDirty = false; + } + + canvas.Save(); + canvas.ClipRect(drawingArea); + canvas.DrawPicture(cachedPicture); + + if (activeStroke is not null) + { + strokePaint.Color = activeStroke.Color; + strokePaint.StrokeWidth = activeStroke.Width; + canvas.DrawPath(activeStroke.Path, strokePaint); + canvas.DrawLine(activeStroke.TailStart, activeStroke.TailEnd, strokePaint); + } + + if (erasing && drawingArea.Contains(pointer.X, pointer.Y)) + { + DrawEraserCursor(canvas); + } + + canvas.Restore(); + + toolbar.Draw(canvas, strokes.Count, nodeCount, CanClear); + } + + public void PointerMove(SKPoint position) + { + pointer = position; + + if (erasing) + { + eraserBuilder.LineTo(position); + erasePending = true; + } + else + { + activeStroke?.Add(position); + } + } + + public void PointerDown(SKPoint position, bool erase) + { + if (!erase && toolbar.IsClearButton(position)) + { + pointer = position; + ClearCanvas(); + } + else if (activeStroke is null && !erasing) + { + pointer = position; + + if (!erase && position.Y <= Toolbar.ToolbarHeight) + { + toolbar.SelectAt(position); + } + else if (drawingArea.Contains(position.X, position.Y)) + { + if (erase) + { + erasing = true; + eraserBuilder.MoveTo(position); + eraserBuilder.LineTo(position); + erasePending = true; + } + else + { + activeStroke = new(toolbar.SelectedColor, toolbar.SelectedStrokeWidth); + activeStroke.Add(position); + } + } + } + } + + public void PointerUp(bool erase) + { + if (erase && erasing) + { + erasing = false; + + if (erasePending) + { + ApplyEraser(); + } + } + else if (!erase && activeStroke is not null) + { + activeStroke.Complete(strokePaint); + strokes.Add(activeStroke); + activeStroke = null; + pictureDirty = true; + } + } + + public void Dispose() + { + foreach (Stroke stroke in strokes) + { + stroke.Dispose(); + } + + strokes.Clear(); + activeStroke?.Dispose(); + cachedPicture?.Dispose(); + + eraserBuilder.Dispose(); + strokePaint.Dispose(); + fillPaint.Dispose(); + + toolbar.Dispose(); + } + + private void EnsureLayout(float width, float height) + { + if (width == size.Width && height == size.Height) + { + return; + } + + size = new(width, height); + drawingArea = new(0.0f, Toolbar.ToolbarHeight, width, MathF.Max(Toolbar.ToolbarHeight, height - Toolbar.StatusHeight)); + + toolbar.Resize(width, height); + pictureDirty = true; + } + + private SKPicture RecordCanvas() + { + const float spacing = 32.0f; + + using SKPictureRecorder recorder = new(); + SKCanvas canvas = recorder.BeginRecording(drawingArea); + int nodes = 0; + + strokePaint.Color = Grid; + strokePaint.StrokeWidth = 1.0f; + + for (float x = drawingArea.Left + spacing; x < drawingArea.Right; x += spacing) + { + canvas.DrawLine(x, drawingArea.Top, x, drawingArea.Bottom, strokePaint); + } + + for (float y = drawingArea.Top + spacing; y < drawingArea.Bottom; y += spacing) + { + canvas.DrawLine(drawingArea.Left, y, drawingArea.Right, y, strokePaint); + } + + foreach (Stroke stroke in strokes) + { + fillPaint.Color = stroke.Color; + canvas.DrawPath(stroke.Path, fillPaint); + nodes += stroke.NodeCount; + } + + nodeCount = nodes; + + return recorder.EndRecording(); + } + + private void DrawEraserCursor(SKCanvas canvas) + { + strokePaint.Color = Cursor; + strokePaint.StrokeWidth = 1.5f; + canvas.DrawCircle(pointer, EraserRadius, strokePaint); + } + + private void ClearCanvas() + { + if (CanClear) + { + foreach (Stroke stroke in strokes) + { + stroke.Dispose(); + } + + strokes.Clear(); + activeStroke?.Dispose(); + activeStroke = null; + eraserBuilder.Reset(); + erasing = false; + erasePending = false; + pictureDirty = true; + } + } + + private void ApplyEraser() + { + using SKPath centerline = eraserBuilder.Detach(); + + if (erasing) + { + eraserBuilder.MoveTo(pointer); + } + + strokePaint.StrokeWidth = EraserRadius * 2.0f; + + using SKPath eraser = strokePaint.GetFillPath(centerline)!; + SKRect eraserBounds = eraser.Bounds; + + erasePending = false; + + for (int index = strokes.Count - 1; index >= 0; index--) + { + Stroke stroke = strokes[index]; + + if (stroke.Erase(eraser, eraserBounds)) + { + if (stroke.IsEmpty) + { + stroke.Dispose(); + strokes.RemoveAt(index); + } + + pictureDirty = true; + } + } + } +} diff --git a/sources/Experiments/InkCanvas/Drawing/CanvasController.cs b/sources/Experiments/InkCanvas/Drawing/CanvasController.cs new file mode 100644 index 00000000..4fb096b2 --- /dev/null +++ b/sources/Experiments/InkCanvas/Drawing/CanvasController.cs @@ -0,0 +1,103 @@ +using System.Numerics; +using Silk.NET.Input; +using Zenith.NET; +using Zenith.NET.Extensions.Skia; + +namespace InkCanvas.Drawing; + +internal class CanvasController : IDisposable +{ + private readonly Canvas canvas = new(); + + private SKTexture texture; + + public CanvasController(GraphicsContext context, IInputContext input, uint width, uint height) + { + Context = context; + + texture = CreateTexture(width, height); + + IMouse mouse = input.Mice[0]; + mouse.MouseDown += OnMouseDown; + mouse.MouseUp += OnMouseUp; + mouse.MouseMove += OnMouseMove; + } + + public GraphicsContext Context { get; } + + public void Render(CommandBuffer commandBuffer, Texture target, Vector2 dpiScale) + { + float width = texture.Desc.Width / dpiScale.X; + float height = texture.Desc.Height / dpiScale.Y; + + texture.Render((skiaCanvas) => + { + skiaCanvas.Save(); + skiaCanvas.Scale(dpiScale.X, dpiScale.Y); + + canvas.Draw(skiaCanvas, width, height); + + skiaCanvas.Restore(); + }); + + commandBuffer.Transition(texture, default, texture.RequiredLayout, TextureLayout.CopySrc); + commandBuffer.CopyTexture(texture, default, default, target, default, default, new() + { + Width = texture.Desc.Width, + Height = texture.Desc.Height, + Depth = 1 + }); + commandBuffer.Transition(texture, default, TextureLayout.CopySrc, texture.RequiredLayout); + } + + public void Resize(uint width, uint height) + { + texture.Dispose(); + texture = CreateTexture(width, height); + } + + public void Dispose() + { + canvas.Dispose(); + texture.Dispose(); + } + + private SKTexture CreateTexture(uint width, uint height) + { + return Context.CreateSKTexture(new() + { + Format = PixelFormat.B8G8R8A8UNorm, + Width = width, + Height = height, + IsMultisamplingEnabled = canvas.MSAA + }); + } + + private void OnMouseDown(IMouse mouse, MouseButton button) + { + if (button is MouseButton.Left or MouseButton.Right) + { + bool msaa = canvas.MSAA; + + canvas.PointerDown(new(mouse.Position.X, mouse.Position.Y), button is MouseButton.Right); + + if (msaa != canvas.MSAA) + { + Resize(texture.Desc.Width, texture.Desc.Height); + } + } + } + + private void OnMouseUp(IMouse mouse, MouseButton button) + { + if (button is MouseButton.Left or MouseButton.Right) + { + canvas.PointerUp(button is MouseButton.Right); + } + } + + private void OnMouseMove(IMouse mouse, Vector2 position) + { + canvas.PointerMove(new(position.X, position.Y)); + } +} diff --git a/sources/Experiments/InkCanvas/Drawing/Stroke.cs b/sources/Experiments/InkCanvas/Drawing/Stroke.cs new file mode 100644 index 00000000..4ebd5b59 --- /dev/null +++ b/sources/Experiments/InkCanvas/Drawing/Stroke.cs @@ -0,0 +1,105 @@ +using SkiaSharp; + +namespace InkCanvas.Drawing; + +internal class Stroke(SKColor color, float width) : IDisposable +{ + private const float MinimumPointDistance = 1.5f; + + private SKPathBuilder? builder = new(); + private SKPath? path; + + private SKRect bounds; + private SKPoint tailStart; + private SKPoint tailEnd; + private int sampleCount; + + public SKColor Color { get; } = color; + + public float Width { get; } = width; + + public int NodeCount => Path.PointCount; + + public bool IsEmpty => Path.IsEmpty; + + public SKPath Path => path ??= builder!.Snapshot(); + + public SKPoint TailStart => tailStart; + + public SKPoint TailEnd => tailEnd; + + public void Add(SKPoint point) + { + if (sampleCount is 0 || DistanceSquared(tailEnd, point) >= MinimumPointDistance * MinimumPointDistance) + { + path?.Dispose(); + path = null; + + if (sampleCount is 0) + { + builder!.MoveTo(point); + tailStart = point; + } + else if (sampleCount > 1) + { + tailStart = new((tailEnd.X + point.X) * 0.5f, (tailEnd.Y + point.Y) * 0.5f); + builder!.QuadTo(tailEnd, tailStart); + } + + tailEnd = point; + sampleCount++; + } + } + + public void Complete(SKPaint paint) + { + builder!.LineTo(tailEnd); + + path?.Dispose(); + + using SKPath centerline = builder.Detach(); + + builder.Dispose(); + builder = null; + + paint.StrokeWidth = Width; + path = paint.GetFillPath(centerline)!; + + bounds = path.TightBounds; + } + + public bool Erase(SKPath eraser, SKRect eraserBounds) + { + if (bounds.IntersectsWith(eraserBounds)) + { + SKPath source = Path; + + using SKPath? overlap = source.Op(eraser, SKPathOp.Intersect); + + if (overlap?.IsEmpty is false && source.Op(eraser, SKPathOp.Difference) is { } result) + { + source.Dispose(); + path = result; + bounds = result.TightBounds; + + return true; + } + } + + return false; + } + + public void Dispose() + { + path?.Dispose(); + builder?.Dispose(); + } + + private static float DistanceSquared(SKPoint first, SKPoint second) + { + float dx = first.X - second.X; + float dy = first.Y - second.Y; + + return (dx * dx) + (dy * dy); + } +} diff --git a/sources/Experiments/InkCanvas/Drawing/Toolbar.cs b/sources/Experiments/InkCanvas/Drawing/Toolbar.cs new file mode 100644 index 00000000..c6be8471 --- /dev/null +++ b/sources/Experiments/InkCanvas/Drawing/Toolbar.cs @@ -0,0 +1,230 @@ +using SkiaSharp; + +namespace InkCanvas.Drawing; + +internal class Toolbar : IDisposable +{ + public const float ToolbarHeight = 64.0f; + public const float StatusHeight = 34.0f; + + private const float SwatchSize = 30.0f; + private const float SwatchGap = 12.0f; + private const float CheckboxSize = 18.0f; + private const float MSAAWidth = 72.0f; + private const float ButtonWidth = 64.0f; + + private static readonly SKColor Panel = new(31, 34, 43); + private static readonly SKColor Divider = new(48, 52, 63); + private static readonly SKColor Label = new(150, 158, 172); + private static readonly SKColor Highlight = new(238, 242, 248); + private static readonly SKColor Selected = new(58, 64, 78); + + private static readonly SKColor[] Palette = + [ + new(238, 242, 248), + new(236, 108, 96), + new(238, 178, 74), + new(96, 196, 154), + new(102, 158, 240), + new(178, 134, 234) + ]; + + private static readonly float[] StrokeWidths = [2.0f, 4.0f, 8.0f, 16.0f]; + + private readonly SKRect[] swatchRects = new SKRect[Palette.Length]; + private readonly SKRect[] strokeWidthRects = new SKRect[StrokeWidths.Length]; + + private readonly SKFont labelFont; + private readonly SKPaint fillPaint = new(); + private readonly SKPaint strokePaint = new() { Style = SKPaintStyle.Stroke }; + + private SKRect msaaRect; + private SKRect clearRect; + private SKSize size; + + private int colorIndex; + private int strokeWidthIndex = 1; + + public Toolbar() + { + string family = OperatingSystem.IsMacOS() ? "SF Pro Text" : OperatingSystem.IsWindows() ? "Segoe UI" : "Noto Sans"; + + using SKTypeface typeface = SKTypeface.FromFamilyName(family, SKFontStyle.Normal); + + labelFont = new(typeface, 12.0f) + { + Subpixel = true, + Edging = SKFontEdging.SubpixelAntialias, + Hinting = SKFontHinting.Slight + }; + } + + public SKColor SelectedColor => Palette[colorIndex]; + + public float SelectedStrokeWidth => StrokeWidths[strokeWidthIndex]; + + public bool MSAA { get; private set; } = true; + + public void Resize(float width, float height) + { + const float top = (ToolbarHeight - SwatchSize) * 0.5f; + const float bottom = top + SwatchSize; + + size = new(width, height); + + for (int index = 0; index < swatchRects.Length; index++) + { + float left = SwatchGap + (index * (SwatchSize + SwatchGap)); + swatchRects[index] = new(left, top, left + SwatchSize, bottom); + } + + float strokeWidthLeft = swatchRects[^1].Right + (SwatchGap * 2.0f); + + for (int index = 0; index < strokeWidthRects.Length; index++) + { + float left = strokeWidthLeft + (index * (SwatchSize + SwatchGap)); + strokeWidthRects[index] = new(left, top, left + SwatchSize, bottom); + } + + float msaaLeft = strokeWidthRects[^1].Right + (SwatchGap * 2.0f); + msaaRect = new(msaaLeft, top, msaaLeft + MSAAWidth, bottom); + + float clearLeft = MathF.Max(msaaRect.Right + (SwatchGap * 2.0f), width - SwatchGap - ButtonWidth); + clearRect = new(clearLeft, top, clearLeft + ButtonWidth, bottom); + } + + public void Draw(SKCanvas canvas, int strokeCount, int nodeCount, bool canClear) + { + DrawToolbar(canvas, canClear); + DrawStatus(canvas, strokeCount, nodeCount); + } + + public void SelectAt(SKPoint position) + { + int swatch = IndexAt(swatchRects, position); + int strokeWidth = IndexAt(strokeWidthRects, position); + + if (swatch >= 0) + { + colorIndex = swatch; + } + else if (strokeWidth >= 0) + { + strokeWidthIndex = strokeWidth; + } + else if (msaaRect.Contains(position.X, position.Y)) + { + MSAA = !MSAA; + } + } + + public bool IsClearButton(SKPoint position) + { + return clearRect.Contains(position.X, position.Y); + } + + public void Dispose() + { + strokePaint.Dispose(); + fillPaint.Dispose(); + labelFont.Dispose(); + } + + private void DrawToolbar(SKCanvas canvas, bool canClear) + { + const float checkboxTop = (ToolbarHeight - CheckboxSize) * 0.5f; + + fillPaint.Color = Panel; + canvas.DrawRect(0.0f, 0.0f, size.Width, ToolbarHeight, fillPaint); + + fillPaint.Color = Divider; + canvas.DrawRect(0.0f, ToolbarHeight - 1.0f, size.Width, 1.0f, fillPaint); + + for (int index = 0; index < Palette.Length; index++) + { + SKRect swatch = swatchRects[index]; + + fillPaint.Color = Palette[index]; + canvas.DrawRoundRect(swatch, 6.0f, 6.0f, fillPaint); + + if (index == colorIndex) + { + strokePaint.Color = Highlight; + strokePaint.StrokeWidth = 2.0f; + canvas.DrawRoundRect(SKRect.Inflate(swatch, 4.0f, 4.0f), 9.0f, 9.0f, strokePaint); + } + } + + for (int index = 0; index < StrokeWidths.Length; index++) + { + SKRect slot = strokeWidthRects[index]; + + fillPaint.Color = index == strokeWidthIndex ? Selected : Panel; + canvas.DrawRoundRect(slot, 6.0f, 6.0f, fillPaint); + + fillPaint.Color = Palette[colorIndex]; + canvas.DrawCircle(slot.MidX, slot.MidY, StrokeWidths[index] * 0.5f, fillPaint); + } + + SKRect checkbox = new(msaaRect.Left, checkboxTop, msaaRect.Left + CheckboxSize, checkboxTop + CheckboxSize); + + fillPaint.Color = MSAA ? Selected : Panel; + canvas.DrawRoundRect(checkbox, 4.0f, 4.0f, fillPaint); + + strokePaint.Color = MSAA ? Highlight : Label; + strokePaint.StrokeWidth = 1.5f; + canvas.DrawRoundRect(checkbox, 4.0f, 4.0f, strokePaint); + + if (MSAA) + { + strokePaint.StrokeWidth = 2.0f; + canvas.DrawLine(checkbox.Left + 4.0f, checkbox.MidY, checkbox.Left + 8.0f, checkbox.Bottom - 4.0f, strokePaint); + canvas.DrawLine(checkbox.Left + 8.0f, checkbox.Bottom - 4.0f, checkbox.Right - 3.0f, checkbox.Top + 4.0f, strokePaint); + } + + fillPaint.Color = Label; + canvas.DrawText("MSAA", checkbox.Right + 8.0f, msaaRect.MidY + 4.0f, SKTextAlign.Left, labelFont, fillPaint); + + SKColor accent = canClear ? Label : Divider; + + fillPaint.Color = Panel; + canvas.DrawRoundRect(clearRect, 6.0f, 6.0f, fillPaint); + + strokePaint.Color = accent; + strokePaint.StrokeWidth = 1.5f; + canvas.DrawRoundRect(clearRect, 6.0f, 6.0f, strokePaint); + + fillPaint.Color = accent; + canvas.DrawText("CLEAR", clearRect.MidX, clearRect.MidY + 4.0f, SKTextAlign.Center, labelFont, fillPaint); + } + + private void DrawStatus(SKCanvas canvas, int strokeCount, int nodeCount) + { + float top = size.Height - StatusHeight; + + fillPaint.Color = Panel; + canvas.DrawRect(0.0f, top, size.Width, StatusHeight, fillPaint); + + fillPaint.Color = Divider; + canvas.DrawRect(0.0f, top, size.Width, 1.0f, fillPaint); + + float baseline = top + (StatusHeight * 0.5f) + 4.0f; + + fillPaint.Color = Label; + canvas.DrawText($"STROKES {strokeCount} NODES {nodeCount}", SwatchGap, baseline, SKTextAlign.Left, labelFont, fillPaint); + canvas.DrawText("DRAG TO DRAW RIGHT DRAG TO ERASE", size.Width - SwatchGap, baseline, SKTextAlign.Right, labelFont, fillPaint); + } + + private static int IndexAt(SKRect[] rects, SKPoint position) + { + for (int index = 0; index < rects.Length; index++) + { + if (rects[index].Contains(position.X, position.Y)) + { + return index; + } + } + + return -1; + } +} diff --git a/sources/Experiments/InkCanvas/Helpers/CocoaHelper.cs b/sources/Experiments/InkCanvas/Helpers/CocoaHelper.cs new file mode 100644 index 00000000..3bec63f1 --- /dev/null +++ b/sources/Experiments/InkCanvas/Helpers/CocoaHelper.cs @@ -0,0 +1,34 @@ +using System.Runtime.InteropServices; + +namespace InkCanvas.Helpers; + +internal static partial class CocoaHelper +{ + private const string LibObjC = "/usr/lib/libobjc.A.dylib"; + + [LibraryImport(LibObjC, EntryPoint = "objc_getClass")] + private static partial nint GetClass([MarshalAs(UnmanagedType.LPUTF8Str)] string name); + + [LibraryImport(LibObjC, EntryPoint = "sel_registerName")] + private static partial nint Selector([MarshalAs(UnmanagedType.LPUTF8Str)] string name); + + [LibraryImport(LibObjC, EntryPoint = "objc_msgSend")] + private static partial nint Send(nint receiver, nint selector); + + [LibraryImport(LibObjC, EntryPoint = "objc_msgSend")] + private static partial nint Send(nint receiver, nint selector, [MarshalAs(UnmanagedType.I1)] bool arg); + + [LibraryImport(LibObjC, EntryPoint = "objc_msgSend")] + private static partial nint Send(nint receiver, nint selector, nint arg); + + public static nint CreateLayer(nint cocoa) + { + nint layer = Send(GetClass("CAMetalLayer"), Selector("layer")); + + nint view = Send(cocoa, Selector("contentView")); + Send(view, Selector("setWantsLayer:"), true); + Send(view, Selector("setLayer:"), layer); + + return layer; + } +} \ No newline at end of file diff --git a/sources/Experiments/InkCanvas/InkCanvas.csproj b/sources/Experiments/InkCanvas/InkCanvas.csproj new file mode 100644 index 00000000..8b441176 --- /dev/null +++ b/sources/Experiments/InkCanvas/InkCanvas.csproj @@ -0,0 +1,20 @@ + + + + Exe + $(StandardTargetFramework) + + + + + + + + + + + + + + + diff --git a/sources/Experiments/InkCanvas/Program.cs b/sources/Experiments/InkCanvas/Program.cs new file mode 100644 index 00000000..6ef9a23a --- /dev/null +++ b/sources/Experiments/InkCanvas/Program.cs @@ -0,0 +1,3 @@ +using InkCanvas; + +App.Run(); \ No newline at end of file diff --git a/sources/Extensions/Zenith.NET.Extensions.Skia/Extensions.cs b/sources/Extensions/Zenith.NET.Extensions.Skia/Extensions.cs new file mode 100644 index 00000000..5e55de6c --- /dev/null +++ b/sources/Extensions/Zenith.NET.Extensions.Skia/Extensions.cs @@ -0,0 +1,34 @@ +namespace Zenith.NET.Extensions.Skia; + +public static class Extensions +{ + private static readonly Lock @lock = new(); + private static readonly Dictionary renderers = []; + + extension(GraphicsContext context) + { + public SKTexture CreateSKTexture(SKTextureDesc desc) + { + using Lock.Scope _ = @lock.EnterScope(); + + if (!renderers.TryGetValue(context, out SKRenderer? renderer)) + { + renderers[context] = renderer = new(context); + } + + renderer.AddReference(); + + return new(renderer, desc); + } + } + + internal static void ReleaseRenderer(SKRenderer renderer) + { + using Lock.Scope _ = @lock.EnterScope(); + + if (renderer.RemoveReference() && renderers.Remove(renderer.Context)) + { + renderer.Dispose(); + } + } +} diff --git a/sources/Extensions/Zenith.NET.Extensions.Skia/SKFormats.cs b/sources/Extensions/Zenith.NET.Extensions.Skia/SKFormats.cs new file mode 100644 index 00000000..77dd5aa9 --- /dev/null +++ b/sources/Extensions/Zenith.NET.Extensions.Skia/SKFormats.cs @@ -0,0 +1,88 @@ +using SkiaSharp; + +namespace Zenith.NET.Extensions.Skia; + +internal static class SKFormats +{ + public static SKColorType Skia(PixelFormat pixelFormat) + { + return pixelFormat switch + { + PixelFormat.R8UNorm => SKColorType.Gray8, + PixelFormat.R16Float => SKColorType.AlphaF16, + PixelFormat.R8G8B8A8UNorm => SKColorType.Rgba8888, + PixelFormat.R8G8B8A8SRgb => SKColorType.Srgba8888, + PixelFormat.R16G16B16A16Float => SKColorType.RgbaF16, + PixelFormat.R32G32B32A32Float => SKColorType.RgbaF32, + PixelFormat.B8G8R8A8UNorm => SKColorType.Bgra8888, + _ => default + }; + } + + public static uint DirectX12(PixelFormat pixelFormat) + { + return pixelFormat switch + { + PixelFormat.R8UNorm => 61, + PixelFormat.R16Float => 54, + PixelFormat.R8G8B8A8UNorm => 28, + PixelFormat.R8G8B8A8SRgb => 29, + PixelFormat.R16G16B16A16Float => 10, + PixelFormat.R32G32B32A32Float => 2, + PixelFormat.B8G8R8A8UNorm => 87, + _ => default + }; + } + + public static uint Vulkan(PixelFormat pixelFormat) + { + return pixelFormat switch + { + PixelFormat.R8UNorm => 9, + PixelFormat.R16Float => 76, + PixelFormat.R8G8B8A8UNorm => 37, + PixelFormat.R8G8B8A8SRgb => 43, + PixelFormat.R16G16B16A16Float => 97, + PixelFormat.R32G32B32A32Float => 109, + PixelFormat.B8G8R8A8UNorm => 44, + _ => default + }; + } + + public static uint Vulkan(TextureUsages textureUsages) + { + uint result = default; + + if (textureUsages.HasFlag(TextureUsages.Sampled)) + { + result |= 1 << 2; + } + + if (textureUsages.HasFlag(TextureUsages.Storage)) + { + result |= 1 << 3; + } + + if (textureUsages.HasFlag(TextureUsages.ColorAttachment)) + { + result |= 1 << 4; + } + + if (textureUsages.HasFlag(TextureUsages.DepthStencilAttachment)) + { + result |= 1 << 5; + } + + if (textureUsages.HasFlag(TextureUsages.TransferSrc)) + { + result |= 1 << 0; + } + + if (textureUsages.HasFlag(TextureUsages.TransferDst)) + { + result |= 1 << 1; + } + + return result; + } +} diff --git a/sources/Extensions/Zenith.NET.Extensions.Skia/SKRenderer.cs b/sources/Extensions/Zenith.NET.Extensions.Skia/SKRenderer.cs new file mode 100644 index 00000000..b333b4f2 --- /dev/null +++ b/sources/Extensions/Zenith.NET.Extensions.Skia/SKRenderer.cs @@ -0,0 +1,192 @@ +using System.Runtime.InteropServices; +using SkiaSharp; + +namespace Zenith.NET.Extensions.Skia; + +internal unsafe class SKRenderer : DisposableObject +{ + private readonly Lock @lock = new(); + private readonly nint commandQueue; + + private uint referenceCount; + + public SKRenderer(GraphicsContext context) + { + Context = context; + + GRContextOptions options = new() + { + AvoidStencilBuffers = true, + RuntimeProgramCacheSize = 1024 + }; + + switch (context.GraphicsApi) + { + case GraphicsApi.DirectX12: + { + using GRD3DBackendContext backendContext = new() + { + Adapter = context.GetNativeObject(NativeObjectType.D3D12Adapter), + Device = context.GetNativeObject(NativeObjectType.D3D12Device), + Queue = context.GraphicsQueue.GetNativeObject(NativeObjectType.D3D12CommandQueue) + }; + + GRContext = GRContext.CreateDirect3D(backendContext, options); + } + break; + + case GraphicsApi.Metal: + { + nint device = context.GetNativeObject(NativeObjectType.MTLDevice); + + using GRMtlBackendContext backendContext = new() + { + DeviceHandle = device, + QueueHandle = commandQueue = SKObjectiveC.SendMessage(device, "newCommandQueue") + }; + + GRContext = GRContext.CreateMetal(backendContext, options); + } + break; + + case GraphicsApi.Vulkan: + { + delegate* unmanaged getInstanceProcAddr = (delegate* unmanaged)context.GetNativeObject(NativeObjectType.VulkanGetInstanceProcAddr); + delegate* unmanaged getDeviceProcAddr = (delegate* unmanaged)context.GetNativeObject(NativeObjectType.VulkanGetDeviceProcAddr); + + nint instance = context.GetNativeObject(NativeObjectType.VulkanInstance); + nint physicalDevice = context.GetNativeObject(NativeObjectType.VulkanPhysicalDevice); + + using GRVkExtensions extensions = GRVkExtensions.Create(GetProcedureAddress, instance, physicalDevice, null, null); + + using GRVkBackendContext backendContext = new() + { + VkInstance = instance, + VkPhysicalDevice = physicalDevice, + VkDevice = context.GetNativeObject(NativeObjectType.VulkanDevice), + VkQueue = context.GraphicsQueue.GetNativeObject(NativeObjectType.VulkanQueue), + GraphicsQueueIndex = (uint)context.GraphicsQueue.GetNativeObject(NativeObjectType.VulkanQueueFamilyIndex), + MaxAPIVersion = (1u << 22) | (4u << 12), + Extensions = extensions, + GetProcedureAddress = GetProcedureAddress + }; + + GRContext = GRContext.CreateVulkan(backendContext, options); + + nint GetProcedureAddress(string name, nint instance, nint device) + { + using ZenithMarshal.Scope scope = new(); + + byte* pointer = (byte*)ZenithMarshal.StringToPointer(scope, name, StringEncoding.UTF8); + + return device is 0 ? getInstanceProcAddr(instance, pointer) : getDeviceProcAddr(device, pointer); + } + } + break; + + default: + GRContext = default!; + break; + } + } + + public GraphicsContext Context { get; } + + public GRContext GRContext { get; } + + public void AddReference() + { + referenceCount++; + } + + public bool RemoveReference() + { + return --referenceCount is 0; + } + + public void Render(SKSurface surface, Action render) + { + using Lock.Scope _ = @lock.EnterScope(); + + render(surface.Canvas); + + GRContext.Flush(surface); + GRContext.Submit(true); + } + + protected override void Destroy() + { + GRContext.Dispose(); + + if (Context.GraphicsApi is GraphicsApi.Metal) + { + SKObjectiveC.Release(commandQueue); + } + } + + public GRBackendTexture CreateBackendTexture(Texture texture, bool isMultisamplingEnabled) + { + switch (Context.GraphicsApi) + { + case GraphicsApi.DirectX12: + return new((int)texture.Desc.Width, (int)texture.Desc.Height, new GRD3DTextureResourceInfo() + { + Resource = texture.GetNativeObject(NativeObjectType.D3D12Resource), + ResourceState = isMultisamplingEnabled ? 0x1000u : 0x4u, + Format = SKFormats.DirectX12(texture.Desc.Format), + SampleCount = 1, + LevelCount = 1, + SampleQualityPattern = isMultisamplingEnabled ? uint.MaxValue : 0 + }); + + case GraphicsApi.Metal: + return new((int)texture.Desc.Width, (int)texture.Desc.Height, false, new GRMtlTextureInfo() { TextureHandle = texture.GetNativeObject(NativeObjectType.MTLTexture) }); + + case GraphicsApi.Vulkan: + uint graphicsQueueFamily = (uint)Context.GraphicsQueue.GetNativeObject(NativeObjectType.VulkanQueueFamilyIndex); + uint computeQueueFamily = (uint)Context.ComputeQueue.GetNativeObject(NativeObjectType.VulkanQueueFamilyIndex); + uint transferQueueFamily = (uint)Context.TransferQueue.GetNativeObject(NativeObjectType.VulkanQueueFamilyIndex); + bool concurrent = graphicsQueueFamily != computeQueueFamily || graphicsQueueFamily != transferQueueFamily; + + return new((int)texture.Desc.Width, (int)texture.Desc.Height, new GRVkImageInfo() + { + Image = (ulong)texture.GetNativeObject(NativeObjectType.VulkanImage), + Alloc = new() + { + Memory = (ulong)texture.GetNativeObject(NativeObjectType.VulkanDeviceMemory), + Offset = (ulong)texture.GetNativeObject(NativeObjectType.VulkanDeviceMemoryOffset), + Size = Context.GetSizeAndAlignment(texture.Desc).SizeInBytes + }, + ImageLayout = isMultisamplingEnabled ? 7u : 2u, + Format = SKFormats.Vulkan(texture.Desc.Format), + ImageUsageFlags = SKFormats.Vulkan(texture.Desc.Usages), + SampleCount = 1, + LevelCount = 1, + CurrentQueueFamily = concurrent ? uint.MaxValue : graphicsQueueFamily, + SharingMode = concurrent ? 1u : 0u + }); + + default: + return default!; + } + } +} + +internal static partial class SKObjectiveC +{ + private const string LibObjC = "/usr/lib/libobjc.A.dylib"; + + [LibraryImport(LibObjC, EntryPoint = "objc_msgSend")] + private static partial nint SendMessage(nint receiver, nint selector); + + [LibraryImport(LibObjC, EntryPoint = "sel_registerName")] + private static partial nint RegisterName([MarshalAs(UnmanagedType.LPUTF8Str)] string name); + + [LibraryImport(LibObjC, EntryPoint = "objc_release")] + public static partial void Release(nint value); + + public static nint SendMessage(nint receiver, string selector) + { + return SendMessage(receiver, RegisterName(selector)); + } +} \ No newline at end of file diff --git a/sources/Extensions/Zenith.NET.Extensions.Skia/SKTexture.cs b/sources/Extensions/Zenith.NET.Extensions.Skia/SKTexture.cs new file mode 100644 index 00000000..c304452a --- /dev/null +++ b/sources/Extensions/Zenith.NET.Extensions.Skia/SKTexture.cs @@ -0,0 +1,70 @@ +using System.Numerics; +using SkiaSharp; + +namespace Zenith.NET.Extensions.Skia; + +public class SKTexture : DisposableObject +{ + private readonly Texture texture; + private readonly SKSurface surface; + + internal SKTexture(SKRenderer renderer, SKTextureDesc desc) + { + Renderer = renderer; + + texture = renderer.Context.CreateTexture(new() + { + Type = TextureType.Texture2D, + Format = desc.Format, + Width = desc.Width, + Height = desc.Height, + Depth = 1, + MipLevels = 1, + ArrayLayers = 1, + SampleCount = SampleCount.Count1, + Usages = TextureUsages.Sampled | TextureUsages.ColorAttachment | TextureUsages.TransferSrc | TextureUsages.TransferDst + }); + + CommandBuffer commandBuffer = renderer.Context.GraphicsQueue.CommandBuffer(); + + commandBuffer.Transition(texture, default, TextureLayout.Undefined, TextureLayout.ColorAttachment); + + commandBuffer.BeginRenderPass([ColorAttachment.Clear(texture, Vector4.Zero)], null); + commandBuffer.EndRenderPass(); + + if ((RequiredLayout = desc.IsMultisamplingEnabled ? TextureLayout.ResolveDst : TextureLayout.ColorAttachment) is not TextureLayout.ColorAttachment) + { + commandBuffer.Transition(texture, default, TextureLayout.ColorAttachment, RequiredLayout); + } + + commandBuffer.Submit().Wait(); + + using GRBackendTexture backendTexture = renderer.CreateBackendTexture(texture, desc.IsMultisamplingEnabled); + + surface = SKSurface.Create(renderer.GRContext, backendTexture, GRSurfaceOrigin.TopLeft, desc.IsMultisamplingEnabled ? 4 : 1, SKFormats.Skia(desc.Format)); + } + + internal SKRenderer Renderer { get; } + + public ref readonly TextureDesc Desc => ref texture.Desc; + + public TextureLayout RequiredLayout { get; } + + public void Render(Action render) + { + Renderer.Render(surface, render); + } + + protected override void Destroy() + { + surface.Dispose(); + texture.Dispose(); + + Extensions.ReleaseRenderer(Renderer); + } + + public static implicit operator Texture(SKTexture texture) + { + return texture.texture; + } +} diff --git a/sources/Extensions/Zenith.NET.Extensions.Skia/SKTextureDesc.cs b/sources/Extensions/Zenith.NET.Extensions.Skia/SKTextureDesc.cs new file mode 100644 index 00000000..65c29c7f --- /dev/null +++ b/sources/Extensions/Zenith.NET.Extensions.Skia/SKTextureDesc.cs @@ -0,0 +1,12 @@ +namespace Zenith.NET.Extensions.Skia; + +public struct SKTextureDesc +{ + public PixelFormat Format; + + public uint Width; + + public uint Height; + + public bool IsMultisamplingEnabled; +} diff --git a/sources/NuGet.Packaging.props b/sources/NuGet.Packaging.props index f80d76e1..e60169d2 100644 --- a/sources/NuGet.Packaging.props +++ b/sources/NuGet.Packaging.props @@ -7,7 +7,7 @@ $(MSBuildThisFileDirectory)..\.nuget - 1.0.0-rc + 1.0.0 qian-o Copyright (c) 2026 qian-o Zenith.NET is a modern rendering hardware interface for .NET with one consistent C# API for graphics and compute across DirectX 12, Metal 4, and Vulkan 1.4. diff --git a/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs b/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs index 6557c768..5f4ec710 100644 --- a/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.Avalonia/ZenithView.cs @@ -81,7 +81,7 @@ public override void Render(DrawingContext context) void IZenithView.UI(Action action) { - Dispatcher.Invoke(action); + Dispatcher.InvokeAsync(action); } void IZenithView.EnsureResources() diff --git a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs index 52163433..e41577a8 100644 --- a/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs +++ b/sources/Views/Zenith.NET.Views.Maui/Platforms/Windows/Surface.cs @@ -11,6 +11,8 @@ internal unsafe partial class Surface : DisposableObject [LibraryImport("kernel32")] private static partial int CloseHandle(nint hObject); + public ComPtr Query = new(); + public ComPtr SwapChain = new(); public ComPtr Texture = new(); @@ -23,6 +25,9 @@ internal unsafe partial class Surface : DisposableObject public Surface(GraphicsContext graphicsContext, uint width, uint height) { + QueryDesc queryDesc = new(); + D3D.Success(D3D.Device.CreateQuery(&queryDesc, Query.GetAddressOf())); + SwapChainDesc1 swapChainDesc = new() { Width = width, @@ -97,7 +102,12 @@ public void Present() AcquireSync(); D3D.DeviceContext.CopyResource((ID3D11Resource*)backBuffer.Handle, (ID3D11Resource*)Texture.Handle); - D3D.DeviceContext.Flush(); + D3D.DeviceContext.End(Query); + + while (D3D.DeviceContext.GetData(Query, default, 0, 0) is 1) + { + Thread.Yield(); + } ReleaseSync(); @@ -116,6 +126,7 @@ protected override void Destroy() Mutex.Dispose(); Texture.Dispose(); SwapChain.Dispose(); + Query.Dispose(); } private static Format DrawableFormat() diff --git a/sources/Views/Zenith.NET.Views.WPF/Surface.cs b/sources/Views/Zenith.NET.Views.WPF/Surface.cs index 332cd3c7..92e36f0e 100644 --- a/sources/Views/Zenith.NET.Views.WPF/Surface.cs +++ b/sources/Views/Zenith.NET.Views.WPF/Surface.cs @@ -15,6 +15,8 @@ internal unsafe partial class Surface : DisposableObject [LibraryImport("kernel32")] private static partial int CloseHandle(nint hObject); + public ComPtr Query = new(); + public ComPtr D3D9RenderTarget = new(); public ComPtr D3D9RenderSurface = new(); @@ -31,6 +33,9 @@ internal unsafe partial class Surface : DisposableObject public Surface(GraphicsContext graphicsContext, uint width, uint height) { + QueryDesc queryDesc = new(); + D3D.Success(D3D.D3D11Device.CreateQuery(&queryDesc, Query.GetAddressOf())); + void* sharedHandle = null; D3D.Success(D3D.D3D9DeviceEx.CreateTexture(width, height, @@ -104,7 +109,12 @@ public void Present(D3DImage image) AcquireSync(); D3D.D3D11DeviceContext.CopyResource((ID3D11Resource*)D3D9SharedTexture.Handle, (ID3D11Resource*)D3D11RenderTarget.Handle); - D3D.D3D11DeviceContext.Flush(); + D3D.D3D11DeviceContext.End(Query); + + while (D3D.D3D11DeviceContext.GetData(Query, default, 0, 0) is 1) + { + Thread.Yield(); + } ReleaseSync(); @@ -126,6 +136,7 @@ protected override void Destroy() D3D9SharedTexture.Dispose(); D3D9RenderSurface.Dispose(); D3D9RenderTarget.Dispose(); + Query.Dispose(); } private static DXGIFormat DrawableFormat() diff --git a/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs b/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs index c0d80027..ab827d58 100644 --- a/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.WPF/ZenithView.cs @@ -4,6 +4,7 @@ using System.Windows.Controls; using System.Windows.Interop; using System.Windows.Media; +using System.Windows.Threading; namespace Zenith.NET.Views.WPF; @@ -88,7 +89,7 @@ protected override void OnRender(DrawingContext drawingContext) void IZenithView.UI(Action action) { - Dispatcher.Invoke(action); + Dispatcher.InvokeAsync(action); } void IZenithView.EnsureResources() diff --git a/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs b/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs index 97d6a1e3..6465e2c2 100644 --- a/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs +++ b/sources/Views/Zenith.NET.Views.WinForms/ZenithView.cs @@ -63,7 +63,7 @@ protected override void OnPaint(PaintEventArgs e) void IZenithView.UI(Action action) { - Invoke(action); + InvokeAsync(action); } void IZenithView.EnsureResources() diff --git a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs index 875ee498..f0dd43f6 100644 --- a/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs +++ b/sources/Views/Zenith.NET.Views.WinUI/ZenithView.WinUI.cs @@ -113,6 +113,8 @@ internal unsafe partial class Surface : DisposableObject [LibraryImport("kernel32")] private static partial int CloseHandle(nint hObject); + public ComPtr Query = new(); + public ComPtr SwapChain = new(); public ComPtr Texture = new(); @@ -125,6 +127,9 @@ internal unsafe partial class Surface : DisposableObject public Surface(GraphicsContext graphicsContext, uint width, uint height) { + QueryDesc queryDesc = new(); + D3D.Success(D3D.Device.CreateQuery(&queryDesc, Query.GetAddressOf())); + SwapChainDesc1 swapChainDesc = new() { Width = width, @@ -199,7 +204,12 @@ public void Present() AcquireSync(); D3D.DeviceContext.CopyResource((ID3D11Resource*)backBuffer.Handle, (ID3D11Resource*)Texture.Handle); - D3D.DeviceContext.Flush(); + D3D.DeviceContext.End(Query); + + while (D3D.DeviceContext.GetData(Query, default, 0, 0) is 1) + { + Thread.Yield(); + } ReleaseSync(); @@ -218,6 +228,7 @@ protected override void Destroy() Mutex.Dispose(); Texture.Dispose(); SwapChain.Dispose(); + Query.Dispose(); } private static Format DrawableFormat() diff --git a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs index 34534b32..3a89ebb8 100644 --- a/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXBottomLevelAccelerationStructure.cs @@ -70,7 +70,12 @@ public void Update(DXCommandBuffer commandBuffer, BottomLevelAccelerationStructu public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.D3D12GpuVirtualAddress => (nint)AccelerationStructure.GPUVirtualAddress, + NativeObjectType.D3D12Resource => (nint)AccelerationStructure.Resource.Handle, + _ => default + }; } protected override void SetResourceName(string name) diff --git a/sources/Zenith.NET.DirectX12/DXBuffer.cs b/sources/Zenith.NET.DirectX12/DXBuffer.cs index 9cf81f2c..90b92467 100644 --- a/sources/Zenith.NET.DirectX12/DXBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXBuffer.cs @@ -89,7 +89,12 @@ public DXBuffer(DXGraphicsContext context, BufferDesc desc, ResourceFlags flags) public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.D3D12GpuVirtualAddress => (nint)GPUVirtualAddress, + NativeObjectType.D3D12Resource => (nint)Resource.Handle, + _ => default + }; } public override nint Map() diff --git a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs index dd86760d..15f9f0ee 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandBuffer.cs @@ -21,7 +21,11 @@ public DXCommandBuffer(DXGraphicsContext context, DXCommandQueue queue) : base(c public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.D3D12GraphicsCommandList => (nint)CommandList.Handle, + _ => default + }; } protected override void BarrierImpl(BarrierStages before, BarrierStages after) @@ -51,6 +55,11 @@ protected override void TransitionImpl(Texture texture, TextureSubresource subre { DXTexture dxTexture = texture.DirectX12(); + if (!dxTexture.CanTransition) + { + return; + } + (BarrierSync syncBefore, BarrierAccess accessBefore, BarrierLayout layoutBefore) = DXFormats.DirectX12(before); (BarrierSync syncAfter, BarrierAccess accessAfter, BarrierLayout layoutAfter) = DXFormats.DirectX12(after); @@ -276,12 +285,12 @@ protected override void BeginRenderPassImpl(ReadOnlySpan colorA Type = DXFormats.DirectX12(attachment.DepthLoadOp), Clear = new() { ClearValue = clearValue } }, - DepthEndingAccess = new() { Type = ZenithHelper.HasDepth(texture.Desc.Format) ? DXFormats.DirectX12(attachment.DepthStoreOp) : RenderPassEndingAccessType.NoAccess }, StencilBeginningAccess = new() { Type = ZenithHelper.HasStencil(texture.Desc.Format) ? DXFormats.DirectX12(attachment.StencilLoadOp) : RenderPassBeginningAccessType.NoAccess, Clear = new() { ClearValue = clearValue } }, + DepthEndingAccess = new() { Type = ZenithHelper.HasDepth(texture.Desc.Format) ? DXFormats.DirectX12(attachment.DepthStoreOp) : RenderPassEndingAccessType.NoAccess }, StencilEndingAccess = new() { Type = ZenithHelper.HasStencil(texture.Desc.Format) ? DXFormats.DirectX12(attachment.StencilStoreOp) : RenderPassEndingAccessType.NoAccess } }; } diff --git a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs index 4eef6297..d95832fb 100644 --- a/sources/Zenith.NET.DirectX12/DXCommandQueue.cs +++ b/sources/Zenith.NET.DirectX12/DXCommandQueue.cs @@ -22,7 +22,11 @@ public DXCommandQueue(DXGraphicsContext context, CommandQueueType type) : base(c public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.D3D12CommandQueue => (nint)CommandQueue.Handle, + _ => default + }; } protected override CommandBuffer CreateCommandBuffer() diff --git a/sources/Zenith.NET.DirectX12/DXFormats.cs b/sources/Zenith.NET.DirectX12/DXFormats.cs index 762a56d3..94f0d586 100644 --- a/sources/Zenith.NET.DirectX12/DXFormats.cs +++ b/sources/Zenith.NET.DirectX12/DXFormats.cs @@ -618,11 +618,6 @@ public static ResourceFlags DirectX12(TextureUsages textureUsages) { ResourceFlags result = default; - if (!textureUsages.HasFlag(TextureUsages.Sampled)) - { - result |= ResourceFlags.DenyShaderResource; - } - if (textureUsages.HasFlag(TextureUsages.Storage)) { result |= ResourceFlags.AllowUnorderedAccess; @@ -635,6 +630,11 @@ public static ResourceFlags DirectX12(TextureUsages textureUsages) if (textureUsages.HasFlag(TextureUsages.DepthStencilAttachment)) { + if (!textureUsages.HasFlag(TextureUsages.Sampled)) + { + result |= ResourceFlags.DenyShaderResource; + } + result |= ResourceFlags.AllowDepthStencil; } diff --git a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs index 8b6d4cd2..04959410 100644 --- a/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs +++ b/sources/Zenith.NET.DirectX12/DXGraphicsContext.cs @@ -40,7 +40,12 @@ internal unsafe class DXGraphicsContext(bool useValidationLayer) : GraphicsConte public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.D3D12Adapter => (nint)Adapter.Handle, + NativeObjectType.D3D12Device => (nint)Device.Handle, + _ => default + }; } protected override void Initialize(bool useValidationLayer, @@ -167,7 +172,7 @@ protected override Texture CreateTextureImpl(TextureDesc desc, NativeTextureType ComPtr resource = new(); Device.OpenSharedHandle((void*)nativeTexture, SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf()).Success(); - return new DXTexture(this, desc, resource); + return new DXTexture(this, desc, resource, false); } protected override TextureView CreateTextureViewImpl(TextureViewDesc desc) diff --git a/sources/Zenith.NET.DirectX12/DXHeap.cs b/sources/Zenith.NET.DirectX12/DXHeap.cs index eeb420dc..f37ed3aa 100644 --- a/sources/Zenith.NET.DirectX12/DXHeap.cs +++ b/sources/Zenith.NET.DirectX12/DXHeap.cs @@ -60,7 +60,7 @@ protected override Texture CreateTextureImpl(ulong offsetInBytes, TextureDesc de SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf()).Success(); - return new DXTexture(Context, desc, resource); + return new DXTexture(Context, desc, resource, true); } protected override void SetResourceName(string name) diff --git a/sources/Zenith.NET.DirectX12/DXSwapChain.cs b/sources/Zenith.NET.DirectX12/DXSwapChain.cs index c023f988..a75db726 100644 --- a/sources/Zenith.NET.DirectX12/DXSwapChain.cs +++ b/sources/Zenith.NET.DirectX12/DXSwapChain.cs @@ -112,7 +112,7 @@ private void CreateTextures() ComPtr resource = new(); SwapChain.GetBuffer((uint)i, SilkMarshal.GuidPtrOf(), (void**)resource.GetAddressOf()).Success(); - textures[i] = new(Context, desc, resource); + textures[i] = new(Context, desc, resource, true); } index = SwapChain.GetCurrentBackBufferIndex(); diff --git a/sources/Zenith.NET.DirectX12/DXTexture.cs b/sources/Zenith.NET.DirectX12/DXTexture.cs index ccb19acb..d5532720 100644 --- a/sources/Zenith.NET.DirectX12/DXTexture.cs +++ b/sources/Zenith.NET.DirectX12/DXTexture.cs @@ -11,6 +11,8 @@ internal unsafe class DXTexture : Texture public ComPtr Resource; + public bool CanTransition; + public DXTexture(DXGraphicsContext context, TextureDesc desc) : base(context, desc) { ResourceDesc1 resourceDesc = ResourceDesc(desc); @@ -28,6 +30,8 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc) : base(context, de SilkMarshal.GuidPtrOf(), (void**)Resource.GetAddressOf()).Success(); + CanTransition = true; + View = new(context, new() { Texture = this, @@ -37,9 +41,10 @@ public DXTexture(DXGraphicsContext context, TextureDesc desc) : base(context, de }); } - public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr resource) : base(context, desc) + public DXTexture(DXGraphicsContext context, TextureDesc desc, ComPtr resource, bool canTransition) : base(context, desc) { Resource = resource; + CanTransition = canTransition; View = new(context, new() { @@ -211,7 +216,11 @@ public CpuDescriptorHandle GetDsvHandle(TextureSubresource subresource) public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.D3D12Resource => (nint)Resource.Handle, + _ => default + }; } protected override void SetResourceName(string name) diff --git a/sources/Zenith.NET.DirectX12/DXTimeline.cs b/sources/Zenith.NET.DirectX12/DXTimeline.cs index 07f6e77c..7e7f6d42 100644 --- a/sources/Zenith.NET.DirectX12/DXTimeline.cs +++ b/sources/Zenith.NET.DirectX12/DXTimeline.cs @@ -20,7 +20,11 @@ public DXTimeline(DXGraphicsContext context, DXCommandQueue queue) : base(contex public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.D3D12Fence => (nint)Fence.Handle, + _ => default + }; } protected override ulong GetCompletedValue() diff --git a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs index b32c76b7..b0771bda 100644 --- a/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.DirectX12/DXTopLevelAccelerationStructure.cs @@ -82,7 +82,12 @@ public void Update(DXCommandBuffer commandBuffer, TopLevelAccelerationStructureD public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.D3D12GpuVirtualAddress => (nint)AccelerationStructure.GPUVirtualAddress, + NativeObjectType.D3D12Resource => (nint)AccelerationStructure.Resource.Handle, + _ => default + }; } protected override void SetResourceName(string name) diff --git a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs index f2ecdd4d..5dfe9013 100644 --- a/sources/Zenith.NET.Metal/MTLGraphicsContext.cs +++ b/sources/Zenith.NET.Metal/MTLGraphicsContext.cs @@ -30,7 +30,11 @@ public void Unregister(MTLAllocation allocation) public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.MTLDevice => Device.NativePtr, + _ => default + }; } protected override void Initialize(bool useValidationLayer, diff --git a/sources/Zenith.NET.Metal/MTLTexture.cs b/sources/Zenith.NET.Metal/MTLTexture.cs index 88cd42ca..68797dd7 100644 --- a/sources/Zenith.NET.Metal/MTLTexture.cs +++ b/sources/Zenith.NET.Metal/MTLTexture.cs @@ -42,7 +42,11 @@ public MTLTexture(MTLGraphicsContext context, TextureDesc desc, MtlTexture textu public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.MTLTexture => Texture.NativePtr, + _ => default + }; } protected override void SetResourceName(string name) diff --git a/sources/Zenith.NET.Metal/MTLTimeline.cs b/sources/Zenith.NET.Metal/MTLTimeline.cs index ca93362f..ce042a35 100644 --- a/sources/Zenith.NET.Metal/MTLTimeline.cs +++ b/sources/Zenith.NET.Metal/MTLTimeline.cs @@ -8,7 +8,11 @@ internal class MTLTimeline(MTLGraphicsContext context, MTLCommandQueue queue) : public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.MTLSharedEvent => Event.NativePtr, + _ => default + }; } protected override ulong GetCompletedValue() diff --git a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs index b542a751..3760d856 100644 --- a/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Vulkan/VKBottomLevelAccelerationStructure.cs @@ -83,7 +83,12 @@ public void Update(VKCommandBuffer commandBuffer, BottomLevelAccelerationStructu public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.VulkanAccelerationStructure => (nint)AccelerationStructure.Handle, + NativeObjectType.VulkanDeviceAddress => (nint)DeviceAddress, + _ => default + }; } protected override void SetResourceName(string name) diff --git a/sources/Zenith.NET.Vulkan/VKBuffer.cs b/sources/Zenith.NET.Vulkan/VKBuffer.cs index 0370a18f..e982585a 100644 --- a/sources/Zenith.NET.Vulkan/VKBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKBuffer.cs @@ -152,7 +152,14 @@ public VKBuffer(VKGraphicsContext context, BufferDesc desc, VkBuffer buffer, VKA public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.VulkanBuffer => (nint)Buffer.Handle, + NativeObjectType.VulkanDeviceAddress => (nint)DeviceAddress, + NativeObjectType.VulkanDeviceMemory => (nint)Allocation.DeviceMemory.Handle, + NativeObjectType.VulkanDeviceMemoryOffset => (nint)Allocation.OffsetInBytes, + _ => default + }; } public override nint Map() diff --git a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs index 96dc0631..513f3e38 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandBuffer.cs @@ -34,7 +34,11 @@ public VKCommandBuffer(VKGraphicsContext context, VKCommandQueue queue) : base(c public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.VulkanCommandBuffer => CommandBuffer.Handle, + _ => default + }; } protected override void BarrierImpl(BarrierStages before, BarrierStages after) diff --git a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs index 97fd5a42..edc102d4 100644 --- a/sources/Zenith.NET.Vulkan/VKCommandQueue.cs +++ b/sources/Zenith.NET.Vulkan/VKCommandQueue.cs @@ -22,7 +22,12 @@ public VKCommandQueue(VKGraphicsContext context, CommandQueueType type, Queue qu public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.VulkanQueue => Queue.Handle, + NativeObjectType.VulkanQueueFamilyIndex => (nint)QueueFamilyIndex, + _ => default + }; } protected override CommandBuffer CreateCommandBuffer() diff --git a/sources/Zenith.NET.Vulkan/VKFormats.cs b/sources/Zenith.NET.Vulkan/VKFormats.cs index 948165f0..e7380972 100644 --- a/sources/Zenith.NET.Vulkan/VKFormats.cs +++ b/sources/Zenith.NET.Vulkan/VKFormats.cs @@ -61,8 +61,8 @@ public static (PipelineStageFlags2 Stage, AccessFlags2 Access) Vulkan(BarrierSta if (barrierStages.HasFlag(BarrierStages.VertexShading)) { - stage |= PipelineStageFlags2.IndexInputBit | PipelineStageFlags2.VertexAttributeInputBit | PipelineStageFlags2.VertexShaderBit | PipelineStageFlags2.DrawIndirectBit; - access |= AccessFlags2.VertexAttributeReadBit | AccessFlags2.UniformReadBit | AccessFlags2.IndexReadBit | AccessFlags2.ShaderReadBit | AccessFlags2.ShaderWriteBit | AccessFlags2.IndirectCommandReadBit | AccessFlags2.AccelerationStructureReadBitKhr; + stage |= PipelineStageFlags2.DrawIndirectBit | PipelineStageFlags2.VertexShaderBit | PipelineStageFlags2.IndexInputBit | PipelineStageFlags2.VertexAttributeInputBit; + access |= AccessFlags2.IndirectCommandReadBit | AccessFlags2.IndexReadBit | AccessFlags2.VertexAttributeReadBit | AccessFlags2.UniformReadBit | AccessFlags2.ShaderReadBit | AccessFlags2.ShaderWriteBit | AccessFlags2.AccelerationStructureReadBitKhr; } if (barrierStages.HasFlag(BarrierStages.FragmentShading)) @@ -73,8 +73,8 @@ public static (PipelineStageFlags2 Stage, AccessFlags2 Access) Vulkan(BarrierSta if (barrierStages.HasFlag(BarrierStages.ComputeShading)) { - stage |= PipelineStageFlags2.ComputeShaderBit | PipelineStageFlags2.DrawIndirectBit; - access |= AccessFlags2.UniformReadBit | AccessFlags2.ShaderReadBit | AccessFlags2.ShaderWriteBit | AccessFlags2.IndirectCommandReadBit | AccessFlags2.AccelerationStructureReadBitKhr; + stage |= PipelineStageFlags2.DrawIndirectBit | PipelineStageFlags2.ComputeShaderBit; + access |= AccessFlags2.IndirectCommandReadBit | AccessFlags2.UniformReadBit | AccessFlags2.ShaderReadBit | AccessFlags2.ShaderWriteBit | AccessFlags2.AccelerationStructureReadBitKhr; } if (barrierStages.HasFlag(BarrierStages.Copy)) diff --git a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs index 1b200450..c9eb5bc7 100644 --- a/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs +++ b/sources/Zenith.NET.Vulkan/VKGraphicsContext.cs @@ -129,7 +129,15 @@ public uint FindMemoryTypeIndex(uint memoryTypeBits, MemoryResidency residency) public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.VulkanDevice => Device.Handle, + NativeObjectType.VulkanGetDeviceProcAddr => Vk.GetInstanceProcAddr(Instance, "vkGetDeviceProcAddr"), + NativeObjectType.VulkanGetInstanceProcAddr => Vk.GetInstanceProcAddr(default, "vkGetInstanceProcAddr"), + NativeObjectType.VulkanInstance => Instance.Handle, + NativeObjectType.VulkanPhysicalDevice => PhysicalDevice.Handle, + _ => default + }; } protected override void Initialize(bool useValidationLayer, diff --git a/sources/Zenith.NET.Vulkan/VKHeap.cs b/sources/Zenith.NET.Vulkan/VKHeap.cs index 8971ba17..371d7556 100644 --- a/sources/Zenith.NET.Vulkan/VKHeap.cs +++ b/sources/Zenith.NET.Vulkan/VKHeap.cs @@ -25,7 +25,11 @@ public VKHeap(VKGraphicsContext context, HeapDesc desc) : base(context, desc) public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.VulkanDeviceMemory => (nint)DeviceMemory.Handle, + _ => default + }; } protected override Buffer CreateBufferImpl(ulong offsetInBytes, BufferDesc desc) diff --git a/sources/Zenith.NET.Vulkan/VKTexture.cs b/sources/Zenith.NET.Vulkan/VKTexture.cs index 88945368..2eabdcb6 100644 --- a/sources/Zenith.NET.Vulkan/VKTexture.cs +++ b/sources/Zenith.NET.Vulkan/VKTexture.cs @@ -78,7 +78,13 @@ public VKTexture(VKGraphicsContext context, TextureDesc desc, Image image, VKAll public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.VulkanDeviceMemory => (nint)Allocation.DeviceMemory.Handle, + NativeObjectType.VulkanDeviceMemoryOffset => (nint)Allocation.OffsetInBytes, + NativeObjectType.VulkanImage => (nint)Image.Handle, + _ => default + }; } public ImageView GetAttachmentView(TextureSubresource subresource) diff --git a/sources/Zenith.NET.Vulkan/VKTimeline.cs b/sources/Zenith.NET.Vulkan/VKTimeline.cs index 578fc051..a7379bd4 100644 --- a/sources/Zenith.NET.Vulkan/VKTimeline.cs +++ b/sources/Zenith.NET.Vulkan/VKTimeline.cs @@ -21,7 +21,11 @@ public VKTimeline(VKGraphicsContext context, VKCommandQueue queue) : base(contex public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.VulkanSemaphore => (nint)Semaphore.Handle, + _ => default + }; } protected override ulong GetCompletedValue() diff --git a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs index cbd0eb80..f81304c5 100644 --- a/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs +++ b/sources/Zenith.NET.Vulkan/VKTopLevelAccelerationStructure.cs @@ -100,7 +100,11 @@ public void Update(VKCommandBuffer commandBuffer, TopLevelAccelerationStructureD public override nint GetNativeObject(NativeObjectType type) { - return 0; + return type switch + { + NativeObjectType.VulkanAccelerationStructure => (nint)AccelerationStructure.Handle, + _ => default + }; } protected override void SetResourceName(string name) diff --git a/sources/Zenith.NET/Enums/NativeObjectType.cs b/sources/Zenith.NET/Enums/NativeObjectType.cs index 1872e9d6..a7ac7219 100644 --- a/sources/Zenith.NET/Enums/NativeObjectType.cs +++ b/sources/Zenith.NET/Enums/NativeObjectType.cs @@ -2,4 +2,53 @@ public enum NativeObjectType { + D3D12Adapter, + + D3D12CommandQueue, + + D3D12Device, + + D3D12Fence, + + D3D12GpuVirtualAddress, + + D3D12GraphicsCommandList, + + D3D12Resource, + + MTLDevice, + + MTLSharedEvent, + + MTLTexture, + + VulkanAccelerationStructure, + + VulkanBuffer, + + VulkanCommandBuffer, + + VulkanDevice, + + VulkanDeviceAddress, + + VulkanDeviceMemory, + + VulkanDeviceMemoryOffset, + + VulkanGetDeviceProcAddr, + + VulkanGetInstanceProcAddr, + + VulkanImage, + + VulkanInstance, + + VulkanPhysicalDevice, + + VulkanQueue, + + VulkanQueueFamilyIndex, + + VulkanSemaphore }